【问题标题】:The most useful user-made C-macros (in GCC, also C99)? [closed]最有用的用户制作的 C 宏(在 GCC 中,也是 C99 中)? [关闭]
【发布时间】:2010-12-18 19:56:55
【问题描述】:

您认为哪个 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);
...

【问题讨论】:

  • 而且您在这里没有实现任何标准形式的向量乘法(不是点积、叉积或张量积)。
  • 按分量乘法有它的用处,但宏强加的维度限制使它没那么有用。
  • @outis:这旨在用于由应用领域的性质严格施加尺寸限制的领域。例如,就像在 3D 图形中一样。
  • 但是假设宇宙中的维数发生变化,代码就会出错——这就是导致千年虫问题的短视。
  • 是的...就像施乐手册中的那句名言。将 PI 声明为命名常量的好处之一是它可以帮助您在 Pi 的值发生变化时调整代码。

标签: c macros c-preprocessor c99


【解决方案1】:
#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 中的函数实现。

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

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

【讨论】:

  • 你的macro-fu令人印象深刻(你的括号正确并且知道do...while(0)技术),但仍有多个评估问题你真的无法解决。
  • @David Thornley:使用宏的“多重评估”也给了我们“惰性评估”。它既是一个功能,也是一个问题。一个人只需要学会正确使用它。说“多重评估”的危险不知何故意味着一个人不能使用宏,这与说一个人不能使用除法运算符是完全相同的,因为存在被零除的危险。
  • @Tomas:没有必要,但是你的方式意味着想要做SWAPINT(*xp, *yp)的呼叫者现在需要先检查xp != yp。这些天来,为了(也许)保存一个寄存器和/或 4 个字节的堆栈,我认为添加一个尴尬的边缘情况并没有多大意义。
  • 很好,最喜欢“IMPLIES”:)
  • @JPMC: SET 只是将给定值 v 分配给数组 d 的所有 n 元素。 ZERO 使用 SET 将值 0 分配给数组 d 的所有 n 元素。
【解决方案2】:

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

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

【讨论】:

  • 看起来更像 VHDL 左右:)
  • 你是一个邪恶的恶人哈哈
【解决方案3】:

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

【讨论】:

  • 当你的数组突然变成堆分配时,这很容易被滥用......
  • @Alexandre C. 就像手动 sizeof-with-forloop 方式一样容易误用。我怀疑更好的方法是仔细检查 C 中的所有操作 :)
  • 是的,我的评论是针对成语的,而不是针对宏的。
  • keep 变量的用途是什么,为什么foreach 会扩展为嵌套的for 循环?
  • @ToddLehman 的目的是使break 工作(我的代码被破坏了。我以为我是从我的另一个答案中复制它的,但显然我还不够清醒:p。我修好了,请仔细检查)
【解决方案4】:

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

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

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

【讨论】:

【解决方案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 之间没有逗号是故意的。它打印一个带有源位置的格式化字符串。我经常在实时嵌入式系统中工作,所以我也会在输出中包含时间戳。

【讨论】:

  • NDEBUG 不应该是#define TRACE (format, ...) ((void)0) 以避免像if(cond) TRACE(...) 这样的陷阱吗?
  • @Gregory:什么是“陷阱”?如果您未能在宏调用后放置分号,则无论如何都可能是错误的 - if(x) TRACE("y = %d", y ); 将解析为 if(x);,这将不起作用。
  • 我的错...我很确定我使用了一个编译器,; 不是一个有效的语句,但它对 GCC 来说不是问题。 assert.h 似乎仍然使用#define assert(ignore)((void) 0)
  • 这是因为 C 标准要求 assert 扩展为 void 表达式,而不管 NDEBUG 的值如何
【解决方案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);
}

