为什么C宏不是类型安全的?

如果多次遇到这种说法并且无法弄清楚它应该是什么意思。 由于生成的代码是使用常规C编译器编译的,因此最终会像任何其他代码一样(或很少)检查类型。

那么为什么宏不安全呢? 这似乎是他们应被视为邪恶的主要原因之一。

好吧,他们不是直接类型安全的…我想在某些场景/用法中你可以说他们可以间接 (即结果代码)类型安全。 但你当然可以创建一个用于整数的宏并传递它的字符串…处理宏的预处理器当然不关心。 编译器可能会阻塞它,具体取决于使用情况……

考虑典型的“最大”宏,与function:

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

这将编译没有问题,但几乎肯定不会给你想要的结果。

另外当然副作用也不同,请考虑function案例:

 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)F("A", 2)F(1, (2, 3, 4))或……

如果宏中的某些内容需要某种类型的安全性,您可能会从编译器中获得错误,或者您可能不会收到错误。 但这不是预处理器。

将字符串传递给期望数字的宏时,您可以得到一些非常奇怪的结果,因为您最终可能会使用字符串地址作为数字而不会从编译器发出吱吱声。

由于宏由预处理器处理,并且预处理器不理解类型,因此它将很乐意接受错误类型的变量。

这通常只是类似函数的宏的问题,即使预处理器没有,编译器也经常会捕获任何类型错误,但这不能保证。

一个例子

在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 inline T max(T a, T b) { return a > b ? a : b; } 

MAX(1,2u)将按预期工作,但max(1,2u)不会。 (由于1和2u是不同的类型,因此无法在两者上实例化max。)

在大多数情况下,这并不是使用宏的理由(它们仍然是邪恶的),但它是C和C ++类型安全的有趣结果。

在某些情况下,宏的function比函数更不安全。 例如

 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源代码(“懒惰”)作者(在作者/读者意义上)的帮助。 就这样。