使用gcc 4.9.2 20150304 64位我碰到了这个看似奇怪的行为:
double doit() {
double *ptr = (double *)malloc(sizeof(double));
ptr[0] = 3.14;
return (double)((uintptr_t) ptr);
}
在代码中,我在堆上分配double
,初始化它,然后返回另一个double
初始化的第一个转换为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
令人惊讶的是,分配的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
我做了什么无效的事情(我正在考虑别名规则,即使在我看来跳过初始化没有理由)或者这只是一个gcc错误?
请注意,编译为64位代码时会出现同样的问题(在64位模式下正式intptr_t
为8字节,因此ad double
无法准确表示它。但这并不会发生,因为在x86-64上只使用了64位地址中的48位,而double
可以完全代表所有这些值。
答案 0 :(得分:4)
在UB的情况下允许优化删除代码,但在这里它不应该。
Value *ptr = (Value *)malloc(sizeof(Value));
中有不必要的演员,但这应该是无害的。
此行res.d = (unsigned long long) ptr;
最好是res.d = (intptr_t) ptr;
,因为intptr_t
明确允许接收指针,然后您可以在double
变量中设置一个整数值:you可能会失去精确度,但它不应该是UB。
我无法测试它(因为我没有gcc 4.9)但如果你遇到同样的问题:
#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上使用clang版本3.4.1编译简化版本的代码
cc -O3 -S doit.c
给出(条带化到代码部分):
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甚至在-O3优化级别进行3.14
初始化(3.14的转储十六进制为0x40091eb851eb851f
)
在阅读其他评论和答案之后,我认为问题的真正原因是gcc 跳过中间演员并将return (double)((uintptr_t) ptr);
读作return (double) ptr;
- 不完全是因为它会出现语法错误,但仍然会考虑UB,因为在结尾指针值结束为双变量。但是如果我们用中间演员分解那么它应该被读取(恕我直言):
register intptr_t intermediate = (intptr_t) ptr; // valid conversion
return (double) intermediate; // valid conversion
答案 1 :(得分:2)
我觉得这里没什么奇怪的。您从未读过7
您写的malloc
,而是将double
的结果写入Value *ptr = (Value*) malloc(sizeof(Value));
ptr[0].u = 7;
Value res; res.d = (uintptr_t) ptr; // ptr is a result of malloc
return res; // ptr is lost here which probably makes
// GCC think that it is no longer accessible
// so "7" is lost here too
:
.u
将指针转换为double很可能会失去精度,从而使内存无法访问(UB)。
但是,如果将指针保存为整数(Value res; res.u = (uintptr_t) ptr; // Saving to .u, not .d
),GCC会将其视为别名内存并保持初始化:
0x0000000000400570 <+0>: sub $0x8,%rsp
0x0000000000400574 <+4>: mov $0x8,%edi
0x0000000000400579 <+9>: callq 0x400460 <malloc@plt>
0x000000000040057e <+14>: movq $0x7,(%rax)
0x0000000000400585 <+21>: add $0x8,%rsp
0x0000000000400589 <+25>: retq
编译到
(double)ptr
所以问题是您将指针保存为double。
BTW,Send_Email ( $BeforeScript, $AfterScript)
是编译错误,标准要求:
6.5.4投注运营商
[...]
4指针类型不得转换为任何浮点类型。浮动类型不应转换为任何指针类型。
截至N1548草案
答案 2 :(得分: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;
}
使用-O2
编译时不会初始化内存。
代码正常返回intptr_t
(或unsigned long long
);但转换为double
后返回它不起作用gcc
显然假设在这种情况下你将无法再访问内存。
这在32位模式下显然是错误的(其中intptr_t
为4个字节,double
为整数提供53位精度),但对于64位模式,其中uintptr_t
为-ftree-dce
确实是8个字节,使用的值是48位)。
对此不确定,但问题可能与“树上的死代码消除”(-O2
)有关。在32位模式下进行编译时,启用优化-fno-tree-dce
但禁用具有doit
的特定版本,程序输出会更改并且正确但生成的代码不是。
更具体地说,main
的非内联版本不包含初始化代码,但3.14
中生成的代码内联调用,优化器“知道”内存的值为{{1}并直接在输出中打印。
确认为一个错误,已在行李箱中更正。
下一版本的解决方法是-fno-tree-pta
答案 3 :(得分:0)
C不是汇编程序。 C可以调用未定义的行为,其中将其视为高级汇编程序的人无法查看原因。作为一个例子:给定两个数组int [10]和int b [10],有可能是巧合&amp; a [10] ==&amp; b [0]。但是,以下代码
int a [10], b [10];
int* p = &a [10];
if (p == &b [0]) *p = 0;
如果p ==&amp; b [0],将调用未定义的行为。两个指针p和&amp; b [0]比较相等,并且由相同的位组成,但表现不同。 (如果你不同意,请查看“限制”指针,其中整点是比较相等的指针可以表现不同)。
转换为uintptr_t的规则如下:每个有效指针都可以转换为uintptr_t,结果可以转换回指针,给出相同的指针。这些值是实现定义的,除了将空指针转换为uintptr_t必须给出零,并且将0转换为指针必须给出空指针。没有什么要求转换应该简单,或者应该是您认为的应该是。
转换为uintptr_t是实现定义的。如果体系结构中的指针被限制为n <= 62位,则完全有可能转换如下:如果p是空指针,则将其转换为零。如果p不是空指针,则取n位,将它们向左移位(63-n)位,或将结果移位到0x8000 0000 0000 0001.结果保证不能转换为double而不丢失。当uintptr_t转换为double时,结果是不能再将其转回有效指针。
结果,如果(double)(uintptr_t)p是从p导出的唯一值,那么p不能重建,指针p丢失,并且* p的赋值可以被优化掉,因为* p不能再读一遍。
答案 4 :(得分:0)
我相信gcc是正确的。您没有使用该值并且没有返回指针,因此它认为该值已无法访问。
你应该已经返回指针并在其他地方强制转换为double,或者你需要一个联合,以便gcc知道指向该值的指针仍然存在:
require_once('/path/to/Zend/Json/Expr.php')