如何在C语言中编译时打印sizeof()的结果?

94

如何在C语言的编译时打印出sizeof()函数的结果?

目前,我使用静态断言(基于其他网络资源自制)将sizeof()的结果与各种常量进行比较。虽然这样做可行,但并不优雅,也不快速。我还可以创建变量/结构的实例,并查看映射文件,但这也不如直接调用/命令/运算符来得优雅和快速。此外,这是一个使用多个交叉编译器的嵌入式项目...因此,在目标设备上构建并加载一个示例程序,然后读取一个值甚至比上述任一方法都更麻烦。

在我的情况下(使用旧版GCC),#warning sizeof(MyStruct)在打印警告之前并没有解释sizeof()。


什么是动机? - Ed Heal
4
不需要深入查看映射文件即可了解多层结构的大小。 - altendky
1
嗯,#warning语句在适当的编译器启动之前由预处理器处理 - 所以我认为这是不可能的。我猜编写一个小的测试程序,在构建过程中作为自定义步骤调用它,可能是一个解决方案。祝你好运。 - user422005
1
“far from ... fast”是什么意思?静态断言在编译时进行测试(sizeof也在编译时评估)。 - mafso
2
如果你有一个针对目标的C++编译器,你可以使用https://dev59.com/h3I-5IYBdhLWcg3wHUjP进行检查。 - nos
显示剩余11条评论
14个回答

123

我在寻找类似功能时偶然发现了这个:

Is it possible to print out the size of a C++ class at compile-time?

这启发了我想到了以下内容:

char (*__kaboom)[sizeof( YourTypeHere )] = 1;

这会导致在VS2015中出现以下警告:

warning C4047: 'initializing': 'DWORD (*)[88]' differs in levels of indirection from 'int'

在这种情况下,88将是你要查找的大小。

虽然有点hacky,但确实起到了作用。可能已经晚了几年,但希望对某些人有用。

我还没有机会尝试使用gcc或clang,但如果有人在我之前没有尝试过,我会确认它是否有效。

编辑:在clang 3.6中可以直接使用

唯一我能让GCC工作的技巧是滥用-Wformat并且宏定义一个像以下这样的函数:

void kaboom_print( void )
{
    printf( "%d", __kaboom );
}

这将会给你一个警告,例如:

...blah blah blah... argument 2 has type 'char (*)[88]'

这个建议可能比原来的建议更加恶心,但也许对于熟悉gcc更好的人可以想到更好的警告来滥用。


2
一年后再次访问,我发现上述gcc的解决方案不再适用(gcc 4.4.2)。经过更多搜索,我发现https://dev59.com/PHvZa4cB1Zd3GeqP-hFr(使用一个太大的数组,带有`-Wframe-larger-than`)仍然有效(您必须向下滚动到已接受的答案,因为它不在顶部,原因未知...)。 - blackghost
1
我最近使用的Clang版本运行良好,但你提供的链接也有效,所以非常好。 - Michael Dorgan
1
我喜欢这个方案!不管怎样,可以有人删除kaboom_print函数中printf语句中的最后一个引号吗?这只会给我带来额外的错误,我对此不感兴趣。 - ola1olsson
1
今天这个确实为我节省了一些时间。唯一奇怪的是,由于大小不是X,静态断言失败了... 这样做是为了检查编译器认为的大小... 给我X :P - inquam
这是今天的贴士:-),此外我可以宣布它与得克萨斯仪器(TI)的C2000编译器兼容。 - Otzen
显示剩余3条评论

28

在所有的C编译器中,复制 case 常量是一个能够保证运行的技巧,无论每个编译器如何报告错误。对于Visual C++来说,它非常简单:

struct X {
    int a,b;
    int c[10];
};
int _tmain(int argc, _TCHAR* argv[])
{
    int dummy;

    switch (dummy) {
    case sizeof(X):
    case sizeof(X):
        break;
    }
    return 0;
}

编译结果:

 ------ Build started: Project: cpptest, Configuration: Debug Win32 ------
 cpptest.cpp c:\work\cpptest\cpptest\cpptest.cpp(29): error C2196: case value '48' already used
 ========== Build: 0 succeeded, 1 failed, 0 up-to-date, 0 skipped ==========

所以结构体X的大小为48

编辑(2020年6月3日): 对于只打印“重复情况值”的gcc或任何其他编译器,我使用这个技巧来缩小该值:

1)添加一个案例值1==2(表示false)

2)通过试错法,缩小值,例如我尝试猜测sizeof(X)是否大于16:

#include <stdio.h>
typedef struct _X {
    int a;
    char b[10];
} X;
int main()
{
    printf("Hello World");

    int dummy=0   ;
    switch (dummy) {
    case  1==2:
    case sizeof( X)>16:
    //case 16:
    break;
    }
    return 0;
}

结果:

main.c: In function ‘main’:
main.c:14:5: error: duplicate case value
     case sizeof( X)>16:
     ^~~~
