650*_*502 8 c gcc strict-aliasing
使用gcc 4.9.2 20150304 64位我碰到了这个看似奇怪的行为:
double doit() {
double *ptr = (double *)malloc(sizeof(double));
ptr[0] = 3.14;
return (double)((uintptr_t) ptr);
}
Run Code Online (Sandbox Code Playgroud)
在代码中我double
在堆上分配一个,初始化它然后返回另一个double
初始化的第一个转换为a的地址intptr_t
.通过优化-O2
,这将在32位模式下生成以下汇编代码:
sub $0x28,%esp
push $0x8 ;; 8 bytes requested
call 8048300 <malloc@plt> ;; malloc 'em
movl $0x0,0x14(%esp) ;; store zeros in upper 32bits
mov %eax,0x10(%esp) ;; store address in lower 32bits
fildll 0x10(%esp) ;; convert a long long to double
add $0x2c,%esp
ret
Run Code Online (Sandbox Code Playgroud)
令人惊讶的是,分配的初始化double
完全消失了.
生成代码时,-O0
一切都按预期工作,而相关的代码是:
push %ebp
mov %esp,%ebp
sub $0x28,%esp
sub $0xc,%esp
push $0x8 ;; 8 bytes requested
call 8048300 <malloc@plt> ;; malloc 'em
add $0x10,%esp
mov %eax,-0xc(%ebp)
mov -0xc(%ebp),%eax
fldl 0x8048578 ;; load 3.14 constant
fstpl (%eax) ;; store in allocated memory
mov -0xc(%ebp),%eax
mov %eax,-0x28(%ebp) ;; store address in low 32 bits
movl $0x0,-0x24(%ebp) ;; store 0 in high 32 bits
fildll -0x28(%ebp) ;; convert the long-long to a double
fstpl -0x20(%ebp)
fldl -0x20(%ebp)
leave
ret
Run Code Online (Sandbox Code Playgroud)
我做了什么无效的事情(我正在考虑别名规则,即使在我看来跳过初始化也没有理由)或者这只是一个gcc错误?
请注意,编译为64位代码时会出现同样的问题(正式intptr_t
在64位模式下是8字节,因此广告double
无法正确表示...但这不会发生,因为仅在x86-64上使用64位地址中的48位,并且a double
可以精确地表示所有这些值.
这似乎是一个错误......即使使用简化的代码
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
double doit() {
double *ptr = (double *)malloc(sizeof(double));
ptr[0] = 3.14;
uintptr_t ip = (uintptr_t)ptr;
return (double)ip;
}
int main(int argc, const char *argv[]) {
double v = doit();
double *p = (double *)((intptr_t)v);
printf("sizeof(uintptr_t) = %i\n", (int)sizeof(uintptr_t));
printf("*p = %0.3f\n", *p);
return 0;
}
Run Code Online (Sandbox Code Playgroud)
编译时-O2
不会初始化内存。
该代码工作正常,直接返回一个intptr_t
(或一个unsigned long long
);但转换为 a 后返回它double
不起作用,因为gcc
显然假设在这种情况下您将无法再访问内存。
这在 32 位模式下显然是错误的(其中intptr_t
是 4 字节,double
为整数提供 53 位精度),但对于 64 位模式也是如此,其中 whileuintptr_t
确实是 8 字节,使用的值是 48 位)。
对此不确定,但问题可能与“树上的死代码消除”( -ftree-dce
) 有关。在 32 位模式下编译时,启用优化-O2
但禁用此特定优化,-fno-tree-dce
程序输出会发生变化并且是正确的,但生成的代码不是。
更具体地说,非内联版本doit
不包含初始化代码,但内联调用中生成的代码main
和优化器“知道”内存的值3.14
并直接在输出中打印该值。
确认为错误,已在主干中更正。
下一个版本之前的解决方法是-fno-tree-pta