【问题标题】:compile-time struct size check, error out if odd编译时结构大小检查,如果奇数则出错
【发布时间】:2013-10-24 13:21:36
【问题描述】:

如果sizeof (struct Astruct) 不均匀,有什么方法可以让编译器退出?

背景资料: 我们有一个 16 位微处理器,如果 16 位值未对齐,它将给出处理器对齐错误。这可能发生在以下情况:

typedef struct
{
    U8BIT u8BitValue1;
    U8BIT u8BitValue2;
    U8BIT u8BitValue3;
} unevenAmountOf8BitValues;

typedef struct
{
    U16BIT u16BitValue1;
    U16BIT u16BitValue2;
} my16BitValues;

#define U8BIT_COUNT 3
#define U16BIT_COUNT 2

typedef struct
{
    unevenAmountOf8BitValues u8BitValues;
    my16BitValues u16BitValues;
} valuesCombined;

typedef union
{
    valuesCombined myValues;
    U8BIT buffer[sizeof(valuesCombined)];

    struct
    {
         U8BIT bufferU8[U8BIT_COUNT];
         U16BIT bufferU16[U16BIT_COUNT]; /* <<-- missalignment */
    } valuesPerType;
} myValuesInRamAndRom

我们现在要做的是手动计算 U8BIT/U16BIT/U32BIT 值的数量(嗯,使用 excel 跟踪数量)并将其放入 U(8/16/32)BIT_COUNT 定义中,然后执行以下操作:

#if U8BIT_COUNT % 2 == 1
#error The number of U8BIT parameters need to be even, add a dummy
#endif

跟踪 U8-/U16-/U32BIT 值的数量很容易出错,我们有相当多的时间在想“嘿,它不工作”,一个小时或之后,哦!该死,忘了调整定义的值的数量。

一种首选方法是使用 sizeof 运算符,但它不能用于错误检查,我真的很想保留它。

那么无论如何要使用 sizeof 运算符 and 来保持某种形式的错误检查,以确保 U8BIT 值的数量必须是偶数?


LundinAaron McDaid 的组合解决方案:

#define COMPILE_TIME_ASSERT(expr) {typedef U8BIT COMP_TIME_ASSERT[((!!(expr))*2-1)];}

【问题讨论】:

  • 如果错位总是出现在单个结构中,请编写一个单独的 perl/python/bash(?) 脚本,该脚本将对其进行检查并将其放入您的Makefile
  • @Dariusz 可能需要注意,我们使用的是富士通 Softune 编译器,所以没有方便的花花公子 Makefile mumbo-jumbo。更准确地说:FFMC-16 Family Softune Workbench V30L29
  • @Dariusz 我更喜欢不使用像 perl/python/bash 这样的外部工具,因为我是这家公司中唯一一个除了 C 之外什么都懂的人(Python,耶!)。我是一个外来人员,一两个月后就要离开这里。所以我更喜欢在 C 中工作的东西,如果可能的话。
  • @DaanTimmer C++ 怎么样?你能用那个吗?那时可以使用static_assert
  • @H2CO3 这在 C 中也是可能的,从 C11 开始。

标签: c


【解决方案1】:

这是允许在同一个版本中多次使用同一个断言宏的版本 文件。

/*
    General purpose static assert.

    Works in/out -side of scope:
        STATIC_ASSERT(sizeof(long)==8);
        int main()
        {
            STATIC_ASSERT(sizeof(int)==4);
        }
*/
#define STATIC_ASSERT(X)            STATIC_ASSERT2(X,__LINE__)

/*
    These macros are required by STATIC_ASSERT to make token pasting work.
    Not really useful by themselves.
*/
#define STATIC_ASSERT2(X,L)         STATIC_ASSERT3(X,L)
#define STATIC_ASSERT3(X,L)         STATIC_ASSERT_MSG(X,at_line_##L)

