Submitter: Hans-J. Boehm
Sumission Date: 2021-05-20
Doc. No.: WG14 N2741
Email: hboehm@google.com

Clarify atomics compatibility between C and C++

Abstract

Atomics for C and C++ were originally developed jointly and designed to be compatible. However, this vision was never fully realized. WG21/P0943 was recently adopted into the C++23 wording paper. It adds a <stdatomic.h> header to the C++ standard to support shared C and C++ headers that mention atomics. In doing so, it makes some assumptions about eventual representation compatibility between atomics implementations in C and C++. We call this to the attention of WG14, and propose some implementation guidance wording for the C standard.

Background

WG21/P0943 provides a more thorough discussion of some of the issues here.

C's atomics were based on a design by WG21 which, at the request of WG14, included a C-compatible subset. The goal was to make this subset usable inside headers that can be included from either C or C++. Unfortunately this vision was long delayed by several obstacles:

  1. C++ did not standardize stdatomic.h until recently.
  2. C's _Atomic was added after our original plan, and its role in the overall vision remained unclear.
  3. Perhaps partially due to the preceding point, C and C++ atomics implementations were not as compatible as expected. Some confusing C++ advice about representation compatibility between T and atomic<T> did not improve matters.

WG21/P0943 addresses the first two points by providing a C++ header stdatomic.h that essentially introduces C-compatible global name space aliases for the facilities already available in C++'s atomic header. It is expected to effectively macro-define _Atomic(T) to std::atomic<T> (with more delicate wording to avoid requiring inclusion of the atomic header). Using this to share headers between C and C++ works only if C compilers implement _Atomic(T) in the same way that C++ compilers implement std::atomic<T>. The same runtime data must be accessible as either, thus returning us to the third issue.

P0943 cannot fully address this third problem, in part because it is formally outside the domain of any specific standard. In addition current implementations of C and C++, while they share a common representation for frequently used types like _Atomic(int), often do not do so for less frequently used cases, such as _Atomic(struct{ char a, b, c; }) or _Atomic(double) on 32-bit platforms. (We also observed some similar incompatibilities between different compilers compiling the same language on the same platform.)

There is general agreement that this last issue should be fixed, in spite of the ABI changes i the more obscure cases. It causes surprises, even in the absence of P0943, but it may take time to do so. Hence P0943 added the following to the current C++23 working draft:

Recommended Practice: Implementations should ensure that C and C++ representations of atomic objects are compatible, so that the same object can be accessed as both an _Atomic(T) from C code and atomic<T> from C++ code. The representations should be the same, and the mechanisms used to ensure atomicity and memory ordering should be compatible.

This topic was discussed in the May 7, 2021 C and C++ compatibility study group meeting, with consensus, and no negative votes in either group, in favor of this direction. It was observed that the C standard currently contains no "recommended practice" wording, but suggested that it be proposed to WG14 anyway. Thus the purpose of this paper is two-fold: To call WG14's attention to this issue, and to propose that the above "recommended practice" wording also appear in the C standard.

The same study group meeting also spent a bit of time discussing the role of the _Atomic type qualifier, and whether it might serve as a substitute for the recently introduced C++ atomic_ref<>. This might involve restricting the type qualifier to cases in which the representations of T and _Atomic T are sufficiently compatible. There was no consensus on how to proceed. An earlier reflector discussion had a similar outcome. Hence there is no proposal here in that area.

Proposal

We propose to add the same "recommended practice text" that currently appears in the C++ working paper (N4885, [stdatomic.h.syn], page 1563, paragraph 4) to the C standard, at a place of the editor's choice.

I.e., we propose to add:

Recommended Practice: Implementations should ensure that C and C++ representations of atomic objects are compatible, so that the same object can be accessed as both an _Atomic(T) from C code and atomic<T> from C++ code. The representations should be the same, and the mechanisms used to ensure atomicity and memory ordering should be compatible.

Note that the last clause: "the mechanisms used to ensure atomicity and memory ordering should be compatible" in fact applies to more than C and C++: It should apply to any pair of languages that can be linked in the same executable. For example, a release store needs to ensure visibility of all prior memory accesses, not just those performed by C and C++; anything else results in very surprising behaviors. But here it seems cleaner just to replicate the C++ wording here.