Reproducible: Why does passing this object in C break my code?

那年仲夏 提交于 2019-12-31 05:29:21

问题


From my understanding C assumes all parameters are int's and it returns ints. I'd like to pass around this object but i have no idea how and AFAIK its the same size of int but it breaks. Here is the Reproducible code.

In testc.c. Note: this MUST be in a C file.

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

In testcpp.cpp

#include <iostream>
using namespace std;
struct MyType{
    int a, b;
};

template <class T>
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<MyType> 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 compile flags

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

It should crash and as you can see the only warning i ever got was the function is implicit :(. Why does a crash? especially when i asserted that ObjPtr is indeed the same size as int. How do i fix this so that i can pass around ObjPtr? I CAN NOT modify the C library so testc.c is off limits.

-edit- instead of crashing in VS 2010 i get this printout which shows the passed object is incorrect. I don't understand where "B" comes from at the end. This happens in debug mode. Release crashes with access violation.

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

If your curious, if you comment out the constructors (and change nothing else) this will work in gcc. If you change class into struct so no member is private it will work in msvc2010. This fix is nonsense but it appears its consider POD when i do this and magically the code works. Which is weird since the definition in C hasn't changed (as there is no definition). And the constructors aren't doing anything different.


回答1:


From my understanding C assumes all parameters are int's and it returns ints.

Not quite.

Prior to the 1999 ISO C standard, calling a function without a visible declaration would cause the compiler to assume that it returns a result of type int. This does not apply to parameters; they're assumed to be of the (promoted) type(s) of the argument(s), if any.

C99 dropped the "implicit int" rule; calling a function without a visible declaration is a constraint violation, which basically means that it's illegal. It's not a good idea even in pre-1999 C.

If you're going to call C++ from C, any functions you call should have parameter and return types that are compatible with both languages. C doesn't have classes or templates, so having a C program call a C++ function that returns a WrappedPointer<MyType> is at least questionable.

Assuming that pointers are the same size as int makes your code extremely non-portable. Even if they're the same size, they're not interchangeable; int and pointer function results might be returned using different mechanisms (different CPU registers, for example).

I suggest having test_c1() and test_c2() return void*, which can point to an object of any type. And your C source needs to have visible declarations (preferably prototypes) for any functions it calls.




回答2:


The function test_c2() creates ObjPtr s on the stack and then returns it. But s goes out of scope (and its memory is deallocated) when test_c2() returns.




回答3:


If you read here, you can see that calling conventions varying significantly between platforms and compilers, and the differences can be subtle: http://www.angelcode.com/dev/callconv/callconv.html

I think what's happening here is that the calling convention in use will always return ints in registers, but has more stringent criteria to determine if an object can be returned in a register, as you've noticed when you make changes to the class. When the compiler decides that the object doesn't meet those criteria, it decides that the object should be returned in memory instead of via a register. This results in disaster. The C++ code ends up trying to read more arguments from the stack than the C code actually pushed, and misinterprets part of the stack as a pointer to memory that it thinks it can write an ObjPtr into. Depending on the compiler and platform you might get "lucky" and crash immediately, or "unlucky" and write the ObjPtr somewhere unexpected and then crash later or do something weird.

I couldn't recommend this, but you will probably have the most chance of making this work if you make sure the function declarations in the C++ match the C - i.e. that they use ints. Then do whatever reinterpret_casts you need in the C++, cross your fingers and say a prayer. At least that way you know that you won't be tripped up by call signature mismatches, which is what's hitting you right now.


Okay, so you want to know why non-POD types are treated differently by the calling convention. Consider what the presence of a copy-constructor means. Whenever you return an ObjPtr by value, that copy-constructor must be called with a reference to the copied object and a this pointer for the target object. The only way to be able to do that is if the caller has passed in a hidden pointer to the memory it has already allocated to store the return value. The compiler doesn't look inside the copy-constructor to see that it doesn't do anything fancy. It just sees that you've defined one and notes that the class is non-trivial and will never be returned in a register.

See also here for great information about aggregates and PODs. I especially like the answer about what has changed in C++11. What are Aggregates and PODs and how/why are they special?



来源:https://stackoverflow.com/questions/9639844/reproducible-why-does-passing-this-object-in-c-break-my-code

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!