Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using std::atomic from C

I have a C library that defined a bunch of platform specific macros for atomic operations. How can I use std::atomic as the implementation for this?

For example the C code has:

#define mylib_atomic_int_add(_pi, _val) do_atomic_int_add(_pi, _val)
int number = 0;
mylib_atomic_int_add(&number, 7);

And the C++ platform abstraction layer (i.e a static library compiled as C++11 that is linked with the C code) has:

extern "C"
{
    int do_atomic_int_add(volatile int* i, volatile int v)
    {
        return *i + v;
    } 
}

Obviously this isn't safe because it isn't atomic, since the int is declared in the C code std::atomic can't be used there, but is there some way to use it within do_atomic_int_add()? like return std::atomic<int>::add(*i, v);?

Edit:

I don't know why no one understands what I'm trying to do. Its basically how do I atomically increment an int in C++ which has been passed over from a C function.

like image 374
paulm Avatar asked Jun 19 '26 10:06

paulm


2 Answers

Why this question is invalid

C++11 atomics require you to use C++11 atomic types for atomic operations. You can step outside the standard by casting from non-atomic to atomic types, but this is not portable code, even if it often works. It's not even valid from an entirely C++ code, let alone a mixture of C and C++.

Because of this limitation, you will not be able to write a C++ atomics library with an interface to C, because you cannot declare C++11 atomic types in C code.

Hans Boehm, one of the foremost authorities on programming language concurrency features, has a proposal for C++0y titled N4013: Atomic operations on non-atomic data that explains some of the issues here.

A different way to solve the problem

You can solve this entirely within C, which is the safest and most portable solution.

The most common way to do atomics in C and C++ prior to the introduction of native language types in C11 and C++11 is with compiler intrinsics. There are two variants of these:

  • Built-in Functions for Memory Model Aware Atomic Operations, which are based on the C++11 memory model.
  • Legacy __sync Built-in Functions for Atomic Memory Access, which are based upon the Itanium ABI document.

Both of these are supported by GCC, Clang and Intel compilers as well as other compilers (IBM for sure, Cray mostly but with at least one bug, and probably PGI). I don't use Windows but I know MSVC has some sort of compiler intrinsics for atomics.

In addition, there are at least two different open-source implementations of C atomics with permissive licenses:

  • OpenPA
  • Preshing's minatomic

Additionally, OpenMP 3+ gives you the ability to use atomics from C89 code, but I don't know if MSVC supports it sufficiently.

Finally, C11 atomics will solve your problem beautifully in an entirely ISO standard way. However, compiler support is rather limited right now. I have used these features with GCC 5+ and late-model Clang, and perhaps Intel 16, but I do not expect MSVC to ever support them.

Code

I removed volatile from the value, since it does nothing. The extern "C" is not required except to turn off C++ name-mangling, as none of the implementations use C++. Because you didn't specify a memory ordering requirement, I assume relaxed (unordered) atomics are sufficient.

__sync version

extern "C"
{
    int do_atomic_int_add(volatile int* i, int v)
    {
        //return (*i + v);
        return __sync_add_and_fetch(i,v);
    } 
}

__atomic version

extern "C"
{
    int do_atomic_int_add(volatile int* i, int v)
    {
        //return (*i + v);
        return __atomic_add_fetch(i,v, __ATOMIC_RELAXED);
    } 
}

OpenMP version

extern "C"
{
    int do_atomic_int_add(volatile int* i, int v)
    {
        int r;
        #pragma omp atomic update
        { r = (*i + v); }
        return r;
    } 
}
like image 152
Jeff Hammond Avatar answered Jun 21 '26 00:06

Jeff Hammond


Formally, the problem is that std::atomic<int> is a type whose objects can be modified in such a way that .load() will only return values that are .store()d. C doesn't call .load so it can't use the int directly.

Of course, you can define extern "C" int atomic_load(struct atomic_int*, enum std_memory_order) with the obvious implementation. You have to essentially wrap std::atomic_int in a C-compatible struct and forward all methods.

like image 32
MSalters Avatar answered Jun 20 '26 23:06

MSalters



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!