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


What is your favorite C programming trick?

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

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

因此,在您的代码中,如果您有一些必须是的结构,比如说8字节的倍数,可能是由于一些硬件限制,您可以这样做:

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

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

我知道的另一个技巧来自于《图形宝石》,它允许一个头文件在一个模块中声明和初始化变量,而在使用该模块的其他模块中,只将它们声明为外部变量。

1
2
3
4
5
6
7
8
9
10
#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和FANFUNC的代码可以:

1
2
#define DEFINE_MYHEADER_GLOBALS
#include"the_above_header_file.h"

虽然只使用x和somefunc()的代码会:

1
#include"the_above_header_file.h"

因此,您得到一个头文件,声明全局实例和函数原型(在需要它们的地方)的两个实例,以及相应的外部声明。

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


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

删除无意义变量

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

变成

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

传递可变数量的参数

1
2
3
4
5
6
7
8
void func(type* values) {
    while(*values) {
        x = *values++;
        /* do whatever with x */
    }
}

func((type[]){val1,val2,val3,val4,0});

静态链接列表

1
2
3
4
5
6
7
8
9
10
11
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
"
, p->a);
        p = p->next;
    }
}

我肯定还有很多其他我没想到的酷技术。


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

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

(或多或少,我现在没有现成的代码来检查它)。

从那时起,一个创造性地使用预处理器的新世界在我眼前打开了。我不再只包含头,而是偶尔包含整个代码块(它大大提高了可重用性):-p

谢谢约翰·卡马克!除息的


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

1
struct something X = {0};

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

但是您应该注意,对于大型的、动态分配的结构,这有一些问题。


如果我们谈论的是C技巧,我最喜欢的是达夫的循环展开装置!我只是在等待一个合适的机会来让我愤怒地使用它…


使用__FILE____LINE__进行调试

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


在C99

1
2
3
4
5
6
7
8
9
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};

有一次,我和我的一个伙伴重新定义了返回,找到了一个棘手的堆栈腐败问题。

类似:

1
#define return DoSomeStackCheckStuff, return


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

1
2
3
4
5
6
7
8
9
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(因为str1包含在size of(x)中)。

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


通过模拟类,使用C编写面向对象的代码。

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


而不是

1
2
printf("counter=%d
"
,counter);

使用

1
2
3
#define print_dec(var)  printf("%s=%d
",#var,var);
print_dec(counter);

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

1
2
3
4
5
6
7
8
9
#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;

用于创建一个变量,该变量在所有模块中都是只读的,除非它声明为:

1
2
3
4
5
// Header1.h:

#ifndef SOURCE1_C
   extern const int MyVar;
#endif
1
2
3
4
5
6
// Source1.c:

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

int MyVar; // Declared in this file, and is writeable
1
2
3
// Source2.c

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


声明指向函数的数组的指针以实现有限状态机。

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

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

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


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

如果您希望有一个需要使用更高移位值的计算移位,该怎么办?下面是Theora提供编解码器的方式:

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

或者更易读:

