最有用的用户自制C-宏(在GCC中,也是C99)?

The most useful user-made C-macros (in GCC, also C99)?

你认为什么C宏最有用?我发现了下面的一个,我用它来做C中的向量运算:

1
2
3
#define v3_op_v3(x, op, y, z) {z[0]=x[0] op y[0]; \
                               z[1]=x[1] op y[1]; \
                               z[2]=x[2] op y[2];}

工作原理如下:

1
2
3
v3_op_v3(vectorA, +, vectorB, vectorC);
v3_op_v3(vectorE, *, vectorF, vectorJ);
...


1
2
3
4
5
6
7
8
9
10
11
12
#define IMPLIES(x, y) (!(x) || (y))

#define COMPARE(x, y) (((x) > (y)) - ((x) < (y)))
#define SIGN(x) COMPARE(x, 0)

#define ARRAY_SIZE(a) (sizeof(a) / sizeof(*a))

#define SWAP(x, y, T) do { T tmp = (x); (x) = (y); (y) = tmp; } while(0)
#define SORT2(a, b, T) do { if ((a) > (b)) SWAP((a), (b), T); } while (0)

#define SET(d, n, v) do{ size_t i_, n_; for (n_ = (n), i_ = 0; n_ > 0; --n_, ++i_) (d)[i_] = (v); } while(0)
#define ZERO(d, n) SET(d, n, 0)

当然,还有各种最小值、最大值、绝对值等。

注意,顺便说一句,上面的任何一个都不能由C中的函数实现。

P.S.I可能会把上面的IMPLIES宏作为最有用的宏之一。它的主要目的是促进更优雅和可读的断言的编写,如

