您最喜欢的C编程技巧是什么?

2020-11-22 20:18:10

因此,在您的代码中,如果您具有某种必须为8个字节大小的倍数的结构,可能由于某些硬件限制,您可以执行以下操作:

除非struct mystruct的大小是8的倍数,否则它不会编译;如果它是8的倍数,则根本不会生成任何运行时代码。

我知道的另一个技巧是《图形宝石》一书,它允许单个头文件在一个模块中声明和初始化变量,而在其他使用该模块的模块中,仅将其声明为externs。

#ifdef DEFINE_MYHEADER_GLOBALS#定义GLOBAL#定义INIT(x,y)(x)=(y)#else#定义GLOBAL extern#定义INIT(x,y)#endifGLOBAL int INIT(x,0); GLOBAL int somefunc(int a,int b);

因此,您将获得一个头文件,该头文件声明需要它们的全局实例和函数原型的实例,以及相应的extern声明。

void func(type * values){while(* values){x = * values ++; / *使用x * /}} func((type []){val1,val2,val3,val4,0})进行任何操作;

int main(){struct llist {int a; struct llist * next;}; #define cons(x,y)(struct llist []){{x,y}} struct llist * list = cons(1,cons(2,cons(3,cons(4,NULL))))); struct llist * p =列表; while(p!= 0){printf(“%d \ n”,p-> a); p = p->下一个; }}

我相信您的第一个示例也可以写为&(int){1},如果您想使其更清楚地说明您的意图。 –莉莉·巴拉德

从那时起,对预处理器进行创造性使用的新世界在我眼前张开。我不再仅包含标题,而是不时地包含整个代码块(它大大提高了可重用性):-p

您不能在优化线程中说出carmack,而不必提及地震源中的快速逆平方根。 zh.wikipedia.org/wiki/Fast_inverse_square_root – pg1989

@RoryHarvey:从查找时可以发现,似乎纯粹是经验性的。一些研究(我不记得我在哪里看到过)表明它接近最佳,但不是完全最佳。同样,对于64位,似乎发现了该值,而不是计算出来的值。 –马提厄M.

这会将结构(或数组)的所有成员初始化为零(但不会填充任何填充字节-如果您也需要将其填充为零,请使用memset)。

静态变量不需要。全局变量可能为零,但这不是必需的。 - 杰米

有时我将其扩展为:const struct something zero_something = {0};然后我可以使用结构X = zero_something即时重置变量;或者在例行程序中我可以使用'X = zero_something;'。唯一可能的反对意见是,它涉及从某处读取数据。如今,“ memset()”可能会更快-但我喜欢赋值的清晰性,也可以在初始化器中使用非零值(和memset(),然后对单个成员进行调整)可能比简单副本要慢)。 –乔纳森·莱夫勒

如果我们在谈论c技巧,我最喜欢的必须是Duff的用于循环展开的设备!我只是在等待正确的机会,让我真正地在愤怒中使用它...

我曾经用它来产生可衡量的性能提升,但是如今,它在很多硬件上都没有用。一律剖析! –丹·奥尔森

是的,那种不了解上下文的人是由Duff的设备创建的:如果代码不够快,无法正常工作,那么“代码可读性”就没用了。几乎没有人为您投票,而不必为硬实时编写代码。 –罗布K

+1,我实际上需要使用Duff的设备几次。第一次是一个循环,该循环基本上只是复制内容,并在此过程中进行了一些小的转换。它比该体系结构中的简单memcpy()快得多。 – Makis

愤怒来自于您的同事和继任者,他们必须在您之后维护您的代码。 –乔纳森·莱夫勒

就像我说的那样,我仍在等待合适的机会-但没有人使我感到烦恼。我已经写了25年的C语言,我想我是90年代初第一次接触Duff的设备,而我还没有使用过它。正如其他人所评论的那样,由于编译器在这种优化上变得更好,因此这种技巧现在越来越少了。 杰克逊

__FUNCTION__只是__func__的别名,而__func__在c99中。非常方便。 C(GCC)中的__PRETTY_FUNCTION__只是__func__的另一个别名,但是在C ++中,它将为您提供完整的函数签名。 – sklnd

typedef struct {int value; int otherValue;} s; s test = {.value = 15,.otherValue = 16}; / *或* / int a [100] = {1,2,[50] = 3,4,5,[23] = 6,7};

有一次,我和我重新定义了伙伴关系,找到了一个棘手的堆栈损坏错误。

希望这是在函数体中#define'd并在末尾#undefine'd! –更强

