WaeCo WaeCo - 3 months ago 18
C++ Question

std::atomic trivially copyable structs

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) {

std::atomic<T> a;

int main() {
return 0;

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

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
works but
does not.

Any ideas?

EDIT: added -latomic flag and g++ version


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
mov     BYTE PTR [rsp], 1
mov     QWORD PTR [rsp], 1
mov     QWORD PTR [rsp], rax
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).