5

I'm building a C++ library which uses many functions and struct's defined in a C library. To avoid porting any code to C++, I add the 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() is compiled in C. It simply allocates memory via malloc() and initializes 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 have the expected memory layout, i.e., any access such as ms->z1 evaluates to the expected value. I find this really cool considering that (correct me if I'm wrong) the memory layout of my_struct during allocation is decided 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 any way around it?

NOTE : I don't have enough knowledge to argue against alignment, padding, and other implementation-defined specifics. But it is noteworthy that the GNU scientific library, which is C-compiled, is implementing the same approach (although their structs do not involve C99 complex numbers) for use in C++. On the other hand, I've done sufficient research to conclude that C++11 guarantees layout compatibility between C99 double complex and std::complex<double>.

smiling_nameless
  • 1,047
  • 1
  • 9
  • 23
downhillFromHere
  • 1,967
  • 11
  • 11

5 Answers5

6

C and C++ do share memory layout rules. In both languages structs are placed in memory in the same way. And even if C++ did want to do things a little differently, placing the struct inside extern "C" {} guarantees C layout.

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

So see:

Community
  • 1
  • 1
Zan Lynx
  • 53,022
  • 10
  • 79
  • 131
  • For op is using gcc, `_Complex` should be good enough. https://gcc.gnu.org/onlinedocs/gcc-3.2/gcc/Complex.html . But I guess C++ code need to compile with `-std=gnu++11` – user3528438 Aug 24 '15 at 19:25
  • Even if `complex` layout is guaranteed to be the same, there is no guarantee that the whole `my_struct` struct layout will be identical. – Alex Lop. Aug 24 '15 at 19:30
  • 5
    @AlexLop. If that were true, that means there would be absolutely no inter interoperability between C and C++. In the contrary, C++ standard spend so much effort enforcing the memory layout of POD types that C/C++ interoperability is almost guaranteed even without packing the struct. For the same reason, quite a few Linux system calls that uses `struct` are usable in both C and C++, eg `stafs` http://man7.org/linux/man-pages/man2/statfs.2.html – user3528438 Aug 24 '15 at 19:48
  • @user3528438 Maybe they spend much effort BUT I personally dealt with a case when ICC compiler and Microsoft Visual C++ compiler produced different sizes for the same POD struct definitions. I just say that if it works, there is no guarantee that it will keep working with other compilers/compiler versions/build flags. – Alex Lop. Aug 24 '15 at 20:10
  • 1
    @AlexLop. which is a inter-implementation interoperability issue, isn't it? – user3528438 Aug 24 '15 at 20:15
  • @user3528438 In my case probably. But l am not sure that even on different versions of ICC and GCC this would never happen. What I lerned is that in order to be compatible one better to use the same compiler with the same version (I would say even with padding disabled but it may have performance impact). – Alex Lop. Aug 24 '15 at 20:26
  • 1
    @AlexLop. Different compilers will sometimes pad structures in different ways. But this is almost always because of things like optimal 64-bit alignment. I would bet that your case had an 8 byte object in a 32-bit compile, and ICC decided to pad it to 8 bytes even though in 32-bit it isn't necessary. Doing that makes the struct look the same in 64 and 32 bit modes. Compilers will also sometimes pad a struct out to an 8 byte alignment just for better fit into CPU cache lines. I would trust ICC to get it right for Intel. – Zan Lynx Aug 25 '15 at 16:32
  • Generally, @AlexLop., it's safe to assume that a given standard-layout type will have the exact same layout in both C and C++, provided: 1) It's a valid type in both languages, 2) you allow the compiler to determine its packing instead of specifying it manually, and 3) you use the exact same version of the exact same compiler to generate it in C code as you do in C++ code. (This is mainly due to C++ standard layout effectively meaning "C layout", without explicitly mentioning C by name.) – Justin Time - Reinstate Monica Aug 11 '19 at 17:20
  • The same is true for `T complex` and `complex` as of C++11, interestingly enough. It's not explicitly mentioned by name, but the C++11 specifications for `complex` requires it to have the same layout as the C equivalent `T complex`. – Justin Time - Reinstate Monica Aug 11 '19 at 17:22
