C的隐藏功能


141

我知道所有C编译器实现背后都有一个标准,因此应该没有隐藏的功能。尽管如此,我确信所有C开发人员都有他们一直使用的隐藏/秘密技巧。


如果您/某人要编辑“问题”以指示最佳隐藏功能的选择,例如在此问题的C#和Perl版本中,那将是非常不错的。
Donal Fellows,2010年

Answers:


62

函数指针。您可以使用函数指针表来实现,例如,快速间接线程代码解释器(FORTH)或字节码分派器,或模拟类似于OO的虚拟方法。

然后在标准库中有隐藏的gems,例如qsort(),bsearch(),strpbrk(),strcspn()[后两者对于实现strtok()替换很有用]。

C的一个缺点是有符号算术溢出是未定义行为(UB)。因此,每当看到x + y之类的表达式都被签名为整数时,它可能会溢出并导致UB。


29
但是,如果他们在溢出时指定了行为,那么在那些不是正常行为的体系结构上,它将变得非常缓慢。极低的运行时开销一直是C的设计目标,这意味着许多类似的事情尚未定义。
Mark Ba​​ker

9
我非常清楚为什么 UB是溢出的。这仍然是一个缺点,因为该标准至少应提供提供的库例程,该例程可以测试(不包括导致UB)算术溢出(所有基本操作)。
zvrba

2
@zvrba,“可以测试(所有基本运算的)算术溢出的库例程”(如果添加了该例程,那么对于任何整数算术运算而言,其性能都会受到严重影响)。=====案例研究Matlab专门添加了控制整数溢出行为以进行环绕或饱和的功能。每当溢出发生时,它也会引发异常==> Matlab整数运算的性能:非常慢。我自己的结论:我认为Matlab是一个引人注目的案例研究,它说明了为什么您不想要整数溢出检查。
特雷弗·博伊德·史密斯,

15
我说过,该标准应该提供支持以检查算术溢出。现在,如果您从不使用库例程,那么如何导致性能下降?
zvrba

5
一个很大的缺点是,GCC没有标志来捕获有符号整数溢出并抛出运行时异常。尽管有x86标志可以检测到这种情况,但GCC并未使用它们。拥有这样的标志将使非性能关键的(特别是传统的)应用程序受益于安全性,而无需进行最少的代码审查和重构。
Andrew Keeton

116

GCC编译器的更多技巧,但是您可以向编译器提供分支指示提示(在Linux内核中很常见)

#define likely(x)       __builtin_expect((x),1)
#define unlikely(x)     __builtin_expect((x),0)

参见:http : //kerneltrap.org/node/4705

我对此的喜欢之处在于,它还为某些功能增加了表达能力。

void foo(int arg)
{
     if (unlikely(arg == 0)) {
           do_this();
           return;
     }
     do_that();
     ...
}

2
这个技巧很酷... :)特别是使用您定义的宏。:)
sundar-恢复莫妮卡

77
int8_t
int16_t
int32_t
uint8_t
uint16_t
uint32_t

这些是标准中的可选项,但是它必须是隐藏的功能,因为人们不断地对其进行重新定义。我正在研究的一个代码库(至今仍然这样做)具有多个重新定义,所有重新定义都有不同的标识符。大多数情况下,它与预处理器宏一起使用:

#define INT16 short
#define INT32  long

等等。这使我想拔头发。 只需使用怪异的标准整数typedefs!


3
我认为它们是C99左右。我还没有找到一种可移植的方式来确保这些都可以实现。
akauppi

3
它们是C99的可选部分,但是我知道没有编译器供应商没有实现此功能。
本·柯林斯

10
stdint.h在C99中不是可选的,但是遵循C99标准显然是针对某些供应商的(咳嗽的 Microsoft)。
本·康比

5
@Pete,如果您想肛门:(1)此线程与任何Microsoft产品无关。(2)该线程根本与C ++无关。(3)是如C ++ 97没有这样的事情
本柯林斯

5
看看azillionmonkeys.com/qed/pstdint.h-接近于便携式的stdint.h
gnud

73

逗号运算符没有广泛使用。它当然可以被滥用,但它也非常有用。这种用法是最常见的一种:

for (int i=0; i<10; i++, doSomethingElse())
{
  /* whatever */
}

但是您可以在任何地方使用此运算符。观察:

int j = (printf("Assigning variable j\n"), getValueFromSomewhere());

每个语句都会被求值,但是表达式的值将是最后一个语句的值。


7
在CI的20年中,从未见过如此!
Martin Beckett

