首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >gcc优化跳过初始化分配的内存。

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

Stack Overflow用户
提问于 2015-05-09 08:01:02
回答 4查看 725关注 0票数 8

使用gcc 4.9.2 20150304 64位时,我遇到了一个明显奇怪的行为:

代码语言:javascript
复制
double doit() {
    double *ptr = (double *)malloc(sizeof(double));
    ptr[0] = 3.14;
    return (double)((uintptr_t) ptr);
}

在代码中,我在堆上分配一个double,初始化它,然后返回另一个被初始化的double,这个地址被转换成intptr_t。通过优化-O2,在32位模式下生成以下汇编代码:

代码语言:javascript
复制
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    

令人惊讶的是,已经分配的double的初始化完全消失了。

当使用-O0生成代码时,一切都按预期工作,相关代码是:

代码语言:javascript
复制
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    

问题

我是否做了什么无效的事情(我特别想到的是混叠规则,即使在我看来跳过初始化是没有道理的),还是说这只是gcc的错误?

请注意,在编译到64位代码时也存在相同的问题(64位模式下的形式intptr_t为8字节,因此ad double无法准确地表示它.但是,这并没有发生,因为在x86-64上只使用了64位地址中的48位,double可以准确地表示所有这些值)。

EN

回答 4

Stack Overflow用户

回答已采纳

发布于 2015-05-09 09:25:23

好像是个虫子..。即使使用简化的代码

代码语言:javascript
复制
#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;
}

使用-O2编译时,不会初始化内存。

代码可以正确地直接返回intptr_t (或unsigned long long);但是在转换为double之后返回它并不有效,因为gcc显然假定在这种情况下,您将无法再访问内存。

在32位模式( intptr_t为4字节,double为整数提供53位精度)中,这显然是错误的,但对于64位模式,当uintptr_t确实是8字节时,使用的值为48位)。

编辑

对此不确定,但问题可能与“树上的死代码消除”(-ftree-dce)有关。当在32位模式编译时,启用优化-O2,但禁用这个特定的-fno-tree-dce,程序输出会改变,并且是正确的,但是生成的代码不是

更具体地说,doit的非内联版本不包含初始化代码,但是在main中生成的代码插入调用和优化器“知道”内存的值是3.14,并在输出中直接打印。

编辑2

确认为窃听器,已经在树干上更正了。

下一个版本的变通方法是-fno-tree-pta

票数 1
EN

Stack Overflow用户

发布于 2015-05-09 09:06:59

在UB的情况下,允许优化移除代码,但这里不应该这样做。

Value *ptr = (Value *)malloc(sizeof(Value));中有一个不必要的强制转换,但这应该是无害的。

这行res.d = (unsigned long long) ptr;最好是res.d = (intptr_t) ptr;,因为显然允许intptr_t接收指针,然后可以在double变量中设置一个整数值:您可能会丢失精度,但不应该是UB。

我不能测试它(因为我没有gcc 4.9),但是如果你对此有同样的问题:

代码语言:javascript
复制
#include <stdint.h>

...

Value doit() {
    Value *ptr = malloc(sizeof(Value));
    ptr[0].u = 7;
    Value res; res.d = (double) ((intptr_t) ptr);
    return res;
}

我想总结一下gcc的一个错误。

我可以尝试用FreeBSD 10.1上的clangVersion3.4.1编译代码的简化版本

cc -O3 -S doit.c给出(条形到代码部分):

代码语言:javascript
复制
doit:                                   # @doit
# BB#0:
    pushl   %ebp
    movl    %esp, %ebp
    andl    $-8, %esp
    subl    $16, %esp
    movl    $8, (%esp)
    calll   malloc
    movl    $1074339512, 4(%eax)    # imm = 0x40091EB8
    movl    $1374389535, (%eax)     # imm = 0x51EB851F
    movl    %eax, 8(%esp)
    movl    $0, 12(%esp)
    fildll  8(%esp)
    movl    %ebp, %esp
    popl    %ebp
    ret

它与gcc的编译不同,但clang即使在3.14优化级别也进行-O3初始化( 3.14的转储十六进制是0x40091eb851eb851f)。

在阅读了其他评论和答案后,我认为问题的真正原因是gcc跳过中间转换,将return (double)((uintptr_t) ptr);读入return (double) ptr; --这并不完全是因为它将是语法错误,但仍然考虑到存在UB,因为指针值在结尾以双变量结尾。但是,如果我们用中间类型的分解行,则应该将其理解为(IMHO):

代码语言:javascript
复制
register intptr_t intermediate = (intptr_t) ptr; // valid conversion
return (double) intermediate;  // valid conversion
票数 4
EN

Stack Overflow用户

发布于 2015-05-09 23:16:07

C不是汇编程序。C可以调用未定义的行为,如果有人认为它是一个高级别的汇编程序看不到原因。例如:给定两个数组int a 10和int b 10,可能是巧合&a 10 == &b。但是,下面的代码

代码语言:javascript
复制
int a [10],  b [10];
int* p = &a [10];
if (p == &b [0]) *p = 0;

如果p == &b调用未定义的行为。两个指针,p和&b,比较相等,由相同的位组成,但行为不同。(如果您不同意,请查看“限制”指针,其中的要点是,比较相等的指针可以表现得不同)。

转换为uintptr_t的规则如下:每个有效指针都可以转换为uintptr_t,结果可以转换回指针,给出相同的指针。这些值是实现定义的,只是将空指针转换为uintptr_t必须给出零,而将0转换为指针必须给出空指针。没有什么要求转换应该是简单的,或者应该是您所认为的那样。

向uintptr_t的转换是定义的实现。如果体系结构中的指针被限制为n个<= 62位,那么转换完全可能是这样的:如果p是空指针,则将其转换为零。如果p不是空指针,则取n位,将其左移(63-n)位,或结果为0x8000 0000000 0001。这一结果保证不会在没有损失的情况下翻倍。当将uintptr_t转换为double时,其结果是不能再将其转换为有效的指针。

因此,如果(double)(uintptr_t)p是从p导出的唯一值,则不能重构p,则指针p丢失,并且由于不能再读取*p,所以可以优化对*p的赋值。

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/30137521

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档