1
2
3
void foo(int array[], int n) {
  assert(IMPLIES(n > 0, array != NULL));
  ...


C宏的关键是正确使用它们。在我看来,有三个类别(不考虑仅仅用它们来给常量起描述性的名称)

  • 作为一段代码的简写,人们不想重复
  • 提供通用功能
  • 修改C语言的结构(显然)
  • 在第一种情况下,宏将只存在于您的程序中(通常只是一个文件),因此您可以使用与您发布的宏类似的宏,这些宏不受参数双重评估的保护,并且使用{...};(可能很危险!).

    在第二种情况下(第三种情况下甚至更多),您需要非常小心地确保宏的行为正确,就像它们是真正的C构造一样。

    您从gcc(最小和最大)发布的宏就是一个例子,它们使用全局变量_a_b来避免双重评估的风险(就像在max(x++,y++)中一样)(好吧,它们使用gcc扩展,但概念是相同的)。

    我喜欢使用宏,它有助于使事情更清楚,但它们是一个锋利的工具!也许正是因为这个,他们才有了这样一个坏名声,我认为他们是一个非常有用的工具,如果他们不在场的话,C会更穷。

    我看到其他人已经提供了点2(宏作为函数)的例子,让我举一个创建新C结构的例子:有限状态机。(我已经把这个贴在上面了,但是我找不到)

    1
    2
    3
     #define FSM            for(;;)
     #define STATE(x)       x##_s
     #define NEXTSTATE(x)   goto x##_s

    你用这种方式:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
     FSM {
        STATE(s1):
          ... do stuff ...
          NEXTSTATE(s2);

        STATE(s2):
          ... do stuff ...
          if (k<0) NEXTSTATE(s2);
          /* fallthrough as the switch() cases */

        STATE(s3):
          ... final stuff ...
          break;  /* Exit from the FSM */
     }

    你可以在这个主题上增加一些变化,以获得你需要的FSM的味道。

    有人可能不喜欢这个例子,但我发现演示简单的宏如何使代码更易读和更具表现力是完美的。


    对于C99中的每个回路:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    #define foreach(item, array) \
        for(int keep=1, \
                count=0,\
                size=sizeof (array)/sizeof *(array); \
            keep && count != size; \
            keep = !keep, count++) \
          for(item = (array)+count; keep; keep = !keep)


    int main() {
      int a[] = { 1, 2, 3 };
      int sum = 0;
      foreach(int const* c, a)
        sum += *c;
      printf("sum = %d
    "
    , sum);

      // multi-dim array
      int a1[][2] = { { 1, 2 }, { 3, 4 } };
      foreach(int (*c1)[2], a1)
        foreach(int *c2, *c1)
          printf("c2 = %d
    "
    , *c2);
    }


    如果需要在不同的上下文中多次定义数据,宏可以帮助您避免多次重复相同的内容。

    例如,假设您要定义一个颜色枚举和一个枚举到字符串函数,而不是列出所有颜色两次,您可以创建一个颜色文件(colors.def):

    1
    2
    3
    4
    5
    c(red)
    c(blue)
    c(green)
    c(yellow)
    c(brown)

    现在,您可以在C文件中定义枚举和字符串转换函数:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    enum {
    #define c(color) color,
    # include"colors.def"
    #undef c
    };

    const char *
    color_to_string(enum color col)
    {
        static const char *colors[] = {
    #define c(color) #color,
    # include"colors.def"
    #undef c
        };
        return (colors[col]);
    };


    1
    2
    3
    4
    5
    #if defined NDEBUG
        #define TRACE( format, ... )
    #else
        #define TRACE( format, ... )   printf("%s::%s(%d)" format, __FILE__, __FUNCTION__,  __LINE__, __VA_ARGS__ )
    #endif

    注意,"%s::%s(%d)"format之间缺少逗号是故意的。它打印一个格式化的字符串,并预先设置源位置。我在实时嵌入式系统中工作,所以经常在输出中包含时间戳。


    用于gcc的foreach循环,特别是带有GNU扩展的c99。使用字符串和数组。动态分配的数组可以通过将它们强制转换为指向数组的指针,然后取消对它们的引用来使用。

    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
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <stdbool.h>

    #define FOREACH_COMP(INDEX, ARRAY, ARRAY_TYPE, SIZE) \
      __extension__ \
      ({ \
        bool ret = 0; \
        if (__builtin_types_compatible_p (const char*, ARRAY_TYPE)) \
          ret = INDEX < strlen ((const char*)ARRAY); \
        else \
          ret = INDEX < SIZE; \
        ret; \
      })


    #define FOREACH_ELEM(INDEX, ARRAY, TYPE) \
      __extension__ \
      ({ \
        TYPE *tmp_array_ = ARRAY; \
        &tmp_array_[INDEX]; \
      })


    #define FOREACH(VAR, ARRAY) \
    for (void *array_ = (void*)(ARRAY); array_; array_ = 0) \
    for (size_t i_ = 0; i_ && array_ && FOREACH_COMP (i_, array_, \
                                        __typeof__ (ARRAY), \
                                        sizeof (ARRAY) / sizeof ((ARRAY)[0])); \
                                        i_++) \
    for (bool b_ = 1; b_; (b_) ? array_ = 0 : 0, b_ = 0) \
    for (VAR = FOREACH_ELEM (i_, array_, __typeof__ ((ARRAY)[0])); b_; b_ = 0)


    /* example's */
    int
    main (int argc, char **argv)
    {
      int array[10];
      /* initialize the array */
      int i = 0;
      FOREACH (int *x, array)
        {
          *x = i;
          ++i;
        }

      char *str ="hello, world!";
      FOREACH (char *c, str)
        printf ("%c
    "
    , *c);

      /* Use a cast for dynamically allocated arrays */
      int *dynamic = malloc (sizeof (int) * 10);
      for (int i = 0; i < 10; i++)
        dynamic[i] = i;

      FOREACH (int *i, *(int(*)[10])(dynamic))
        printf ("%d
    "
    , *i);

      return EXIT_SUCCESS;
    }

    此代码已经过测试,可以在GNU/Linux上与gcc、icc和clang一起使用。

    lambda表达式(仅限gcc)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    #define lambda(return_type, ...) \
      __extension__ \
      ({ \
        return_type __fn__ __VA_ARGS__ \
        __fn__; \
      })


    int
    main (int argc, char **argv)
    {
      int (*max) (int, int) =
        lambda (int, (int x, int y) { return x > y ? x : y; });
      return max (1, 2);
    }


    1
    2
    3
    4
    5
    6
    7
    8
    9
    #define COLUMNS(S,E) [ (E) - (S) + 1 ]


    struct
    {
        char firstName COLUMNS ( 1, 20);
        char LastName  COLUMNS (21, 40);
        char ssn       COLUMNS (41, 49);
    }

    省去一些容易出错的计数


    其他人提到了()的容器_,但没有为这个非常方便的宏提供解释。假设您有一个这样的结构:

    1
    2
    3
    4
    struct thing {
        int a;
        int b;
    };

    现在,如果我们有一个指向b的指针,我们可以使用container_of()以一种类型安全的方式获取指向事物的指针:

    1
    2
    int *bp = ...;
    struct thing *t = container_of(bp, struct thing, b);

    这对于创建抽象数据结构很有用。例如,与使用approach queue.h创建slist(每个操作都有大量疯狂的宏)不同,现在可以编写一个slist实现,其外观如下:

    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
    struct slist_el {
        struct slist_el *next;
    };

    struct slist_head {
        struct slist_el *first;
    };

    void
    slist_insert_head(struct slist_head *head, struct slist_el *el)
    {
        el->next = head->first;
        head->first = el;
    }

    struct slist_el
    slist_pop_head(struct slist_head *head)
    {
        struct slist_el *el;

        if (head->first == NULL)
            return NULL;

        el = head->first;
        head->first = el->next;
        return (el);  
    }

    这不是疯狂的宏代码。它将在出现错误时提供良好的编译器行号,并与调试器一起工作良好。除了结构使用多种类型的情况(例如,如果我们允许下面示例中的结构颜色出现在更多的链接列表中,而不仅仅是颜色列表中),这也是相当安全的类型。

    用户现在可以像这样使用您的库:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    struct colors {
        int r;
        int g;
        int b;
        struct slist_el colors;
    };

    struct *color = malloc(sizeof(struct person));
    color->r = 255;
    color->g = 0;
    color->b = 0;
    slist_insert_head(color_stack, &color->colors);
    ...
    el = slist_pop_head(color_stack);
    color = el == NULL ? NULL : container_of(el, struct color, colors);

    这一个来自Linux内核(特定于GCC):

    1
    2
    3
    #define container_of(ptr, type, member) ({                  \
    const typeof( ((type *)0)->member ) *__mptr = (ptr);    \
        (type *)( (char *)__mptr - offsetof(type,member) ); })

    其他答案中的另一个缺失:

    1
    #define LSB(x) ((x) ^ ((x) - 1) & (x))   // least significant bit


    1
    #define kroundup32(x) (--(x), (x)|=(x)>>1, (x)|=(x)>>2, (x)|=(x)>>4, (x)|=(x)>>8, (x)|=(x)>>16, ++(x))

    找到最接近的大于x的32位无符号整数。我用它来加倍数组的大小(即高水位线)。


    只有标准的:

    1
    2
    3
    #define LENGTH(array) (sizeof(array) / sizeof (array[0]))
    #define QUOTE(name) #name
    #define STR(name) QUOTE(name)

    但没有什么太快的。


    我也喜欢这个:

    1
    #define COMPARE_FLOATS(a,b,epsilon) (fabs(a - b) <= epsilon * fabs(a))

    你们这些讨厌宏的人是如何进行公平的浮点比较的?


    将字节、字、双字打包成字、双字和Q字:

    1
    2
    3
    4
    #define ULONGLONG unsigned __int64
    #define MAKEWORD(h,l) ((unsigned short) ((h) << 8)) | (l)
    #define MAKEDWORD(h,l) ((DWORD) ((h) << 16)) | (l)
    #define MAKEQWORD(h,l) ((ULONGLONG)((h) << 32)) | (l)

    加括号的论点,避免对扩展的副作用总是一个好的实践。


    我经常使用的一个宏(非常少的一个)是将参数或变量声明为未使用的宏。注意到这一点的最兼容的解决方案(IMHO)因编译器而异。


    还有多类型的最小值和最大值。

    1
    2
    3
    //NOTE: GCC extension !
    #define max(a,b) ({typeof (a) _a=(a); typeof (b) _b=(b); _a > _b ? _a:_b; })
    #define min(a,b) ({typeof (a) _a=(a); typeof (b) _b=(b); _a < _b ? _a:_b; })


    检查浮点x是否不是数字:

    1
    #define ISNAN(x) ((x) != (x))


    这个太棒了:

    1
    #define NEW(type, n) ( (type *) malloc(1 + (n) * sizeof(type)) )

    我使用它就像:

    1
    object = NEW(object_type, 1);


    对错似乎很流行。