C++ 为什么预处理器宏是邪恶的,有哪些替代方案?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/14041453/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Why are preprocessor macros evil and what are the alternatives?
提问by user1849534
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?
我一直问这个,但我从来没有得到一个很好的答案;我想几乎所有程序员在写第一个“Hello World”之前都遇到过“永远不应该使用宏”、“宏是邪恶的”等短语,我的问题是:为什么?这么多年了,新的 C++11 有没有真正的替代品?
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.
简单的部分是关于宏#pragma
,它们是平台特定的和编译器特定的,并且大多数时候它们有严重的缺陷,例如#pragma once
在至少两种重要情况下容易出错:不同路径中的相同名称以及一些网络设置和文件系统。
But in general, what about macros and alternatives to their usage?
但总的来说,宏及其用法的替代方法呢?
回答by Mats Petersson
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):
宏有几个方面使它们“不好”(我将在后面详细介绍,并提出替代方案):
- You can not debug macros.
- Macro expansion can lead to strange side effects.
- Macros have no "namespace", so if you have a macro that clashes with a name used elsewhere, you get macro replacements where you didn't want it, and this usually leads to strange error messages.
- Macros may affect things you don't realize.
- 您不能调试宏。
- 宏扩展会导致奇怪的副作用。
- 宏没有“命名空间”,因此如果您的宏与其他地方使用的名称发生冲突,您会在不想要的地方获得宏替换,这通常会导致奇怪的错误消息。
- 宏可能会影响您没有意识到的事情。
So let's expand a little here:
所以让我们在这里扩展一点:
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.
1) 无法调试宏。当您有一个可转换为数字或字符串的宏时,源代码将具有宏名称和许多调试器,您无法“看到”宏转换为什么。所以你实际上不知道发生了什么。
Replacement: Use enum
or const T
更换:使用enum
或const 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) Macro expansions can have strange side effects.
2) 宏扩展会产生奇怪的副作用。
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.
著名的一个是#define SQUARE(x) ((x) * (x))
和使用x2 = SQUARE(x++)
。这导致x2 = (x++) * (x++);
,即使它是有效的代码 [1],也几乎肯定不是程序员想要的。如果是函数,做x++就好了,x只会自增一次。
Another example is "if else" in macros, say we have this:
另一个例子是宏中的“if else”,假设我们有这个:
#define safe_divide(res, x, y) if (y != 0) res = x/y;
and then
进而
if (something) safe_divide(b, a, x);
else printf("Something is not set...");
It actually becomes completely the wrong thing....
它实际上变成了完全错误的事情......
Replacement: real functions.
替换:真正的功能。
3) Macros have no namespace
3) 宏没有命名空间
If we have a macro:
如果我们有一个宏:
#define begin() x = 0
and we have some code in C++ that uses begin:
我们在 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;
Now, what error messge 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) Macros have effects you don't realize
4)宏有你没有意识到的效果
Take this 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();
}
Now, without looking at the macro, you would think that begin is a function, which shouldn't affect x.
现在,不看宏,你会认为 begin 是一个函数,它不应该影响 x。
This sort of thing, and I've seen much more complex examples, can REALLY mess up your day!
这种事情,而且我见过更复杂的例子,真的会让你的一天变得一团糟!
Replacement: Either don't use a macro to set x, or pass x in as an argument.
替换:要么不使用宏来设置 x,要么将 x 作为参数传入。
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__)
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.
现在我们可以my_debug_malloc
在代码中使用常规的 malloc,但它有额外的参数,所以当它结束时,我们扫描“哪些内存元素没有被释放”,我们可以打印分配的位置,以便程序员可以追踪泄漏。
[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.
[1] 在“序列点”中多次更新一个变量是未定义的行为。序列点与语句并不完全相同,但对于大多数意图和目的,这就是我们应该考虑的。这样做x++ * x++
会更新x
两次,这是未定义的,并且可能会导致不同系统上的不同值,以及不同的结果值x
。
回答by utnapistim
The saying "macros are evil" usually refers to the use of #define, not #pragma.
俗话说“宏是邪恶的”通常是指使用#define,而不是#pragma。
Specifically, the expression refers to these two cases:
具体来说,表达式是指这两种情况:
defining magic numbers as macros
using macros to replace expressions
将幻数定义为宏
使用宏替换表达式
with the new C++ 11 there is a real alternative after so many years ?
这么多年后,新的 C++ 11 有真正的替代品吗?
Yes, for the items in the list above (magic numbers should be defined with const/constexpr and expressions should be defined with [normal/inline/template/inline template] functions.
是的,对于上面列表中的项目(幻数应该用 const/constexpr 定义,表达式应该用 [normal/inline/template/inline template] 函数定义。
Here are some of the problems introduced by defining magic numbers as macros and replacind expressions with macros (instead of defining functions for evaluating those expressions):
以下是通过将幻数定义为宏并使用宏替换表达式(而不是定义用于评估这些表达式的函数)引入的一些问题:
when defining macros for magic numbers, the compiler retains no type information for the defined values. This can cause compilation warnings (and errors) and confuse people debugging the code.
when defining macros instead of functions, programmers using that code expect them to work like functions and they do not.
为幻数定义宏时,编译器不会保留已定义值的类型信息。这可能会导致编译警告(和错误)并使调试代码的人感到困惑。
在定义宏而不是函数时,使用该代码的程序员希望它们像函数一样工作,而实际上并非如此。
Consider this code:
考虑这个代码:
#define max(a, b) ( ((a) > (b)) ? (a) : (b) )
int a = 5;
int b = 4;
int c = max(++a, b);
You would expect a and c to be 6 after the assignment to c (as it would, with using std::max instead of the macro). Instead, the code performs:
您希望 a 和 c 在分配给 c 后为 6(就像使用 std::max 而不是宏一样)。相反,代码执行:
int c = ( ((++a) ? (b)) ? (++a) : (b) ); // after this, c = a = 7
On top of this, macros do not support namespaces, which means that defining macros in your code will limit the client code in what names they can use.
最重要的是,宏不支持命名空间,这意味着在代码中定义宏将限制客户端代码可以使用的名称。
This means that if you define the macro above (for max), you will no longer be able to #include <algorithm>
in any of the code below, unless you explicitly write:
这意味着如果你定义了上面的宏(对于 max),你将不再能够#include <algorithm>
在下面的任何代码中,除非你明确地写:
#ifdef max
#undef max
#endif
#include <algorithm>
Having macros instead of variables / functions also means that you cannot take their address:
使用宏而不是变量/函数也意味着您不能获取它们的地址:
if a macro-as-constant evaluates to a magic number, you cannot pass it by address
for a macro-as-function, you cannot use it as a predicate or take the function's address or treat it as a functor.
如果宏作为常量的计算结果为幻数,则不能通过地址传递它
对于宏作为函数,您不能将其用作谓词或获取函数的地址或将其视为函子。
Edit: As an example, the correct alternative to the #define max
above:
编辑:例如,上述的正确替代方案#define max
:
template<typename T>
inline T max(const T& a, const T& b)
{
return a > b ? a : b;
}
This does everything the macro does, with one limitation: if the types of the arguments are different, the template version forces you to be explicit (which actually leads to safer, more explicit code):
这完成了宏所做的一切,但有一个限制:如果参数的类型不同,模板版本会强制您明确(这实际上会导致更安全、更明确的代码):
int a = 0;
double b = 1.;
max(a, b);
If this max is defined as a macro, the code will compile (with a warning).
如果将此最大值定义为宏,则代码将编译(带有警告)。
If this max is defined as a template function, the compiler will point out the ambiguity, and you have to say either max<int>(a, b)
or max<double>(a, b)
(and thus explicitly state your intent).
如果这个 max 被定义为模板函数,编译器会指出歧义,你必须说max<int>(a, b)
或者max<double>(a, b)
(从而明确说明你的意图)。
回答by phaazon
A common trouble is this :
一个常见的问题是:
#define DIV(a,b) a / b
printf("25 / (3+2) = %d", DIV(25,3+2));
It will print 10, not 5, because the preprocessor will expand it this way:
它将打印 10,而不是 5,因为预处理器会以这种方式扩展它:
printf("25 / (3+2) = %d", 25 / 3 + 2);
This version is safer:
这个版本更安全:
#define DIV(a,b) (a) / (b)
回答by user1284631
Macros are valuable especially for creating generic code (macro's parameters can be anything), sometimes with parameters.
宏对于创建通用代码(宏的参数可以是任何东西)尤其有价值,有时还带有参数。
More, this code is placed (ie. inserted) at the point of the macro is used.
此外,此代码放置(即插入)在使用宏的位置。
OTOH, similar results may be achived with:
OTOH,类似的结果可以通过以下方式实现:
overloaded functions (different parameter types)
templates, in C++ (generic parameter types and values)
inline functions (place code where they are called, instead of jumping to a single-point definition -- however, this is rather a recommandation for the compiler).
重载函数(不同的参数类型)
模板,在 C++ 中(通用参数类型和值)
内联函数(将代码放在调用它们的地方,而不是跳转到单点定义——然而,这更像是对编译器的建议)。
edit: as for why the macro are bad:
编辑:至于为什么宏不好:
1) no type-checking of the arguments (they have no type), so can be easily misused 2) sometimes expand into very complex code, that can be difficult to identify and understand in the preprocessed file 3) it is easy to make error-prone code in macros, such like:
1) 没有对参数进行类型检查(它们没有类型),所以很容易被误用 2) 有时会扩展成非常复杂的代码,在预处理文件中很难识别和理解 3) 很容易出错- 宏中的代码,例如:
#define MULTIPLY(a,b) a*b
and then call
然后打电话
MULTIPLY(2+3,4+5)
that expands in
扩展到
2+3*4+5 (and not into: (2+3)*(4+5)).
2+3*4+5(而不是:(2+3)*(4+5))。
To have the latter, you should define:
要拥有后者,您应该定义:
#define MULTIPLY(a,b) ((a)*(b))
回答by Sandi Hrvi?
I don't think that there is anything wrong with using preprocessor definitions or macros as you call them.
我不认为使用预处理器定义或宏来调用它们有什么问题。
They are a (meta) language concept found in c/c++ and like any other tool they can make your life easier if you know what you're doing. The trouble with macros is that they are processed before your c/c++ code and generate new code that can be faulty and cause compiler errors which are all but obvious. On the bright side they can help you keep your code clean and save you a lot of typing if used properly, so it comes down to personal preference.
它们是在 c/c++ 中发现的(元)语言概念,并且与任何其他工具一样,如果您知道自己在做什么,它们可以使您的生活更轻松。宏的问题在于它们在您的 c/c++ 代码之前被处理并生成新代码,这些代码可能有缺陷并导致编译器错误,这些错误几乎是显而易见的。从好的方面来说,它们可以帮助您保持代码整洁,如果使用得当,可以为您节省大量输入,因此这取决于个人喜好。
回答by indiangarg
Macros in C/C++ can serve as an important tool for version control. Same code can be delivered to two clients with a minor configuration of Macros. I use things like
C/C++ 中的宏可以作为版本控制的重要工具。相同的代码可以通过少量的宏配置交付给两个客户端。我使用类似的东西
#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
This kind of functionality is not so easily possible without macros. Macros are actually a great Software Configuration Management Tool and not just a way to create shortcuts for reuse of code. Defining functions for the purpose of reusability in macros can definitely create problems.
如果没有宏,这种功能就不是那么容易实现的。宏实际上是一个很棒的软件配置管理工具,而不仅仅是创建代码重用快捷方式的一种方式。为了在宏中可重用性而定义函数肯定会产生问题。
回答by Davide Icardi
I think that the problem is that macros are not well optimized by the compiler and are "ugly" to read and debug.
我认为问题在于编译器没有很好地优化宏,并且阅读和调试“丑陋”。
Often a good alternatives are generic functions and/or inline functions.
通常一个很好的选择是通用函数和/或内联函数。
回答by indiangarg
Preprocessor macros are not evil when they are used for intended purposes like:
预处理器宏在用于预期目的时并不邪恶,例如:
- Creating different releases of the same software using #ifdef type of constructs, for example the release of windows for different regions.
- For defining code testing related values.
- 使用#ifdef 类型的构造创建同一软件的不同版本,例如为不同区域发布窗口。
- 用于定义代码测试相关值。
Alternatives-One can use some sort of configuration files in ini,xml,json format for similar purposes. But using them will have run time effects on code which a preprocessor macro can avoid.
替代方案 -出于类似目的,可以使用某种 ini、xml、json 格式的配置文件。但是使用它们会对代码产生运行时影响,这是预处理器宏可以避免的。