当前位置:  开发笔记 > 运维 > 正文

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

如何解决《你最喜欢的C编程技巧是什么?》经验,为你挑选了19个好方法。

例如,我最近在linux内核中遇到过这个问题:

/* Force a compilation error if condition is true */
#define BUILD_BUG_ON(condition) ((void)sizeof(char[1 - 2*!!(condition)]))

所以,在你的代码中,如果你有一些必须的结构,比如8个字节的大小,可能是因为一些硬件限制,你可以这样做:

BUILD_BUG_ON((sizeof(struct mystruct) % 8) != 0);

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

我知道的另一个技巧是"Graphics Gems"一书,它允许单个头文件在一个模块中声明和初始化变量,而在使用该模块的其他模块中,只是将它们声明为externs.

#ifdef DEFINE_MYHEADER_GLOBALS
#define GLOBAL
#define INIT(x, y) (x) = (y)
#else
#define GLOBAL extern
#define INIT(x, y)
#endif

GLOBAL int INIT(x, 0);
GLOBAL int somefunc(int a, int b);

有了它,定义x和somefunc的代码可以:

#define DEFINE_MYHEADER_GLOBALS
#include "the_above_header_file.h"

而仅使用x和somefunc()的代码可以:

#include "the_above_header_file.h"

所以你得到一个头文件,它声明了需要它们的全局变量和函数原型的实例,以及相应的extern声明.

那么,你最喜欢的C编程技巧是什么?



1> Evan Teran..:

C99使用匿名数组提供了一些非常酷的东西:

删除无意义的变量

{
    int yes=1;
    setsockopt(yourSocket, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int));
}

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

传递可变数量的参数

