C语言中宏定义的良好编程实践(#define)
例如,永远不要像这样定义一个宏:
#define DANGER 60 + 2
当我们执行这样的操作时,这可能是危险的:
int wrong_value = DANGER * 2; // Expecting 124
相反,定义这样,因为你不知道宏的用户如何使用它:
#define HARMLESS (60 + 2)
这个例子很简单,但这几乎解释了我的问题。 在编写宏时,您会建议使用哪些指南或最佳实践吗?
谢谢你的时间!
你不仅应该在参数周围放置parens,你应该在返回的表达式周围放置parens。
#define MIN(a,b) a < b ? a : b // WRONG int i = MIN(1,2); // works int i = MIN(1,1+1); // breaks #define MIN(a,b) (a) < (b) ? (a) : (b) // STILL WRONG int i = MIN(1,2); // works int i = MIN(1,1+1); // now works int i = MIN(1,2) + 1; // breaks #define MIN(a,b) ((a) < (b) ? (a) : (b)) // GOOD int i = MIN(1,2); // works int i = MIN(1,1+1); // now works int i = MIN(1,2) + 1; // works
然而, MIN(3,i++)
仍然被打破......
最好的规则只是在没有其他方法工作时才使用#defines! 我知道你在问C而不是C ++,但仍记得他的想法。
在执行一个运行其参数并且表现得像表达式的宏时,这是惯用的:
#define DOIT(x) do { x } while(0)
该表格具有以下优点:
- 它需要一个终止分号
- 它适用于嵌套和大括号,例如if / else
在整个宏周围以及扩展列表中引用的每个参数周围使用括号:
#define MAX(x, y) ((x) > (y) ? (x) : (y))
避免编写多次评估其参数的宏。 当参数有副作用时,这些宏将不会按预期运行:
MAX(a++, b);
如果a
大于b
将评估a++
两次。
对宏使用大写名称可以清楚地表明它是一个宏而不是一个函数,因此可以相应地考虑差异(另一个一般的好习惯是不传递对函数有副作用的参数)。
不要使用宏来重命名这样的类型:
#define pint int *
因为当有人打字时,它不会按预期运行
pint a, b;
请改用typedef。
使用静态const值而不是宏来表示常量值,整数或其他值。 编译器通常可以优化它们,并且它们仍然是语言类型系统中的一级公民。
static const int DANGER = 60 + 2;
在扩展中,在参数周围加上括号,这样如果它们传入表达式,您将获得预期的行为。
#define LESS_THAN(X,Y) (((X) < (Y) ? (X) : (Y))
响应MAX / MIN宏,取自Linux内核中的GCC hacks :
#define min(x, y) ({ \ typeof(x) _min1 = (x); \ typeof(y) _min2 = (y); \ (void) (&_min1 == &_min2); \ _min1 < _min2 ? _min1 : _min2; })
为宏使用相当独特的名称,因为它们具有全局范围并且可以与任何内容发生冲突,因此:
#define MAX 10
很容易与其他代码冲突,所以:
#define MYPROJECT_MAX 10
或者更独特的东西,会更好。
我已经看到这种冲突没有产生编译错误但产生稍微错误的代码的情况,所以它可能是非常隐蔽的。
取消定义您的宏。
你的#defines
应该与#undef
匹配。 这可以防止预处理程序堵塞并影响意外的代码片段。
如果你是小心谨慎和专家,你可以通过使用宏作为简单的代码生成器来完成DRY(不要重复自己)代码。 您必须向其他程序员解释您正在做什么,但它可以节省大量代码。 例如,list-macro技术:
// define a list of variables, error messages, opcodes // or anything that you have to write multiple things about #define VARLIST \ DEFVAR(int, A, 1) \ DEFVAR(double, B, 2) \ DEFVAR(int, C, 3) \ // declare the variables #define DEFVAR(typ, name, val) typ name = (val); VARLIST #undef DEFVAR // write a routine to set a variable by name void SetVar(string varname, double value){ if (0); #define DEFVAR(typ, name, val) else if (varname == #name) name = value; VARLIST #undef DEFVAR else printf("unrecognized variable %s\n", varname); } // write a routine to get a variable's value, given its name // .. you do it ..
现在,如果要添加新变量,删除一个变量或重命名变量,它就是一行编辑。
对于多行宏,使用do { } while (0)
:
#define foo(x) do { \ (x)++; \ printf("%d", x); \ } while(0)
你做完了吗
#define foo(x) { \ (x)++; \ printf("%d", x); \ }
代替,
if (xyz) foo(y); else foo(z);
会失败的。
另外,在宏中引入临时变量时要小心:
#define foo(t) do { \ int x = (t); \ printf("%d\n", x); \ } while(0) int x = 42; foo(x);
将打印0
而不是42
。
如果您有一个需要返回值的复杂表达式,则可以使用逗号运算符:
#define allocate(foo, len) (foo->tmp = foo->head, foo->head += len, foo->tmp)
看看我怎么讨厌这个:
void bar(void) { if(some_cond) { #define BAZ ... /* some code */ #undef BAZ } }
总是这样说:
void bar(void) { if(some_cond) { #define BAZ ... /* some code */ #undef BAZ } }