How to properly mix stdatomic between C and C++

648 Views Asked by At

Consider a library that uses stdatomic.h header. This one will not compile in C++ project, especially if it is required to use structure with atomic components.

How to properly implement library with atomic components, to build with C or C++ compiler? Small demo that builds well - but it seems very shady and holds potential undefined behavior.

lib.h header file

#ifndef LIB_HDR_H
#define LIB_HDR_H

#include <stdint.h>
#include <stddef.h>

#if defined(__cplusplus)
/* C++ atomic header */
#include <atomic>
#define lib_size_t std::atomic<size_t>
#else
/* STDATOMIC C header */
#include <stdatomic.h>
typedef atomic_size_t lib_size_t;
#endif

#if defined(__cplusplus)
extern "C" {
#endif /* defined(__cplusplus) */

/* Tricky part here... */
typedef struct {
  lib_size_t a;
  lib_size_t b;
} lib_t;

void lib_init(lib_t *l);

#if defined(__cplusplus)
}
#endif /* defined(__cplusplus) */

#endif /* LIB_HDR_H */

lib.c implementation file

#include "lib.h"

/* Not much to do here right now... */
void lib_init(lib_t* l) {
    /* These operations shall be atomic */
    l->a = 5;
    l->b = 10;
}

C++ application file that uses the library

#include <iostream>
#include "lib.h"

lib_t my_lib;

int main() {
    lib_init(&my_lib);
}
1

There are 1 best solutions below

6
user17732522 On

In C++ std::atomic_size_t is an alias for std::atomic<size_t> and in C atomic_size_t is an alias for _Atomic size_t.

With a using std::atomic_size_t in the C++ side of the #if, you can simply use typedef atomic_size_t lib_size_t; for both languages and starting with C++23 you can include <stdatomic.h> and use typedef atomic_size_t lib_size_t; directly in both C and C++.

The two types are not the same in the same way that size_t is supposed to be the same type in both C and C++, because C++ doesn't have any equivalent to the _Atomic modifier. So it is not obvious that the ABI handles the two types in the same way and that what you are doing is allowed. (In practice, most real implementations did handle them the same way.)

However, the C++23 standard draft contains an implementation recommendation that same representation and compatibility of memory ordering mechanism between _Atmomic T and std::atomic<T> should be assured. Of course this can only be a recommendation and the details will be on the particular implementation to decide.

So, assuming your compiler supports both C atomics and C++ atomics, it should make an effort to support the approach you are using here, although that is not a requirement for conformance. However, it doesn't seem to be a given. See the proposal introducing the implementation recommendation P0943 for some details. For example it mentions that GCC had an alignment difference between 64-bit atomic C vs. C++ types on 32-bit targets like ARMv7 and x86.

This was probably a GCC bug, like x86 GCC bug 65146 resulting in lack of atomicity for _Atomic long long inside structs in some C programs, which was fixed in GCC11. It went unfixed for a long time partly because GCC already chose to align 8-byte objects by 8 when they were outside structs so it was allowed to. (g++ originally had an equivalent bug, but it was fixed with alignas() in the std::atomic<> class definition in library headers, vs. _Atomic requiring the compiler internals to do that.) We don't know for sure what ARMv7 detail they were talking about, but it's likely similar and probably only affects 64-bit types.

So you probably need to verify that this is properly supported for your specific compiler/architecture.