17

在 Scott Meyer 的Effective C++第 20 节中,他指出:

一些编译器拒绝将仅包含双精度的对象放入寄存器

当按值传递内置类型时,编译器会很乐意将数据放入寄存器并快速发送///ints等。沿着。然而,并不是所有的编译器都会以同样的优雅对待小对象。我可以很容易地理解为什么编译器会以不同的方式处理对象 - 按值传递对象可能比在 vtable 和所有构造函数之间复制数据成员要多得多的工作。doublesfloats

但还是。对于现代编译器来说,这似乎是一个容易解决的问题:“这个类很小,也许我可以区别对待”。Meyer 的声明似乎暗示编译器将针对仅由int(orcharshort) 组成的对象进行这种优化。

有人可以进一步了解为什么这种优化有时不会发生吗?

4

2 回答 2

15

我在“不同 C++ 编译器和操作系统的调用约定”(2018-04-25 更新)上 在线找到了这份文档,其中有一个描述“传递结构、类和联合对象的方法”的表格。

从表中您可以看到,如果一个对象包含long double,则整个对象的副本将传输到此处显示的所有编译器的堆栈。

在此处输入图像描述

也来自相同的资源(强调添加):

如果参数是结构、类或联合对象,则有几种不同的方法可以将参数传递给函数。始终会制作对象的副本,并且该副本在寄存器中、堆栈上或通过指针传输到被调用的函数,如表 6 中所指定。表中的符号指定要使用的方法。S 优先于 I 和 R。PI 和 PS 优先于所有其他传递方法。

如表 6 所示,如果对象太大或太复杂,则无法在寄存器中传输对象。例如,具有复制构造函数的对象不能在寄存器中传输,因为复制构造函数需要对象的地址。复制构造函数由调用者调用,而不是被调用者。

在堆栈上传递的对象按堆栈字大小对齐,即使需要更高的对齐方式。所研究的任何编译器都不对齐指针传递的对象,即使明确要求对齐也是如此。64 位 Windows ABI 要求指针传递的对象按 16 对齐。

数组不被视为对象,而是指针,并且不会制作数组的副本,除非数组被包装到结构、类或联合中。

Linux 的 64 位编译器与 ABI(0.97 版)在以下方面不同: 具有继承、成员函数或构造函数的对象可以在寄存器中传递。具有复制构造函数、析构函数或虚函数的对象通过指针而不是堆栈传递。

适用于 Windows 的英特尔编译器与 Microsoft 兼容。适用于 Linux 的英特尔编译器与 Gnu 兼容。

于 2018-08-31T04:56:16.303 回答
1

这是一个示例,显示具有优化级别的 LLVM clangO3将具有单个双精度数据成员的类视为双精度:

$ cat main.cpp
#include <stdio.h>
class MyDouble {
public:
    double d;
    MyDouble(double _d):d(_d){}
};
void foo(MyDouble d)
{
    printf("%lg\n",d.d);
}
int main(int argc, char **argv)
{
    if (argc>5)
    {
        double x=(double)argc;
        MyDouble d(x);
        foo(d);
    }
    return 0;
}

当我编译它并查看生成的位码文件时,我看到 foo 的行为就像它对double类型输入参数进行操作一样:

$ clang++ -O3 -c -emit-llvm main.cpp
$ llvm-dis main.bc

以下是相关部分:

; Function Attrs: nounwind uwtable
define void @_Z3foo8MyDouble(double %d.coerce) #0 {
entry:
  %call = tail call i32 (i8*, ...)* @printf(i8* getelementptr inbounds ([5 x i8]* @.str, i64 0, i64 0), double %d.coerce)
  ret void
}

看看如何foo将其输入参数声明为double,并移动它以“按原样”打印。现在让我们用 编译完全相同的代码O0

$ clang++ -O0 -c -emit-llvm main.cpp
$ llvm-dis main.bc

当我们查看相关部分时,我们看到 clang 使用getelementptr指令来访问它的第一个(也是唯一一个)数据成员d

; Function Attrs: uwtable
define void @_Z3foo8MyDouble(double %d.coerce) #0 {
entry:
  %d = alloca %class.MyDouble, align 8
  %coerce.dive = getelementptr %class.MyDouble* %d, i32 0, i32 0
  store double %d.coerce, double* %coerce.dive, align 1
  %d1 = getelementptr inbounds %class.MyDouble* %d, i32 0, i32 0
  %0 = load double* %d1, align 8
  %call = call i32 (i8*, ...)* @printf(i8* getelementptr inbounds ([5 x i8]* @.str, i32 0, i32 0), double %0)
  ret void
}
于 2018-08-31T04:33:16.263 回答