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

时间:2013-07-31 12:38:04

标签: c++ c types macros

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

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

7 个答案:

答案 0 :(得分:37)

考虑典型的“最大”宏,而不是函数:

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

同样,没有编译器警告或错误,但不是您期望的行为。

答案 1 :(得分:11)

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

答案 2 :(得分:11)

宏不是类型安全的,因为它们不了解类型。

你不能告诉宏只采取整数。预处理器识别宏用法,它用一组令牌替换一个令牌序列(带有参数的宏)。如果使用正确,这是一个强大的工具,但它很容易使用不正确。

使用函数,您可以定义函数void f(int, int),如果您尝试使用f的返回值或传递字符串,编译器将标记。

用宏 - 没有机会。唯一的检查是给出正确数量的参数。然后它适当地替换令牌并传递给编译器。

#define F(A, B)

允许您拨打F(1, 2)F("A", 2)F(1, (2, 3, 4))或...

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

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

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

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

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

答案 4 :(得分:2)

在某些情况下,宏的功能比函数更不安全。 E.g。

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函数,结果可能是灾难性的。

答案 5 :(得分:1)

当宏运行时,它只是通过源文件进行文本匹配。这是在任何编译之前,所以它不知道它改变的任何数据类型。

答案 6 :(得分:1)

宏不是类型安全的,因为它们从来就不是类型安全的。

编译器在扩展宏之后进行类型检查。

宏和那里的扩展意味着C源代码(“懒惰”)作者(在作者/读者意义上)的帮助。就是这样。