首页 文章

你最喜欢的C编程技巧是什么? [关闭]

提问于
浏览
134

例如,我最近在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的倍数,则根本不生成运行时代码 .

我所知道的另一个技巧来自“图形宝石”一书,它允许单个头文件在一个模块中声明和初始化变量,而在使用该模块的其他模块中,仅将它们声明为外部 .

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

30 回答

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

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

  • 2

    这类东西的两本好的源书是The Practice of ProgrammingWriting Solid Code . 其中一个(我不记得哪个)说:首选enum到#define你可以,因为枚举被编译器检查 .

  • 0

    我不会用它,但提到Duff的设备让我想起了this article关于在C中实现Coroutines的问题 . 它总是让我笑一笑,但我相信它可能会有用一段时间 .

  • 3

    我们的代码库有一个类似的技巧

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

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

  • 7

    代替

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

    使用

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

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

    在 Headers 声明中

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

  • 45

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

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

    等等

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

  • 6

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

  • 2

    下面是一个示例,说明如何让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;
    }
    
  • 28
    if(---------)  
    printf("hello");  
    else   
    printf("hi");
    

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

  • 6

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

  • 5

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

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

    edit: 以下代码仅适用于C . 感谢smcameron和Evan Teran .

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

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

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

    实际 Headers 中还有许多其他有用的宏,很容易就位 .

    我尽我所能通过坚持内联函数来抵制黑暗的一面(以及预处理器滥用),但我确实喜欢你所描述的聪明,有用的宏 .

  • 17

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

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

    最令人愉悦的优点是强制每个都很简单刺激/状态来检查所有代码路径 .

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

  • 8

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

  • 42

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

    Removing pointless variables

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

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

    Passing a Variable Amount of Arguments

    void func(type* values) {
        while(*values) {
            x = *values++;
            /* do whatever with x */
        }
    }
    
    func((type[]){val1,val2,val3,val4,0});
    

    Static linked lists

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

    任何我肯定还有很多其他很酷的技术我都没有想过 .

  • 14

    使用 __FILE____LINE__ 进行调试

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

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

    struct something X = {0};
    

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

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

  • 11

    我喜欢"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)中) .

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

  • 8

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

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

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

    查看 include/linux/list.h 了解真实案例 .

  • 81

    在阅读Quake 2源代码时,我提出了类似这样的事情:

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

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

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

    谢谢John Carmack!的xD

  • 0

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

    就像是:

    #define return DoSomeStackCheckStuff, return
    
  • 1

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

  • 16

    在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

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

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

  • 3

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

    #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

    有趣的宏:

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

相关问题