我不太喜欢这一点-我想到的第一件事是DoSomeStackCheckStuff由于某些错误而占用了内存,而谁在读取代码的人都不知道return的重新定义,并且想知道/ hell /会发生什么。 –吉利根

@strager但这会使它基本上无用。重点是在每个函数调用中添加一些跟踪。否则,您只需将对DoSomeStackCheckStuff的调用添加到要跟踪的函数即可。 –无知

@gilligan我认为这不是您一直都启用的功能;一键式调试工作似乎非常方便。 – sunetos

这真的有效吗? :)我会写#define return if((DoSomeStackCheckStuff)&& 0);否则返回...我猜就像疯了一样! – Paolo Bonzini

我喜欢具有动态大小的对象的“结构黑客”。该站点也对此进行了很好的解释(尽管它们引用的是C99版本,您可以在其中编写“ str []”作为结构的最后一个成员)。您可以像这样创建一个字符串“对象”:

结构X {int len; char str [1];}; int n = strlen(“ hello world”); struct X * string = malloc(sizeof(struct X)+ n); strcpy(string-> str,“ hello world”); string- > len = n;

在这里,我们在堆上分配了一个类型X的结构,该结构是一个int的大小(对于len),加上“ hello world”的长度,再加上1(因为str1包含在sizeof(X)中)。

当您想在同一块中的某些可变长度数据之前紧跟着一个“标题”时,它通常很有用。

我个人发现自己自己更容易malloc()和realloc(),并在需要查找长度时使用strlen()容易,但是如果您需要一个永远不知道字符串长度并且可能需要查找很多字符串的程序,时代,这可能是更好的路。 –克里斯·卢茨(Chris Lutz)

“ ...您可以在其中编写” str []“的C99版本。在这种情况下,我看到了零大小的数组,例如str [0];相当频繁。我认为是C99。我知道老的编译器抱怨零大小的数组。 – smcameron

我也喜欢这一点,但是,您应该使用malloc(offsetof(X,str)+ numbytes)之类的东西,否则由于填充和对齐问题会出错。例如。 sizeof(结构X)可能是8,而不是5。 –佛子

@Fozi:我实际上认为这不是问题。由于此版本具有str [1](不是str []),因此sizeof(struct X)中包含str的1个字节。这包括len和str之间的任何填充。 –埃文·特兰(Evan Teran)

@Rusky:那会对什么产生负面影响?假设在str之后有“填充”。好的,当我分配sizeof(struct X)+ 10时,这将使str有效地变为10-sizeof(int)(或更多,因为我们说有填充)。这将覆盖str及其后面的任何填充。唯一有区别的方法是,如果在str之后有一个无论如何都会破坏整个事物的成员,则灵活成员必须是最后一个成员。最后的任何填充都只会导致分配过多。请提供一个具体示例,说明它实际上可能如何出错。 –埃文·特兰(Evan Teran)

只需创建一个结构和一组函数,并将指向该结构的指针作为第一个参数即可。

是否还有某种东西可以像以前的cfront一样将C ++转换为C? – MarkJ

这几乎不是面向对象的。对于具有继承的OO,您需要在对象结构中添加某种虚拟函数表,而“子类”可能会重载该表。为此,有很多半熟的“带有类的C”风格的框架,但是我建议不要使用。 – exDM69

@ exDM69,面向对象既是一种思考问题的方式,又是一种编码范例;您无需继承即可成功完成此操作。在深入研究C ++之前,我在几个项目上做了这个。 –马克·兰索姆(Mark Ransom)

#define COLUMNS(S,E)[(E)-(S)+ 1] typedef struct {char studentNumber COLUMNS(1,9); char firstName COLUMNS(10,30); char lastName COLUMNS(31,51);} StudentRecord;

为了创建一个变量,该变量在除声明的变量之外的所有模块中都是只读的:

// Source1.c:#define SOURCE1_C#include Header1.h // MyVar在headerint MyVar中看不到; //在此文件中声明,并且可写

感觉很危险。这些是不匹配的声明和定义。在编译Source2.c时,编译器可能会假设MyVar不会更改,即使在对Source1.c的函数调用中也是如此。 (请注意,作为一个实际的const变量,它与指向const的指针不同。在后一种情况下,指向的对象仍可以通过其他指针进行修改。) –吉尔

这不会产生仅在某些编译单元中为只读的变量。这会产生不确定的行为(请参见ISO 9899的第6.2.7.2页和第6.7.3.5页)。 –艾尔斯·哈克尔

移位最多只能定义为31(32位整数)上的移位量。

如果要计算移位也需要使用较高的移位值,该怎么办?这是Theora视频编码解码器的工作方式:

