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

例如,我最近在 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 和 somfunc 的代码就可以:

#define DEFINE_MYHEADER_GLOBALS
#include "the_above_header_file.h"

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

#include "the_above_header_file.h"

因此,您可以得到一个头文件,该文件声明需要全局变量和函数原型的实例,以及相应的外部声明。

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

145471 次浏览

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

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

在实际的头部中还有很多其他有用的宏,它们很容易放置到位。

我尽我所能地试图通过坚持使用内联函数来抵制黑暗面(以及预处理器滥用)的影响,但我确实喜欢像您描述的那些聪明、有用的宏。

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

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

编辑: 下面的代码只能在 C + + 上运行。

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

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

有两本关于这类东西的好书是 编程实践编写可靠的代码。其中之一(我不记得是哪一个了)说: 宁愿枚举数 # 定义你可以的地方,因为枚举数被编译器检查。

如果我们正在谈论 c 技巧我最喜欢的是 达夫的装置循环展开!我只是在等待一个合适的机会让我在愤怒的时候使用它。

我喜欢“ struct hack”,因为它有一个动态大小的对象。这个网站对此也做了很好的解释(尽管它们引用了 C99版本,在该版本中,您可以将“ str []”作为 struct 的最后一个成员编写)。你可以像这样创建一个字符串“对象”:

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包含在 sizeof (X)中)。

当您希望在同一块中的某些可变长度数据之前有一个“头”时,它通常是非常有用的。

因为我从来没有使用过它,所以我不会把它称为我最喜欢的技巧,但是提到 Duff 的设备让我想起了 这篇文章关于在 C 语言中实现协程的内容。它总是让我发笑,但我相信它可以是有用的一些时间。

我不知道这是不是个圈套。但是当我还是一个大三学生的时候,我和我的一个朋友在我们的 C + + 入门课程中完成了一个实验。我们必须取一个人的名字,并将其大写,显示回来,然后给他们的选项显示他们的名字“最后,第一”。在这个实验室里,我们被禁止使用数组符号。

他给我看了这个代码,我觉得这是我见过的最酷的东西。

char * ptr = "first name";


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

有一次,我和我的一个朋友重新定义了返回,发现了一个棘手的堆栈损坏 bug。

比如:

#define return DoSomeStackCheckStuff, return
#if TESTMODE == 1
debug=1;
while(0);     // Get attention
#endif

While (0) ; 对程序没有影响,但是编译器会发出“ this does nothing”的警告,这足以让我去查看那个冒犯的行,然后看到我想要引起注意的真正原因。

这句话出自《足够的绳子射穿自己的脚》一书:

在头声明中

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

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

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

Do/while 在宏的内容扩展为多个语句时有所帮助。

只有在没有使用编译器的“-DRELEASE”标志时,才会打印该语句。

然后可以将标志传递给 makefile 等。

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

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

struct something X = {0};

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

但你应该知道有 对于动态分配的大型结构,这种方法存在一些问题

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

只需创建一个 struct 和一组函数,它们将指向该 struct 的指针作为第一个参数。

位移只定义为移位量为31(对于32位整数)。

如果你想有一个计算的移位,需要与更高的移位值的工作呢?Theora vide-codec 是这样做的:

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

使用 __FILE____LINE__进行调试

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

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

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

我认为 用户数据指针的使用非常巧妙。如今时尚界正在失去阵地。它不是一个 C 特性,但是在 C 中使用起来非常简单。

在 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};

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

// 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

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

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

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

在嵌入式系统中,我通常会映射一个 ISR 来指向这样的表,并根据需要(在 ISR 之外)对其进行反向。

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

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

我肯定还有很多我没想到的酷技巧。

下面是一个如何让 C 代码完全不知道 HW 实际上用于运行应用程序的示例。C 进行设置,然后可以在任何编译器/arch 上实现空闲层。我认为抽象 C 代码非常简洁,所以不需要太具体。

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

/* 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\n",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\n");
return 0;
}
int my_hw_do_sending(ubyte send_this)
{
printf("doing HW sending 0x%02x\n",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;
}

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

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

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

在阅读 Quake 2源代码时,我想到了这样一些东西:

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

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

从此,一个全新的创造性运用预处理器的世界展现在我的眼前。我不再只包含头部,而是时不时地包含整个代码块(它大大提高了可重用性) :-p

谢谢 John Carmack

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

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

if(---------)
printf("hello");
else
printf("hi");

填充空格,这样在输出中就不会出现 hello 和 hi。
安排: fclose(stdout)

在不使用任何运算符的情况下加两个数(a 和 b) :

printf("%d", printf("%*s%*s",a,"\r",b,"\r") );

打印出 a + b。

请在下面的空格中填写“正确”和“错误”:

if(--------)
printf("correct");
else
printf("wrong");

答案是 !printf("correct")

而不是

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

使用

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

我是 Xor Hacks 的粉丝:

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

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

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

链表中的每个节点都是前一个节点和下一个节点的异或。要向前遍历,节点的地址按以下方式查找:

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;

等等。

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

使用无意义的 ? :操作符初始化常量变量

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

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

/* 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

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

#define VALUE_TYPE int
#include "list.h"
typedef struct myFancyStructure *myFancyStructureP;
#define VALUE_TYPE myFancyStructureP
#define CONTAINER_TYPE mfs
#include "list.h"

再也不要写链表了。如果 VALUE _ TYPE 始终是一个指针,那么这是一个过度杀伤,因为 void * 也同样适用。但是通常有一些非常小的结构,对于这些结构,间接的开销通常是没有意义的。此外,还可以进行类型检查(即,您可能不希望将一个链接的字符串列表与一个链接的双精度字符串列表连接起来,即使这两者都可以在 void * 链接列表中工作)。

不是特指 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 = 2,b = 1

宏的乐趣:

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

在 C99中,你可以直接将 URL 嵌入到函数的源代码中。例如:

#include <stdio.h>


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

我喜欢空的 if-else 和 while (0)运算符。

例如:

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