/*
    Static assertion with special error message.
    Note: It depends on compiler whether message is visible or not!

    STATIC_ASSERT_MSG(sizeof(long)==8, long_is_not_eight_bytes);
*/
#define STATIC_ASSERT_MSG(COND,MSG) \
    typedef char static_assertion_##MSG[(!!(COND))*2-1]

【讨论】:

    【解决方案2】:

    使用 C11 编译器,使用:

    static_assert (sizeof(the struct) % 2 == 0,
                   "Misaligned");
    

    对于较旧的编译器,您可以使用诸如

    之类的肮脏技巧
    #define COMPILE_TIME_ASSERT(expr) {typedef char COMP_TIME_ASSERT[(expr) ? 1 : 0];}
    
    ...
    
    COMPILE_TIME_ASSERT(sizeof(the_struct) % 2 == 0);
    

    然而,针对您的特定问题的真正解决方案可能是确保启用结构填充。那么你不应该得到任何错位。

    【讨论】:

    • 遗憾的是没有 C11 编译器。见评论:stackoverflow.com/questions/19403233/…
    • @DaanTimmer 然后使用 COMPILE_TIME_ASSERT。每次结构具有奇数大小时,它都会给出“数组不能以零大小声明”编译器错误。
    • 是的,我读得太快了,没有看到你答案的第二部分。我会将这两个答案都扔到小组中,看看哪一个是首选方法。
    • @DaanTimmer 如果 static_assert 不是一个选项,我会说 typedef。因为如果需要,您可以轻松更改宏并将编译时检查放在函数之外。只需删除 { }。但是我想你也可以创建一个永远不会从任何地方调用的函数,并将所有静态检查放入其中。
    • 你的没有给出任何编译错误,只有 1 个(偶数)或 2 个(奇数)警告。
    【解决方案3】:

    这是可能的,使用Linux kernel 中也使用的技巧:

    #define BUILD_BUG_OR_ZERO(e) (sizeof(struct{ int:-!!(e);}))
    #define ENSURE_EVEN_SIZE(e) BUILD_BUG_OR_ZERO(sizeof(e) % 2 == 1)
    
    struct uneven{
      char a,b,c;
    };
    
    struct even{
      char a,b,c,d;
    };
    
    int main(){
      ENSURE_EVEN_SIZE(struct even);
      /* compiler error: */
      ENSURE_EVEN_SIZE(struct uneven);
    }
    

    如果sizeof(e) % 2 == 1 为真,则位域int:-!!(e) 将具有负大小,这是被禁止的。 (Ideone)

    【讨论】:

    • 然而,这个 Linux 内核宏背后的基本原理是相当模糊的 iirc。他们本可以声明一个大小为零的数组,但由于 Linux 内核依赖于非标准的 GCC 特性,比如声明为大小为 0 的灵活数组成员,所以他们不能这样做。如果您使用标准 C,只需 typedef 一个长度为零的数组即可。更加理智和可读。
    • (换句话说,Linux 内核正在使用来自 GCC 的晦涩的 C99 之前的非标准特性,它使您能够编写:struct a {int x; int arr[0]; }。这就是为什么他们编写了一个晦涩的零大小位字段版本,而不是声明一个零长度数组。)
    • 我们可以将数组的大小设置为 1 或 -1,这取决于真假。这将避免对零长度数组的有效性产生任何混淆。 #define BUILD_BUG_OR_ZERO(e) (sizeof(struct{ char x[ (!!(e))*2-1 ]; }))
    • 遗憾的是,这个答案不会在“位域负数”或“位域零”上出错。除此之外,它还会引发 2 个额外的警告,遗憾的是我们不得不将警告视为错误。
    • 如果使用这种位域技术,那么((!!(e))*2-1) 而不是-!!(e) 呢?这将等于 -1 或 1,避免零宽度位域的问题。
    猜你喜欢
    • 2013-10-24
    • 2015-03-09
    • 1970-01-01
    • 1970-01-01
    • 2017-05-30
    • 2019-05-09
    • 1970-01-01
    • 2015-07-31
    • 1970-01-01
    相关资源
    最近更新 更多