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

jpa*_*jpa 18 c gcc conditional-operator undefined-behavior

在下面的代码中,我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;
}
Run Code Online (Sandbox Code Playgroud)
user@host:~$ gcc -Wall -O0 test.c
user@host:~$ ./a.out 
123

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

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

小智 15

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

不是直接的,但后来从对象中读取的是.

引用C99:

6.2.6类型的表示

6.2.6.1总则

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

基本上,这意味着如果一个特定的实现已经确定a的唯一两个有效字节bool01,那么你最好确保你不使用任何技巧来尝试将它设置为任何其他值.


Dav*_*d Z 15

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

movzbl (%rax), %eax
movzbl %al, %eax
movl %eax, -4(%rbp)
Run Code Online (Sandbox Code Playgroud)

执行以下操作:

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

所以这段代码是汇编语言的翻译

int bar = *foo & 0xff;
Run Code Online (Sandbox Code Playgroud)

很明显,GCC已根据以下事实对线路进行了优化:a bool应该永远不会保留0或1以外的任何值.

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

int bar = *((int*)foo) ? 1 : 0;
Run Code Online (Sandbox Code Playgroud)

然后装配变为

movl (%rax), %eax
testl %eax, %eax
setne %al
movzbl %al, %eax
movl %eax, -4(%rbp)
Run Code Online (Sandbox Code Playgroud)

执行以下操作:

  1. 将32位*foo((%rax)在汇编中表示)复制到寄存器%eax.
  2. 测试32位%eax自身,这意味着与自身进行AND运算并根据结果在处理器中设置一些标志.(这里不需要ANDing,但是没有指令简单地检查寄存器并设置标志.)
  3. 如果ANDing的结果为0,则将低位8位%eax(表示为%al)设置为1,否则设置为0.
  4. 复制低位8位%eax(表示为%al)%eax%eax用零填充高位,如第一个片段所示.
  5. 将值复制%eax到上面的4个字节%rbp,这是bar堆栈上的位置; 也像第一个片段一样.

这实际上是C代码的忠实翻译.事实上,如果您将演员表添加到(int*)并编译并运行该程序,您将看到它确实输出1.

  • 这告诉“GCC 实际做什么”,这是对 hvd 回答“为什么允许 GCC 这样做”的一个很好的补充。我碰巧对后者更感兴趣,这就是为什么我接受了那个。 (3认同)

oua*_*uah 12

在C中存储不同于01在a中的值bool是未定义的行为.

实际上这个:

int bar = *foo ? 1 : 0;
Run Code Online (Sandbox Code Playgroud)

优化与接近这个:

int bar = *foo ? *foo : 0;
Run Code Online (Sandbox Code Playgroud)

  • 你可以更进一步说,因为`x?x:0`在这种情况下是一个标识,它进一步优化到只有'x`,从而得到结果. (8认同)