在以下代码中,我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上复制。
答案 0 :(得分:15)
(也许这是未定义的行为?)
不是直接的,但之后从对象中读取是。
引用C99:
6.2.6类型表示
6.2.6.1一般
5某些对象表示不需要表示对象类型的值。如果存储 对象的值具有这样的表示,并由左值表达式读取 没有字符类型,行为是未定义的。 [...]
基本上,这意味着如果某个特定的实现确定bool
的唯一两个有效字节是0
和1
,那么你最好确保你不要使用任何技巧来尝试将其设置为任何其他值。
答案 1 :(得分:15)
当GCC编译该程序时,汇编语言输出包括序列
movzbl (%rax), %eax
movzbl %al, %eax
movl %eax, -4(%rbp)
执行以下操作:
*foo
(在汇编中由(%rax)
表示)中的32位复制到寄存器%eax
,并用零填充%eax
的高位(不是有任何,因为%eax
是32位寄存器。)%eax
的低8位(由%al
表示)复制到%eax
,并用零填充%eax
的高位。作为C程序员,您会将其理解为%eax &= 0xff
。%eax
的值复制到%rbp
上方4个字节,这是堆栈中bar
的位置。所以这段代码是
的汇编语言翻译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)
执行以下操作:
*foo
(在汇编中用(%rax)
表示)复制32位到寄存器%eax
。%eax
,这意味着与自身进行AND运算并根据结果在处理器中设置一些标志。 (这里不需要ANDing,但是没有指令只需检查寄存器并设置标志。)%eax
的低位8位(由%al
表示)设置为1,否则设置为0。%eax
的低8位(由%al
表示)复制到%eax
,并用零填充%eax
的高位,如第一个片段。%eax
的值复制到%rbp
以上4个字节,这是堆栈中bar
的位置;也像第一个片段一样。这实际上是对C代码的忠实翻译。事实上,如果您将演员表添加到(int*)
并编译并运行该程序,您将看到它输出1
。
答案 2 :(得分:12)
在0
中存储与1
或bool
不同的值是C中未定义的行为。
实际上是这样的:
int bar = *foo ? 1 : 0;
使用接近此的东西进行优化:
int bar = *foo ? *foo : 0;