unsigned int shiftmystuff(unsigned int a,unsigned int v){unsigned int halfshift = v >> 1; unsigned int otherhalf =(v + 1)>> 1;返回(a >> halfshift)>> otherhalf; }

与使用这样的分支相比,以上述方式执行任务要快得多:

unsigned int shiftmystuff(unsigned int a,unsigned int v){如果(v > v;否则返回0;}

在我的机器上,gcc-4.3.2通过使用cmov指令(条件移动)摆脱了第二个分支 –·亚当·罗森菲尔德

“比使用分支要快得多”:区别在于分支对于v的所有值都是正确的,而Halfshift技巧仅将允许范围加倍到32位体系结构上的63和127。一。 – Pascal Cuoq

最令人高兴的优点是,强制每个刺激/状态检查所有代码路径很简单。

在嵌入式系统中,我经常映射一个ISR以指向这样的表,并根据需要对其进行重新引导(在ISR之外)。

我喜欢的一种技术是,如果您有一个需要初始化的函数,则可以通过调用初始化例程来初始化指针。运行该命令时,最后要做的是将指针替换为指向实际函数的指针,然后调用该函数。这样,在第一次调用该函数时会自动调用初始化程序,然后在以后每次调用实函数。 – TMN

另一个不错的预处理器“技巧”是使用“#”字符来打印调试表达式。例如:

#定义MY_ASSERT(cond)\ do {\ if(!(cond)){\ printf(“ MY_ASSERT(%s)失败\ n”,#cond); \ exit(-1); \} \} while(0)

不过,COMPILE_ASSERT宏不能使用两次,因为它会用typedef污染名称空间,而第二种用法是:error:typedef'__compile_time_assert'的重新定义 – smcameron

您真的尝试过吗?您可以“ typedef foo;”尽可能多的次数。这就是您进行预声明的方式。我已经在gcc,VC和嵌入式环境的多个编译器上使用了2.5年,并且从未遇到过任何困难。 –吉拉德·纳尔

是的,我尝试过。我剪切并粘贴了来自gcc编译器的错误消息。 – smcameron

@Gilad:在c ++中拥有冗余的typedef是合法的,但在c中则不行。 –埃文·特兰(Evan Teran)

因为我从未使用过它,所以我不会真的把它称为最喜欢的把戏,但是提到Duff的设备使我想起了这篇关于用C实现协程的文章。它总是给我带来欢笑,但是我敢肯定有用一些时间。

实际上,我实际上已经使用了这种技术,以使驱动一系列相关的异步I / O的代码几乎可以被人类阅读。主要区别在于,我不将协程状态存储在静态变量中,而是动态分配结构,并将指向该结构的指针传递到协程函数中。一堆宏使它更加美味。它不是很好,但是比跳转到各处的异步/回调版本更好。如果可以的话,我会使用绿色线程(通过* nixes上的swapcontext())。 – pmdj

while(0);对程序没有影响,但是编译器将发出有关“此操作无效”的警告,这足以使我了解有问题的行,然后查看我想引起注意的真正原因。

显然,我可以。它不是完全标准,但是可以在我使用的编译器中使用。有趣的是,嵌入式编译器翻译了#define,而gcc却没有。 – gbarry

链表中的每个节点都是上一个节点和下一个节点的Xor。为了遍历,可以通过以下方式找到节点的地址:

LLNode *第一=头; LLNode *第二= first.linked_nodes; LLNode *第三= second.linked_nodes ^第一; LLNode *第四= Third.linked_nodes ^第二;

LLNode * last =尾巴; LLNode * second_to_last = last.linked_nodes; LLNode * third_to_last = second_to_last.linked_nodes ^ last; LLNode * third_to_last = third_to_last.linked_nodes ^ second_to_last;

虽然不是很有用(您不能从任意节点开始遍历),但我发现它非常酷。

仅当不使用编译器的-D RELEASE标志时,才会打印该语句。

定义RELEASE时,您可能想将D(x)扩展为{},以便它与if语句配合使用。否则为“如果(a)D(x);”当您定义了RELEASE时,它将扩展为“ if(a)”。这将在RELEASE版本中给您一些不错的错误 – MarkJ

@MarkJ:不。它的方式是“ if(a)D(x);”扩展为“ if(a);”很好。如果您将D(x)扩展为{},则为“ if(a)if(b)D(x); else foo();”会不正确地扩展为“ if(a)if(b){}; else foo();”,从而导致“ else foo()”与第二个if而不是第一个if匹配。 –·亚当·罗森菲尔德

老实说,我主要使用此宏来测试打印语句,或者如果我有条件语句,则将其全部括起来。 D(if(a)foo();); –西蒙·沃克

@AdamRosenfield:使用#define D(x)做{} while(0)处理这种情况(并且可以应用于插入x的分支以保持一致性) – rpetrich

Rusty实际上在ccan中生成了一整套构建条件,请查看build assert模块:

#include #include struct foo {char string [5]; int x;}; char * foo_string(struct foo * foo){//此技巧要求字符串在结构BUILD_ASSERT(offsetof(struct foo,string)== 0)中位于第一return(char *)foo;}

实际的标头中还有许多其他有用的宏,这些宏很容易就位。

我尽一切努力通过主要坚持使用内联函数来抵制黑暗面(和预处理程序滥用),但是我确实喜欢像您所描述的那样聪明,有用的宏。

是的,我最近遇到了ccan,并正在考虑提供一些代码,但还没有完全按照“ ccan方式”来做。不过,感谢您提供的链接,希望能有更多的动力去研究ccan,我真的希望能引起大家的注意。 – smcameron

好吧,我不会太在意“ cancan方法”,直到它更加成熟为止……目前,有人提议将ccan-lint作为GSOC项目。这是一个很小而相当友好的团体..也是丢弃片段的好地方:) – Tim Post

