gpt4 book ai didi

c++ - linux/amd64 C 与 C++ 上的 abi 差异

转载 作者:塔克拉玛干 更新时间:2023-11-03 00:05:23 25 4
gpt4 key购买 nike

我有带有这样 API 的 C 库:

#ifdef __cplusplus
extern "C" {
#endif
struct Foo {
void *p;
int len;
};

struct Foo f(void *opaque, int param);
void foo_free(struct Foo *);
#ifdef __cplusplus
}
#endif

为了简化我的 C++ 生活,我决定做一些简单的事情:

 struct Foo {
void *p;
int len;
#ifdef __cplusplus
~Foo() { foo_free(this); }
#endif
};

然后事情变得疯狂:例如,如果我打电话f(0xfffeeea0, 40)C++ 中,然后在 C 端我得到了 0x7fff905d2050 -69984:

没有析构函数的汇编:

   0x000055555555467a <+0>: push   %rbp
0x000055555555467b <+1>: mov %rsp,%rbp
0x000055555555467e <+4>: sub $0x10,%rsp
0x0000555555554682 <+8>: mov $0x28,%esi
0x0000555555554687 <+13>: mov $0xfffeeea0,%edi
0x000055555555468c <+18>: callq 0x5555555546a0 <f>
0x0000555555554691 <+23>: mov %rax,-0x10(%rbp)
0x0000555555554695 <+27>: mov %rdx,-0x8(%rbp)
0x0000555555554699 <+31>: mov $0x0,%eax
0x000055555555469e <+36>: leaveq
0x000055555555469f <+37>: retq

用析构函数组装:

   0x00000000000006da <+0>: push   %rbp
0x00000000000006db <+1>: mov %rsp,%rbp
0x00000000000006de <+4>: sub $0x20,%rsp
0x00000000000006e2 <+8>: mov %fs:0x28,%rax
0x00000000000006eb <+17>: mov %rax,-0x8(%rbp)
0x00000000000006ef <+21>: xor %eax,%eax
0x00000000000006f1 <+23>: lea -0x20(%rbp),%rax
0x00000000000006f5 <+27>: mov $0x28,%edx
0x00000000000006fa <+32>: mov $0xfffeeea0,%esi
0x00000000000006ff <+37>: mov %rax,%rdi
0x0000000000000702 <+40>: callq 0x739 <f>
0x0000000000000707 <+45>: lea -0x20(%rbp),%rax
0x000000000000070b <+49>: mov %rax,%rdi
0x000000000000070e <+52>: callq 0x72e <Foo::~Foo()>
0x0000000000000713 <+57>: mov $0x0,%eax
0x0000000000000718 <+62>: mov -0x8(%rbp),%rcx
0x000000000000071c <+66>: xor %fs:0x28,%rcx
0x0000000000000725 <+75>: je 0x72c <main()+82>
0x0000000000000727 <+77>: callq 0x5c0 <__stack_chk_fail@plt>
0x000000000000072c <+82>: leaveq
0x000000000000072d <+83>: retq

请问这是怎么回事?我能理解为什么编译器应该以不同的方式处理返回方式,但为什么它在不同的寄存器 %esi%edi 中移动参数。

为了清楚起见,我知道我做错了事,我重写了代码某种智能指针而不是真正的 Foo。但我想知道 c++c 的 ABI 在这种特殊情况下是如何工作的。

完整示例:

//test.cpp
extern "C" {
struct Foo {
void *p;
int len;
~Foo() {/*call free*/}
};

struct Foo f(void *opaque, int param);
}

int main()
{
auto foo = f(reinterpret_cast<void *>(0xfffeeea0), 40);
}

//test.c
#include <stdio.h>

struct Foo {
void *p;
int len;
};

struct Foo f(void *opaque, int param)
{
printf("!!! %p %d\n", opaque, param);
struct Foo ret = {0, 0};
return ret;
}
#makefile:
prog: test.cpp test.c
gcc -Wall -ggdb -std=c11 -c -o test.c.o test.c
g++ -Wall -ggdb -std=c++11 -o $@ test.cpp test.c.o
./prog

最佳答案

在您的代码的第一个版本(没有析构函数)中,我们有:

// allocate 16 bytes on the stack (for a Foo instance)
sub $0x10,%rsp

// load two (constant) arguments into %edi and %esi
mov $0x28,%esi
mov $0xfffeeea0,%edi

// call f
callq 0x5555555546a0 <f>

// a 2-word struct was returned by value (in %rax/%rdx).
// move the values to the corresponding slots on the stack
mov %rax,-0x10(%rbp)
mov %rdx,-0x8(%rbp)

在第二个版本中(带有析构函数):

// load address of Foo instance into %rax
lea -0x20(%rbp),%rax

// load three arguments:
// - 40 in %edx
// - 0xfffeeea0 in %esi
// - &foo in %rdi
mov $0x28,%edx
mov $0xfffeeea0,%esi
mov %rax,%rdi

// ... and call f
callq 0x739 <f>

// ignore f's return value; load &foo into %rax again
lea -0x20(%rbp),%rax

// call ~Foo on &foo
mov %rax,%rdi
callq 0x72e <Foo::~Foo()>

我的猜测是,如果没有析构函数,结构将被视为普通的 2 字元组并按值返回。

但是对于析构函数,编译器假设它不能只是复制成员值,所以它将结构返回值转换为隐藏的指针参数:

struct Foo f(void *opaque, int param);

// actually implemented as:
void f(struct Foo *_hidden, void *opaque, int param);

通常 f 会负责将返回值写入 *_hidden

因为函数的调用者和实现者看到了不同的返回类型,所以他们对函数实际具有的参数个数意见不一。 C++ 代码传递了 3 个参数,但 C 代码只查看其中的两个。它错误地将 Foo 实例的地址解释为 opaque 指针,而应该是 opaque 指针的地址最终出现在 param

换句话说,析构函数的存在意味着 Foo 不再是 POD 类型,它禁止通过寄存器进行简单的按值返回。

关于c++ - linux/amd64 C 与 C++ 上的 abi 差异,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/47823143/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com