我需要在 C 预处理器 #if 中测试一个值是否会创建一个 0 大小的数组

RD *_*ida 63 c preprocessor

我有一个结构必须填充到 64K 才能完美地适合嵌入式项目,以便它填充闪存块。所以有一个#define将结构中的元素加起来使用sizeof()并确定pad[]最后需要多大才能使总大小为64K。

例如 :

#define SIZE_OF_MY_PAD (0x10000 - (sizeof(uint16_t) + sizeof(uint8_t)*32 + ... ))

typedef struct {
  uint16_t firstElement;
  uint8_t  secondElementArray[32];
  ...
  uint8_t pad[SIZE_OF_MY_PAD];
};
Run Code Online (Sandbox Code Playgroud)

这在很长一段时间内一直有效,直到突然我们在某些构建配置中根本不需要垫,因为它已经正好是 64k。这会导致代码失败,因为我们的编译器(不是 GCC)不允许pad[0].

我曾尝试过各种方法来创建一个预处理价值,我可以在使用#if声明中当检测到,但它总是失败,因为虽然sizeof()是合法的#define,它不是法律#if

tst*_*isl 118

借助 C11 中引入的匿名结构,无需任何预处理器即可解决此问题。

将 flash 类型定义为包含嵌入匿名结构的成员的联合。使联合char _pad[0x10000]的另一个成员强制引入类型的总大小。

typedef union {
    struct {
        uint16_t firstElement;
        uint8_t  secondElementArray[32];
        float thirdElement;
    };
    char _pad[0x10000];
} flash_t;
Run Code Online (Sandbox Code Playgroud)

该解决方案对于对结构成员布局的任何修改都是稳健的。此外,这避免了定义零长度数组的问题,该问题在技术上被 C 标准禁止(尽管在 GCC 中允许)。此外,可以添加一个静态断言来检查闪存的最大大小是否溢出。

示例程序:

#include <stdio.h>
#include <stdint.h>
#include <stddef.h>

typedef union {
    struct {
        uint16_t firstElement;
        uint8_t  secondElementArray[32];
        float thirdElement;
        // int kaboom[20000]; // will trigger assert if uncommented
    };
    char _pad[0x10000];
} flash_t;

_Static_assert(sizeof(flash_t) == 0x10000, "Oops, flash_t got too large");

int main() {
    flash_t flash;
    printf("offsetof(flash.firstElement) = %zi\n", offsetof(flash_t, firstElement));
    printf("offsetof(flash.secondElementArray) = %zi\n", offsetof(flash_t, secondElementArray));
    printf("offsetof(flash.thirdElement) = %zi\n", offsetof(flash_t, thirdElement));
    printf("sizeof(flash) = %zi\n", sizeof flash);
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

产生预期输出:

offsetof(flash.firstElement) = 0
offsetof(flash.secondElementArray) = 2
offsetof(flash.thirdElement) = 36
sizeof(flash) = 65536
Run Code Online (Sandbox Code Playgroud)

编辑

  • 正如评论中所建议的,_pad可以将联合成员重命名为,_rawData因为 的语义_padpad问题中的不同。

  • 如果pad需要类型中的成员,则可以将其添加为匿名结构末尾的灵活成员。

typedef union { struct { ...; uint8_t pad[]; }; char _rawData[0x10000]; } flash_t;
Run Code Online (Sandbox Code Playgroud)

  • @theonlygusti,它依赖于编译器。它尝试将“float”放置在可被 4 整除的地址处。数字 36 是 34 之后的第一个地址。 (10认同)
  • 也许还值得同时从“pad”重命名为“rawData”之类的东西 - 否则在填充数据结构的其余部分后用已知数据填充“pad”的代码可能会做完全错误的事情。 (2认同)
  • @JanDorniak,据我所知,C 标准只要求连续成员的偏移量严格增加,并且第一个成员的偏移量为 0。其他一切都是特定于实现的。 (2认同)