为什么预处理宏是邪恶的,还有什么可供选择的?

为什么预处理宏是邪恶的,还有什么可供选择的?

我一直问这个问题,但我从来没有收到过一个很好的答案,我想几乎所有程序员在写第一个“HelloWorld”之前都会遇到“宏不应该被使用”、“宏是邪恶的”等短语,我的问题是:为什么?有了新的C+11,在这么多年之后,还有真正的替代品吗?

简单的部分是关于宏的,比如#pragma,它们都是特定于平台的和编译器特定的,而且大多数情况下它们都有严重的缺陷,例如#pragma once这在至少2种重要情况下是容易出错的:相同的名称在不同的路径以及一些网络设置和文件系统中。

但总的来说,宏和其使用的替代方案又如何呢?


一只萌萌小番薯
浏览 238回答 3
3回答

有只小跳蛙

宏就像任何其他工具一样-用于谋杀的锤子不是邪恶的,因为它是锤子。以这种方式使用它是邪恶的。如果你想钉子,锤子是一个完美的工具。宏有几个方面使它们变得“糟糕”(稍后我将对每个方面进行详细讨论,并提出备选方案):无法调试宏。宏观扩张会导致奇怪的副作用。宏没有“命名空间”,因此如果宏与其他地方使用的名称冲突,则会得到不需要的宏替换,这通常会导致奇怪的错误消息。宏可能会影响你没有意识到的事情。让我们在这里展开一点:1)宏不能调试。当您有一个转换为数字或字符串的宏时,源代码将具有宏名称,并且有许多调试器,您无法“看到”宏转换为什么。所以你不知道到底发生了什么。更换*使用enum或const T对于“类似函数”的宏,因为调试器工作在“您所在的每个源行”级别上,因此无论是一个语句还是100个语句,宏都将充当单个语句。很难弄清楚到底是怎么回事。更换:使用函数-如果需要“快速”,则使用内联(但请注意,过多的内联不是一件好事)2)宏观扩张会产生奇怪的副作用。著名的是#define SQUARE(x) ((x) * (x))以及使用x2 = SQUARE(x++)..这导致了x2 = (x++) * (x++);,即使它是有效的代码[1],几乎肯定不是程序员想要的。如果它是一个函数,那么做x+就可以了,而x只会增加一次。另一个例子是宏中的“如果其他”,比如我们有:#define&nbsp;safe_divide(res,&nbsp;x,&nbsp;y)&nbsp;&nbsp;&nbsp;if&nbsp;(y&nbsp;!=&nbsp;0)&nbsp;res&nbsp;=&nbsp;x/y;然后if&nbsp;(something)&nbsp;safe_divide(b,&nbsp;a,&nbsp;x);else&nbsp;printf("Something&nbsp;is&nbsp;not&nbsp;set...");它实际上变成了完全错误的东西.。更换*真正的职能。3)宏没有命名空间。如果我们有一个宏:#define&nbsp;begin()&nbsp;x&nbsp;=&nbsp;0我们在C+中有一些使用BEGIN的代码:std::vector<int>&nbsp;v;...&nbsp;stuff&nbsp;is&nbsp;loaded&nbsp;into&nbsp;v&nbsp;...&nbsp;for&nbsp;(std::vector<int>::iterator&nbsp;it&nbsp;=&nbsp;myvector.begin()&nbsp;;&nbsp;it&nbsp;!=&nbsp;myvector.end();&nbsp;++it) &nbsp;&nbsp;&nbsp;std::cout&nbsp;<<&nbsp;'&nbsp;'&nbsp;<<&nbsp;*it;现在,您认为您得到了什么错误,您在哪里查找错误(假设您已经完全忘记-或者甚至不知道-其他人编写的某个头文件中的开始宏?[更有趣的是,如果您在包含之前包含了这个宏-当您查看代码本身时,您将陷入完全没有意义的奇怪错误中。更换好吧,与其说是替换,不如说是“规则”-只对宏使用大写名称,而从不将所有大写名称用于其他内容。4)宏有你没有意识到的效果承担这一职能:#define&nbsp;begin()&nbsp;x&nbsp;=&nbsp;0#define&nbsp;end()&nbsp;x&nbsp;=&nbsp;17...&nbsp;a&nbsp;few&nbsp;thousand&nbsp;lines&nbsp;of&nbsp;stuff&nbsp;here&nbsp;...&nbsp;void&nbsp;dostuff(){ &nbsp;&nbsp;&nbsp;&nbsp;int&nbsp;x&nbsp;=&nbsp;7; &nbsp;&nbsp;&nbsp;&nbsp;begin(); &nbsp;&nbsp;&nbsp;&nbsp;...&nbsp;more&nbsp;code&nbsp;using&nbsp;x&nbsp;...&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;printf("x=%d\n",&nbsp;x); &nbsp;&nbsp;&nbsp;&nbsp;end();}现在,如果不看宏,您可能会认为BEGIN是一个函数,它不应该影响x。这类事情,我见过更复杂的例子,真的会把你的一天搞得一团糟!更换:不要使用宏设置x,也不要将x作为参数传入。有时,使用宏肯定是有益的。一个例子是用宏包装函数以传递文件/行信息:#define&nbsp;malloc(x)&nbsp;my_debug_malloc(x,&nbsp;__FILE__,&nbsp;__LINE__)#define&nbsp;free(x)&nbsp;&nbsp;my_debug_free(x,&nbsp;__FILE__,&nbsp;__LINE__)现在我们可以用my_debug_malloc作为代码中的常规malloc,但是它有额外的参数,所以当它结束时,我们扫描“哪些内存元素尚未被释放”,我们可以打印分配的位置,这样程序员就可以跟踪泄漏。[1]“在顺序点”不止一次更新一个变量是未定义的行为。序列点与语句不完全相同,但对于大多数意图和目的,这是我们应该考虑的。这样做x++ * x++将更新x两次,这是未定义的,可能会导致不同系统上的不同值,以及x也是。

