WaeCo WaeCo - 11 days ago 4
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) {
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

Answer

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

Comments