4

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

You're gambling that alignment, padding and various other things will not change between the two compilers, which is bad enough… but since this is UB anything could happen even if it were true!

Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055
  • The rules about lexically identical only apply intra-language, not inter-language. The C++ standard says the struct must be identical in other C++ translation units, but not with C translation units (because that's outside the scope of the C++ standard so it can't say that). If the struct is a standard layout type then in fact it _should_ work fine when shared with C, and libstdc++ ensures that `std::complex` is laid out identically to a C `complex float`. – Jonathan Wakely Sep 24 '15 at 10:38
4

In general, C and C++ have compatible struct layouts, because the layout is dictated by the platform's ABI rules, not just by the language, and (for most implementations) C and C++ follow the same ABI rules for type sizes, data layout, calling conventions etc.

C++11 even defined a new term, standard-layout, which means the type will have a compatible layout to a similar type in C. That means it can't use virtual functions, private data members, multiple inheritance (and a few other things). A C++ standard-layout type should have the same layout as an equivalent C type.

As noted in other answers, your specific code is not safe in general because std::complex<double> and complex double are not equivalent types, and there is no guarantee that they are layout-compatible. However GCC's C++ standard library ensures it will work because std::complex<double> and std::complex<float> are implemented in terms of the underlying C types. Instead of containing two double, GCC's std::complex<double> has a single member of type __complex__ double, which the compiler implements identically to the equivalent C type.

GCC does this specifically to support code like yours, because it's a reasonable thing to want to do.

So combining GCC's special efforts for std::complex with the standard-layout rules and the platform ABI, means that your code will work with that implementation.

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

Jonathan Wakely
  • 166,810
  • 27
  • 341
  • 521
3

It may not always be identical!

In this case looks like sizeof(std::complex<double>) is identical to sizeof(double complex). Also pay attention to the fact that the compilers may (or may not) add padding to the structs to make them aligned to a specific value, based on the optimization configuration. And the padding may not always be identical resulting in different structure sizes (between C and c++).

Links to related posts:

C/C++ Struct memory layout equivalency

I would add compiler-specific attributes to "pack" the fields, thereby guaranteeing all the ints are adjacent and compact. This is less about C vs. C++ and more about the fact that you are likely using two "different" compilers when compiling in the two languages, even if those compilers come from a single vendor.

Adding a constructor will not change the layout (though it will make the class non-POD), but adding access specifiers like private between the two fields may change the layout (in practice, not only in theory).

C struct memory layout?

In C, the compiler is allowed to dictate some alignment for every primitive type. Typically the alignment is the size of the type. But it's entirely implementation-specific.

Padding bytes are introduced so every object is properly aligned. Reordering is not allowed.

Possibly every remotely modern compiler implements #pragma pack which allows control over padding and leaves it to the programmer to comply with the ABI. (It is strictly nonstandard, though.)

From C99 §6.7.2.1:

12 Each non-bit-field member of a structure or union object is aligned in an implementation- defined manner appropriate to its type.

13 Within a structure object, the non-bit-field members and the units in which bit-fields reside have addresses that increase in the order in which they are declared. A pointer to a structure object, suitably converted, points to its initial member (or if that member is a bit-field, then to the unit in which it resides), and vice versa. There may be unnamed padding within a structure object, but not at its beginning.

Alex Lop.
  • 6,810
  • 1
  • 26
  • 45
1

Also note that by malloc() a struct with C++ object (std::complex<double>) you skipped the ctor and this is also UB - even if you expect the ctor is empty or just zero the value and harmless to be skipped, you can't complain if this breaks. So your program work is by pure luck.

Non-maskable Interrupt
  • 3,841
  • 1
  • 19
  • 26