【讨论】:

    【解决方案7】:

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

    struct thing {
        int a;
        int b;
    };
    

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

    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 color 位于更多的链表上,而不仅仅是 colors 一个) .

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

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

    【讨论】:

      【解决方案8】:
      #define COLUMNS(S,E) [ (E) - (S) + 1 ]
      
      
      struct 
      {
          char firstName COLUMNS ( 1, 20);
          char LastName  COLUMNS (21, 40);
          char ssn       COLUMNS (41, 49);
      }
      

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

      【讨论】:

      • 谢谢,真的很有用! (您忘记了示例中的类型)
      【解决方案9】:

      这个来自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
      

      【讨论】:

      • 最后一个宏的名称相当混乱。 LSB 唤起(x) &amp; 1
      • 后者应该是 LSSB -- 最低有效设置位
      • 或 LSD - 最低有效位,虽然我担心这可能有负面含义......
      【解决方案10】:

      我也喜欢这个:

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

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

      【讨论】:

      • “讨厌宏的人如何进行公平的浮点比较”静态内联函数。
      • 没有“宏观仇恨者”之类的东西。每次您看到“宏加热器”时,实际上都是初学者通过模仿他认为听起来“成熟”的最大值来试图将自己展示为比实际更成熟的程序员。
      • @AndreyT:鉴于我讨厌宏观,你愿意估计一下我在 C 和 C++ 方面的专业工作年限,以及我在大学级别教授过多少课程用 C 编程?
      • 我也讨厌宏。我也讨厌割草机——我通常承认需要更短的草,我只是不喜欢使用一种来实现另一种的过程。我使用了另一种语言中的宏,它更丰富(例如,您可以循环而不使用递归包含),但也比 C 宏更容易正确使用。
      • @David Thornley:相关吗?一位祖母从她家开车到她最喜欢的杂货店 40 年,从技术上讲,她有 40 年的驾驶经验。这会让她成为一个好司机吗?优秀的程序员从不讨厌任何事情,因为绝对任何事情都有它的时间和地点。
      【解决方案11】:

      只是标准的:

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

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

      【讨论】:

      • 旁白:有人从 Google 发布了一个 LENGTH 宏,如果传递一个指针会导致编译时错误,但我再也找不到它了。有谁知道这个宏并且有一个链接到显示它的答案(或另一个页面)?
      • 这个适用于一维数组,但你需要将元素类型传递给它:#define REQUIRE_ARRAY(T, X) (void)(sizeof (((char(*)(T(*)[]))0)(&amp;(X))))
      • 以下代码在 C99 中也适用于大于 1 的维度。您需要为具有 2 个维度的数组调用 REQUIRE_ARRAY2,例如:#define REQUIRE_ARRAY2(T, X) (void)(sizeof (((char(*)(T(*)[*][*]))0)(&amp;(X))))
      • @outis - 迟到了,但我相信#define IS_ARRAY(a) ((void *)&amp;a == (void *)a) 可以作为 runtime 测试某物是数组还是指针(除非指针指向到自身,这可能作为循环连接结构的一部分发生,但有些不常见)。
      • 派对永远不会结束。 Google LENGTH macro 可能是 Michael Burr 在回复 "Common array length macro for C?" 时发布的。请注意,它不是测试某个东西是否是数组,而是一个导致数组长度或其他类型错误的宏。
      【解决方案12】:
      #define kroundup32(x) (--(x), (x)|=(x)>>1, (x)|=(x)>>2, (x)|=(x)>>4, (x)|=(x)>>8, (x)|=(x)>>16, ++(x))
      

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

      【讨论】:

      • 使用__builtin_clz 和一个班次不是更快更简单吗?
      【解决方案13】:

      将字节、单词、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) 
      

      为参数加括号始终是避免扩展产生副作用的好习惯。

      【讨论】:

      • 你应该输入def ULONGLONG:typedef unsigned __int64 ULONGLONG;
      • MAKEQWORD 是不是错了?应该在移动之前将 h 转换为 ULONGLONG,而不是之后。
      【解决方案14】:

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

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

      【讨论】:

      • 太短了,太吓人了。此外,这是一个教科书示例,说明为什么 使用宏。 (因为参数(有条件地)被多次评估)
      • 我认为,min/max 有 gcc 扩展。类似a &gt;? b
      • @贾斯汀:嗯?上面的代码专门使用 GCC 扩展来避免多次评估。以上没有多重评价。
      • @Xeor:这些是 g++ 扩展,自 gcc 4.0 以来已被弃用。 gcc.gnu.org/onlinedocs/gcc-3.4.6/gcc/…
      • @Hernán,四个参数是什么?我会选择 max(max(a, b), c) 来代替
      【解决方案15】:

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

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

      【讨论】:

      • 再说一遍,为什么不将其实现为(内联)函数?
      • @nikie 因为许多编译器不支持函数的 C99 内联规范。
      • @Remo:它得到了广泛的支持,只是它并不总是被称为inline
      • @nikie:如果您使用 C99 功能 (inline),只需使用在 &lt;math.h&gt; 中定义的 C99 isnan( ) 宏。
      【解决方案16】:

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

      【讨论】:

      • 你能给我们看一个典型的例子吗?
      【解决方案17】:

      这个太棒了:

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

      我会这样使用它:

      object = NEW(object_type, 1);
      

      【讨论】:

      • 1+ 为每个分配分配一个额外的字节是怎么回事?
      • 为什么要选演员?切勿在 malloc() 或相关函数的结果上使用该强制转换。打字需要时间,而且它唯一能做的就是如果你忘记了#include &lt;stdlib.h&gt;。如果我这样做,我宁愿进行诊断,也不愿让事情继续可能有效。
      • 宏的名称为NEW——对我来说似乎很清楚它可能会分配内存。此外,C++ 需要强制转换,它不会将 void * 隐式转换为另一种类型。
      • @LnxPrgr3:如果你用 C++ 编写:不要那样做!请改用new,永远不要使用这样的宏。
      • 这个不能在 C++ 中工作可以说是一个特性。它告诉您您错误地运行了错误的编译器。如果你真的想用 C++ 编译器编译你的 C,你总是可以根据是否设置了 __cplusplus 来定义不同版本的宏。在真正的 C++ 中使用这个宏是有问题的,当它与 new 无关时在 C++ 中调用它可能不是一个好计划。
      【解决方案18】:

      TRUE 和 FALSE 似乎很流行。

      【讨论】:

      • 误用了。可惜 C 没有真正的布尔类型,如 C++ 或 Java。
      • C99 有一个 bool 类型,stdbool.h
      • 我经常看到if (var == TRUE) 并且拒绝使用这些。
      猜你喜欢
      • 2018-05-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-04-03
      • 2011-06-30
      • 2016-12-15
      • 1970-01-01
      • 2023-03-08
      相关资源
      最近更新 更多