为什么C宏的类型不安全
Why are C macros not type-safe?
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源代码("懒惰")作者(在编写者/读者的意义上)的助手。仅此而已。
- 提升错误:变量"TimeSpec RQTP"具有初始值设定项,但类型不完整
- 'HMAC_CTX'类型不完整
- 使用wchar_t更改字符的类型不像L
- C++/CLI 和 C#/VB 与不安全和外部有什么区别?
- 将 unordered_map 与 Catch2 谓词一起使用时类型不匹配
- 错误:字段'dateOfBirth'的类型不完整'Poco::Data::Date'
- 问:Apache Arrow 数组生成器不安全追加
- 不安全的 MPI 非阻塞通信示例?
- C++ 聚合的类型不完整,无法使用模板类定义
- 类型不可知的抽象以使用相同的运行时接口处理正向和反向迭代器和范围?
- 成员引用基类型不是结构或联合
- 重载函数的地址与所需类型不匹配
- 有没有一种简单的方法来检查C++中的不安全表达式
- 内建数组出现不允许的类型不完整错误
- QDate 的类型不完整,声明为私有成员
- 参数错误可能与类型不匹配有关?
- 编译器在 const ref 类型参数上使用临时对象时是否应该警告不安全的行为?
- 为什么将指针铸成数字类型是不安全的
- 为什么C宏的类型不安全
- 将较小的整数类型赋值给较大的整数类型是否不安全?