理解GCC在汇编列表输出中的浮点常量

4

只是出于好奇,我正在使用编译器浏览器来查看一些简单的C++代码的汇编输出。

考虑以下示例

int main(void){
    double x = -5.3;
}

汇编输出

main:
        push    rbp
        mov     rbp, rsp
        movsd   xmm0, QWORD PTR .LC0[rip]
        movsd   QWORD PTR [rbp-8], xmm0
        mov     eax, 0
        pop     rbp
        ret
.LC0:
        .long   858993459
        .long   -1072352461

我想了解如何使用

.LC0:
        .long   858993459
        .long   -1072352461

为了取回我的-5.3

我猜测需要合并两个32位整数的位模式,并将其解释为双精度浮点数的位模式。但是,具体怎么做呢?我必须将该模式解释为IEEE754双精度吗?按什么顺序进行?


4
第一个长整型(32位)是64位双精度浮点数的低位,第二个长整型是高位。你可以将这两个值转换成十六进制,然后将它们拼接在一起得到64位十六进制值。64位十六进制值将采用IEEE-754格式。在这种情况下,第一个值的十六进制值为33333333,第二个值的十六进制值为C0153333。64位十六进制值为C015333333333333,应该是-5.3。 - Michael Petch
3
您可以使用在线转换工具将64位编码的IEE754值转换为十进制浮点数:https://babbage.cs.qc.cuny.edu/IEEE-754.old/64bit.html - Michael Petch
维基百科以及其他地方展示了IEEE 754浮点数的分解/格式,因此您可以看到这些位如何/在哪里使用。更容易从单精度开始,然后逐步升级,但同时随着升级,只是有更多的位而已。这些格式的工作方式相同。 - old_timer
在CE中,您可以将鼠标悬停在常量上以查看十六进制值。在这种情况下,Clang和ICC的输出更易读,建议尝试使用它们。 - phuclv
2个回答

4

但是,具体来说应该如何实现呢?...

是的,这是IEEE754二进制64位(也称为double位模式的整数表示。 GCC总是以这种方式打印FP常量,因为它们有时是常量传播的结果,而不是出现在源中的FP字面量。(此外,它避免了对汇编器中FP舍入的任何依赖。)

gcc在其汇编输出中始终使用十进制整数常量,这对人类来说相当不方便。(在Godbolt编译器资源管理器上,使用鼠标悬停工具提示获取任何数字的十六进制表示。)

Clang的汇编输出更加清晰,包括带有数字十进制值的注释:

    .quad   -4605718748921121997    # double -5.2999999999999998

按什么顺序?

x86的浮点数字节顺序与整数字节顺序相同:都是小端序。(虽然有可能不是这种情况,但所有现代主流架构都使用相同的整数和浮点数字节顺序,即大端或小端。浮点数字节顺序?。以及浮点数的字节顺序

因此,当作为64位IEEE-754 double加载时,内存中的低32位是double的低32位。

正如@MichaelPetch在评论中解释的那样,第一个/低位双字是0x33333333,第二个/高位双字是0xC0153333因此,整个double具有C015333333333333的位模式

对于单精度浮点数,可以参考https://www.h-schmidt.net/FloatConverter/IEEE754.html。该网站将位数拆分成二进制复选框、十六进制位模式和小数部分,非常适合学习浮点数指数/尾数的工作原理。
另外,对于双精度浮点数,可以查看https://babbage.cs.qc.cuny.edu/IEEE-754.old/64bit.html。你可以输入位模式并查看十六进制值。

为什么它被转换为long类型?是因为汇编中没有浮点数立即数还是其他原因? - srilakshmikanthanp
@Srilakshmikanthan:编译器已经知道它想要的位模式,因此更容易和高效的方法是将其作为整数在汇编中发出。这也避免了对主机浮点格式与目标浮点格式的任何依赖。double->string很慢,string->double也很慢(对于必须解析此文件的汇编程序)。GAS支持浮点文字,但编译器生成的代码选择不使用它们。(您说“立即数”:这是机器指令的文字操作数。在这种情况下,它只是.quad.long的文字数据) - Peter Cordes
我不理解的是,汇编语言中没有浮点数立即数,因此浮点数在全局声明( https://dev59.com/aqfja4cB1Zd3GeqP0Ltf )。如果在 C 中使用整数转换为汇编,则不包含 int 的全局变量,但对于 float,存在全局变量,因此我认为所有浮点变量都有一个全局变量,因为没有浮点立即数,例如 (movsd xmm0,12.12) ,这样对吗? - srilakshmikanthanp
@Srilakshmikanthan:哦,是的,那是正确的。x86没有立即源FP指令。(有趣的事实:我认为ARM有一些)。一些汇编器具有语法,让您执行mov rax,3.14以获取双精度位模式到RAX中,因此您可以执行movq xmm0,rax。当然,对于任何汇编器,您都可以使用整数位模式进行mov rax,0xC015333333333333。但通常比从.rodata加载浮点/双精度数效率低。 - Peter Cordes
非常感谢,我有一个关于这行代码movsd xmm0, QWORD PTR .LC0[rip]的疑问。rip.LC0之间有什么关系?我看到一些与此相关的问题,但是我不理解为什么不能这样写movsd xmm0, QWORD PTR [.LC0],它是错误的吗? - srilakshmikanthanp
@Srilakshmikanthan:那是另一个不同的问题。你试过谷歌搜索吗?site:stackoverflow.com RIP static data找到了几个问答,包括为什么x86-64中的全局变量相对于指令指针访问?(为什么使用RIP相对寻址模式)和像“[RIP + _a]”这样的RIP相对变量引用在x86-64 GAS Intel语法中如何工作?(语法的含义),以及其他版本的相同问题:为什么这个MOVSS指令使用RIP相对寻址? - Peter Cordes

0
#include <iostream>
typedef struct{
    union{
        double decimal;
        struct{
            int a;
            int b;
        }v;
    };
}Double2Int_t;
int main(){
    int a1=858993459;
    int a2=-1072352461;
    double value=-5.3;
    Double2Int_t decimal;
    decimal.decimal=value;
    std::cout<<decimal.v.a<<" "
            <<decimal.v.b<<std::endl;
    Double2Int_t decimal2;
    decimal2.v.a=a1;
    decimal2.v.b=a2;
    std::cout<<decimal2.decimal<<std::endl;
    return 0;
}


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接