为什么“1?(int *)1:((void *)((x)* 0l))”工作正常?

时间:2018-04-02 16:56:06

标签: c gcc

我一直在使用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))

具有正确行为的场景。

  • ICE_P2( 5 ) - (void*)((5)*0l))将为NULL

根据标准,如果三元运算符的一侧被评估为“NULL”,则无论条件表达式如何,都将返回NULL以外的值。

因此,在这种情况下,总是会(sizeof(int) == sizeof(int*))进行评估,结果将是1

有我不理解的行为的情景。

  • ICE_P2( a ) - (void*)((a)*0l))不会是NULL

我的期望:

  • 现在,1 ? (int*)1 : (void*)(<Not NULL>)应评估为(int*)1,因为1始终为真。
  • 整体表达式宏将被评估为(sizeof(int) == sizeof(*(int*)))
  • 结果是 1

实际

  • 现在,1 ? (int*)1 : (void*)(<Not NULL>)正在将值评估为(void*)(<Not NULL>)
  • 整体表达式宏将被评估为(sizeof(int) == sizeof(void*))
  • 结果是 0

要更好地了解ICE_P,请参阅stackoverflow question或此reddit post

1 个答案:

答案 0 :(得分:6)

让我们通过标准。 C11 6.3.2.3p3

  
      
  1. 值为0的整数常量表达式,或者类型为void *的表达式,称为空指针常量。 [...]
  2.   

在构造(void*)((x) * 0l)中,对于任何整数值,该值显然始终为0,因此在大多数平台上给定任何类型的整数值时,它总是会产生空指针 ,但是空指针常量如果x整数常量表达式

现在,问题是为什么x ? (void *)0 : (int *)1x ? (int *)1 : (void *)0中包含时与sizeof(* (...))的工作方式相同。为此,我们需要阅读6.5.11p6

  
      
  1. 如果第二个和第三个操作数都是指针,或者一个是空指针常量而另一个是指针,则结果类型是指向使用两个操作数引用的类型的所有类型限定符限定的类型的指针。此外,如果两个操作数都是兼容类型的指针或兼容类型的不同限定版本,则结果类型是指向复合类型的适当限定版本的指针; 如果一个操作数是空指针常量,则结果具有另一个操作数的类型;否则,一个操作数是指向voidvoid的限定版本的指针,在这种情况下,结果类型是指向void的适当限定版本的指针。 < / LI>   

因此,如果第二次第三次尝试是空指针常量,则条件运算符的结果具有其他的类型表达式,即x ? (void *)0 : (int *)1x ? (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数组维度,静态初始值设定项和位域宽度。