11
在C ++中,您甚至可以重载它。
Wouter Lievens

6
当然可以!!重载的危险在于,内建函数已经适用于所有内容,包括void,因此永远不会因缺少可用重载而进行编译。即,给程序员很多绳索。
亚伦,2009年

循环中的int不适用于C:这是C ++的改进。“,”与(i = 0,j = 10; i <j; j--,i ++)的运算是否相同?
Aif 2010年

63

将结构初始化为零

struct mystruct a = {0};

这将所有结构元素清零。


2
但是,它不会将填充零置零。
Mikeage

2
@simonn,如果结构包含非整数类型,它不会执行未定义的行为。当您解释浮点数/双精度数时,浮点数/双精度数内存中的值为0的memset仍将为零(浮点数/双精度设计是故意设计的)。
Trevor Boyd Smith

6
@Andrew:memset/ calloc做“所有字节为零”(即物理零),的确不是所有类型都定义。{ 0 } 保证使用适当的逻辑零值对所有内容进行初始化。例如,即使给定平台上的null值为,也保证指针会获得其正确的null值。0xBAADFOOD
AnT

1
@nvl:仅将对象占用的所有内存强制设置为all-bits-zero状态时,您将获得物理零。这是做什么的memset0作为第二个参数)。在源代码中初始化/分配(或)该对象时,将得到逻辑零。这两种零不一定会产生相同的结果。如带有指针的示例。当你在一个指针,你会得到一个指针。但是,当您分配一个指针时,您将获得null指针值,它在物理级别可能是或其他任何值。0{ 0 }memset0x000000xBAADF00D
AnT 2010年

3
@nvl:嗯,实际上区别通常只是概念上的。但是从理论上讲,几乎任何类型都可以拥有它。例如,double。通常,它是根据IEEE-754标准实现的,其中逻辑零和物理零是相同的。但是该语言不需要IEEE-754。因此,可能会发生这样的情况,当您这样做时double d = 0;(逻辑零),物理上内存所占用的某些位d将不会为零。
AnT

52

多字符常量:

int x = 'ABCD';

设置x0x41424344(或0x44434241,取决于体系结构)。

编辑:此技术不可移植,特别是如果您序列化int。但是,创建自记录枚举可能非常有用。例如

enum state {
    stopped = 'STOP',
    running = 'RUN!',
    waiting = 'WAIT',
};

如果您正在查看原始内存转储并且需要确定枚举的值而不必查找它,则这将变得更加简单。


我很确定这不是可移植的结构。创建多字符常量的结果是实现定义的。
Mark Bessey,

8
“不可移植”的注释完全忽略了这一点。这就像因为INT_MAX是“不可移植的”而批评使用INT_MAX的程序一样:)此功能可以根据需要进行移植。多字符常量是一项非常有用的功能,它提供了一种可读的方式来生成唯一的整数ID。
AnT

1
@Chris Lutz-我很确定尾随逗号会一直回到K&R。在第二版(1988)中对此进行了描述。
Ferruccio

1
@Ferruccio:您必须考虑汇总inititailizer列表中的结尾逗号。至于枚举声明中的尾部逗号-这是最近添加的C99。
AnT

3
您忘记了“

44

我从未使用过位字段,但对于超低级的东西,它们听起来很酷。

struct cat {
    unsigned int legs:3;  // 3 bits for legs (0-4 fit in 3 bits)
    unsigned int lives:4; // 4 bits for lives (0-9 fit in 4 bits)
    // ...
};

cat make_cat()
{
    cat kitty;
    kitty.legs = 4;
    kitty.lives = 9;
    return kitty;
}

这意味着sizeof(cat)可以小到sizeof(char)


谢谢大家加入Aaronleppie的评论。


结构和联合的结合更加有趣-在嵌入式系统或低级驱动程序代码上。一个示例是,当您想解析SD卡的寄存器时,可以使用并(1)读取它,并使用作为位域结构的并(2)读出它。
ComSubVie

5
位域不是可移植的-在您的示例中,编译器可以自由选择是将支腿分配到最高3位,还是分配最低3位。
zvrba

3
位域就是一个示例,其中标准为实现提供了很大的自由实现方式,实际上,它们几乎是无用的。如果您关心一个值占用多少位以及如何存储它,那么最好使用位掩码。
Mark Bessey,

26
只要将位域视为它们的结构元素,而不是“整数”,它们就确实是可移植的。在内存有限的嵌入式系统中,大小而不是位置很重要,因为每一位都是宝贵的……但是,当今的大多数编码器还太年轻,无法记住这一点。:-)
亚当·利斯

