Memory Compatibility between C and C ++

I am creating a C ++ library that uses many of the functions and struct defined in the C library. To avoid porting any code to C ++, I add typical conditional preprocessing to the C header files. For example,

 //my_struct.h of the C library #include <complex.h> #ifdef __cplusplus extern "C" { #endif typedef struct { double d1,d2,d3; #ifdef __cplusplus std::complex<double> z1,z2,z3; std::complex<double> *pz; #else double complex z1,z2,z3; double complex *pz; #endif int i,j,k; } my_struct; //Memory allocating + initialization function my_struct * alloc_my_struct(double); #ifdef __cplusplus } #endif 

The implementation of alloc_my_struct() compiled in C. It simply allocates memory through malloc() and initializes the members of my_struct .

Now, when I do the following in my C ++ code,

 #include "my_struct.h" ... my_struct *const ms = alloc_my_struct(2.); 

I notice that *ms always has the expected memory layout, that is, any access, such as ms->z1 , is evaluated to the expected value. I find this really cool, considering that (correct me if I am wrong) the location of the memory my_struct during allocation is determined by the C compiler (in my case gcc -std=c11 ), while during access by the C ++ compiler (in my case g++ -std=c++11 ).

My question is: is this compatibility standardized? If not, is there a way around this?

NOTE I do not have enough knowledge to argue with alignment, complement and other implementation-specific features. But it should be noted that the GNU science library, which compiles in C, implements the same approach (although their struct does not contain C99 complex numbers) for use in C ++. On the other hand, I have done enough research to conclude that C ++ 11 guarantees layout compatibility between C99 double complex and std::complex<double> .

+5
source share
5 answers

C and C ++ have common memory layout rules. In both languages, structures are placed in memory the same way. And even if C ++ really wanted to do something differently, placing the structure inside extern "C" {} guarantees the layout of C.

But what your code does relies on C ++ std :: complex and C99 complex to be the same.

So see:

+6
source

Your program has undefined behavior: your definitions of my_struct are not lexically identical.

You gamble that alignment, filling and other other things will not change between two compilers, which is bad enough and hellip; but since it's UB, anything can happen, even if it's true!

+4
source

This may not always be the same!

In this case, it looks like sizeof(std::complex<double>) identical to sizeof(double complex) . Also note that compilers may (or may not) add padding to structures so that they are bound to a specific value based on the optimization configuration. And filling can not always be identical, which leads to different sizes of the structure (between C and C ++).

Links to related posts:

Equivalent to C / C ++ memory layout

I would add compiler-specific attributes to "pack" the fields, thereby ensuring that all CPUs are contiguous and compact. This is less about C versus C ++ and more about the fact that you are probably using two “different” compilers when compiling in two languages, even if these compilers come from the same provider.

Adding a constructor will not change the layout (although the non-POD class will do it), but adding access specifiers such as private between these two fields can change the layout (in practice, not only in theory).

The structure of the memory structure C <

In C, the compiler is allowed to dictate some alignment for each primitive type. Usually alignment is the size of the type. But it is completely implementation specific.

Fill bytes are added so that each object is correctly aligned. Reordering is not allowed.

Perhaps every remote modern compiler implements the #pragma package, which allows you to control the filling and leaves it to the programmer in accordance with the ABI. (This is strictly non-standard.)

From C99 §6.7.2.1:

12 Each member of the non-bit field of the structure or association object is aligned in accordance with the implementation corresponding to its type.

13 Inside a structure object, members that are not bit fields, and blocks in which bit fields are located, have addresses that increase in the order in which they are declared. A pointer to a structure object that is suitably converted, points to its initial member (or if this member is a bit field, then to the block in which it is located) and vice versa. An object of a structure may have an unnamed fill, but not at its beginning.

+3
source

In general, C and C ++ have compatible structure layouts, since the layout is dictated by the rules of the ABI platform, and not just the language, and (for most implementations) C and C ++ follow the same ABI rules for type sizes, data layout, agreement on calls, etc.

C ++ 11 even defined the new term standard-layout, which means that the type will have a compatible layout with a similar type in C. This means that it cannot use virtual functions, private data members, multiple inheritance (and a few other things) . The C ++ standard layout type should have the same layout as the equivalent C type.

As pointed out in other answers, your specific code is generally unsafe, since std::complex<double> and complex double are not equivalent types, and there is no guarantee that they are compatible with layouts. However, the GCC C ++ standard library guarantees that it will work because std::complex<double> and std::complex<float> implemented in terms of the base types of C. Instead of containing two doubles, GCC std::complex<double> has one member of type __complex__ double , which the compiler implements is identically equivalent to type C.

GCC does this specifically to support code like yours because it is the smart thing you want to do.

Thus, combining the GCC efforts for std::complex with standard layout rules and the ABI platform means that your code will work with this implementation.

This is not necessarily portable for other C ++ implementations.

+3
source

Also note that in the malloc() structure with a C ++ object ( std::complex<double> ) you missed ctor, and this is also UB - even if you expect ctor to be empty or just a null value and harmless to skip, You cannot complain if it breaks. Thus, your work on the program is just luck.

0
source

All Articles