为什么预处理器macros是邪恶的,有什么select?

我一直都这样问,但是我从来没有收到过很好的答案。 我认为在编写第一个“Hello World”之前,几乎所有的程序员都遇到过“macros不该用”,“macros都是恶”等字眼,我的问题是:为什么? 随着新的C + + 11有这么多年后真正的select?

这个简单的部分是关于像#pragma这样的macros,它们是平台特定的和编译器特定的,而且大多数时候像#pragma once这样的严重缺陷#pragma once在至less2个重要的情况下容易出错:在不同的path中有相同的名字,networking设置和文件系统。

但总的来说,macros和它们的用法的替代方法呢?

macros像其他任何工具一样 – 用于谋杀的锤子不是邪恶的,因为它是一把锤子。 这种人以这种方式使用它是邪恶的。 如果你想锤钉子,锤子是一个完美的工具。

macros有几个方面使它们变得“不好”(我将在后面展开,并提出其他select):

  1. 您不能debuggingmacros。
  2. macros观扩张可能会导致奇怪的副作用。
  3. macros没有“命名空间”,所以如果你有一个与其他地方使用的名字冲突的macros,你会得到你不需要的macrosreplace,这通常会导致奇怪的错误信息。
  4. macros可能会影响你没有意识到的事情。

所以让我们在这里展开一下:

1)macros不能被debugging。 当你有一个macros转换为数字或string,源代码将有macros名称和许多debugging器,你不能“看到”什么macros转换。 所以你实际上不知道发生了什么事情。

replace :使用enumconst 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中的可重用性而定义函数肯定会产生问题。