5
@Adam:如果取决于位域在其字节内的位置,则位置在嵌入式系统(或其他位置)中可能很重要。使用蒙版可以消除任何歧义。工会也是如此。
史蒂夫·梅尔尼科夫

37

C有一个标准,但是并非所有C编译器都完全兼容(我还没有看到任何完全兼容的C99编译器!)。

也就是说,我更喜欢的技巧是那些不明显的技巧,因为它们依赖于C语义,因此可以跨平台移植。它们通常是关于宏或位算术的。

例如:不使用临时变量就交换两个无符号整数:

...
a ^= b ; b ^= a; a ^=b;
...

或“扩展C”来表示有限状态机,例如:

FSM {
  STATE(x) {
    ...
    NEXTSTATE(y);
  }

  STATE(y) {
    ...
    if (x == 0) 
      NEXTSTATE(y);
    else 
      NEXTSTATE(x);
  }
}

可以通过以下宏实现:

#define FSM
#define STATE(x)      s_##x :
#define NEXTSTATE(x)  goto s_##x

但是,总的来说,我不喜欢巧妙的技巧,但会使代码的阅读变得不必要地复杂(作为交换示例),我喜欢使代码更清晰并直接传达意图的代码(例如FSM示例) 。


18
C支持链接,因此您可以执行^ = b ^ = a ^ = b;
OJ。

4
严格来说,状态示例只是预处理器的一小部分,而不是C语言-可以使用前者而不使用后者。
格雷格·惠特菲尔德

15
OJ:实际上,由于序列点规则,您建议的是未定义的行为。它可能适用于大多数编译器,但不正确或不可移植。
埃文·特兰

5
在免费寄存器的情况下,异或交换实际上可能效率较低。任何体面的优化器都会使temp变量成为寄存器。根据实现方式(以及对并行性支持的需要),交换实际上可能使用实内存而不是寄存器(这是相同的)。
Paul de Vrieze,

27
:请永远不要真正做到这一点en.wikipedia.org/wiki/...
基督教Oudard

37

Duff's Device这样交错结构:

strncpy(to, from, count)
char *to, *from;
int count;
{
    int n = (count + 7) / 8;
    switch (count % 8) {
    case 0: do { *to = *from++;
    case 7:      *to = *from++;
    case 6:      *to = *from++;
    case 5:      *to = *from++;
    case 4:      *to = *from++;
    case 3:      *to = *from++;
    case 2:      *to = *from++;
    case 1:      *to = *from++;
               } while (--n > 0);
    }
}

29
@ComSubVie,使用Duff设备的任何人都是脚本小子,看到Duff设备并认为如果使用Duff设备,其代码将为1337。(1.)Duff的设备在现代处理器上没有提供任何性能提升,因为现代处理器具有零开销循环。换句话说,它是过时的代码。(2.)即使您的处理器不提供零开销循环,它也可能具有SSE / altivec / vector-processing之类的功能,当您使用memcpy()时,这会使Duff的设备感到羞耻。(3.)我是否提到过进行memcpy()duff没什么用?
Trevor Boyd Smith

