我的C
库有这样的API:
#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
vs Foo
。
为了清楚起见,我明白我做错了,我用了重写代码
某种智能指针而不触及真正的c++
。
但我想知道c
和//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
的ABI在这种特殊情况下是如何起作用的。
完整示例:
PUT _xpack/watcher/watch/error_report
{
"trigger" : {
"schedule": {
"interval": "1h"
}
},
"actions" : {
"email_admin" : {
"email": {
"to": "'Recipient Name <recipient@example.com>'",
"subject": "Error Monitoring Report",
"attachments" : {
"error_report.pdf" : {
"reporting" : {
"url": "http://0.0.0.0:5601/api/reporting/generate/dashboard/Error-Monitoring?_g=(time:(from:now-1d%2Fd,mode:quick,to:now))",
"retries":6,
"interval":"1s",
"auth":{
"basic":{
"username":"elastic",
"password":"changeme"
}
}
}
}
}
}
}
}
}
答案 0 :(得分:2)
在代码的第一个版本(没有析构函数)中,我们有:
// 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返回值转换为隐藏的指针参数:
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类型,它禁止通过寄存器进行简单的按值返回。