如果多次 遇到此声明并且无法弄清楚它应该是什么意思。由于生成的代码是使用常规 C 编译器编译的,因此最终会像任何其他代码一样多(或少)进行类型检查。
那么为什么宏不是类型安全的呢?这似乎是他们应该被视为邪恶的主要原因之一。
考虑典型的“max”宏与函数:
#define MAX(a,b) a < b ? a : b
int max(int a, int b) {return a < b ? a : b;}
当人们说宏在函数的方式上不是类型安全的时,这就是人们的意思:
如果函数的调用者写
char *foo = max("abc","def");
编译器会发出警告。
然而,如果宏的调用者写道:
char *foo = MAX("abc", "def");
预处理器将替换为:
char *foo = "abc" < "def" ? "abc" : "def";
这将毫无问题地编译,但几乎可以肯定不会给出你想要的结果。
另外当然副作用是不同的,请考虑功能案例:
int x = 1, y = 2;
int a = max(x++,y++);
max() 函数将对 x 和 y 的原始值进行操作,并且后增量将在函数返回后生效。
在宏观情况下:
int x = 1, y = 2;
int b = MAX(x++,y++);
第二行经过预处理以给出:
int b = x++ < y++ ? x++ : y++;
同样,没有编译器警告或错误,但不会是您预期的行为。
宏不是类型安全的,因为它们不理解类型。
你不能告诉宏只取整数。预处理器识别宏的用法,并用另一组标记替换一个标记序列(带有其参数的宏)。如果使用正确,这是一个强大的工具,但很容易使用不正确。
使用函数,您可以定义一个函数void f(int, int)
,如果您尝试使用 f 的返回值或将其传递给字符串,编译器将进行标记。
用宏 - 没有机会。唯一要做的检查是给定正确数量的参数。然后它适当地替换标记并传递给编译器。
#define F(A, B)
将允许您调用F(1, 2)
, or F("A", 2)
or F(1, (2, 3, 4))
or ...
如果宏中的某些内容需要某种类型的安全性,您可能会从编译器中收到错误,或者您可能不会收到错误。但这不取决于预处理器。
将字符串传递给需要数字的宏时,您可能会得到一些非常奇怪的结果,因为您最终可能会使用字符串地址作为数字而不会从编译器中发出吱吱声。
好吧,它们不是直接类型安全的……我想在某些场景/用法中,您可能会争辩说它们可以是间接(即生成的代码)类型安全的。但是您当然可以创建一个用于整数的宏并将其传递给字符串……处理宏的预处理器当然不在乎。编译器可能会窒息,具体取决于使用情况......
由于宏由预处理器处理,而预处理器不理解类型,它会很乐意接受类型错误的变量。
这通常只是类函数宏的一个问题,即使预处理器没有,编译器也经常会捕获任何类型错误,但这并不能保证。
一个例子
在 Windows API 中,如果您想在编辑控件上显示气球提示,您将使用Edit_ShowBalloonTip。Edit_ShowBalloonTip 被定义为采用两个参数:编辑控件的句柄和指向 EDITBALLOONTIP 结构的指针。但是,Edit_ShowBalloonTip(hwnd, peditballoontip);
实际上是一个计算结果为的宏
SendMessage(hwnd, EM_SHOWBALLOONTIP, 0, (LPARAM)(peditballoontip));
由于配置控件通常是通过向它们发送消息来完成的,Edit_ShowBalloonTip
因此必须在其实现中进行类型转换,但是由于它是宏而不是内联函数,因此它不能在其 peditballoontip 参数中进行任何类型检查。
题外话
有趣的是,有时 C++ 内联函数有点过于类型安全。考虑标准的 C MAX 宏
#define MAX(a, b) ((a) > (b) ? (a) : (b))
及其 C++ 内联版本
template<typename T>
inline T max(T a, T b) { return a > b ? a : b; }
MAX(1, 2u) 将按预期工作,但 max(1, 2u) 不会。(由于 1 和 2u 是不同的类型,max 不能在这两个上实例化。)
这并不是在大多数情况下使用宏的真正理由(它们仍然是邪恶的),但它是 C 和 C++ 类型安全的一个有趣结果。
在某些情况下,宏的类型安全性甚至低于函数。例如
void printlog(int iter, double obj)
{
printf("%.3f at iteration %d\n", obj, iteration);
}
用颠倒的参数调用它会导致截断和错误的结果,但没有危险。相比之下,
#define PRINTLOG(iter, obj) printf("%.3f at iteration %d\n", obj, iter)
导致未定义的行为。公平地说,GCC 会警告后者,但不会警告前者,但那是因为它知道printf
——对于其他 varargs 函数,结果可能是灾难性的。
当宏运行时,它只是通过您的源文件进行文本匹配。这是在任何编译之前,因此它不知道它更改的任何数据类型。
宏不是类型安全的,因为它们从来就不是类型安全的。
编译器在宏展开后进行类型检查。
宏和扩展是作为 C 源代码的(“懒惰”)作者(在作者/读者的意义上)的帮助者。就这样。