为什么C宏的类型不安全



If多次遇到此索赔,但无法理解其含义。由于生成的代码是使用常规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)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必须在其实现中进行类型转换,但由于它是一个宏而不是内联函数,因此它不能在其peditbalontip参数中进行任何类型检查。

离题

有趣的是,有时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; }

最大值(1,2u)将按预期工作,但最大值(1/2u)不会。(由于1和2u是不同的类型,max不能同时在两者上实例化。)

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

在某些情况下,宏的类型安全性甚至不如函数。例如

void printlog(int iter, double obj)
{
    printf("%.3f at iteration %dn", obj, iteration);
}

用相反的论点调用它会导致截断和错误的结果,但并没有什么危险。相比之下,

#define PRINTLOG(iter, obj) printf("%.3f at iteration %dn", obj, iter)

导致未定义的行为。公平地说,GCC警告了后者,但没有警告前者,但这是因为它知道printf——对于其他varargs函数,结果可能是灾难性的。

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

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

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

宏和那里的扩展意味着作为C源代码("懒惰")作者(在编写者/读者的意义上)的助手。仅此而已。

相关内容

最新更新