顺便说一句,我注意到Rusty的BuILD_ASSERT就像Linux内核中的宏(毫不奇怪)一样,但是缺少“ nots”(或刘海或!)之一,并且注意到,我认为我发布的宏的示例用法是不正确。应该是:“ BUILD_BUG_ON(((sizeof(struct mystruct)%8))” – smcameron

关于这类东西的两本很好的参考书是《编程和编写固态代码的实践》。其中一个(我不记得是哪个)说:最好在可能的地方使用#define枚举,因为枚举由编译器检查。

AFAIK,在C89 / 90中没有枚举的类型检查。枚举只是某种程度上更方便#defines。 – cschol

第二版ED K&R,第39页底部。至少有机会进行检查。 –乔纳森·沃特莫夫

不特定于C,但我一直很喜欢XOR运算符。它可以做的一件很酷的事情是“没有临时值的交换”:

int a = 1; int b = 2; printf(“ a =%d,b =%d \ n”,a,b); a ^ = b; b ^ = a; a ^ = b; printf(“ a =%d,b =%d \ n“,a,b);

a = 1; b = 2; a = a + b; b = a-b; a = a-b;也给出相同的结果 – Grambot

这也将交换a和b:a ^ = b ^ = a ^ = b; – vikhyat

我喜欢在列表中使用例如container_of的概念。基本上,您不需要为将在列表中的每个结构指定下一个和最后一个字段。而是将列表结构标头附加到实际的链接项。

我认为userdata指针的使用非常简洁。如今,时尚正在逐渐失控。它不是C功能,而是在C中非常容易使用。

我希望我能理解您的意思。您能解释更多吗?什么是userdata指针? – Zan Lynx

它主要用于回调。您希望每次触发回调时都将这些数据还给您。对于将C指针传递给回调函数特别有用,因此您可以将对象绑定到事件。 –埃文·特兰(Evan Teran)

是的。谢谢。我经常使用它,但我从未这样称呼过。 – Zan Lynx

我使用X-Macros来让预编译器生成代码。它们对于在一处定义错误值和相关的错误字符串特别有用,但是它们可以远远超出此范围。

#ifdef DEBUG#定义my_malloc(amt)my_malloc_debug(amt,__FILE__,__LINE __)void * my_malloc_debug(int amt,char *文件,int行)#elsevoid * my_malloc(int amt)#endif {//记住文件和行号。在调试模式下为此malloc}

它允许在调试模式下跟踪内存泄漏。我一直认为这很酷。

#定义SOME_ENUMS(F)\ F(零,0)\ F(一,一)\ F(二,二)/ *现在定义常数值。看看这有多简洁。 * /枚举常量{#define DEFINE_ENUM(A,B)A,SOME_ENUMS(DEFINE_ENUMS)#undef DEFINE_ENUM}; / *现在返回一个枚举名称的函数:* / const char * ToString(int c){switch( c){默认值:返回NULL; /* 管他呢。 * /#定义CASE_MACRO(A,B)情况A:返回#b; SOME_ENUMS(CASE_MACRO)#undef CASE_MACRO}}

这是一个示例,该示例如何使C代码完全不了解硬件在运行该应用程序时实际使用了什么。 main.c进行设置,然后可以在任何编译器/架构上实现自由层。我认为稍微抽象一下C代码是很整洁的事情,所以不要太专一了。

/ * free.h * /#ifndef _FREE_H_#定义_FREE_H_#include #include typedef unsigned char ubyte;

......