使用GCC和bool指针的条件运算符的奇怪结果

时间:2014-12-26 20:42:04

标签: c gcc undefined-behavior conditional-operator

在以下代码中,我memset() stdbool.h bool变量值为123。 (也许这是未定义的行为?)然后我将指向此变量的指针传递给受害者函数,该函数尝试使用条件操作来防止意外值。但是,由于某种原因,GCC似乎完全取消了条件操作。

#include <stdio.h>
#include <stdbool.h>
#include <string.h>

void victim(bool* foo)
{
    int bar = *foo ? 1 : 0;
    printf("%d\n", bar);
}

int main()
{
    bool x;
    bool *foo = &x;
    memset(foo, 123, sizeof(bool));
    victim(foo);
    return 0;
}
user@host:~$ gcc -Wall -O0 test.c
user@host:~$ ./a.out 
123

这使得特别恼人的是victim()函数实际上在库中,如果值大于1,则会崩溃。

转载于GCC版本4.8.2-19ubuntu1和4.7.2-5。没有在clang上复制。

3 个答案:

答案 0 :(得分:15)

  

(也许这是未定义的行为?)

不是直接的,但之后从对象中读取是。

引用C99:

  

6.2.6类型表示

     

6.2.6.1一般

     

5某些对象表示不需要表示对象类型的值。如果存储   对象的值具有这样的表示,并由左值表达式读取   没有字符类型,行为是未定义的。 [...]

基本上,这意味着如果某个特定的实现确定bool的唯一两个有效字节是01,那么你最好确保你不要使用任何技巧来尝试将其设置为任何其他值。

答案 1 :(得分:15)

当GCC编译该程序时,汇编语言输出包括序列

movzbl (%rax), %eax
movzbl %al, %eax
movl %eax, -4(%rbp)

执行以下操作:

  1. *foo(在汇编中由(%rax)表示)中的32位复制到寄存器%eax,并用零填充%eax的高位(不是有任何,因为%eax是32位寄存器。)
  2. %eax的低8位(由%al表示)复制到%eax,并用零填充%eax的高位。作为C程序员,您会将其理解为%eax &= 0xff
  3. %eax的值复制到%rbp上方4个字节,这是堆栈中bar的位置。
  4. 所以这段代码是

    的汇编语言翻译
    int bar = *foo & 0xff;
    

    显然,GCC根据bool永远不应该保留除0或1之外的任何值这一事实来优化该行。

    如果您将C源中的相关行更改为此

    int bar = *((int*)foo) ? 1 : 0;
    

    然后程序集更改为

    movl (%rax), %eax
    testl %eax, %eax
    setne %al
    movzbl %al, %eax
    movl %eax, -4(%rbp)
    

    执行以下操作:

    1. *foo(在汇编中用(%rax)表示)复制32位到寄存器%eax
    2. 对自身测试32位%eax,这意味着与自身进行AND运算并根据结果在处理器中设置一些标志。 (这里不需要ANDing,但是没有指令只需检查寄存器并设置标志。)
    3. 如果ANDing的结果为0,则将%eax的低位8位(由%al表示)设置为1,否则设置为0。
    4. %eax的低8位(由%al表示)复制到%eax,并用零填充%eax的高位,如第一个片段。
    5. %eax的值复制到%rbp以上4个字节,这是堆栈中bar的位置;也像第一个片段一样。
    6. 这实际上是对C代码的忠实翻译。事实上,如果您将演员表添加到(int*)并编译并运行该程序,您将看到它输出1

答案 2 :(得分:12)

0中存储与1bool不同的值是C中未定义的行为。

实际上是这样的:

int bar = *foo ? 1 : 0;

使用接近此的东西进行优化:

int bar = *foo ? *foo : 0;