如何在预处理器宏中使用“ sizeof”?


95

有什么方法可以sizeof在预处理器宏中使用吗?

例如,多年来,我想做很多事情,例如:

#if sizeof(someThing) != PAGE_SIZE
#error Data structure doesn't match page size
#endif

我在这里检查的确切内容已完全组成-重点是,我经常喜欢放入这些类型的(大小或对齐)编译时检查,以防止有人修改数据结构而导致对齐或重新对齐的情况。大小会破坏它们的东西。

不用说-我似乎无法以sizeof上述方式使用。


这就是构建系统存在的确切原因。
西蒙·托特

3
这就是#error指令应始终用双引号引起的确切原因(由于“ dosn't”而未定的字符常量)。
詹斯

1
您好@Brad。请考虑将接受的答案更改为虚无的答案,因为与此同时,当前接受的答案已过时。
博多·蒂森

@BodoThiesen完成。
布拉德,

Answers:


69

有几种方法可以做到这一点。如果sizeof(someThing)等于PAGE_SIZE,后面的代码片段将不产生任何代码;否则会产生编译时错误。

1. C11方式

从C11开始,您可以使用static_assert(需要#include <assert.h>)。

用法:

static_assert(sizeof(someThing) == PAGE_SIZE, "Data structure doesn't match page size");

2.自定义宏

如果您只是想在sizeof(something)预期之外获得编译时错误,则可以使用以下宏:

#define BUILD_BUG_ON(condition) ((void)sizeof(char[1 - 2*!!(condition)]))

用法:

BUILD_BUG_ON( sizeof(someThing) != PAGE_SIZE );

本文详细说明了其工作原理。

3.特定于MS

在Microsoft C ++编译器上,您可以使用C_ASSERT宏(requires #include <windows.h>),该宏使用与第2节中描述的技巧类似的技巧。

用法:

C_ASSERT(sizeof(someThing) == PAGE_SIZE);

4
...太疯狂了 为什么这不是公认的答案,@ Brad(OP)?
工程师

很好的参考BUILD_BUG_ON。
PetrVepřek'16

2
该宏在GNU gcc(在4.8.4版中测试)(Linux)中不起作用。在((void)sizeof(...expected identifier or '(' before 'void'和时出错expected ')' before 'sizeof'。但是原则上size_t x = (sizeof(...确实可以按预期工作。您必须以某种方式“使用”结果。为了允许在函数内部或全局范围内多次调用此方法,可以extern char _BUILD_BUG_ON_ [ (sizeof(...) ];重复使用类似的方法(无副作用,实际上不引用_BUILD_BUG_ON_任何地方)。
JonBrave

使用静态声明的时间比2011年已经长了一年。

1
@工程师的眼神,疯狂已经停止了;)
Bodo Thiesen

70

无论如何,sizeof在预处理器宏中使用“ ”?

否。条件指令采用一组受限制的条件表达式。sizeof是不允许的事情之一。

预处理指令在解析源之前(至少在概念上)进行评估,因此尚无任何类型或变量来获取其大小。

但是,有一些技巧可以在C语言中获得编译时断言(例如,请参阅此页面)。


很棒的文章-聪明的解决方案!尽管您必须管理-他们确实将C语法推到了极限,才能使这一语法正常工作!:-O
Brad

1
结果-如文章所述-我现在正在构建Linux内核代码-内核中已经有一个定义-BUILD_BUG_ON-内核将其用于诸如以下的事情:BUILD_BUG_ON(sizeof(char)!= 8)
Brad 2010年

2
@Brad BUILD_BUG_ON和其他人生成肯定不正确的代码,这些代码将无法编译(并在处理过程中提供一些非显而易见的错误消息)。实际上不是#if语句,因此您不能例如基于此排除代码块。
凯塔尔

10

我知道这是一个较晚的答案,但是要添加到Mike的版本中,这是我们使用的不分配任何内存的版本。我没有提出原始的尺寸检查,几年前我在互联网上找到了它,很遗憾无法参考作者。其他两个只是同一概念的扩展。

因为它们是typedef的,所以不分配任何东西。名称中带有__LINE__的名称总是一个不同的名称,因此可以将其复制并粘贴到需要的位置。这适用于MS Visual Studio C编译器和GCC Arm编译器。它在CodeWarrior中不起作用,CW抱怨重新定义,没有使用__LINE__预处理器构造。

//Check overall structure size
typedef char p__LINE__[ (sizeof(PARS) == 4184) ? 1 : -1];

//check 8 byte alignment for flash write or similar
typedef char p__LINE__[ ((sizeof(PARS) % 8) == 0) ? 1 : 1];

//check offset in structure to ensure a piece didn't move
typedef char p__LINE__[ (offsetof(PARS, SUB_PARS) == 912) ? 1 : -1];

对于标准的C项目,这实际上非常有效……我喜欢它!
阿什莉·邓肯

1
由于分配为零,因此该答案应该是正确的答案。甚至更好地定义为:#define STATIC_ASSERT(condition) typedef char p__LINE__[ (condition) ? 1 : -1];
Renaud Cerrato

p__LINE__没有产生唯一的名称。它产生p__LINE__作为变量。您将需要一个preproc宏并使用sys / cdefs.h中的__CONCAT。
Coroos

9

我知道这个线程真的很旧,但是...

我的解决方案:

extern char __CHECK__[1/!(<<EXPRESSION THAT SHOULD COME TO ZERO>>)];

只要该表达式等于零,它就可以编译。其他什么,它就在那里炸毁。因为变量是extern'd,所以它将不占用空间,并且只要没有人引用它(他们不会),就不会导致链接错误。

不像assert宏那样灵活,但是我无法在我的GCC版本中进行编译,因此可以...而且我认为它将几乎可以在任何地方编译。


6
切勿发明以两个下划线开头的宏。这条路就是疯狂(也就是未定义的行为)。
詹斯


用arm gcc编译器编译时不起作用。给出了预期的错误“错误:在文件范围内可变地修改了' CHECK '”
雷鸟

@Jens你是对的,但这实际上不是一个宏,它是一个变量声明。当然,它可能会干扰宏。
Melebius

4

现有的答案仅显示了如何基于类型的大小来实现“编译时断言”的效果。在这种特殊情况下,这可能可以满足OP的需求,但是在其他情况下,您确实需要基于类型大小的预处理器。方法如下:

给自己写一个小的C程序,例如:

/* you could call this sizeof_int.c if you like... */
#include <stdio.h>
/* 'int' is just an example, it could be any other type */
int main(void) { printf("%zd", sizeof(int); }

编译。用您喜欢的脚本语言编写一个脚本,该脚本将运行上述C程序并捕获其输出。使用该输出生成C头文件。例如,如果您使用的是Ruby,它可能看起来像:

sizeof_int = `./sizeof_int`
File.open('include/sizes.h','w') { |f| f.write(<<HEADER) }
/* COMPUTER-GENERATED, DO NOT EDIT BY HAND! */
#define SIZEOF_INT #{sizeof_int}
/* others can go here... */
HEADER

然后将规则添加到Makefile或其他构建脚本中,这将使其运行上述脚本build sizes.h

包括sizes.h在需要使用基于大小的预处理器条件的任何地方。

做完了!

(您是否曾经键入过./configure && make要构建程序的代码?configure基本上像上面的脚本一样执行脚本...)


当您使用“ autoconf”之类的工具时,情况与此类似。
亚历山大·斯托尔

4

那下一个宏呢:

/* 
 * Simple compile time assertion.
 * Example: CT_ASSERT(sizeof foo <= 16, foo_can_not_exceed_16_bytes);
 */
#define CT_ASSERT(exp, message_identifier) \
    struct compile_time_assertion { \
        char message_identifier : 8 + !(exp); \
    }

例如,在MSVC的注释中,类似以下内容:

test.c(42) : error C2034: 'foo_can_not_exceed_16_bytes' : type of bit field too small for number of bits

1
这不是问题的答案,因为您不能在#if预处理指令中使用它。
cmaster-恢复莫妮卡2014年

1

作为此讨论的参考,我报告说某些编译器会在预处理时间获得sizeof()。

JamesMcNellis的答案是正确的,但是某些编译器会遇到此限制(这可能违反了严格的ansi c)。

在这种情况下,我指的是IAR C编译器(可能是专业微控制器/嵌入式编程的领先者)。


你确定吗?IAR 声称其编译器符合ISO C90和C99标准,不允许sizeof在预处理时进行评估。sizeof应该被当作一个标识符。
基思·汤普森

6
1998年,comp.std.c新闻组中的某人写道:“ #if (sizeof(int) == 8)在某些情况下(在某些编译器上)确实有效,这真是太好了。” 回复:“一定是在我的时间之前。”来自丹尼斯·里奇。
基思·汤普森

抱歉,您的回复很晚...是的,我敢肯定,我有一些针对8/16/32位微控制器,瑞萨编译器(R8和RX)编译的代码示例。
graziano Governoratori

实际上,应该有一些要求“严格” ISO C的选项
graziano Governoratori 2013年

只要标准不禁止它,就不会违反该标准。那么我将其称为罕见的非标准功能-因此,在常规情况下,为了保持编译器独立性和平台可移植性,您将避免使用它。
亚历山大·斯托尔

1

#define SIZEOF(x) ((char*)(&(x) + 1) - (char*)&(x)) 可能有用


这是一个有趣的解决方案,但是它仅适用于定义的变量,不适用于类型。适用于类型但不适用于变量的另一种解决方案是:#define SIZEOF_TYPE(x) (((x*)0) + 1)
greydet 2013年

7
它不起作用,因为您仍然不能在#if条件内使用它的结果。它没有提供任何好处sizeof(x)
Interjay

1

在C11 _Static_assert中添加了关键字。可以像这样使用:

_Static_assert(sizeof(someThing) == PAGE_SIZE, "Data structure doesn't match page size")

0

在我的便携式C ++代码中(http://www.starmessagesoftware.com/cpcclibrary/)中,我想对某些结构或类的大小进行安全防护。

我没有找到预处理器引发错误的方法(这不能与sizeof()一起使用,如此处所述),而是在这里找到了一种导致编译器引发错误的解决方案。 http://www.barrgroup.com/Embedded-Systems/How-To/C-Fixed-Width-Integers-C99

我必须修改该代码,以使其在我的编译器(xcode)中引发错误:

static union
{
    char   int8_t_incorrect[sizeof(  int8_t) == 1 ? 1: -1];
    char  uint8_t_incorrect[sizeof( uint8_t) == 1 ? 1: -1];
    char  int16_t_incorrect[sizeof( int16_t) == 2 ? 1: -1];
    char uint16_t_incorrect[sizeof(uint16_t) == 2 ? 1: -1];
    char  int32_t_incorrect[sizeof( int32_t) == 4 ? 1: -1];
    char uint32_t_incorrect[sizeof(uint32_t) == 4 ? 1: -1];
};

2
您确定那些“ -1”将永远不会被解释为0xFFFF…FF,从而导致您的程序请求所有可寻址的内存吗?
安东·萨姆索诺夫,2015年

0

在尝试了上述宏之后,该片段似乎产生了所需的结果(t.h):

#include <sys/cdefs.h>
#define STATIC_ASSERT(condition) typedef char __CONCAT(_static_assert_, __LINE__)[ (condition) ? 1 : -1]
STATIC_ASSERT(sizeof(int) == 4);
STATIC_ASSERT(sizeof(int) == 42);

正在运行cc -E t.h

# 1 "t.h"
...
# 2 "t.h" 2

typedef char _static_assert_3[ (sizeof(int) == 4) ? 1 : -1];
typedef char _static_assert_4[ (sizeof(int) == 42) ? 1 : -1];

正在运行cc -o t.o t.h

% cc -o t.o t.h
t.h:4:1: error: '_static_assert_4' declared as an array with a negative size
STATIC_ASSERT(sizeof(int) == 42);
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
t.h:2:84: note: expanded from macro 'STATIC_ASSERT'
  ...typedef char __CONCAT(_static_assert_, __LINE__)[ (condition) ? 1 : -1]
                                                       ^~~~~~~~~~~~~~~~~~~~
1 error generated.

毕竟42并不是一切的答案...


0

为了在编译时根据其约束检查数据结构的大小,我使用了这个技巧。

#if defined(__GNUC__)
{ char c1[sizeof(x)-MAX_SIZEOF_X-1]; } // brakets limit c1's scope
#else
{ char c1[sizeof(x)-MAX_SIZEOF_X]; }   
#endif

如果x的大小大于或等于限制MAX_SIZEOF_X,则gcc将抱怨“数组大小太大”错误。VC ++将发出错误C2148(“数组的总大小不能超过0x7fffffff字节”)或C4266“无法分配常量大小为0的数组”。

这两个定义是必需的,因为gcc允许以这种方式定义零大小的数组(sizeof x-n)。


-10

sizeof运算符不适用于预处理器,但是您可以转移sizeof到编译器并在运行时检查条件:

#define elem_t double

#define compiler_size(x) sizeof(x)

elem_t n;
if (compiler_size(elem_t) == sizeof(int)) {
    printf("%d",(int)n);
} else {
    printf("%lf",(double)n);
}

13
如何改善已被接受的答案?定义的目的是什么compiler_size?您的示例尝试显示什么?
ugoren 2013年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.