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可能会把上面的
1 2 3 |
C宏的关键是正确使用它们。在我看来,有三个类别(不考虑仅仅用它们来给常量起描述性的名称)
在第一种情况下,宏将只存在于您的程序中(通常只是一个文件),因此您可以使用与您发布的宏类似的宏,这些宏不受参数双重评估的保护,并且使用
在第二种情况下(第三种情况下甚至更多),您需要非常小心地确保宏的行为正确,就像它们是真正的C构造一样。
您从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 |
注意,
用于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); |
对错似乎很流行。