慕标琳琳

“宏是邪恶的”这句谚语通常指的是#Definition,而不是#实用主义。具体而言,这一表述指的是以下两种情况:将幻数定义为宏使用宏替换表达式随着新的C+11有一个真正的选择,经过这么多年?是的,对于上面列表中的项目(神奇的数字应该用const/conexpr来定义,表达式应该用[Normal/inlineTemplate/inlineTemplate]函数来定义。以下是将魔术数字定义为宏和用宏替换表达式(而不是定义用于计算这些表达式的函数)所带来的一些问题:当为魔术数字定义宏时,编译器不保留定义值的类型信息。这可能导致编译警告(和错误),并混淆调试代码的人员。当定义宏而不是函数时,使用该代码的程序员期望它们像函数一样工作,而它们没有。考虑以下代码:#define&nbsp;max(a,&nbsp;b)&nbsp;(&nbsp;((a)&nbsp;>&nbsp;(b))&nbsp;?&nbsp;(a)&nbsp;:&nbsp;(b)&nbsp;)int&nbsp;a&nbsp;=&nbsp;5;int&nbsp;b&nbsp;=&nbsp;4;int&nbsp;c&nbsp;=&nbsp;max(++a,&nbsp;b);在分配给c之后,您将期望a和c为6(使用std:max而不是宏)。相反,代码执行:int&nbsp;c&nbsp;=&nbsp;(&nbsp;((++a)&nbsp;?&nbsp;(b))&nbsp;?&nbsp;(++a)&nbsp;:&nbsp;(b)&nbsp;);&nbsp;//&nbsp;after&nbsp;this,&nbsp;c&nbsp;=&nbsp;a&nbsp;=&nbsp;7此外,宏不支持命名空间,这意味着在代码中定义宏将限制客户端代码的名称。这意味着,如果您定义了上面的宏(对于max),您将不再能够#include <algorithm>在下面的任何代码中,除非您显式地编写:#ifdef&nbsp;max#undef&nbsp;max#endif#include&nbsp;<algorithm>拥有宏而不是变量/函数也意味着您无法获取它们的地址:如果宏作为常量计算为魔术数字,则不能按地址传递它。对于宏作为函数,您不能使用它作为谓词,或接受该函数的地址或将其视为函式。编辑:例如,#define max上文:template<typename&nbsp;T>inline&nbsp;T&nbsp;max(const&nbsp;T&&nbsp;a,&nbsp;const&nbsp;T&&nbsp;b){ &nbsp;&nbsp;&nbsp;&nbsp;return&nbsp;a&nbsp;>&nbsp;b&nbsp;?&nbsp;a&nbsp;:&nbsp;b;}这将执行宏所做的一切,但有一个限制:如果参数的类型不同,模板版本将强制您显式(这实际上会导致更安全、更显式的代码):int&nbsp;a&nbsp;=&nbsp;0;double&nbsp;b&nbsp;=&nbsp;1.;max(a,&nbsp;b);如果此max被定义为宏,则代码将编译(带有警告)。如果这个max被定义为一个模板函数,编译器将指出歧义,您必须说max<int>(a, b)或max<double>(a, b)(从而明确说明您的意图)。

拉风的咖菲猫

一个常见的问题是:#define&nbsp;DIV(a,b)&nbsp;a&nbsp;/&nbsp;b printf("25&nbsp;/&nbsp;(3+2)&nbsp;=&nbsp;%d",&nbsp;DIV(25,3+2));它将打印10,而不是5,因为预处理器会这样扩展它:printf("25&nbsp;/&nbsp;(3+2)&nbsp;=&nbsp;%d",&nbsp;25&nbsp;/&nbsp;3&nbsp;+&nbsp;2);这个版本更安全:#define&nbsp;DIV(a,b)&nbsp;(a)&nbsp;/&nbsp;(b)
打开App,查看更多内容
随时随地看视频慕课网APP