为什么预处理器宏是邪恶的,有哪些替代方案? [英] Why are preprocessor macros evil and what are the alternatives?

查看:36
本文介绍了为什么预处理器宏是邪恶的,有哪些替代方案?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I have always asked this but I have never received a really good answer; I think that almost any programmer before even writing the first "Hello World" had encountered a phrase like "macro should never be used", "macro are evil" and so on, my question is: why? With the new C++11 is there a real alternative after so many years?

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

The easy part is about macros like #pragma, that are platform specific and compiler specific, and most of the time they have serious flaws like #pragma once that is error prone in at least 2 important situation: same name in different paths and with some network setups and filesystems.

但一般来说,宏及其使用的替代方案如何?

But in general, what about macros and alternatives to their usage?

推荐答案

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

Macros are just like any other tool - a hammer used in a murder is not evil because it's a hammer. It is evil in the way the person uses it in that way. If you want to hammer in nails, a hammer is a perfect tool.

宏有几个方面让它们变得不好"(我稍后会详细介绍每个方面,并提出替代方案):

There are a few aspects to macros that make them "bad" (I'll expand on each later, and suggest alternatives):

  1. 您不能调试宏.
  2. 宏扩展会导致奇怪的副作用.
  3. 宏没有命名空间",因此如果您的宏与其他地方使用的名称发生冲突,您会在不想要的地方替换宏,这通常会导致奇怪的错误消息.
  4. 宏可能会影响您没有意识到的事情.

所以让我们在这里扩展一点:

So let's expand a little here:

1) 无法调试宏.当您有一个可转换为数字或字符串的宏时,源代码将具有宏名称和许多调试器,您无法看到"宏转换为什么.所以你实际上不知道发生了什么.

1) Macros can't be debugged. When you have a macro that translates to a number or a string, the source code will have the macro name, and many debuggers, you can't "see" what the macro translates to. So you don't actually know what is going on.

替换:使用 enumconst T

对于类函数"宏,因为调试器在您所在的每个源代码行"级别上工作,您的宏将像单个语句一样运行,无论是一个语句还是一百个语句.很难弄清楚发生了什么.

For "function-like" macros, because the debugger works on a "per source line where you are" level, your macro will act like a single statement, no matter if it's one statement or a hundred. Makes it hard to figure out what is going on.

替换:如果需要快速",则使用函数 - 内联(但要注意内联过多不是一件好事)

Replacement: Use functions - inline if it needs to be "fast" (but beware that too much inline is not a good thing)

2) 宏扩展会产生奇怪的副作用.

著名的是#define SQUARE(x) ((x) * (x)) 和使用x2 = SQUARE(x++).这导致 x2 = (x++) * (x++);,即使它是有效的代码 [1],也几乎肯定不是程序员想要的.如果是函数,做x++就好了,x只会自增一次.

The famous one is #define SQUARE(x) ((x) * (x)) and the use x2 = SQUARE(x++). That leads to x2 = (x++) * (x++);, which, even if it was valid code [1], would almost certainly not be what the programmer wanted. If it was a function, it would be fine to do x++, and x would only increment once.

另一个例子是宏中的if else",比如我们有这个:

Another example is "if else" in macros, say we have this:

#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...");

它实际上变成了完全错误的事情......

It actually becomes completely the wrong thing....

替换:真正的功能.

3) 宏没有命名空间

如果我们有一个宏:

#define begin() x = 0

我们有一些使用 begin 的 C++ 代码:

and we have some code in C++ that uses 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;

现在,你认为你得到了什么错误信息,你在哪里寻找错误[假设你已经完全忘记 - 或者甚至不知道 - 位于其他人编写的某个头文件中的 begin 宏?[如果你在包含之前包含那个宏,那就更有趣了——当你查看代码本身时,你会陷入奇怪的错误中,这些错误完全没有意义.

Now, what error message do you think you get, and where do you look for an error [assuming you have completely forgotten - or didn't even know about - the begin macro that lives in some header file that someone else wrote? [and even more fun if you included that macro before the include - you'd be drowning in strange errors that makes absolutely no sense when you look at the code itself.

替换:嗯,没有比规则"更多的替换 - 只对宏使用大写名称,而不要将所有大写名称用于其他事物.

Replacement: Well there isn't so much as a replacement as a "rule" - only use uppercase names for macros, and never use all uppercase names for other things.

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
", x);

    end();

}

现在,不看宏,你会认为 begin 是一个函数,它不应该影响 x.

Now, without looking at the macro, you would think that begin is a function, which shouldn't affect x.

这种事情,而且我见过更复杂的例子,真的会让你的一天一团糟!

This sort of thing, and I've seen much more complex examples, can REALLY mess up your day!

替换:要么不使用宏来设置 x,要么将 x 作为参数传入.

Replacement: Either don't use a macro to set x, or pass x in as an argument.

有时使用宏肯定是有益的.一个例子是用宏包装一个函数来传递文件/行信息:

There are times when using macros is definitely beneficial. One example is to wrap a function with macros to pass on file/line information:

#define malloc(x) my_debug_malloc(x, __FILE__, __LINE__)
#define free(x)  my_debug_free(x, __FILE__, __LINE__)

现在我们可以使用 my_debug_malloc 作为代码中的常规 malloc,但是它有额外的参数,所以当它结束时我们扫描哪些内存元素没有被释放",我们可以打印分配的位置,以便程序员可以追踪泄漏.

Now we can use my_debug_malloc as the regular malloc in the code, but it has extra arguments, so when it comes to the end and we scan the "which memory elements hasn't been freed", we can print where the allocation was made so the programmer can track down the leak.

[1] 在序列点"中多次更新一个变量是未定义的行为.序列点与语句并不完全相同,但对于大多数意图和目的,这就是我们应该考虑的.所以做 x++ * x++ 会更新 x 两次,这是未定义的,可能会导致不同系统上的值不同,x 也是.

[1] It is undefined behaviour to update one variable more than once "in a sequence point". A sequence point is not exactly the same as a statement, but for most intents and purposes, that's what we should consider it as. So doing x++ * x++ will update x twice, which is undefined and will probably lead to different values on different systems, and different outcome value in x as well.

这篇关于为什么预处理器宏是邪恶的,有哪些替代方案?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