我一直在使用C语言黑客来检测使用宏的整数常量表达式 - 由Martin Uecker提出的想法。 https://lkml.org/lkml/2018/3/20/805
当我开始玩代码时,通过交换三元运算符的表达式,我发现了一种奇怪的行为。
见下面的代码,
#include <stdio.h>
#include <stdlib.h>
// https://lkml.org/lkml/2018/3/20/805
#define ICE_P(x) (sizeof(int) == sizeof(*(1 ? ((void*)((x) * 0l)) : (int*)1)))
//Why is this working?
#define ICE_P2(x) (sizeof(int) == sizeof(*(1 ? (int*)1 : ((void*)((x) * 0l)))))
int main() {
int a = 5;
int b = ICE_P(a);
int c = ICE_P2(a);
int d = ICE_P(5);
int e = ICE_P2(5);
return 0;
}
ICE_P(x)完美无缺,如上面的链接所述。但是,当我互换三元运算符的左右表达式时,程序仍然表现得与我没想到的相同。
当调用ICE_P2时,将评估1 ? (int*)1 : ((void*)((x) * 0l))
。
(void*)((5)*0l))
将为NULL
根据标准,如果三元运算符的一侧被评估为“NULL”,则无论条件表达式如何,都将返回NULL
以外的值。
因此,在这种情况下,总是会(sizeof(int) == sizeof(int*))
进行评估,结果将是1
。
(void*)((a)*0l))
不会是NULL
我的期望:
1 ? (int*)1 : (void*)(<Not NULL>)
应评估为(int*)1
,因为1始终为真。(sizeof(int) == sizeof(*(int*)))
实际
1 ? (int*)1 : (void*)(<Not NULL>)
正在将值评估为(void*)(<Not NULL>)
。(sizeof(int) == sizeof(void*))
要更好地了解ICE_P
,请参阅stackoverflow question或此reddit post。
答案 0 :(得分:6)
让我们通过标准。 C11 6.3.2.3p3
- 值为
醇>0
的整数常量表达式,或者类型为void *
的表达式,称为空指针常量。 [...]
在构造(void*)((x) * 0l)
中,对于任何整数值,该值显然始终为0,因此在大多数平台上给定任何类型的整数值时,它总是会产生空指针 ,但是空指针常量如果x
是整数常量表达式。
现在,问题是为什么x ? (void *)0 : (int *)1
在x ? (int *)1 : (void *)0
中包含时与sizeof(* (...))
的工作方式相同。为此,我们需要阅读6.5.11p6:
- 如果第二个和第三个操作数都是指针,或者一个是空指针常量而另一个是指针,则结果类型是指向使用两个操作数引用的类型的所有类型限定符限定的类型的指针。此外,如果两个操作数都是兼容类型的指针或兼容类型的不同限定版本,则结果类型是指向复合类型的适当限定版本的指针; 如果一个操作数是空指针常量,则结果具有另一个操作数的类型;否则,一个操作数是指向
void
或void
的限定版本的指针,在这种情况下,结果类型是指向void
的适当限定版本的指针。 < / LI> 醇>
因此,如果第二次或第三次尝试是空指针常量,则条件运算符的结果具有其他的类型表达式,即x ? (void *)0 : (int *)1
和x ? (int*)1 : (void *)0
都有类型int *
!
在其他案例中,即x ? (void *)non_constant : (int *)1
,粗体文字的后半部分表示该表达式的类型必须为void *
。
在编译阶段,根据第二个和第三个操作数的类型,完全决定类型;并且第一个操作数的值在其中不起作用。 如果然后在sizeof
中使用它,那么整个表达式就变成另一个整数常量表达式。
然后是可疑的部分:sizeof(* ...)
。指针被“取消引用”,并且在计算中使用解除引用值的sizeof
。 ISO C不允许评估sizeof(void)
- 实际上,它甚至不允许取消引用void *
- 但GCC将其定义为sizeof(void)
。
正如莱纳斯所说:"That is either genius, or a seriously diseased mind. - I can't quite tell which."
顺便说一句,对于许多类似的用途,GCC内置__builtin_constant_p
就足够了 - 但是对于所有整数常量表达式它会返回1
,它会返回1
用于优化器可以在代码中替换常量的任何其他左值;不同之处在于,只有常量整数表达式可以用作非VLA数组维度,静态初始值设定项和位域宽度。