可重复:为什么在C中传递此对象会破坏我的代码?

根据我的理解,C假设所有参数都是int,并返回int。 我想传递这个对象,但我不知道如何和AFAIK的大小相同,但它会中断。 这是可重复的代码。

在testc.c中。 注意:这必须在C文件中。

int test_c1() { return test_c3(test_c2()); } 

在testcpp.cpp中

 #include  using namespace std; struct MyType{ int a, b; }; template  struct WrappedPointer { T* lhs; public: void LHS(T*v) { lhs=v; } T* LHS() { return lhs; } WrappedPointer(){} WrappedPointer(T*value) : lhs(value){} WrappedPointer(const WrappedPointer&v) : lhs(v.lhs){} T* operator->() const { return lhs; } T* operator*() const { return lhs; } }; typedef WrappedPointer ObjPtr; static_assert(sizeof(ObjPtr) == sizeof(int), ""); static_assert(sizeof(ObjPtr) == sizeof(void*),""); extern "C" { ObjPtr test_c1(); ObjPtr test_c2() { //ObjPtr s=0; ObjPtr s; s.LHS(0); cout <<"c2 " << s.LHS() << endl; return s; } ObjPtr test_c3(ObjPtr v) { cout <<"c3 " << v.LHS() << endl; return v; } }; int main() { auto v = test_c1(); cout <<"main " << v.LHS() << endl; } 

gcc编译标志

 gcc -Wall -c testc.c testc.c: In function 'test_c1': testc.c:2:2: warning: implicit declaration of function 'test_c3' [-Wimplicit-function-declaration] testc.c:2:2: warning: implicit declaration of function 'test_c2' [-Wimplicit-function-declaration] g++ -std=c++0x -Wall -c testcpp.cpp g++ testc.o testcpp.o a.exe 

它应该崩溃,因为你可以看到我得到的唯一警告是函数是隐式的:(。为什么崩溃?特别是当我断言ObjPtr确实与int相同的大小。我如何解决这个问题,这样我就可以绕过ObjPtr?我不能修改C库,所以testc.c是不受限制的。

-edit-而不是在VS 2010中崩溃我得到这个打印输出,显示传递的对象不正确。 我不明白最后“B”来自哪里。 这在调试模式下发生。 发布访问冲突的崩溃。

 c2 00000000 c3 0046F8B0 main CCCCCCCC B Press any key to continue . . . 

如果你好奇,如果你注释掉构造函数(并且不做其他改动),这将在gcc中有效。 如果您将类更改为struct,因此没有成员是私有的,它将在msvc2010中工作。 这个修复是无稽之谈,但是当我这样做并且神奇地代码工作时它似乎考虑了POD。 这很奇怪,因为C中的定义没有改变(因为没有定义)。 而构造者并没有做任何不同的事情。

根据我的理解,C假设所有参数都是int,并返回int。

不完全的。

在1999 ISO C标准之前,调用没有可见声明的函数会导致编译器假定它返回int类型的结果。 这不适用于参数; 如果有的话,它们被假定为参数的(推广的)类型。

C99放弃了“隐含整数”规则; 调用没有可见声明的函数是违反约束的行为,这基本上意味着它是非法的。 即使在1999年之前,这也不是一个好主意。

如果您要从C调用C ++,您调用的任何函数都应该具有与这两种语言兼容的参数和返回类型。 C没有类或模板,因此让C程序调用返回WrappedPointer的C ++函数至少是有问题的。

假设指针与int大小相同,则会使代码极不可移植。 即使它们的大小相同,它们也不可互换; 可以使用不同的机制(例如,不同的CPU寄存器)返回int和指针函数结果。

我建议让test_c1()test_c2()返回void* ,它可以指向任何类型的对象。 并且你的C源需要为它调用的任何函数提供可见的声明(最好是原型)。

函数test_c2()在堆栈上创建ObjPtr s然后返回它。 但是当test_c2()返回时, s超出范围(并且其内存被释放)。

如果您在此处阅读,您可以看到调用约定在平台和编译器之间存在显着差异,并且差异可能很微妙: http : //www.angelcode.com/dev/callconv/callconv.html

我认为这里发生的事情是使用的调用约定总是在寄存器中返回int,但是有更严格的标准来确定对象是否可以在寄存器中返回,就像你在对类进行更改时所注意到的那样。 当编译器确定该对象不符合这些条件时,它决定该对象应该在内存中而不是通过寄存器返回。 这会导致灾难。 C ++代码最终试图从堆栈中读取比实际推送的C代码更多的参数,并错误解释堆栈的一部分作为指向内存的指针,它认为它可以写入ObjPtr。 根据编译器和平台的不同,您可能会“幸运”并立即崩溃,或者“不幸”并将ObjPtr写入意外的地方,然后再崩溃或做一些奇怪的事情。

我不能推荐这个,但是如果你确保C ++中的函数声明与C匹配,那么你可能最有可能做到这一点 – 即他们使用int。 然后在C ++中做任何重新解释你需要的东西,交叉你的手指并祈祷。 至少那种方式你知道你不会被呼叫签名不匹配绊倒,这就是现在正在打你的东西。


好的,所以你想知道为什么非POD类型被调用约定区别对待。 考虑复制构造函数的存在意味着什么。 每当按值返回ObjPtr时,必须使用对复制对象的引用和目标对象的this指针调用该复制构造函数。 能够做到这一点的唯一方法是,如果调用者已经传入一个隐藏指针,该指针指向已经分配用于存储返回值的内存。 编译器不会在复制构造函数中查看它没有做任何花哨的事情。 它只是看到你已经定义了一个,并指出该类是非平凡的,永远不会在寄存器中返回。

有关聚合和POD的详细信息,另请参见此处。 我特别喜欢关于C ++ 11中发生了什么变化的答案。 什么是聚合和POD以及它们如何/为何特殊?