三元运算符在ICE_P宏的语法中为何表现异常?
解析Linus的ICE判断宏与三元指针类型规则
最近在Linux邮件列表里看到Linus贴的这个用来判断表达式是否触发编译器内部错误(ICE)的宏,当时我看到他解释的三元运算符规则时,也跟你一样一头雾水,后来自己写了几段测试代码试了试,终于搞明白其中的门道了!
先看这个神奇的ICE检测宏
Linus给出的宏是这样的:
#define ICE_P(x) (sizeof(int) == sizeof(*(1 ? ((void*)((x) * 0l)) : (int*)1)))
拆解宏的逻辑与Linus提到的规则
Linus解释时提到的核心规则是:若三元运算符的指针操作数之一为NULL,结果类型为另一操作数类型。这里得先明确,(void*)0(也就是(x)*0l的结果,因为任何表达式乘0都是0,转成void*就是NULL指针)属于C标准里的「空指针常量」,它可以被隐式转换成任何指针类型。
我们一步步拆解这个宏:
- 三元表达式
1 ? ((void*)((x) * 0l)) : (int*)1的条件是1(永远为真),所以运行时会取第一个分支,但编译器在编译阶段会对两个分支都做类型检查 - 如果表达式
x会触发ICE,那么编译器在处理(x)*0l这一步就直接崩溃了,根本走不到后面的sizeof比较 - 如果
x能正常编译,(x)*0l的结果是0,转成void*就是空指针常量;这时候三元的另一个分支是int*类型,根据Linus说的规则,整个三元表达式的类型会被推导为int*(空指针常量被转换成int*类型) - 对这个
int*类型的结果解引用,得到的是int类型,所以sizeof(*(...))等于sizeof(int),宏的结果为真
我对规则的测试验证
当时我对Linus的规则半信半疑,于是写了几段代码验证:
测试1:NULL与int*的三元类型
#include <stdio.h> int main() { // 空指针常量(void*类型的NULL) void* null_void = (void*)0; int* int_ptr = (int*)0x1234; // 结果可以直接赋值给int*,说明三元表达式类型是int* int* res = 1 ? null_void : int_ptr; printf("res的类型是int*\n"); return 0; }
这段代码编译完全没问题,印证了空指针常量和int*一起作为三元分支时,结果类型是int*。
测试2:int类型的NULL与void的三元类型
int main() { int* null_int = NULL; void* void_ptr = (void*)0x5678; // 结果可以直接赋值给void*,说明三元表达式类型是void* void* res = 1 ? null_int : void_ptr; printf("res的类型是void*\n"); return 0; }
这里null_int是int*类型的NULL,和void*分支组合,结果类型是void*,也符合Linus规则的延伸:只要其中一个分支是空指针,结果类型就和另一个非空指针分支的类型一致。
本质:对应C标准的空指针转换规则
其实Linus的说法是C标准规则的简化版:C标准规定,空指针常量可以被转换成任何指针类型。在三元运算符的类型推导中,如果两个操作数都是指针类型,其中一个是空指针常量,那么编译器会把空指针常量转换成另一个操作数的指针类型,最终三元表达式的类型就是该指针类型。
而这个ICE检测宏就是利用了「编译器会先对所有分支做编译检查」的特性——如果x触发ICE,编译直接失败;如果x合法,就能顺利完成类型推导和sizeof比较,得到结果。
内容的提问来源于stack exchange,提问作者Alex Garcia
相关产品推荐
相关产品推荐