2
@ComSubVie,请满足我的拳头的死亡(en.wikipedia.org/wiki/...
特雷弗·博伊德·史密斯

12
@Trevor:所以只有脚本小子程序8051和PIC微控制器对不对?
SF。

6
@Trevor Boyd Smith:尽管Duff的装置似乎已经过时,但这仍然是历史的好奇心,它验证了ComSubVie的回答。无论如何,引用Wikipedia:“当从版本4.0的XFree86 Server中删除Duff设备的许多实例时,性能有了显着提高。” ...
paercebal,2010年

2
在Symbian上,我们曾经评估过各种循环以进行快速像素编码。在组装机中,duff的设备是最快的。因此,它仍然与当今智能手机上的主流ARM内核有关。
威尔

33

我非常喜欢在C99中添加的指定的初始化程序(并在gcc中长期支持):

#define FOO 16
#define BAR 3

myStructType_t myStuff[] = {
    [FOO] = { foo1, foo2, foo3 },
    [BAR] = { bar1, bar2, bar3 },
    ...

数组初始化不再依赖于位置。如果更改FOO或BAR的值,则数组初始化将自动对应于它们的新值。


gcc长期支持的语法与标准C99语法不同。
Mark Ba​​ker

28

C99具有一些很棒的任意顺序结构初始化。

struct foo{
  int x;
  int y;
  char* name;
};

void main(){
  struct foo f = { .y = 23, .name = "awesome", .x = -38 };
}


27

匿名结构和数组是我最喜欢的一种。(请参阅http://www.run.montefiore.ulg.ac.be/~martin/resources/kung-f00.html

setsockopt(yourSocket, SOL_SOCKET, SO_REUSEADDR, (int[]){1}, sizeof(int));

要么

void myFunction(type* values) {
    while(*values) x=*values++;
}
myFunction((type[]){val1,val2,val3,val4,0});

它甚至可以用于实例化链接列表...


3
此功能通常称为“复合文字”。匿名(或未命名)结构表示没有成员名称的嵌套结构。
calandoa,2009年

根据我的GCC,“ ISO C90禁止使用复合文字”。
2009年

“ ISO C99支持复合文字。” “作为扩展,GCC在C89模式和C ++中支持复合文字”(dixit info gcc)。另外,“作为GNU扩展,GCC允许通过复合文字初始化具有静态存储持续时间的对象(在ISO C99中是不可能的,因为初始化器不是常数)。”
PypeBros'2

24

gcc有许多我喜欢的C语言扩展,可以在这里找到。我最喜欢的是函数属性。一个非常有用的示例是format属性。如果您定义一个采用printf格式字符串的自定义函数,则可以使用此方法。如果启用此功能属性,则gcc会检查您的参数以确保格式字符串和参数匹配,并会酌情生成警告或错误。

int my_printf (void *my_object, const char *my_format, ...)
            __attribute__ ((format (printf, 2, 3)));

24

第一次看到时,“震惊”我的(隐藏)功能与printf有关。此功能允许您使用变量来格式化格式说明符本身。寻找代码,您会看到更好的结果:

#include <stdio.h>

int main() {
    int a = 3;
    float b = 6.412355;
    printf("%.*f\n",a,b);
    return 0;
}

*字符可达到此效果。


24

好吧...我认为C语言的强项之一是它的可移植性和标准性,因此,每当我在当前使用的实现中发现一些“隐藏技巧”时,我都尽量不要使用它,因为我会尽量保持自己的风格。 C代码尽可能标准且可移植。


但是实际上,您必须多久使用另一个编译器来编译代码?
乔D

3
@Joe D,如果它像Windows / OSX / Linux这样的跨平台项目,可能有点,而且还有不同的架构,例如x86 vs x86_64等...
Pharaun

@JoeD除非您处在一个狭narrow的项目中,否则很乐意与一个编译器供应商结婚。您可能想要避免实际上不必切换编译器,但是您确实想保持该选项为打开状态。但是,对于嵌入式系统,您并非总能选择。AHS,ASS。
XTL 2012年

19

编译时断言,如此处已讨论

//--- size of static_assertion array is negative if condition is not met
#define STATIC_ASSERT(condition) \
    typedef struct { \
        char static_assertion[condition ? 1 : -1]; \
    } static_assertion_t

//--- ensure structure fits in 
STATIC_ASSERT(sizeof(mystruct_t) <= 4096);

16

常量字符串串联

我很惊讶没有看到答案,因为我所知道的所有编译器都支持它,但是许多程序员似乎都忽略了它。有时,它非常方便,不仅在编写宏时。

我目前的代码中有一个用例:我#define PATH "/some/path/"在配置文件中有一个(确实是由makefile设置的)。现在,我想构建包括文件名的完整路径以打开资源。它只是去:

fd = open(PATH "/file", flags);

而不是可怕的,但很常见:

char buffer[256];
snprintf(buffer, 256, "%s/file", PATH);
fd = open(buffer, flags);

请注意,常见的可怕解决方案是:

  • 长三倍
  • 不太容易阅读
  • 慢得多
  • 设置为任意缓冲区大小限制时,它的功能不那么强大(但是您将不得不使用更长的代码来避免这种情况,而不会受到常量字符串的污染)。
  • 使用更多的堆栈空间

1
在不使用肮脏的\的情况下,在多个源代码行上拆分字符串常量也很有用。
支石墓

15

好吧,我从未使用过它,并且我不确定是否会向任何人推荐它,但是我觉得如果不提西蒙·塔瑟姆(Simon Tatham)的日常惯例,这个问题是不完整的


12

初始化数组或枚举时,可以在初始化列表中的最后一项后面加上逗号。例如:

int x[] = { 1, 2, 3, };

enum foo { bar, baz, boom, };

这样做是为了使您在自动生成代码时不必担心消除最后一个逗号。


这在多开发人员环境中也很重要,例如,埃里克(Eric)添加“ baz”,然后乔治(George)添加“ boom”。如果Eric决定为下一个项目构建发布自己的代码,那么它仍将与George的更改一起编译。对于多分支源代码控制和重叠的开发计划非常重要。
哈罗德·班福德

枚举可能是C99。数组初始值设定项和结尾逗号为K&R。
Ferruccio

普通枚举在AFAIK中的c89中。至少他们已经存在了很长时间。
XTL 2012年

12

结构分配很酷。许多人似乎都没有意识到结构也是值,并且可以通过memcpy()简单的赋值来赋值,而无需使用。

例如,考虑一些虚构的2D图形库,它可能会定义一种类型来表示(整数)屏幕坐标:

typedef struct {
   int x;
   int y;
} Point;

现在,您执行看似“错误”的操作,例如编写一个函数,该函数创建一个从函数参数初始化的点,然后将其返回,如下所示:

Point point_new(int x, int y)
{
  Point p;
  p.x = x;
  p.y = y;
  return p;
}

这是安全的,只要(当然)只要使用结构赋值按值复制返回值即可:

Point origin;
origin = point_new(0, 0);

这样,您可以使用纯标准C编写相当干净且面向对象的代码。


4
当然,以这种方式传递大型结构会对性能产生影响。它通常很有用(确实很多人没有意识到您可以做到),但是您需要考虑传递指针是否更好。
Mark Ba​​ker

1
当然,也有可能是。这对于编译器来说也很有可能检测到用法并对其进行优化。
放松

如果任何元素都是指针,请当心,因为您将复制指针本身,而不是其内容。当然,如果使用memcpy(),也是如此。
亚当·利斯

除非编译器可以进行全局优化,否则它无法通过副引用优化此转换按值传递。
Blaisorblade

可能值得注意的是,在C ++中,该标准专门允许优化副本(该标准必须允许编译器实现它,因为这意味着可能不会调用可能具有副作用的副本构造函数),并且由于大多数C ++编译器也是C编译器,您的编译器很有可能会进行此优化。
约瑟夫·加文

10

奇怪的向量索引:

int v[100]; int index = 10; 
/* v[index] it's the same thing as index[v] */

4
甚至更好... char c = 2 [“ Hello”]; (c =='l'之后)。
yrp

5
事实并非如此奇怪,当你考虑到v [指数] == *(V +指数)和指数[V] == *(索引+ V)
费鲁乔

17
请告诉我您实际上并没有“一直”使用此问题,就像问题所问的那样!
Tryke

9

C编译器实现了几种标准之一。但是,拥有标准并不意味着已定义语言的所有方面。 例如,达夫(Duff)的设备已成为最受欢迎的“隐藏”功能,以至于现代编译器都具有特殊目的的识别代码,以确保优化技术不会掩盖这种常用模式的预期效果。

通常,当您在编译器使用的任何C标准的严格边缘上运行时,都不建议使用隐藏功能或语言技巧。从一个编译器到另一个编译器,许多这样的技巧不起作用,并且从给定制造商的一个编译器套件版本到另一个版本,这些功能通常会失效。

破坏了C代码的各种技巧包括:

  1. 依靠编译器如何在内存中布置结构。
  2. 整数/浮点数的字节序的假设。
  3. 功能ABI的假设。
  4. 关于堆叠框架增长方向的假设。
  5. 关于语句内执行顺序的假设。
  6. 关于函数参数中语句执行顺序的假设。
  7. 关于short,int,long,float和double类型的位大小或精度的假设。

每当程序员对所有C标准中都指定为“依赖于编译器”的行为的执行模型进行假设时,还会出现其他问题。


要解决大多数问题,请根据您平台的特性做出这些假设,并在自己的标头中描述每个平台。订单执行是一个例外-永远不要依赖它;另一方面,每个平台都需要做出可靠的决定。
Blaisorblade

2
@Blaisorblade,甚至更好,使用编译时断言来记录您的假设,以使编译在违反假设的平台上失败。
RBerteig

我认为应该将两者结合起来,以便您的代码可以在多个平台上工作(这是初衷),如果功能宏设置错误,编译时断言将捕获该宏。我不确定函数ABI的假设是否可以作为编译时断言来检查,但对于大多数其他(有效)假设(执行顺序;-除外),它应该是可能的。
Blaisorblade

功能ABI检查应由测试套件处理。
支石墓

9

使用sscanf时,您可以使用%n找出您应该继续阅读的位置:

sscanf ( string, "%d%n", &number, &length );
string += length;

显然,您无法添加其他答案,因此我将在此处添加第二个答案,您可以使用“ &&”和“ ||” 作为条件:

#include <stdio.h>
#include <stdlib.h>

int main()
{
   1 || puts("Hello\n");
   0 || puts("Hi\n");
   1 && puts("ROFL\n");
   0 && puts("LOL\n");

   exit( 0 );
}

此代码将输出:

你好
劳氏

8

我一直以来最喜欢使用INT(3)在代码处设置断点


3
我认为它不是便携式的。它可以在x86上运行,但是其他平台呢?
Cristian Ciupitu

1
我不知道-您应该发布一个问题
Dror Helper

2
这是一项很好的技术,它是X86专用的(尽管在其他平台上可能也有类似的技术)。但是,这不是C的功能。它取决于非标准的C扩展名或库调用。
Ferruccio

1
在GCC中有__builtin_trap,对于MSVC __debugbreak可以在任何受支持的体系结构上使用。
Axel Gneiting 2010年

8

我最喜欢的C“隐藏”功能是在printf中使用%n来写回堆栈。通常,printf根据格式字符串从堆栈中弹出参数值,但是%n可以将其写回。

在此处查看第3.4.2节。可以导致很多讨厌的漏洞。


该链接不再起作用,实际上该站点本身似乎不起作用。您可以提供另一个链接吗?
Thequark 2011年

@thequark:关于“格式字符串漏洞”的任何文章都将包含一些信息..(例如crypto.stanford.edu/cs155/papers/formatstring-1.2.pdf)..但是,由于该字段的性质,安全性网站本身有点片状,而且很难获得(通过实施)真正的学术文章。
Sridhar Iyer

8

使用枚举进行编译时假设检查:这是一个愚蠢的示例,但对于具有编译时可配置常量的库而言确实有用。

#define D 1
#define DD 2

enum CompileTimeCheck
{
    MAKE_SURE_DD_IS_TWICE_D = 1/(2*(D) == (DD)),
    MAKE_SURE_DD_IS_POW2    = 1/((((DD) - 1) & (DD)) == 0)
};

2
+1整洁。我曾经使用过Microsoft的CompilerAssert宏,但是您的也不错。(#define CompilerAssert(exp) extern char _CompilerAssert[(exp)?1:-1]
PatrickSchlüter10年

1
我喜欢枚举方法。我之前使用的方法利用了消除无效代码的优势:“ if(something_bad){void BLORG_IS_WOOZLED(void); BLORG_IS_WOOZLED();}”直到链接时才出错,尽管它确实提供了让程序员通过错误消息知道blorg受到了警告。
supercat

8

Gcc(c)具有一些可以启用的有趣功能,例如嵌套函数声明以及?:运算符的a?:b形式,如果a不为false,则返回a。


8

我最近发现了0个位域。

struct {
  int    a:3;
  int    b:2;
  int     :0;
  int    c:4;
  int    d:3;
};

这将给出一个布局

000aaabb 0ccccddd

而不是没有:0;

0000aaab bccccddd

宽度字段0表示应在下一个原子实体(char)上设置以下位字段


7

C99风格的变量参数宏,又名

#define ERR(name, fmt, ...)   fprintf(stderr, "ERROR " #name ": " fmt "\n", \
                                  __VAR_ARGS__)

就像

ERR(errCantOpen, "File %s cannot be opened", filename);

在这里,我还使用了stringize运算符和字符串常量连接,这是我非常喜欢的其他功能。


VA_ARGS中有一个额外的“ R” 。
Blaisorblade

6

可变大小自动变量在某些情况下也很有用。这些是在nC99中添加的,并且已经在gcc中支持很长时间了。

void foo(uint32_t extraPadding) {
    uint8_t commBuffer[sizeof(myProtocol_t) + extraPadding];

您最终将在堆栈上保留一个缓冲区,以容纳固定大小的协议标头和可变大小的数据。使用alloca()可以获得相同的效果,但是此语法更紧凑。

在调用此例程之前,必须确保extraPadding是一个合理的值,否则最终会破坏堆栈。在调用malloc或任何其他内存分配技术之前,您必须精打细算检查参数,因此这并非罕见。


如果目标平台上字节/字符的宽度不完全是8位,这是否也能正常工作?我知道,那些情况很少见,但仍然... :)
Stephan202
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.