我有以下几段代码:(注意:由于我无法共享数据,所以代码经过简化和更改很多)
exercise.cpp
#include "some_api.h"
extern "C" int extension_init(func_t*, VExtension*);
int main(int argc, char** argv)
{
VExtension ve;
extension_init(&func, &ve);
return 0;
}
some_api.h
bool func(int const& a, void* const& b, VExtension* const& v)
{
std::cout << a << b << std::endl;
}
api.h
typedef int (func_t)(int c, void* p, VExtension* v)
file.cpp
#include "api.h" // this is included implicitly
extern "C" int extension_init(func_t* F, VExtension* v)
{
intptr_t ver = 7;
F(1, (void*)ver, v);
}
因此,当从F
调用func
到some_api.h
时,但是在尝试输出值Seg Fault
和{{1}时出现a
}。
静态分析器给出以下错误消息:
b
答案 0 :(得分:1)
如果将func_t
使用与func()
相同的签名来定义,那么一切都会好起来的,因为从不取消引用b的任意值:
typedef bool (func_t)(int const& a, void* const& b, VExtension* const& v);
请参见demo。
您的代码似乎可以编译,至少从您显示的运行时跟踪来看。但是根据您发布的摘录,代码根本不应该编译。因此,我怀疑您的不同标头在不同的编译单元中使用来自func_t
的不同定义来创建危险的混合。如果是这种情况,那么您将处于不确定行为的世界。
例如,返回类型int
与返回类型bool
不匹配。但更致命的是,在func_t
中,参数是纯值,而在func()
中,它们都是引用。因此,如果欺骗了编译器,func()
的调用者使用的调用约定将与func()
的调用约定不兼容,从而导致无效的内存访问(很可能是–但是这将是实现)依赖-生成的调用代码将发送值参数,但是生成的接收代码将期望这些参数是指向值的指针并尝试访问指向的内存位置(这将是无效的)。
现在,我不是一名语言律师,但是看到使用C调用约定(extern "C"
)的函数,以及使用C中不存在的参数和返回类型(例如bool和reference),也很有趣)。
使用更正的extension_init
定义,在一个编译器上查看func_t
的{{3}}:
lea rdx, [rsp+8] ; load effective address of third paramvalue
lea rsi, [rsp+24] ; load effective address of second param value
mov QWORD PTR [rsp+24], 7 ; initialize value
lea rdi, [rsp+20] ; load effective address of first param value
mov DWORD PTR [rsp+20], 1 ; initilize value
call rax
如果为assembly code generated,则生成的代码看起来会不同:
; the third value was left out by the global initializer
; but it was previously set with an LEA to load the effective
; address of the struct.
mov esi, 7 ; pass the second value
mov edi, 1 ; pass the first value
call rax