C++ 为什么T*可以在寄存器中传递,但却是唯一的\u ptr<;T>;不能
我正在看钱德勒·卡拉斯在CppCon 2019的演讲: 在这篇文章中,他举了一个例子,说明他是如何惊讶于在C++ 为什么T*可以在寄存器中传递,但却是唯一的\u ptr<;T>;不能,c++,assembly,unique-ptr,calling-convention,abi,C++,Assembly,Unique Ptr,Calling Convention,Abi,我正在看钱德勒·卡拉斯在CppCon 2019的演讲: 在这篇文章中,他举了一个例子,说明他是如何惊讶于在int*上使用std::unique_ptr所产生的开销;该段大约在时间点17:25开始 你可以看一下他的示例代码片段对(godbolt.org)——事实上,编译器似乎不愿意将唯一的_ptr值传递到寄存器中,而实际上它只是一个地址,只在直接内存中传递 卡鲁思先生在27点左右提出的一个观点是,C++ abi需要通过值参数(一些但不是全部;也许非原始类型?非平凡构造类型)在内存中传递而不是登记
int*
上使用std::unique_ptr
所产生的开销;该段大约在时间点17:25开始
你可以看一下他的示例代码片段对(godbolt.org)——事实上,编译器似乎不愿意将唯一的_ptr值传递到寄存器中,而实际上它只是一个地址,只在直接内存中传递
卡鲁思先生在27点左右提出的一个观点是,C++ abi需要通过值参数(一些但不是全部;也许非原始类型?非平凡构造类型)在内存中传递而不是登记在寄存器中。
我的问题是:
PS-为了不让这个问题没有代码: 普通指针:
void bar(int* ptr) noexcept;
void baz(int* ptr) noexcept;
void foo(int* ptr) noexcept {
if (*ptr > 42) {
bar(ptr);
*ptr = 42;
}
baz(ptr);
}
唯一指针:
using std::unique_ptr;
void bar(int* ptr) noexcept;
void baz(unique_ptr<int> ptr) noexcept;
void foo(unique_ptr<int> ptr) noexcept {
if (*ptr > 42) {
bar(ptr.get());
*ptr = 42;
}
baz(std::move(ptr));
}
使用std::unique\u ptr;
无效条(int*ptr)不例外;
void baz(唯一)无例外;
void foo(唯一)无例外{
如果(*ptr>42){
bar(ptr.get());
*ptr=42;
}
baz(std::move(ptr));
}
sizeof
不大于16的对象的值才能在寄存器中传递。有关调用约定的详细处理,请参见§7.1传递和返回对象。在寄存器中传递SIMD类型有单独的调用约定
其他CPU架构有不同的ABI
除MSVC外,大多数编译器还遵循以下几点: 如果参数类型对于调用来说是非平凡的,则调用方必须为临时文件分配空间,并通过引用传递该临时文件 在以下情况下,就调用而言,类型被认为是非平凡的:
- 它有一个非平凡的复制构造函数、移动构造函数或析构函数,或
- 将删除其所有复制和移动构造函数
如果没有分配可寻址的存储空间,则对象不能存在于C++中,因为 当需要一个对象的地址,并且该地址包含一个保存在寄存器中的简单复制构造函数时,编译器只需将该对象存储到内存中并获取该地址即可。另一方面,如果复制构造函数是非平凡的,则编译器不能将其存储到内存中,而是需要调用复制构造函数,该构造函数接受引用,因此需要寄存器中对象的地址。调用约定可能无法确定复制构造函数是否内联在被调用方中
考虑这一点的另一种方式是,对于普通的可复制类型,编译器在寄存器中传输对象的值,如果需要,可以通过普通内存存储从寄存器中恢复对象。例如:void f(long*);
void g(long a) { f(&a); }
在带有System V的x86_64上,ABI编译为:
g(long): // Argument a is in rdi.
push rax // Align stack, faster sub rsp, 8.
mov qword ptr [rsp], rdi // Store the value of a in rdi into the stack to create an object.
mov rdi, rsp // Load the address of the object on the stack into rdi.
call f(long*) // Call f with the address in rdi.
pop rax // Faster add rsp, 8.
ret // The destructor of the stack object is trivial, no code to emit.
钱德勒·卡鲁斯(Chandler Carrush)在他发人深省的讲话中指出,为了实施能够改善现状的破坏性举措,可能有必要(除其他外)对ABI进行突破性的改变。在我看来,如果使用新ABI的函数显式地选择具有新的不同链接,例如在
extern“C++20”{}
块中声明它们(可能在用于迁移现有API的新内联命名空间中),则ABI更改可能不会中断。因此,只有针对具有新链接的新函数声明编译的代码才能使用新ABI
注意
struct Foo { int bar; };
Foo test(Foo byval) { return byval; }
test(Foo):
mov eax, edi
ret
struct Foo2 {
int bar;
~Foo2() { }
};
Foo2 test(Foo2 byval) { return byval; }
test(Foo2):
mov edx, DWORD PTR [rsi]
mov rax, rdi
mov DWORD PTR [rdi], edx
ret
void callee(int &i) {
something(&i);
}
void caller() {
int i;
callee(i);
something(&i);
}
struct file_handler { // don't use that class!
file_handler () { this->fileno = -1; }
file_handler (int f) { this->fileno = f; }
file_handler (const file_handler& rhs) {
if (this->fileno != -1)
this->fileno = dup(rhs.fileno);
else
this->fileno = -1;
}
~file_handler () {
if (this->fileno != -1)
close(this->fileno);
}
file_handler &operator= (const file_handler& rhs);
};