1
2
3
4
5
6
7
unsigned int shiftmystuff (unsigned int a, unsigned int v)
{
  unsigned int halfshift = v>>1;
  unsigned int otherhalf = (v+1)>>1;

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

按照上面所示的方式执行任务比使用这样的分支要快得多:

1
2
3
4
5
6
7
unsigned int shiftmystuff (unsigned int a, unsigned int v)
{
  if (v<=31)
    return a>>v;
  else
    return 0;
}


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

1
2
3
4
5
6
7
8
#define MY_ASSERT(cond) \
  do { \
    if( !(cond) ) { \
      printf("MY_ASSERT(%s) failed

", #cond); \
     exit(-1); \
   } \
 } while( 0 )

编辑:下面的代码只对C++工作。感谢斯卡梅隆和埃文·泰兰。

是的,编译时断言总是很好的。也可以写为:

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


我不认为这是一个最喜欢的技巧,因为我从来没有使用过它,但是提到达夫的设备让我想起了这篇关于在C语言中实现协程的文章。它总是让我咯咯地笑,但我相信它在某个时候会有用。


1
2
3
4
#if TESTMODE == 1    
    debug=1;
    while(0);     // Get attention
#endif

while(0);对程序没有影响,但是编译器会发出一个关于"这什么都不做"的警告,这足以让我去看看有问题的行,然后看到我想要引起注意的真正原因。


我是Xor Hacks的粉丝:

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

1
2
3
4
5
int * a;
int * b;
a ^= b;
b ^= a;
a ^= b;

或者我真的很喜欢只有一个指针的XOR链接列表。(http://en.wikipedia.org/wiki/xor_-linked_-list)

链接列表中的每个节点都是前一个节点和下一个节点的XOR。要向前遍历,可以按以下方式找到节点的地址:

1
2
3
4
LLNode * first = head;
LLNode * second = first.linked_nodes;
LLNode * third = second.linked_nodes ^ first;
LLNode * fourth = third.linked_nodes ^ second;

等。

或者向后移动:

1
2
3
4
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;

等。

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


这本书来源于《足够的绳子把你自己射到脚上》:

在报头声明中

1
2
3
4
5
#ifndef RELEASE
#  define D(x) do { x; } while (0)
#else
#  define D(x)
#endif

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

1
2
D(printf("Test statement
"
));

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

只有在未使用编译器的"-d release"标志时,才会打印该语句。

然后你可以把旗子传给你的makefile等。

不确定这在Windows中是如何工作的,但在*nix中,它工作得很好。


不特定于C,但我一直喜欢XOR操作符。它能做的一件很酷的事情是"交换没有温度值":

1
2
3
4
5
6
7
8
9
10
11
12
int a = 1;
int b = 2;

printf("a = %d, b = %d
"
, a, b);

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

printf("a = %d, b = %d
"
, a, b);

结果:

a = 1, b = 2

a = 2, b = 1


Rusty实际上在CCAN中生成了一整套构建条件,请查看构建断言模块:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <stddef.h>
#include <ccan/build_assert/build_assert.h>

struct foo {
        char string[5];
        int x;
};

char *foo_string(struct foo *foo)
{
        // This trick requires that the string be first in the structure
        BUILD_ASSERT(offsetof(struct foo, string) == 0);
        return (char *)foo;
}

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

我尽全力抵制黑暗面的牵引(和预处理器的滥用),主要坚持内联函数,但我确实喜欢像您描述的那样聪明、有用的宏。


这类东西的两本好的源代码书是编程和编写可靠代码的实践。其中一个(我不记得是哪一个)说:宁可使用枚举也不定义可以在哪里使用,因为枚举由编译器检查。


我喜欢列表中使用的container_of的概念。基本上,您不需要为列表中的每个结构指定nextlast字段。相反,您可以将列表结构标题附加到实际的链接项。

看看include/linux/list.h中的真实例子。


参见"C的隐藏特征"问题。


宏的乐趣:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#define SOME_ENUMS(F) \
    F(ZERO, zero) \
    F(ONE, one) \
    F(TWO, two)


/* Now define the constant values.  See how succinct this is. */

enum Constants {
#define DEFINE_ENUM(A, B) A,
    SOME_ENUMS(DEFINE_ENUMS)
#undef DEFINE_ENUM
};

/* Now a function to return the name of an enum: */

const char *ToString(int c) {
    switch (c) {
    default: return NULL; /* Or whatever. */
#define CASE_MACRO(A, B) case A: return #b;
     SOME_ENUMS(CASE_MACRO)
#undef CASE_MACRO
     }
}

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


我们的代码库有一个类似于

1
2
3
4
5
6
7
8
9
10
#ifdef DEBUG

#define my_malloc(amt) my_malloc_debug(amt, __FILE__, __LINE__)
void * my_malloc_debug(int amt, char* file, int line)
#else
void * my_malloc(int amt)
#endif
{
    //remember file and line no. for this malloc in debug mode
}

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


我认为用户数据指针的使用是相当简洁的。时下流行时尚。这不是一个C功能,而是很容易在C.使用。


在C99中,您可以直接将URL嵌入到函数内部的源代码中。例子:

1
2
3
4
5
6
#include <stdio.h>

int main(int argc, char** argv) {
    http://stackoverflow.com/
    printf("Hello World");
}

使用其他没有意义的?:初始化常量变量的运算符

const int bytesPerPixel = isAlpha() ? 4 : 3;


我喜欢空的if-else和while(0)操作符。

例如:

1
2
#define CMD1(X) do { foo(x); bar(x); } while (0)
#define CMD2(X) if (1) { foo(x); bar(x); } else

1
2
3
4
if(---------)  
printf("hello");  
else  
printf("hi");

填充空白,以便在输出中既不显示hello也不显示hi。答:fclose(stdout)


下面是一个例子,如何使C代码完全不了解运行应用程序的HW实际使用的是什么。main.c进行设置,然后自由层可以在任何编译器/arch上实现。我认为稍微抽象一点C代码是非常好的,所以它不需要进行特殊化。

在这里添加一个完整的可编译示例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
/* free.h */
#ifndef _FREE_H_
#define _FREE_H_
#include <stdio.h>
#include <string.h>
typedef unsigned char ubyte;

typedef void (*F_ParameterlessFunction)() ;
typedef void (*F_CommandFunction)(ubyte byte) ;

void F_SetupLowerLayer (
F_ParameterlessFunction initRequest,
F_CommandFunction sending_command,
F_CommandFunction *receiving_command);
#endif

/* free.c */
static F_ParameterlessFunction Init_Lower_Layer = NULL;
static F_CommandFunction Send_Command = NULL;
static ubyte init = 0;
void recieve_value(ubyte my_input)
{
    if(init == 0)
    {
        Init_Lower_Layer();
        init = 1;
    }
    printf("Receiving 0x%02x
"
,my_input);
    Send_Command(++my_input);
}

void F_SetupLowerLayer (
    F_ParameterlessFunction initRequest,
    F_CommandFunction sending_command,
    F_CommandFunction *receiving_command)
{
    Init_Lower_Layer = initRequest;
    Send_Command = sending_command;
    *receiving_command = &recieve_value;
}

/* main.c */
int my_hw_do_init()
{
    printf("Doing HW init
"
);
    return 0;
}
int my_hw_do_sending(ubyte send_this)
{
    printf("doing HW sending 0x%02x
"
,send_this);
    return 0;
}
F_CommandFunction my_hw_send_to_read = NULL;

int main (void)
{
    ubyte rx = 0x40;
    F_SetupLowerLayer(my_hw_do_init,my_hw_do_sending,&my_hw_send_to_read);

    my_hw_send_to_read(rx);
    getchar();
    return 0;
}


我一直喜欢使用愚蠢的预处理器技巧来生成通用容器类型:

1
2
3
4
5
6
7
8
9
10
11
12
/* list.h */
#ifndef CONTAINER_TYPE
#define CONTAINER_TYPE VALUE_TYPE ## List
#endif
typedef struct CONTAINER_TYPE {
    CONTAINER_TYPE *next;
    VALUE_TYPE v;
} CONTAINER_TYPE;
/* Possibly Lots of functions for manipulating Lists
*/

#undef VALUE_TYPE
#undef CONTAINER_TYPE

然后您可以这样做,例如:

1
2
3
4
5
6
#define VALUE_TYPE int
#include"list.h"
typedef struct myFancyStructure *myFancyStructureP;
#define VALUE_TYPE myFancyStructureP
#define CONTAINER_TYPE mfs
#include"list.h"

不要再写链接列表了。如果value_类型总是一个指针,那么这是一个过度杀戮,因为void*也可以工作。但是,通常有非常小的结构,间接间接的开销往往是没有意义的。此外,您还可以进行类型检查(即,您可能不想将字符串的链接列表与双精度链接列表连接起来,即使两者都在一个void*链接列表中工作)。


我不知道这是不是个骗局。但是当我还是大学三年级的时候,我的一个朋友和我在我们的C++课程中完成了一个实验室。我们必须取一个人的名字并大写,然后再显示出来,然后给他们一个显示名字"姓,名"的选项。对于这个实验室,我们被禁止使用数组符号。

他给我看了这段代码,我觉得这是当时我看到的最酷的东西。

1
2
3
4
char * ptr ="first name";

//flies to the end of the array, regardless of length
while( *ptr++ );


在不使用任何运算符的情况下添加两个数字(A和B):

1
2
3
printf("%d", printf("%*s%*s",a,"
"
,b,"
"
) );

它打印+B。


在下面的空白处填写"正确"和"错误"字样:

1
2
3
4
if(--------)
printf("correct");
else
printf("wrong");

答案是!printf("correct")