gcc优化跳过初始化分配的内存

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可以精确地表示所有这些值.

650*_*502 1

这似乎是一个错误......即使使用简化的代码

#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并直接在输出中打印该值。

编辑2

确认为错误,已在主干中更正。

下一个版本之前的解决方法是-fno-tree-pta

  • 不,因为尖头区域没有逃逸,你正在施放它。 (2认同)