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

时间:2009-03-01 06:21:09

标签: 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”一书,它允许单个头文件在一个模块中声明和初始化变量,而在使用该模块的其他模块中,只是将它们声明为外部。

#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编程技巧是什么?

37 个答案:

答案 0 :(得分:81)

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

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

答案 1 :(得分:68)

在阅读Quake 2源代码时,我提出了类似的内容:

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

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

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

感谢John Carmack!的xD

答案 2 :(得分:50)

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

struct something X = {0};

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

但你应该知道有some issues with this for large, dynamically allocated structures

答案 3 :(得分:45)

如果我们谈论c技巧,我最喜欢的是Duff's Device循环展开!我只是在等待合适的机会来让我真正用它来生气......

答案 4 :(得分:42)

使用__FILE____LINE__进行调试

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

答案 5 :(得分:31)

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

答案 6 :(得分:28)

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

类似的东西:

#define return DoSomeStackCheckStuff, return

答案 7 :(得分:23)

我喜欢“struct hack”,因为它有一个动态大小的对象。 This site也很好地解释了它(虽然它们引用了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)。

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

答案 8 :(得分:17)

面向对象的代码,带有C,通过模拟类。

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

答案 9 :(得分:16)

而不是

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

使用

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

答案 10 :(得分:14)

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

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

答案 11 :(得分:11)

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

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

答案 12 :(得分:8)

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

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

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

在嵌入式系统中,我经常会将ISR映射到指向这样的表并根据需要进行渲染(在ISR之外)。

答案 13 :(得分:8)

位移仅定义为移位量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;
}

答案 14 :(得分:7)

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

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

答案 15 :(得分:6)

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

while(0);对程序没有任何影响,但编译器会发出一个警告“这没什么用”,这足以让我去查看有问题的行,然后看看我想引起注意的真正原因。

答案 16 :(得分:6)

我不会把它称为最喜欢的技巧,因为我从来没有使用它,但是提到Duff的设备让我想起this article关于在C中实现Coroutines的问题。它总是给我一个轻笑,但是我相信它可能会有用一段时间。

答案 17 :(得分:6)

我是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;

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

答案 18 :(得分:5)

这本书来自“足够的绳索射击自己的脚”:

在标题声明

#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中效果很好

答案 19 :(得分:3)

这类内容的两本优秀资料书籍是The Practice of ProgrammingWriting Solid Code。其中一个(我不记得是哪个)说:首选枚举到#define,因为enum会被编译器检查。

答案 20 :(得分:3)

不是特定于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

答案 21 :(得分:3)

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

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

我试着通过坚持内联函数来尽力抵制黑暗面(和预处理程序滥用)的拉动,但我确实喜欢你所描述的那些聪明有用的宏。

答案 22 :(得分:2)

请参阅"Hidden features of C"问题。

答案 23 :(得分:2)

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

查看include/linux/list.h的真实示例。

答案 24 :(得分:1)

我认为使用 userdata 指针非常简洁。时尚潮流正在逐渐消失。它不是一个C功能,而是很容易在C中使用。

答案 25 :(得分:1)

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

答案 26 :(得分:1)

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

的技巧
#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
}

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

答案 27 :(得分: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
     }
}

答案 28 :(得分:0)

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

例如:

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

答案 29 :(得分:0)

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

#include <stdio.h>

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

答案 30 :(得分:0)

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

/* 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 *链接列表中工作)。

答案 31 :(得分:0)

使用其他无意义的? :运算符初始化一个const变量

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

答案 32 :(得分:0)

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

填写空白,以便输出中既不会出现hello也不会出现hi ans:fclose(stdout)

答案 33 :(得分:0)

以下是一个示例,说明如何使C代码完全不知道用于运行应用程序的HW实际使用的内容。 main.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;
}

答案 34 :(得分:-1)

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

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

打印+ b。

答案 35 :(得分:-1)

我不知道这是不是一招。但是,当我在大学读大学时,我和我的朋友在我们的C ++入门课程中完成了一个实验室。我们不得不取一个人的名字并将其大写,然后将其展示回来,然后让他们选择“最后,第一次”显示他们的名字。对于本实验,我们被禁止使用数组表示法。

他向我展示了这段代码,我认为这是我当时看到的最酷的东西。

char * ptr = "first name";

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

答案 36 :(得分:-4)

填写空白以打印下面的“正确”和“错误”:

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

答案是!printf("correct")