0

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
    Are you just trying to pass a C++ object into a C function? Have you tried declaring your `test_c()` functions to accept and return `void *`? – Seth Mar 09 '12 at 19:44
  • @Seth: I don't see how that would help nor how i should do it. Are you saying add yet another indirection or cast it to `void*` –  Mar 09 '12 at 19:50

3 Answers3

3

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.

Keith Thompson
  • 254,901
  • 44
  • 429
  • 631
  • I -cant- change the C source so does this mean no matter what i do this will be illegal and not portable? –  Mar 09 '12 at 19:49
  • Yes. Why can't you change the C source? Can someone else change it? – Keith Thompson Mar 09 '12 at 19:51
  • It's likely that you could get it to work (but not portably) without changing the C source, but it would be an ugly hack. If `ObjPtr`, `int`, and `void*` are all the same size, you might be able to get away with treating them as if they were interchangeable. It would be an ugly hack. I haven't (yet) looked past the undefined behavior to figure out what your code is trying to do. See if you can work out a way to do it right before you resort to a kludge that's incorrect but happens to "work". – Keith Thompson Mar 09 '12 at 20:02
  • Actually for the reproducible i can't imagine how to define the struct in C. I tried `typedef struct { void*lhs; } ObjPtr;` and changing ints to ObjPtr and defining the classes. Still no go. However as my edit mentioned if i comment out the constructors it works. –  Mar 09 '12 at 20:04
  • Do both C and C++ code need to know about the innards of `ObjPtr` or `WrappedPointer`? If the C code treats it as an opaque type, `void*` is probably the best solution. – Keith Thompson Mar 09 '12 at 20:10
  • C doesn't need to know. It think its all void*/ints (the doc says it must be either, doesnt specify which is defined in their source). But the annoying part is ObjPtr IS a pointer, it just has type information. I'm just going to add a POD assert and so far it has been doing the job. I just wanted to know why the restrictions are there. Theres not a single warning (in gcc) about it. MSVC does say it but it doesnt tell me how to fix it. I don't get the MS warnings once its POD so i believe (at least for MSVC) i'm safe –  Mar 09 '12 at 20:37
  • No, ObjPtr is not a pointer, it's a class type that has a member that's a pointer. gcc didn't warn you because you didn't give ot enough information. And treating `int` and `void*` as interchangeable is dangerous; there are plenty of systems where they're not the same size. – Keith Thompson Mar 09 '12 at 21:03
  • Thats what bothers me the most. The 3rd party documentation doesn't say which they use. We need this to work in windows, linux and mac 32 and 64bit systems. We are compiling with gcc and possibility clang if we can get the 3rd party to give us a clang lib. Maybe we should ditch them? What systems are they different sizes? if its a specialized linux distro than maybe we are fine. The meat of our customers are windows tho. –  Mar 09 '12 at 21:17
  • Can you give us a pointer to the 3rd party documentation, or is it proprietary? Can you ask the provider? – Keith Thompson Mar 09 '12 at 21:25
0

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.

Adam Liss
  • 47,594
  • 12
  • 108
  • 150
  • Why does that happen to cause a crash? (Note that it's returning `s` by value; there's no destructor; and the copy-constructor just shallowly copies the only field.) – ruakh Mar 09 '12 at 19:43
  • I disagree. There is no memory to deallocate and s is COPIED across the stack. Since s is 4bytes it should be copied entirely and the ptr should still be intact (still be null) –  Mar 09 '12 at 19:45
  • 1
    `test_c2()` returns (a copy of) the *value* of `s`, not its address. It's no more a problem than `int func() { int x = 42; return x; }`. Returning the address of a local variable does cause the kind of problem you're thinking of; for example, `int* bad() { int x = 42; return &x; }` has undefined behavior. – Keith Thompson Mar 09 '12 at 19:59
  • @acidzombie24: I think we need more information about this third party library. Your `test_c1()` function doesn't look like library code. – Keith Thompson Mar 09 '12 at 20:03
  • @KeithThompson test_c1() was to reproduce the crash. Essentially the documentation says all my callbacks must return int and certain callbacks (cX) must have X int parameters (ie c0 is `int fn()` while c2 is `int fn(int, int)`). Also some of these functions must exist at linktime so not all of these are callbacks as well. Complicated i know. –  Mar 09 '12 at 20:12
  • 1
    @acidzombie24: Ok, so the callbacks need to return `int`. Who says that `int` has to be the result of type-punning a pointer or `ObjPtr` object? Make it an index into a table that you create and maintain in C++. – Keith Thompson Mar 09 '12 at 20:20
  • @KeithThompson: I rather figure out how to make ObjPtr work. So far forcing it to be a POD seems to work. I don't want to change that many lines if i dont have to. Also it isn't just ObjPtr, there are literally dozens of types. But all of them are pointers, its just a few that i'd like to use the wrapper (for overloading operator purpose as some are in a vector which we call find on) –  Mar 09 '12 at 20:34
0

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?

Community
  • 1
  • 1
Weeble
  • 17,058
  • 3
  • 60
  • 75
  • Do you have any suggestions for calling conventions? I think all of the C code uses cdecl. There should be a attribute i can set. ATM making it POD does the job (at least MSVC no longer claims its not compatible). hmmmm... Maybe POD is the only solution but i still dont understand why POD VS not POD affects this. –  Mar 10 '12 at 01:56