1

C++ reference says: http://en.cppreference.com/w/cpp/atomic/atomic

std::atomic may be instantiated with any TriviallyCopyable type T

However following example does not work under g++ 6.2.0

#include <atomic>
#include <functional>

struct Test11 {
    int x;
};
struct Test12 {
    char x;
};
struct Test13 {
    long x;
};
struct Test2 {
    char x;
    int y;
};
struct Test3 {
    int y;
    long x;
};

template<typename T, typename... ARGS>
void test(ARGS&& ... args) {
    static_assert(std::is_trivially_copyable<T>::value);

    std::atomic<T> a;
    a.store(T{std::forward<ARGS>(args)...});
}

int main() {
    test<Test11>(1);
    test<Test12>('\1');
    test<Test13>(1L);
    test<Test2>('\1',2);
    test<Test3>(1,2L);
    return 0;
}

Compile: g++-6 -std=c++14 -latomic test.cpp

/tmp/cchademz.o: In function std::atomic<Test3>::store(Test3, std::memory_order): test.cpp:(.text._ZNSt6atomicI5Test3E5storeES0_St12memory_order[_ZNSt6atomicI5Test3E5storeES0_St12memory_order]+0x3e): undefined reference to __atomic_store_16 collect2: error: ld returned 1 exit status

g++-6 --version

g++ (Ubuntu 6.2.0-7ubuntu11) 6.2.0 20161018

Especially I do not understand why Test2 works but Test3 does not.

Any ideas?

EDIT: added -latomic flag and g++ version

WaeCo
  • 1,155
  • 1
  • 10
  • 21
  • 2
    Did you try adding `-latomic` at the end of the compilation line by chance? The code compiles fine on coliru with `-latomic` (g++6.2): http://coliru.stacked-crooked.com/a/fd421bd3d1715897 – Holt Nov 22 '16 at 10:43
  • Could you try to include instead? – Kevin G. Nov 22 '16 at 10:44
  • moving `-latomic` to the end worked. But why? – WaeCo Nov 22 '16 at 10:44
  • @WaeCo On Coliru, it works even if you put `-latomic` before `test.cpp`. This is probably linked to the linker used, see e.g. http://stackoverflow.com/questions/45135/why-does-the-order-in-which-libraries-are-linked-sometimes-cause-errors-in-gcc. – Holt Nov 22 '16 at 10:48
  • Ok did not know that. But why does it compile if I comment out Test3 without linking libatomic? – WaeCo Nov 22 '16 at 10:55
  • 2
    @WaeCo `Test3` is the only struct with a size greater than `8` (on standard architectures), so the compiler probably does not need a specific function for storing atomically structure with a size smaller than 8 bytes (`std::atomic<>::store` is probably specialized for small types). – Holt Nov 22 '16 at 10:58
  • 2
    @WaeCo Typically, if you check the generated ASM (e.g. on [godbolt](https://godbolt.org/g/8cLZXC)), you'll see that up to 8 bytes, the compiler uses the `mov QWORD ...` instruction, but there are no instructions to move more than 8 bytes at once, which is why you need a custom function. – Holt Nov 22 '16 at 11:00
  • Thanks. Would you add this as answer so I can mark it as resolved – WaeCo Nov 22 '16 at 11:03
  • @KevinG there is no such header as ``, so that's not going to help. – Jonathan Wakely Nov 22 '16 at 12:32

1 Answers1

4

As mentioned by @TartanLlama in its now-deleted answer, you need to link against libatomic:

g++-6 -std=c++14 test.cpp -latomic

You need to add -latomic at the end of the compilation line. Some compilers (linkers) may work correctly if you put -latomic before test.cpp (e.g. g++ on Coliru), but some won't (see Why does the order in which libraries are linked sometimes cause errors in GCC?).

Disclaimer: I am not an expert in linkage, so I cannot provide a detailed explanation as to why it works with -latomic before on some platform and not on other (I am guessing the linkers are different, but... ).


As to why your code compiles if you remove Test3, this is compiler and architecture dependent. If you look at the generated ASM with -O2 and g++6.2 on godbolt:

sub     rsp, 24
movabs  rax, 8589934593
mov     ecx, 5
mov     DWORD PTR [rsp], 1
mov     rdi, rsp
mov     esi, 1
mov     edx, 2
mfence
mov     BYTE PTR [rsp], 1
mfence
mov     QWORD PTR [rsp], 1
mfence
mov     QWORD PTR [rsp], rax
mfence
call    __atomic_store_16

You see that for structure that takes less than 8 bytes (Test1X, Test2), the compiler can use the mov QWORD instruction (a qword is usually 8 bytes long on nowadays architectures), but it cannot generate a single instruction to handle the cases where the size is strictly greater than 8 (sizeof(Test3) will usually be 16).

Basically, there is probably a specialization of std::atomic<T> (or of some operations of std::atomic<T>) in g++1 when T is "small", and the definition of "small" is probably architecture dependent.

Disclaimer: Again, I am not an expert regarding <atomic> so this is mainly from experiments regarding generated ASM on godbolt and behaviour of g++ and clang on Coliru.

1 clang has a __atomic_store_8 procedure and a __atomic_store procedure, and without -latomic it will not compile for Test2 and Test3. However it manages to compile Test13 even if sizeof(Test13) is 8 so it does not use __atomic_store_8 for some structures. icc has a completely different behavior and does not generate any call (cannot test on Coliru, but you can look it up on godbolt).

Community
  • 1
  • 1
Holt
  • 36,600
  • 7
  • 92
  • 139