为什么GCC在不使用结果的情况下调用libc的sqrt()?

Ben*_*oît 56 c++ assembly gcc x86-64

使用GCC 6.3,以下C++代码:

#include <cmath>
#include <iostream>

void norm(double r, double i)
{
    double n = std::sqrt(r * r + i * i);
    std::cout << "norm = " << n;
}
Run Code Online (Sandbox Code Playgroud)

生成以下x86-64程序集:

norm(double, double):
        mulsd   %xmm1, %xmm1
        subq    $24, %rsp
        mulsd   %xmm0, %xmm0
        addsd   %xmm1, %xmm0
        pxor    %xmm1, %xmm1
        ucomisd %xmm0, %xmm1
        sqrtsd  %xmm0, %xmm2
        movsd   %xmm2, 8(%rsp)
        jbe     .L2
        call    sqrt
.L2:
        movl    std::cout, %edi
        movl    $7, %edx
        movl    $.LC1, %esi
        call    std::basic_ostream<char, std::char_traits<char> >& std::__ostream_insert<char, std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*, long)
        movsd   8(%rsp), %xmm0
        movl    std::cout, %edi
        addq    $24, %rsp
        jmp     std::basic_ostream<char, std::char_traits<char> >& std::basic_ostream<char, std::char_traits<char> >::_M_insert<double>(double)
Run Code Online (Sandbox Code Playgroud)

对于调用std::sqrt,GCC首先使用sqrtsd并将结果保存到堆栈中.如果它溢出,则调用libc sqrt函数.但它永远不会保存xmm0之后,在第二次调用之前operator<<,它会从堆栈中恢复值(因为xmm0第一次调用时丢失了operator<<).

更简单std::cout << n;,它更加明显:

subq    $24, %rsp
movsd   %xmm1, 8(%rsp)
call    sqrt
movsd   8(%rsp), %xmm1
movl    std::cout, %edi
addq    $24, %rsp
movapd  %xmm1, %xmm0
jmp     std::basic_ostream<char, std::char_traits<char> >& std::basic_ostream<char, std::char_traits<char> >::_M_insert<double>(double)
Run Code Online (Sandbox Code Playgroud)

为什么GCC不使用xmm0libc计算的值sqrt

Ros*_*dge 76

它不需要调用sqrt来计算结果; 它已经由SQRTSD指令计算出来.sqrt当传递负数时sqrt(例如,设置errno和/或引发浮点异常),它调用根据标准生成所需行为.PXOR,UCOMISD和JBE指令测试参数是否小于0并跳过调用,sqrt如果不是这样.

  • @Benoît还不够吗?在C++ 11中,它还可以(或替代)生成FE_INVALID浮点异常.编译器只是将它留给库实现来处理这种情况. (13认同)
  • @Benoît就像我说的那样,它不需要`sqrt`的结果.它没有调用`sqrt`来获得结果.它正在调用`sqrt`纯粹是因为它的副作用,当`sqrt`的参数小于0时它的错误处理. (12认同)
  • @TheTechel:在参数> = 0的情况下,不会调用`std :: sqrt`,因为它被跳过并且`sqrtsd`汇编指令完成了工作.在其他情况下(参数<0),将跳过`sqrtsd`指令并调用`std :: sqrt`(可能会或可能不会尝试计算负数的根).所以最多只能进行一次计算. (6认同)
  • 哪个副作用?我唯一想到的就是设置errno. (2认同)
  • @hoffmale呃?你在哪里看到'sqrtsd`指令被跳过的问题?在我看来,无条件执行,只有对`sqrt`的调用才会被跳过.在针对常见情况进行优化时,这仍然是足够的. (2认同)