为什么预处理器macros是邪恶的,有什么select?
我一直都这样问,但是我从来没有收到过很好的答案。 我认为在编写第一个“Hello World”之前,几乎所有的程序员都遇到过“macros不该用”,“macros都是恶”等字眼,我的问题是:为什么? 随着新的C + + 11有这么多年后真正的select?
这个简单的部分是关于像#pragma
这样的macros,它们是平台特定的和编译器特定的,而且大多数时候像#pragma once
这样的严重缺陷#pragma once
在至less2个重要的情况下容易出错:在不同的path中有相同的名字,networking设置和文件系统。
但总的来说,macros和它们的用法的替代方法呢?
macros像其他任何工具一样 – 用于谋杀的锤子不是邪恶的,因为它是一把锤子。 这种人以这种方式使用它是邪恶的。 如果你想锤钉子,锤子是一个完美的工具。
macros有几个方面使它们变得“不好”(我将在后面展开,并提出其他select):
- 您不能debuggingmacros。
- macros观扩张可能会导致奇怪的副作用。
- macros没有“命名空间”,所以如果你有一个与其他地方使用的名字冲突的macros,你会得到你不需要的macrosreplace,这通常会导致奇怪的错误信息。
- macros可能会影响你没有意识到的事情。
所以让我们在这里展开一下:
1)macros不能被debugging。 当你有一个macros转换为数字或string,源代码将有macros名称和许多debugging器,你不能“看到”什么macros转换。 所以你实际上不知道发生了什么事情。
replace :使用enum
或const T
对于类似于函数的macros,由于debugging器在“每个源代码行”的级别上工作,因此您的macros将像单个语句一样工作,不pipe它是一个语句还是一个语句。 使得很难弄清楚发生了什么事情。
replace :使用函数 – 内联,如果它需要“快”(但要小心,太多内联不是一件好事)
2)macros观扩展可能有奇怪的副作用。
着名的是#define SQUARE(x) ((x) * (x))
和使用x2 = SQUARE(x++)
。 这导致x2 = (x++) * (x++);
,即使它是有效的代码[1],几乎肯定不会是程序员想要的。 如果它是一个函数,那么x ++就可以,而x只会增加一次。
另一个例子是macros“如果其他”,比如说我们有这个:
#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...");
它实际上变成了完全错误的东西….
replace :真正的function。
3)macros没有名字空间
如果我们有一个macros:
#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;
现在,你认为你得到了什么样的错误,你在哪里寻找一个错误(假设你已经完全忘记了 – 或者甚至不知道) – 一些生活在其他人写的头文件中的macros? [甚至更有趣的是,如果你在包含这个macros之前包含了这个macros,那么当你看到这个代码本身的时候,你会被一个奇怪的错误淹没,这是毫无意义的。
replace :那么没有什么比“规则”更换 – 只使用macros的大写名称,并且从不使用所有大写的名字作为其他的东西。
4)macros有你没有意识到的影响
采取这个function:
#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(); }
现在,没有看macros,你会认为开始是一个函数,它不应该影响x。
这样的事情,我已经看到更复杂的例子,可以真正搞砸你的一天!
replace :或者不要使用macros来设置x,或者将x作为parameter passing。
有时候使用macros是绝对有益的。 一个例子是用macros包装一个函数来传递文件/行信息:
#define malloc(x) my_debug_malloc(x, __FILE__, __LINE__) #define free(x) my_debug_free(x, __FILE__, __LINE__)
现在我们可以使用my_debug_malloc
作为代码中的常规malloc,但是它有额外的参数,所以当我们结束扫描“哪些内存元素没有被释放”时,我们可以打印出分配的位置程序员可以追踪泄漏。
[1]在“序列点”中多次更新一个variables是未定义的行为。 顺序点与声明不完全相同,但是对于大多数意图和目的,这就是我们应该考虑的。 因此, x++ * x++
会更新x
两次,这是未定义的,可能会导致不同系统上的不同值,以及x
不同的结果值。
俗话说“macros是邪恶的”通常是指使用#define而不是#pragma。
具体来说,expression式就是指这两种情况:
-
定义幻数为macros
-
使用macros来replaceexpression式
新的C ++ 11在这么多年之后有一个真正的select?
是的,对于上面列表中的项目(幻数应该用const / constexpr定义,expression式应该用[normal / inline / template / inline template]函数来定义。
下面是通过将幻数定义为macros并用macrosreplaceexpression式(而不是定义用于评估这些expression式的函数)引入的一些问题:
-
当为幻数定义macros时,编译器不保留定义值的types信息。 这可能会导致编译警告(和错误),并使debugging代码的人感到困惑。
-
当定义macros而不是函数时,使用该代码的程序员希望他们能够像函数一样工作,而不是。
考虑这个代码:
#define max(a, b) ( ((a) > (b)) ? (a) : (b) ) int a = 5; int b = 4; int c = max(++a, b);
你会期望a和c在赋值到c之后是6(因为它会使用std :: max而不是macros)。 相反,代码执行:
int c = ( ((++a) ? (b)) ? (++a) : (b) ); // after this, c = a = 7
最重要的是,macros不支持命名空间,这意味着在代码中定义macros将限制客户端代码的名称。
这意味着如果你定义了上面的macros(对于max),除非你明确的写下下面的代码,否则你将不能再在下面的代码中包含#include <algorithm>
:
#ifdef max #undef max #endif #include <algorithm>
有macros而不是variables/函数也意味着你不能把他们的地址:
-
如果一个macros常量计算为一个幻数,你不能通过地址传递它
-
对于macros函数,你不能用它作为谓词,或者把函数的地址作为函子来处理。
编辑:作为一个例子,上面的#define max
的正确替代:
template<typename T> inline T max(const T& a, const T& b) { return a > b ? a : b; }
这样做的一切都是macros的,有一个限制:如果参数的types不同,模板版本强制你是显式的(这实际上导致更安全,更明确的代码):
int a = 0; double b = 1.; max(a, b);
如果这个最大值被定义为一个macros,代码将被编译(带有警告)。
如果这个最大值被定义为一个模板函数,那么编译器会指出不明确的地方,并且你必须说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)
macros是非常有价值的,尤其是对于创buildgenerics代码(macros的参数可以是任何东西),有时候还有参数。
更多的是,这个代码被放置(即插入)在macros点使用。
OTOH,类似的结果可能会达到:
-
重载函数(不同的参数types)
-
模板,C ++(通用参数types和值)
-
内联函数(将代码放在调用它们的位置,而不是跳转到单点定义 – 但是,这对于编译器来说是一个推荐)。
编辑:为什么macros是坏的:
1)没有对参数进行types检查(它们没有types),所以很容易被误用2)有时扩展成非常复杂的代码,在预处理的文件中很难识别和理解3)容易出错macros代码,如:
#define MULTIPLY(a,b) a*b
然后打电话
MULTIPLY(2+3,4+5)
那扩大英寸
2 + 3 * 4 + 5(而不是:(2 + 3)*(4 + 5))。
要有后者,你应该定义:
#define MULTIPLY(a,b) ((a)*(b))
我认为问题是编译器没有对macros进行很好的优化,而且读取和debugging“很难”。
通常一个好的select是通用函数和/或内联函数。
我不认为在调用它们时使用预处理器定义或macros有什么问题。
它们是c / c ++中的一个(元)语言概念,和其他工具一样,如果你知道自己在做什么,它们可以使你的生活更轻松。 macros的问题在于它们是在你的c / c ++代码之前被处理的,并且会产生新的代码,这些代码可能是错误的,并且导致编译器错误,这些错误都是显而易见的。 在光明的一面,他们可以帮助你保持你的代码清洁,如果使用得当,可以节省大量的打字工作,所以可以归结为个人喜好。
C / C ++中的macros可以作为版本控制的重要工具。 相同的代码可以通过较小的macrosconfiguration传递给两个客户端。 我使用类似的东西
#define IBM_AS_CLIENT #ifdef IBM_AS_CLIENT #define SOME_VALUE1 X #define SOME_VALUE2 Y #else #define SOME_VALUE1 P #define SOME_VALUE2 Q #endif
没有macros,这种function不太容易实现。 macros实际上是一个很好的软件configurationpipe理工具,而不仅仅是创build代码重用的快捷方式。 为了macros中的可重用性而定义函数肯定会产生问题。