32

您认为哪个C宏最有用?我找到了以下一个,我用它在C中进行向量算术:

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

它是这样工作的:

v3_op_v3(vectorA, +, vectorB, vectorC);
v3_op_v3(vectorE, *, vectorF, vectorJ);
...
4

18 回答 18

36
#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)

当然,还有各种 MIN、MAX、ABS 等。

请注意,顺便说一句,以上都不能由 C 中的函数实现。

PS 我可能会将上述IMPLIES宏列为最有用的宏之一。它的主要目的是促进编写更优雅和可读的断言,如

void foo(int array[], int n) {
  assert(IMPLIES(n > 0, array != NULL));
  ...
于 2009-11-20T18:11:17.480 回答
23

C 宏的关键在于正确使用它们。在我看来,分为三类(不考虑使用它们只是为了给常量提供描述性名称)

  1. 作为一个不想重复的代码的简写
  2. 提供通用的使用功能
  3. 修改C语言的结构(显然)

在第一种情况下,您的宏将仅存在于您的程序中(通常只是一个文件),因此您可以使用像您发布的那样不受参数和用途双重评估保护的宏{...};(潜在危险!)。

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

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

我喜欢使用宏,它有助于使事情更清晰,但它们是一个锋利的工具!可能这就是让他们名声如此糟糕的原因,我认为他们是一个非常有用的工具,如果他们不在场,C 语言会更差。

我看到其他人提供了第 2 点的示例(宏作为函数),让我举一个创建新 C 构造的示例:有限状态机。(我已经在 SO 上发布了这个,但我似乎无法找到它)

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

你用这种方式:

 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 风格。

有人可能不喜欢这个例子,但我发现它完美地展示了简单的宏如何使您的代码更清晰和更具表现力。

于 2009-11-20T18:14:50.527 回答
18

C99 中的 for-each 循环:

#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\n", sum);

  // multi-dim array
  int a1[][2] = { { 1, 2 }, { 3, 4 } };
  foreach(int (*c1)[2], a1)
    foreach(int *c2, *c1) 
      printf("c2 = %d\n", *c2);
}
于 2009-11-20T20:22:23.717 回答
11

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

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

c(red)
c(blue)
c(green)
c(yellow)
c(brown)

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

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]);
};
于 2010-07-24T22:07:10.387 回答
7
#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故意的。它打印一个带有源位置的格式化字符串。我经常在实时嵌入式系统中工作,所以我也会在输出中包含时间戳。

于 2010-07-24T19:27:53.277 回答
6

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

#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\n", *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\n", *i);

  return EXIT_SUCCESS;
}

此代码已经过测试,可在 GNU/Linux 上与 GCC、ICC 和 Clang 一起使用。

Lambda 表达式(仅限 GCC)

#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);
}
于 2010-07-24T18:56:42.377 回答
6

其他人提到了container_of(),但没有为这个非常方便的宏提供解释。假设您有一个如下所示的结构:

struct thing {
    int a;
    int b;
};

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

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

这在创建抽象数据结构时很有用。例如,您现在可以编写一个看起来像这样的 slist 实现,而不是采用 queue.h 方法来创建 SLIST(每个操作都有大量疯狂的宏)之类的东西:

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

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

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

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);
于 2010-07-24T21:55:34.303 回答
5
#define COLUMNS(S,E) [ (E) - (S) + 1 ]


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

为自己节省一些容易出错的计数

于 2009-11-20T18:45:50.340 回答
4

这个来自 linux 内核(特定于 gcc):

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

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

#define LSB(x) ((x) ^ ((x) - 1) & (x))   // least significant bit
于 2009-11-20T20:05:59.350 回答
2

我也喜欢这个:

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

以及您讨厌宏的人如何进行公平的浮点比较?

于 2009-11-20T17:56:40.660 回答
2

只是标准的:

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

但那里没有什么太漂亮了。

于 2009-11-20T18:08:28.467 回答
2
#define kroundup32(x) (--(x), (x)|=(x)>>1, (x)|=(x)>>2, (x)|=(x)>>4, (x)|=(x)>>8, (x)|=(x)>>16, ++(x))

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

于 2009-11-21T03:44:41.917 回答
1

将 bytes、words、dwords 打包成 words、dwords 和 qwords:

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

为论点加上括号,避免对扩展产生副作用始终是一种好习惯。

于 2009-11-21T07:22:29.417 回答
0

也像这样的多类型最小值和最大值

//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; })
于 2009-11-20T17:40:54.123 回答
0

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

#define ISNAN(x) ((x) != (x))
于 2009-11-20T18:07:50.530 回答
0

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

于 2009-11-20T21:25:02.530 回答
-1

这个太棒了:

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

我像这样使用它:

object = NEW(object_type, 1);
于 2009-11-20T17:51:38.657 回答
-1

TRUE 和 FALSE 似乎很受欢迎。

于 2009-11-20T19:01:59.073 回答