main.c:13:5: error: previously used here
     case  1==2:

所以它是假的,即 sizeof(X)<=16。

3) 使用其他合理的值重复尝试。例如,尝试猜测它是 16,即 sizeof(X)==16。如果它没有抱怨重复情况值,则该表达式为真。

4) 可选地添加一个case 16来验证它,例如:

#include <stdio.h>
typedef struct _X {
    int a;
    char b[10];
} X;
int main()
{
    printf("Hello World");

    int dummy=0   ;
    switch (dummy) {
   // case  1==2:
    case sizeof( X):
    case 16:
    break;
    }
    return 0;
}

结果

main.c: In function ‘main’:
main.c:15:5: error: duplicate case value
     case 16:
     ^~~~
main.c:14:5: error: previously used here
     case sizeof( X):

确认sizeof(X)为16。

另外,观察到gcc可以报告多个重复项,因此这个技巧可以用于在单次遍历中进行多次猜测:

#include <stdio.h>
typedef struct _X {
    int a;
    char b[10];
} X;
int main()
{
    printf("Hello World");

    int dummy=0   ;
    switch (dummy) {
    case  1==2: //represents false
    case 1==1: //represents true
    case sizeof( X)>10:
    case sizeof( X)>12:
    case sizeof( X)>14:
    case sizeof( X)>16:
    case  sizeof( X)==16:
    //case 16:
    break;
    }
    return 0;
}

结果

main.c: In function ‘main’:
main.c:14:5: error: duplicate case value
     case sizeof( X)>10:
     ^~~~
main.c:13:5: error: previously used here
     case 1==1:
     ^~~~
main.c:15:5: error: duplicate case value
     case sizeof( X)>12:
     ^~~~
main.c:13:5: error: previously used here
     case 1==1:
     ^~~~
main.c:16:5: error: duplicate case value
     case sizeof( X)>14:
     ^~~~
main.c:13:5: error: previously used here
     case 1==1:
     ^~~~
main.c:17:5: error: duplicate case value
     case sizeof( X)>16:
     ^~~~
main.c:12:5: error: previously used here
     case  1==2:
     ^~~~
main.c:18:5: error: duplicate case value
     case  sizeof( X)==16:
     ^~~~
main.c:13:5: error: previously used here
     case 1==1:
     ^~~~

建议 sizeof(X) 大于10、大于12、大于14,但不大于16。加上 ==16 作为最后的猜测。


很不幸,它在我较旧版本的gcc 4.2.0上无法运行,它只显示“重复的case值”,而没有打印出该值。 - eresonance
一些在编译期间打印计算出的整数值的通用方法:https://dev59.com/F14b5IYBdhLWcg3wpzLv#44183676 - JavaMan
这是我在C语言中使用GCC编译器唯一有效的方法。 - Michael
修改后使用gcc的技巧,只打印“重复的case值”,而不打印实际的case值。 - JavaMan
为什么这不是最佳答案? 对我来说,使用MSVS C++可以工作。 - Tatiana Racheva

18
以下方法在GCC、Clang、MSVC等多个编译器中都适用,甚至在旧版本中也适用。它基于将一个函数参数从数组指针类型转换为标量类型失败的原理。编译器会打印出该数组的大小,因此您可以从输出中获取该值。无论是在C模式还是C++模式下都可使用。
查找sizeof(long)的示例代码(在线尝试):
char checker(int);
char checkSizeOfInt[sizeof(long)]={checker(&checkSizeOfInt)};

相关输出示例:

  • GCC 4.4.7

<source>:1: 注意:期望的是 'int' 类型,但参数为 'char (*)[8]' 类型

  • clang 3.0.0

<source>:1:6: 注意:候选函数不可行:第一个参数没有已知的从 'char (*)[8]' 到 'int' 的转换

  • MSVC 19.14

<source>(2): 警告 C4047:'function':'int' 比 'char (*)[4]' 的间接级别不同


10
另外一种有效的方法是:
char __foo[sizeof(MyStruct) + 1] = {[sizeof(MyStruct)] = ""};

适用于较老的gcc 5.x版本。会产生以下类似错误:

a.c:8:54: error: initializer element is not computable at load time
a.c:8:54: note: (near initialization for 'a[8]')

注:显然,这个方法只适用于gcc。其他的方法对我来说都没有起作用。


5
你甚至不需要为数组指定大小:char __foo[] = {[sizeof(MyStruct)] = ""}; - Garogolun

4

这是我使用GCC时的快速而简单的解决方案:

(char[sizeof(long long)])"bla";

这会导致一个错误信息,其中透露了 long long 的大小:

ISO C++ forbids casting to an array type 'char [8]'

2
注意,这段代码在我的VS 2019中编译通过。但是将“bla”更改为数字(例如4)可以解决问题。 - default

4

