在尝试to optimize return values on x86_64时,我注意到一件奇怪的事情 . 即,给出代码:
#include <cstdint>
#include <tuple>
#include <utility>
using namespace std;
constexpr uint64_t a = 1u;
constexpr uint64_t b = 2u;
pair<uint64_t, uint64_t> f() { return {a, b}; }
tuple<uint64_t, uint64_t> g() { return tuple<uint64_t, uint64_t>{a, b}; }
Clang 3.8 outputs f
的汇编代码:
movl $1, %eax
movl $2, %edx
retq
这对于 g
:
movl $2, %eax
movl $1, %edx
retq
哪个看起来最佳 . 但是,当compiled with GCC 6.1时, f
生成的程序集与Clang输出相同,为 g
生成的程序集是:
movq %rdi, %rax
movq $2, (%rdi)
movq $1, 8(%rdi)
ret
看起来返回值的类型被GCC归类为MEMORY,而Clang归类为INTEGER . 我可以确认将Clang代码与GCC代码链接这样的代码会导致分段错误(Clang调用GCC编译的 g()
,它写入 %rdi
恰好指向的地方)并返回一个无效值(GCC调用Clang编译的 g()
) . 哪个编译器有问题?
2 回答
正如davmac的回答所示,libstdc
std::tuple
可以简单地复制构造,但不能简单地移动构造 . 两个编译器不一致关于移动构造函数是否应该影响传递约定的参数 .您链接的C ABI线程似乎解释了这种分歧:http://sourcerytools.com/pipermail/cxx-abi-dev/2016-February/002891.html
总而言之,Clang完全实现了ABI规范所说的内容,但G实现了它应该说的内容,但实际上还没有更新 .
ABI声明参数值根据特定算法分类 . 相关的是:
在这种情况下,每个字段(对于元组或一对)都是
uint64_t
类型,因此占用整个"eightbyte" . 那么,每个八字节中要考虑的"two fields"是"NO_CLASS"(按照3)和uint64_t
字段,它被归类为INTEGER .还有,与参数传递有关:
不满足这些要求的对象必须具有地址,因此需要在内存中,这就是存在上述要求的原因 . 对于返回值也是如此,尽管这似乎在规范中被省略(可能是偶然的) .
最后,有:
这显然不适用于此;聚合的大小恰好是两个八字节 .
在返回值时,文字说:
这意味着,如上所述,元组应该被分类为INTEGER . 然后:
这很清楚 .
唯一尚未解决的问题是这些类型是否是非平凡的可复制构造/可破坏的 . 如上所述,这种类型的值不能在寄存器中传递或返回,即使规范似乎没有认识到返回值的问题 . 但是,我们可以使用以下程序轻松地显示元组和对可以是简单的可复制构造和简单可破坏的:
测试程序:
使用GCC或Clang编译时的输出:
这意味着海湾合作委员会错了 . 返回值应在%rax,%rdx中传递 .
(类型之间的主要显着差异是
pair
是标准布局,并且通常是可移动构造的,而tuple
不是,因此GCC总是可以通过指针返回非平凡移动构造值(例如) .