3

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.

paulm
  • 5,629
  • 7
  • 47
  • 70
  • 6
    Are you trying to use a c++ feature in c? That is never a good approach. I don't really understand your question. – Iharob Al Asimi Oct 05 '15 at 22:02
  • 3
    I have a feeling that the `volatile` keyword does not do what you assume it does. – inetknght Oct 05 '15 at 22:02
  • The C code abstracts atomics because there are no cross platform atomics in C, thus I want to use C++11 as the implementation because it has got cross platform atomics- note that the C code is some huge existing library that was using "glib" for atomics previously – paulm Oct 05 '15 at 22:02
  • 5
    C11 has `stdatomic.h`, like C++11. You should not use some obscure library. And you might be confusing C and C++. They are two **different** languages. – too honest for this site Oct 05 '15 at 22:04
  • " there are no cross platform atomics in C" **nonsense**! – too honest for this site Oct 05 '15 at 22:04
  • I'm well aware C and C++ are not the same language, you can call C+11 code from C by marking the function as extern "C" { } – paulm Oct 05 '15 at 22:05
  • "... from C by marking the function as extern "C"" **No**. It is exactly the opposite. Just have a look at standard headers. – too honest for this site Oct 05 '15 at 22:05
  • @Olaf well for some reason this C lib seems to think there isn't - perhaps it was written before C11 existed or the C compilers where too old? - P.S you can call a C++ function from C if its marked extern "C" so it can be linked, not sure why you think this isn't possible – paulm Oct 05 '15 at 22:06
  • You seem to confuse some things here. Please clarify your question. – too honest for this site Oct 05 '15 at 22:06
  • Incoming: "`volatile` is a no-op" lies – Lightness Races in Orbit Oct 05 '15 at 22:06
  • Hopefully everything will be easier in the future. C11 is allegedly the final version of C, and there's a proposal for C++ to be based on C11 eventually, which should make the respective atomic types interoperable. – Kerrek SB Oct 05 '15 at 22:07
  • @KerrekSB: I hope C11 will not be the last release. There are still some features missing. For instance the C++11 feature of explicitly specifying the integer type of `enum`s. That would finally make them useful for embedded systems (the current are near useless for many applications or rely on implementation specifics). `stdatomic.h` actually **is** based on C++11. AFAIK they are actually the same (as much as that is possible) – too honest for this site Oct 05 '15 at 22:08
  • stdatomic.h doesn't seem to exist in MSVC 2013 so I can't use that instead of doing it via the C++11 atomics (which by the sounds of things can't work anyway) – paulm Oct 05 '15 at 22:12
  • @Olaf: It's true that C++11 and C11 both have a memory model that's fairly similar, but C++11 and C++14 only make reference only to C99, so they cannot formally establish any interoperability. It shouldn't be hard to specify, but it hasn't been done yet. – Kerrek SB Oct 05 '15 at 22:12
  • @Olaf: If you care about C, please contribute to WG14... – Kerrek SB Oct 05 '15 at 22:12
  • Whatever that lib does, if the rest is of the same quality, it is far from **atomic** operations. Strong recommendation is to instantly remove from your drive, get a recent compiler and use `stdatomic.h` resp. `std::atomic`. – too honest for this site Oct 05 '15 at 22:13
  • 3
    MSVC is no standard compliant compiler. You should not use it for modern C anyway. – too honest for this site Oct 05 '15 at 22:14
  • @KerrekSB: If I knew how to and that it would be at least been taken into account, I really would. But my experiences with such commitees are - well - not the best. – too honest for this site Oct 05 '15 at 22:15
  • I have to use MSVC 2013 - thus I'll probably just have to call the win32 atomic exchange functions directly which is a shame when C++11 already has objects that wrap this – paulm Oct 05 '15 at 22:15
  • @paulm: You are really confusing things here. Keep things straight: You cannot use C++ easily from C. Partly because of name-mangling, but there are also other difficulties. – too honest for this site Oct 05 '15 at 22:17
  • @Olaf C functions can be implemented in C++, using extern "C" gives the function C name-mangling - the same as exposing any other C API from C++. I already have the C code calling into C++ – paulm Oct 05 '15 at 22:23
  • @Olaf yes, you can call C++ functions from C if you declare them as `extern "C"` *in the C++ code.* This causes the C++ compiler to emit C compatible linkage. – fuz Oct 05 '15 at 22:25
  • 1
    @Olaf - What's so hard about "compile C++ code with C++ compiler, compile C code with C compiler, have C code call C++ code via an `extern "C"` interface, and link using C++ compiler"? Or you can compile a DLL or shared object using a C++ compiler, making sure the library records its dynamic dependenc(ies) on the proper C++ runtime libraries. – Andrew Henle Oct 05 '15 at 22:25
  • 1
    @FUZxxl - You mean like `extern "C" int main( int argc, char *argv[] );`, which almost every C++ program has and gets called from a **C** startup routine? – Andrew Henle Oct 05 '15 at 22:27
  • @FUZxxl: But that would make the effectively C functions, not C++ functions. Anyway, the given example is not atomic anyway. – too honest for this site Oct 05 '15 at 22:29
  • @Olaf no because you can use C++ specific code within main – paulm Oct 05 '15 at 22:30
  • @paulm: So that is a C++ question then. It is still not clear what you actually want. This is leading nowhere. I'm out of this. – too honest for this site Oct 05 '15 at 22:31
  • I want to atomically update an int - this int comes from a c lib, the function thats doing the atomic update is implemented in a C++11 lib, since you think you cant call C++ from C no wonder you're confused – paulm Oct 05 '15 at 22:32
  • If you're really stuck with the MSVC compiler, you should probably use whatever atomic-operation functions the Windows API has to offer. A quick google lead me to the [Interlocked functions, here](https://msdn.microsoft.com/en-us/library/windows/desktop/ms686360(v=vs.85).aspx#interlocked_functions). – Kninnug Oct 05 '15 at 22:36
  • @Kninnug yes - but ideally I wanted to use std::atomic (which calls those interlocked functions on Windows) otherwise I also have to add another implementation for Linux – paulm Oct 05 '15 at 22:37
  • @pm100 there is a huge existing C library, porting it all to c++ is not a trivial task - i.e using std::atomic in do_atomic_int_add would solve my problems so long as it makes the parameter actually atomic (my understand it is that it does not?) – paulm Oct 05 '15 at 22:38
  • you are assuming that the c++ atomics are entirely implemented as libraries; that there are no include specifics with it on some (or all ) platforms – pm100 Oct 05 '15 at 22:40
  • @Olaf That only specifies that the linkage is compatible to C. You can still use any C++ construct in a function declared `extern "C"`, the only thing you cannot do is mentioning types that cannot be expressed in C in the signature of the symbol you declare as `extern "C"`. – fuz Oct 05 '15 at 22:48
  • @FUZxxl: Thanks for explaining. I'm not into C++ programming (looooooong time since and then it was C++ only). – too honest for this site Oct 05 '15 at 23:03
  • @paulm Whatever gave you the knowledge that it's *possible* to do this should also have given you the knowledge of how to do it. Since we don't know how or why you think this is possible, we are at a disadvantage relative to you that prevents us giving you a good answer. (There is, for example, no guarantee that a platform has a capability to perform an atomic read-modify-write operation on an ordinary integer variable. Whoever or whatever told you that this was possible to get should have also told you how to get it.) – David Schwartz Jan 03 '16 at 03:35
  • @DavidSchwartz there are a ton of comments already here about how C can call into C++ this is so common and simple I figured it wouldn't need an explanation.. I guess its a separate question which I may ask and answer since so many people seem to be completely walled by it. – paulm Jan 04 '16 at 07:43
  • @paulm The issue isn't calling into C, it's being able to do an atomic operation on an ordinary integer. You seem to think that's possible to do on any platform that supports `std::atomic`. This has nothing to do with C calling C++. You can't do this in pure C++ either -- that's why we have `std::atomic`! – David Schwartz Apr 06 '20 at 23:45
  • given glib or whatever this was supports it you clearly can do it from C – paulm Apr 07 '20 at 21:31

2 Answers2

3

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:

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:

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;
    } 
}
Jeff Hammond
  • 5,374
  • 3
  • 28
  • 45
1

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.

MSalters
  • 173,980
  • 10
  • 155
  • 350