-
有只小跳蛙
宏就像任何其他工具一样-用于谋杀的锤子不是邪恶的,因为它是锤子。以这种方式使用它是邪恶的。如果你想钉子,锤子是一个完美的工具。宏有几个方面使它们变得“糟糕”(稍后我将对每个方面进行详细讨论,并提出备选方案):无法调试宏。宏观扩张会导致奇怪的副作用。宏没有“命名空间”,因此如果宏与其他地方使用的名称冲突,则会得到不需要的宏替换,这通常会导致奇怪的错误消息。宏可能会影响你没有意识到的事情。让我们在这里展开一点:1)宏不能调试。当您有一个转换为数字或字符串的宏时,源代码将具有宏名称,并且有许多调试器,您无法“看到”宏转换为什么。所以你不知道到底发生了什么。更换*使用enum或const T对于“类似函数”的宏,因为调试器工作在“您所在的每个源行”级别上,因此无论是一个语句还是100个语句,宏都将充当单个语句。很难弄清楚到底是怎么回事。更换:使用函数-如果需要“快速”,则使用内联(但请注意,过多的内联不是一件好事)2)宏观扩张会产生奇怪的副作用。著名的是#define SQUARE(x) ((x) * (x))以及使用x2 = SQUARE(x++)..这导致了x2 = (x++) * (x++);,即使它是有效的代码[1],几乎肯定不是程序员想要的。如果它是一个函数,那么做x+就可以了,而x只会增加一次。另一个例子是宏中的“如果其他”,比如我们有:#define safe_divide(res, x, y) if (y != 0) res = x/y;然后if (something) safe_divide(b, a, x);else printf("Something is not set...");它实际上变成了完全错误的东西.。更换*真正的职能。3)宏没有命名空间。如果我们有一个宏:#define begin() x = 0我们在C+中有一些使用BEGIN的代码:std::vector<int> v;... stuff is loaded into v ... for (std::vector<int>::iterator it = myvector.begin() ; it != myvector.end(); ++it)
std::cout << ' ' << *it;现在,您认为您得到了什么错误,您在哪里查找错误(假设您已经完全忘记-或者甚至不知道-其他人编写的某个头文件中的开始宏?[更有趣的是,如果您在包含之前包含了这个宏-当您查看代码本身时,您将陷入完全没有意义的奇怪错误中。更换好吧,与其说是替换,不如说是“规则”-只对宏使用大写名称,而从不将所有大写名称用于其他内容。4)宏有你没有意识到的效果承担这一职能:#define begin() x = 0#define end() x = 17... a few thousand lines of stuff here ... void dostuff(){
int x = 7;
begin();
... more code using x ...
printf("x=%d\n", x);
end();}现在,如果不看宏,您可能会认为BEGIN是一个函数,它不应该影响x。这类事情,我见过更复杂的例子,真的会把你的一天搞得一团糟!更换:不要使用宏设置x,也不要将x作为参数传入。有时,使用宏肯定是有益的。一个例子是用宏包装函数以传递文件/行信息:#define malloc(x) my_debug_malloc(x, __FILE__, __LINE__)#define free(x) my_debug_free(x, __FILE__, __LINE__)现在我们可以用my_debug_malloc作为代码中的常规malloc,但是它有额外的参数,所以当它结束时,我们扫描“哪些内存元素尚未被释放”,我们可以打印分配的位置,这样程序员就可以跟踪泄漏。[1]“在顺序点”不止一次更新一个变量是未定义的行为。序列点与语句不完全相同,但对于大多数意图和目的,这是我们应该考虑的。这样做x++ * x++将更新x两次,这是未定义的,可能会导致不同系统上的不同值,以及x也是。
-
慕标琳琳
“宏是邪恶的”这句谚语通常指的是#Definition,而不是#实用主义。具体而言,这一表述指的是以下两种情况:将幻数定义为宏使用宏替换表达式随着新的C+11有一个真正的选择,经过这么多年?是的,对于上面列表中的项目(神奇的数字应该用const/conexpr来定义,表达式应该用[Normal/inlineTemplate/inlineTemplate]函数来定义。以下是将魔术数字定义为宏和用宏替换表达式(而不是定义用于计算这些表达式的函数)所带来的一些问题:当为魔术数字定义宏时,编译器不保留定义值的类型信息。这可能导致编译警告(和错误),并混淆调试代码的人员。当定义宏而不是函数时,使用该代码的程序员期望它们像函数一样工作,而它们没有。考虑以下代码:#define max(a, b) ( ((a) > (b)) ? (a) : (b) )int a = 5;int b = 4;int c = max(++a, b);在分配给c之后,您将期望a和c为6(使用std:max而不是宏)。相反,代码执行:int c = ( ((++a) ? (b)) ? (++a) : (b) ); // after this, c = a = 7此外,宏不支持命名空间,这意味着在代码中定义宏将限制客户端代码的名称。这意味着,如果您定义了上面的宏(对于max),您将不再能够#include <algorithm>在下面的任何代码中,除非您显式地编写:#ifdef max#undef max#endif#include <algorithm>拥有宏而不是变量/函数也意味着您无法获取它们的地址:如果宏作为常量计算为魔术数字,则不能按地址传递它。对于宏作为函数,您不能使用它作为谓词,或接受该函数的地址或将其视为函式。编辑:例如,#define max上文:template<typename T>inline T max(const T& a, const T& b){
return a > b ? a : b;}这将执行宏所做的一切,但有一个限制:如果参数的类型不同,模板版本将强制您显式(这实际上会导致更安全、更显式的代码):int a = 0;double b = 1.;max(a, b);如果此max被定义为宏,则代码将编译(带有警告)。如果这个max被定义为一个模板函数,编译器将指出歧义,您必须说max<int>(a, b)或max<double>(a, b)(从而明确说明您的意图)。
-
拉风的咖菲猫
一个常见的问题是:#define DIV(a,b) a / b
printf("25 / (3+2) = %d", DIV(25,3+2));它将打印10,而不是5,因为预处理器会这样扩展它:printf("25 / (3+2) = %d", 25 / 3 + 2);这个版本更安全:#define DIV(a,b) (a) / (b)