在斯科特·迈耶(Scott Meyer)Effective C++的第20节中,他指出:
某些编译器拒绝将仅包含双精度对象的对象放入寄存器
当按值传递内置类型时,编译器将愉快地将数据放入寄存器中,并快速发送ints
/ doubles
/ floats
/ etc。沿。但是,并非所有的编译器都会以相同的宽限期对待小型对象。我很容易理解为什么编译器会以不同的方式对待对象-按值传递对象比在vtable和所有构造函数之间复制数据成员要耗费更多的精力。
但是还是。对于现代编译器来说,这似乎是一个容易解决的问题:“此类很小,也许我可以区别对待”。 Meyer的说法似乎暗示编译器将对仅包含int
(或char
或short
)组成的对象进行此优化。
有人可以进一步解释为何有时无法进行这种优化吗?
答案 0 :(得分:14)
我在“ Calling conventions for different C++ compilers and operating systems”上在线找到了此文档(于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兼容。
答案 1 :(得分:1)
这是一个示例,显示优化级别为O3
的LLVM clang将具有单个double数据成员的类视为double:
$ 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
}