如何在编译时检查结构体的大小?

46

我希望在编译期间添加代码,检查结构体的大小以确保它是预定义的大小。例如,当我将此代码移植或在编译时添加/删除结构体中的项目时,我希望确保结构体的大小为1024个字节:

#pack(1)
struct mystruct
{
    int item1;
    int item2[100];
    char item3[4];
    char item5;
    char padding[615];
 }

我知道如何在运行时使用类似这样的代码来完成此操作:

 if(sizeof(mystruct) != 1024)
 { 
     throw exception("Size is not correct");
 }

但如果我在运行时执行这个操作就会浪费处理资源,因此我需要在编译时执行这个操作。

我应该如何在编译时执行这个操作呢?


预处理器并不了解C++,它只有一组非常有限的标记可以操作。您可以尝试使用static_assert。 - PlasmaHH
13
sizeof 的部分仍然在编译时完成。对于编译时断言,static_assert 可以很好地工作。 - chris
你可以在这里找到一个例子:https://dev59.com/WGgu5IYBdhLWcg3wK0Gr - RonenKr
这是应该在单元测试中检查的事情,而不是在程序本身中检查。 - John Dibling
5个回答

40

自从C++11以来,您可以在编译期间检查大小:

static_assert (sizeof(mystruct) == 1024, "Size is not correct");

对于不支持C++11的编译器,Boost提供了一个解决方法:

BOOST_STATIC_ASSERT_MSG(sizeof(mystruct) == 1024, "Size is not correct");

请查看文档


10
注意:关于解决方法,一个非 Boost 的解决方案就是 char const Array[sizeof(mystruct) == 1024 ? 1 : -1]; - Matthieu M.
@MatthieuM。使用boost你可能会得到一个更好的错误信息(至少会包含"static assertion failure",如果没有则是您自己的信息)。 - n. m.
4
同意,但有些人必须没有。 - Matthieu M.
1
如果编译器支持C++11,Boost将使用static_assert; 如果不支持,则会退回到模板实例化技术,或者可能使用一些特定于编译器的技巧来改进错误消息(它识别许多编译器并为它们进行自适应)。 - n. m.

27

如果您没有C++11或Boost,可以尝试这个:

typedef char assertion_on_mystruct[(   sizeof(mystruct)==1024   )*2-1 ];

如果该语句为假,则使用负数大小定义数组类型,您的编译器应该会给出一个错误消息。如果为真,则大小将为1,是一个有效的大小。例如,g ++会给出以下输出:

template.cpp:10:70: error: size of array ‘assertion_on_mystruct’ is negative

我承认这并不是最有用的东西,因为它只告诉您错误的行号。但这是我能想到的最简单、独立的技术。

更一般的宏是:

#define DUMB_STATIC_ASSERT(test) typedef char assertion_on_mystruct[( !!(test) )*2-1 ]

DUMB_STATIC_ASSERT( sizeof(mystruct)==1024 );
DUMB_STATIC_ASSERT( sizeof(my_other_struct)==23 );
DUMB_STATIC_ASSERT( sizeof(minimum_size_struct) >= 23 );

6
即使你完全没有使用 ++,这仍然可以在纯旧版的 C 语言中实现。不过在 C++ 中,通常会采用模板特化的方法。 - Jan Hudec
我在寻找解决同样问题的答案时,遇到了这个建议。我喜欢它可以放在头文件中,而<linux/kernel.h> BUILD_BUG_ON的所有内容都需要放在函数中。然而,我不喜欢它会创建不必要的编译器符号。有什么办法可以避免这种情况吗???我使用的是“普通的C”语言,并且升级到C11不是一个选项。 - Brian McFarland
1
好的,我想出了这个:#define SIZE_CHECK_STRUCT(sname, maxsize) typedef char sname##_size_check_struct[1 - 2 * !!(sizeof(struct sname) > (maxsize))]。去掉 struct 关键字,以便在 typedef 的结构体上使用。 - Brian McFarland
@BrianMcFarland,感谢您的!!技巧,可以将表达式强制转换为布尔值。我已经相应地编辑了我的代码。 - Aaron McDaid
1
我在使用C语言,而不是C++,但我刚刚测试了一下,显然gcc除非你指定“-Wpedantic”,否则不会为多个相同的typedef发出警告。只有当类型冲突时才会出现错误。 - Brian McFarland
显示剩余2条评论

7
自C++11起,您可以使用编译时处理的 static_assert
static_assert(sizeof(mystruct) == 1024, "Size is not correct");

如果大小不是1024字节,您将会得到编译错误。

4
如果您想在编译时检查它,可以使用模板元编程阶段。在标准C++中,您可以使用boost的静态断言static assert,该断言被宏BOOST_STATIC_ASSERT隐藏。您可以按照以下方式使用它:
#include <boost/static_assert.hpp>
...
BOOST_STATIC_ASSERT(sizeof(mystruct) == 1024);

如果断言未满足,上述代码将无法编译,并显示一些半可读的错误消息。
在C++11中,您可以使用静态断言获得更简单的功能,它引入了一个新关键字static_assert
static_assert(sizeof(mystruct) == 1024,"Size is not correct");

在预处理器阶段做相同的事情似乎是不可能的,但在你的情况下似乎并不真正需要。


2
请注意,sizeof()函数中包含了padding(填充)的大小:
struct A { int i; bool b; };

typedef char assertion_on_A[( ((sizeof(A)))== 8 )*2-1 ];
static_assert(sizeof(A) == 8, "sizeof A");

在这里,typedef和static_assert都期望大小为8。


请注意,OP 使用 #pack(1),这意味着不使用填充,我非常确定... - Alexis Wilke
请注意,OP使用了#pack(1),这意味着没有使用填充。我非常确定... - undefined
@Alexis:使用 -O3 进行测试,没有错误; - karsten
我明白了,我想我误读了问题。#pack(1) 不是问题,测试结果的大小才是。 - Alexis Wilke

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接