@jws 不错的想法!然而,sizeof(xxx)是一个常量表达式(除了VLA,https://en.cppreference.com/w/c/language/sizeof),所以sizeof操作符应该可以在case语句中使用:

enum e1 {dummy=-1};
enum e1 ev;
switch (ev) {
    case sizeof(myType):;
    break;
    default:;
}

..它在我的GCC中有效:“..\WinThreads.c:18:9: 警告:情况值'4'不在枚举类型'enum e1'中[-Wswitch]”


在我的电脑上,使用gcc版本8.3.0可以正常工作。 - Sneh Shikhar

3

我偶然发现了一个类似于Bakhazard好方法,而且这个方法产生的警告信息更加简洁,你可能会觉得它很有用:

char (*__fail)(void)[sizeof(uint64_t)] = 1;

这会产生错误信息。
Function cannot return array type 'char [8]'

这是使用最新版本的clang(1)进行测试的。


3
//main.cpp
#include <cstddef>
template <std::size_t x>
struct show_size;

void foo()
{
    show_size<sizeof(my_type)>();//!!please change `my_type` to your expected
}

int main()
{
    return 0;
}

你可以编译这段相当简单的代码,在预编译阶段,编译器会给出一个错误,在其中sizeof(my_type)将给出具体的值。例如:
g++ main.cpp

请不要仅仅发布代码作为答案,还要提供解释您的代码是如何解决问题的。带有解释的答案通常更有帮助和更高质量,并且更有可能吸引赞同。 - Dima Kozhevin
因为问题是关于C而不是C ++,所以被踩了。 - Michael Ansolis

1

我的gcc没有返回任何带有数组索引的结果,所以我根据这里的一些回复想出了另一种方法来获得实际答案。

int a;
a = 1/ (sizeof(struct page5_data) & 0x0001);
a = 1/ (sizeof(struct page5_data) & 0x0002);
a = 1/ (sizeof(struct page5_data) & 0x0004);
a = 1/ (sizeof(struct page5_data) & 0x0008);
a = 1/ (sizeof(struct page5_data) & 0x0010);
a = 1/ (sizeof(struct page5_data) & 0x0020);
a = 1/ (sizeof(struct page5_data) & 0x0040);
a = 1/ (sizeof(struct page5_data) & 0x0080);
a = 1/ (sizeof(struct page5_data) & 0x0100);
a = 1/ (sizeof(struct page5_data) & 0x0200);
a = 1/ (sizeof(struct page5_data) & 0x0400);
a = 1/ (sizeof(struct page5_data) & 0x0800);
a = 1/ (sizeof(struct page5_data) & 0x1000);
a = 1/ (sizeof(struct page5_data) & 0x2000);
a = 1/ (sizeof(struct page5_data) & 0x4000);
a = 1/ (sizeof(struct page5_data) & 0x8000);
(void)a;

给出:

test.c:115:7:错误:除以零[-Werror = div-by-zero] a = 1 /(sizeof(struct page5_data)&0x0001); ^

test.c:116:7:错误:除以零[-Werror = div-by-zero] a = 1 /(sizeof(struct page5_data)&0x0002); ^

test.c:125:7:错误:除以零[-Werror = div-by-zero] a = 1 /(sizeof(struct page5_data)&0x0400); ^

test.c:126:7:错误:除以零[-Werror = div-by-zero] a = 1 /(sizeof(struct page5_data)&0x0800); ^

test.c:127:7:错误:除以零[-Werror = div-by-zero] a = 1 /(sizeof(struct page5_data)&0x1000); ^

test.c:128:7:错误:除以零[-Werror = div-by-zero] a = 1 /(sizeof(struct page5_data)&0x2000); ^

test.c:129:7:错误:除以零[-Werror = div-by-zero] a = 1 /(sizeof(struct page5_data)&0x4000); ^

test.c:130:7:错误:除以零[-Werror = div-by-zero] a = 1 /(sizeof(struct page5_data)&0x8000); ^

这告诉我们一个16位数字的零位,其他位必须为“1”。 因此,我们可以确定该值为:

0000 0011 1111 1100 = 0x03fc


1
一个更简单的方法是使用gdb。如果您能够使用调试符号编译您的.elf文件。
例如:
在我的test.c文件中,我声明
typedef struct {
  int a;
  int b;
  char d;
  long e[10];
} my_struct_t;

我使用gcc编译它

gcc test.c -o app.elf -g

我运行

gdb app.elf

而且在不运行可执行文件的情况下,你可以做到这一点

gdb app.elf
(gdb) ptype /o my_struct_t

type = struct {
/*    0      |     4 */    int a;
/*    4      |     4 */    int b;
/*    8      |     1 */    char d;
/* XXX  7-byte hole  */
/*   16      |    80 */    long e[10];

/* total size (bytes):   96 */
}

你也可以在gdb中打印sizeof函数的结果。
(gdb) p sizeof(my_struct_t)
$1 = 96

因为您不需要运行可执行文件,.elf 文件甚至可以是交叉编译的产物(只要使用您的工具链的 gdb 或者 gdb-multiarch)。


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