Reproducible: Why is passing this object to C breaking my code?

From my understanding, C takes all int parameters and returns ints. I'd like to get around this object, but I have no idea how AFAIK its the same int size, but it breaks. Here is the code for Reproducibility.

In testc.c. Note: this MUST be in the 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 have ever gotten is the function implicit :( Why is it crashing? Especially when I argued that it ObjPtr

really is the same size as an int. It does what can I get around ObjPtr? I CANNOT modify the C library, so testc.c is not working.

-edit- instead of crashing in VS 2010, I get this printout which shows the past object is not correct. I don't understand where the "B" comes in at the end. This happens in debug mode. An access violation failed.

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

      

If you're wondering if you comment out the constructors (and don't change anything) this will work in gcc. If you change the class to a struct so the member is not private it will work in msvc2010. This fix is ​​pointless, but it seems like it is looking at the POD when I do this and the code magically works. Which is strange, since the definition in C hasn't changed (since there is no definition). And constructors don't do anything else.

0


source to share


3 answers


From my understanding, C assumes that all parameters are int and it returns ints.

Not really.

Prior to the ISO ISO 1999 standard, calling a function without a visible declaration would make the compiler assume that it was returning a result of a type int

. This does not apply to parameters; they are assumed to be of the (promoted) type of the argument (s), if any.

C99 has abolished the implicit int rule; calling a function with no visible declaration is a constraint violation, which basically means it is illegal. This is not a good idea even before 1999.



If you are going to call C ++ from C, any functions you call must have parameters and return types that are compatible with both languages. C has no classes or templates, so having a C program calls a C ++ function, which returns WrappedPointer<MyType>

is at least questionable.

Assuming the pointers are the same size as int

yours, your code is highly non-portable. Even though they are the same size, they are not interchangeable; int

and the results of the pointer function can be returned using various mechanisms (eg, in different registers of the CPU).

I suggest having a test_c1()

and test_c2()

return void*

that can point to an object of any type. And your C source must have visible declarations (prototypes preferred) for whatever functions it calls.

+3


source


The function test_c2()

creates ObjPtr s

on the stack and returns it. But it s

goes out of scope (and its memory is freed) when it returns test_c2()

.



0


source


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

What I think is happening here is that the calling convention used always returns ints in registers, but has stricter criteria to determine if an object can be returned to a register, as you noticed when you make changes to the class. When the compiler decides that an object does not meet these criteria, it decides that the object should be returned in memory and not through a register. This leads to disaster. The C ++ code ends up trying to read more arguments from the stack than actually hovering C code, and misinterpreting part of the stack as a pointer to memory that it thinks ObjPtr could write. Depending on the compiler and platform, you might get "lucky" and immediately fail, or "unsuccessful" and write ObjPtr somewhere unexpected and then fire later or do something strange.

I might not recommend this, but you will probably have a better chance of making this work if you make sure that the function declarations in C ++ are C - ie that they use ints. Then do whatever you need to do on reinterpret_casts in C ++, cross your fingers and say a prayer. At least this way you know you won't run into call signature inconsistencies, which is striking you right now.


Ok, so you want to know why non-POD types are handled differently by calling convention. Think about what it means to have a copy constructor. Whenever you return ObjPtr by value, this constructor instance must be called with a reference to the copied object and the pointer this

to the target object. The only way to do this is if the caller passed in a hidden pointer to memory that he has already allocated in order to store the return value. The compiler doesn't look inside the copy constructor to make sure it doesn't do anything. It just sees that you have defined one and notices that the class is non-trivial and will never be returned to the register.

See also aggregates and POD details. I especially like the answer about what has changed in C ++ 11. What are aggregates and PODs and how / why are they special?

0


source







All Articles