您认为哪种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); ...
AnT.. 34
#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)); ...
@David Thornley:宏的"多重评估"也给了我们"懒惰的评价".这是一个问题,因为它是一个功能.一个人必须学会正确使用它.要说"多重评估"的危险在某种程度上意味着一个人不应该使用宏,那就完全相同,就是说不应该使用除法运算符,因为存在将某事物除零的危险. (13认同)
@Tomas:没有必要,但你的方式意味着想要`SWAPINT(*xp,*yp)`的调用者现在需要首先检查`xp!= yp`.这些天我没有看到添加一个尴尬的边缘情况以便(可能)保存寄存器和/或4个字节的堆栈. (7认同)
你的宏观效果令人印象深刻(你正确地括起来并且知道`do ... while(0)`技术),但是仍然存在多个你无法解决的评估问题. (5认同)
不错,喜欢"IMPLIES"最多:) (3认同)
@JPMC:`SET`只是将给定值`v`赋给数组`d`的所有`n`元素.`ZERO`使用`SET`为数组`d`的所有`n`元素赋值'0`. (2认同)
Remo.D.. 22
C宏的关键是正确使用它们.在我看来有三个类别(不考虑使用它们只是为常量提供描述性名称)
作为一段代码的简写,人们不想重复
提供一般使用功能
修改C语言的结构(显然)
在第一种情况下,您的宏将只存在于您的程序中(通常只是一个文件),因此您可以使用您发布的宏,这些宏不受保护,不会对参数和使用进行双重评估{...};
(有潜在危险!).
在第二种情况下(甚至更多在第三种情况下),您需要非常小心,您的宏行为正确,就像它们是真正的C构造一样.
您从GCC发布的宏(最小值和最大值)就是一个例子,他们使用全局变量_a
并_b
避免双重评估的风险(如in 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风格.
有人可能不喜欢这个例子,但我发现它非常适合演示简单的宏如何使你的代码更易读和富有表现力.
#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)); ...
C宏的关键是正确使用它们.在我看来有三个类别(不考虑使用它们只是为常量提供描述性名称)
作为一段代码的简写,人们不想重复
提供一般使用功能
修改C语言的结构(显然)
在第一种情况下,您的宏将只存在于您的程序中(通常只是一个文件),因此您可以使用您发布的宏,这些宏不受保护,不会对参数和使用进行双重评估{...};
(有潜在危险!).
在第二种情况下(甚至更多在第三种情况下),您需要非常小心,您的宏行为正确,就像它们是真正的C构造一样.
您从GCC发布的宏(最小值和最大值)就是一个例子,他们使用全局变量_a
并_b
避免双重评估的风险(如in 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风格.
有人可能不喜欢这个例子,但我发现它非常适合演示简单的宏如何使你的代码更易读和富有表现力.
for C99中的每个循环:
#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); }
如果需要在不同的上下文中多次定义数据,宏可以帮助您避免多次重复同一事物.
例如,假设您要定义颜色枚举和枚举到字符串函数,而不是列出所有颜色两次,您可以创建颜色文件(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]); };
#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.使用字符串和数组.动态分配的数组可以通过将它们转换为指向数组的指针,然后取消引用它们来使用.
#include#include #include #include #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); }
#define COLUMNS(S,E) [ (E) - (S) + 1 ] struct { char firstName COLUMNS ( 1, 20); char LastName COLUMNS (21, 40); char ssn COLUMNS (41, 49); }
保存自己一些容易出错的计数
有人提到了container_of(),但没有提供这个非常方便的宏的解释.假设您有一个如下所示的结构:
struct thing { int a; int b; };
现在,如果我们有一个指向b的指针,我们可以使用container_of()以类型安全的方式获取指向thing的指针:
int *bp = ...; struct thing *t = container_of(bp, struct thing, b);
这在创建抽象数据结构时很有用.例如,不是采用方法queue.h来创建像SLIST(每个操作的大量疯狂宏)之类的东西,你现在可以编写一个看起来像这样的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);