void func(type* values) {
    while(*values) {
        x = *values++;
        /* do whatever with 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 = list;
    while(p != 0) {
        printf("%d\n", p->a);
        p = p->next;
    }
}

任何我确定我还没有想到的许多其他很酷的技术.


我相信你的第一个例子也可以写成`&(int){1}`,如果你想让它更清楚你的意图是什么.

2> fortran..:

在阅读Quake 2源代码时,我想到了这样的事情:

double normals[][] = {
  #include "normals.txt"
};

(或多或少,我现在没有代码检查它).

从那时起,一个创造性地使用预处理器的新世界在我眼前展开.我不再只包含标题,而是包括整个代码块(它可以提高可重用性):-p

谢谢John Carmack!的xD


如果没有提到地震源中的快速反转sqrt,你不能在优化线程中说carmack.http://en.wikipedia.org/wiki/Fast_inverse_square_root
@RoryHarvey:从我查找的内容中可以看出,它似乎纯粹是经验性的.一些研究(我不记得我在哪里看到它们)证明它接近最优,但并非完全最佳.同样,似乎64位的值是发现的,而不是计算.
神圣的omg!那太疯狂了!

3> John Carter..:

我喜欢使用= {0};初始化结构而不需要调用memset.

struct something X = {0};

这会将struct(或数组)的所有成员初始化为零(但不是任何填充字节 - 如果你需要将memset归零,则使用memset).

但是你应该知道,对于大型动态分配的结构,存在一些问题.


_static_变量不需要.全局变量可以归零,但不是必需的.
我有时将它扩展为:`const struct something zero_something = {0};`然后我可以使用`struct something X = zero_something;`或者通过我可以使用的例程的部分方式重置一个变量'X = zero_something ;".唯一可能的反对意见是它涉及从某个地方读取数据; 这些天,'memset()'可能会更快 - 但我喜欢分配的清晰度,并且也可以在初始化程序中使用非零值(和memset(),然后调整到单个成员可能比简单的副本慢).

4> Jackson..:

如果我们谈论c技巧,我最喜欢的是Duff的设备,用于循环展开!我只是在等待合适的机会来帮我实际使用它...


是的,那些不理解Duff设备的上下文的人是:"代码可读性"如果代码不够快,无法工作.可能没有一个投票给你的人曾经不得不为实时硬编码.
我曾经用它一次产生可测量的性能增益,但是现在它对很多硬件都没用.永远简介!
愤怒将来自您的同事和继任者,他们必须在您之后维护您的代码.

5> Pierre..:

使用 __FILE____LINE__调试

#define WHERE fprintf(stderr,"[LOG]%s:%d\n",__FILE__,__LINE__);


`__FUNCTION__`只是`__func__`的别名,而`__func__`是c99.非常方便.C(GCC)中的`__PRETTY_FUNCTION__`只是`__func__`的另一个别名,但在C++中它会为你提供完整的函数签名.
在某些编译器上,您也可以获得__FUNCTION__.

6> Jasper Bekke..:

在C99

typedef struct{
    int value;
    int otherValue;
} s;

s test = {.value = 15, .otherValue = 16};

/* or */
int a[100] = {1,2,[50]=3,4,5,[23]=6,7};



7> Andrew Barre..:

一旦我的配偶和我重新定义返回找到一个棘手的堆栈损坏错误.

就像是:

#define return DoSomeStackCheckStuff, return


@strager但这会使它基本无用.重点是为*every*函数调用添加一些跟踪.否则,您只需要对要跟踪的函数添加对`DoSomeStackCheckStuff`的调用.
希望那是函数体中的#define'd和最后的#definef!

8> Evan Teran..:

我喜欢拥有动态大小对象的"struct hack".这个网站也很好地解释了它(尽管它们引用了C99版本,你可以在其中编写"str []"作为结构的最后一个成员).你可以像这样创建一个字符串"object":

struct 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(因为str 1包含在sizeof(X)中).

当您希望在同一块中的某些可变长度数据之前有一个"标题"时,它通常很有用.


"...你可以编写"str []"的C99版本"我在这样的上下文中看到了零大小的数组,比如str [0]; 相当频繁.我认为这是C99.我知道较旧的编译器会抱怨零大小的数组.
我也喜欢这个,但是,你应该使用*malloc(offsetof(X,str)+ numbytes)*之类的东西,否则因为填充和对齐问题会出错.例如sizeof(struct X)可能是8,而不是5.
@Fozi:我其实不认为那会是个问题.由于这个版本有`str [1]`(不是`str []`),str的1个字节包含在`sizeof(struct X)`中.这**包括**len`和`str`之间的任何填充.
@Rusky:这会对任何事情产生负面影响吗?假设在`str`之后有"填充".好的,当我分配`sizeof(struct X)+ 10`然后这使得`str`有效`10 - sizeof(int)`(或更多,因为我们说有填充)大.这**覆盖**`str`和后面的任何填充.唯一的方法就是****任何**差异,就是如果在`str`之后有一个成员打破了整个事情,灵活的成员必须是最后一个.最后的任何填充只会导致分配太多.请提供一个如何实际出错的具体示例.

9> Brian R. Bon..:

面向对象的代码用C,通过模拟类.

只需创建一个结构和一组函数,这些函数将指向该结构的指针作为第一个参数.


这几乎不是面向对象.对于具有继承的OO,您需要向对象结构添加某种虚函数表,这可以通过"子类"重载.为了这个目的,有许多半生不熟的"C with classes"式框架,但我建议不要使用它.
@exDM69,面向对象既是一种思考问题的方式,也是一种编码范式; 你可以成功地做到没有继承.在完全投入C++之前,我在一些项目中做了这个.
还有什么东西可以将C++翻译成C,就像cfront一样吗?

10> 小智..:

代替

printf("counter=%d\n",counter);

使用

#define print_dec(var)  printf("%s=%d\n",#var,var);
print_dec(counter);



11> EvilTeach..:

使用愚蠢的宏技巧使记录定义更容易维护.

#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;



12> Steve Melnik..:

用于创建一个在所有模块中只读的变量,除了它声明的变量:

// Header1.h:

#ifndef SOURCE1_C
   extern const int MyVar;
#endif

// Source1.c:

#define SOURCE1_C
#include Header1.h // MyVar isn't seen in the header

int MyVar; // Declared in this file, and is writeable

// Source2.c

#include Header1.h // MyVar is seen as a constant, declared elsewhere



13> Nils Pipenbr..:

位移仅定义为移位量31(在32位整数上).

如果你想要一个需要使用更高移位值的计算移位,你会怎么做?以下是Theora视频编解码器的用法:

unsigned int shiftmystuff (unsigned int a, unsigned int v)
{
  return (a>>(v>>1))>>((v+1)>>1);
}

或者更具可读性:

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

  return (a >> halfshift) >> otherhalf; 
}

以上面显示的方式执行任务比使用这样的分支更快:

unsigned int shiftmystuff (unsigned int a, unsigned int v)
{
  if (v<=31)
    return a>>v;
  else
    return 0;
}


"比使用分支更快":不同之处在于分支对于'v`的所有值都是正确的,而`halfshift`技巧在32位体系结构上只允许将允许范围加倍到63,并且在127一个64位的.
在我的机器上,gcc-4.3.2通过使用cmov指令摆脱了第二个分支(条件移动)

14> Jamie..:

声明用于实现有限状态机的函数的指针数组.

int (* fsm[])(void) = { ... }

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

在嵌入式系统中,我经常将ISR映射到指向这样的表并根据需要对其进行监视(在ISR之外).



15> Gilad Naor..:

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

#define MY_ASSERT(cond) \
  do { \
    if( !(cond) ) { \
      printf("MY_ASSERT(%s) failed\n", #cond); \
      exit(-1); \
    } \
  } while( 0 )

编辑:下面的代码仅适用于C++.感谢smcameron和Evan Teran.

是的,编译时断言总是很好.它也可以写成:

#define COMPILE_ASSERT(cond)\
     typedef char __compile_time_assert[ (cond) ? 0 : -1]



16> Dan Olson..:

我不会把它称为最喜欢的技巧,因为我从来没有使用它,但提到Duff的设备让我想起了这篇关于在C中实现Coroutines的文章.它总是让我笑一笑,但我相信它可以有一段时间有用.



17> gbarry..:
#if TESTMODE == 1    
    debug=1;
    while(0);     // Get attention
#endif

while(0); 对程序没有任何影响,但是编译器会发出一个关于"这无所事事"的警告,这足以让我去查看有问题的行,然后看看我想引起注意的真正原因.


你不能用#warning吗?

18> hamiltop..:

我是xor hacks的粉丝:

交换2个没有第三个临时指针的指针:

int * a;
int * b;
a ^= b;
b ^= a;
a ^= b;

或者我真的很喜欢只有一个指针的xor链表.(http://en.wikipedia.org/wiki/XOR_linked_list)

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

LLNode * first = head;
LLNode * second = first.linked_nodes;
LLNode * third = second.linked_nodes ^ first;
LLNode * fourth = third.linked_nodes ^ second;

等等

或向后移动:

LLNode * last = tail;
LLNode * second_to_last = last.linked_nodes;
LLNode * third_to_last = second_to_last.linked_nodes ^ last;
LLNode * fourth_to_last = third_to_last.linked_nodes ^ second_to_last;

等等

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



19> Simon Walker..:

这一本书来自"足够的绳索射击自己的脚":

在标题声明中

#ifndef RELEASE
#  define D(x) do { x; } while (0)
#else
#  define D(x)
#endif

在您的代码位置测试语句,例如:

D(printf("Test statement\n"));

do/while有助于宏的内容扩展到多个语句.

只有在未使用编译器的'-D RELEASE'标志时才会打印该语句.

你可以,例如.将标志传递给makefile等.

不知道这在Windows中是如何工作的,但在*nix中效果很好


@MarkJ:没有.它是这样的,"if(a)D(x);" 扩展为"if(a);" 这很好.如果你有D(x)扩展到{},那么"if(a)if(b)D(x); else foo();" 将INCORRECTLY扩展为"if(a)if(b){}; else foo();",导致"else foo()"匹配第二个if而不是第一个if.
确实有足够的绳索.
推荐阅读
大大炮
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有