Naturally, in order for a typical modern processor architecture (like x86_64) to perform an atomic load or store, the data to be read/written needs to be aligned.
But how is this requirement actually realized/enforced via C++11 <atomic>
variables?
Suppose I have an architecture that supports 16-byte compare and swap (double-word CAS), so it can atomically read/write 16-byte values, and I define a 16-byte type:
struct double_word
{
std::uint64_t x;
std::uint64_t y;
};
Now, suppose I include an std::atomic<double_word>
as a member field of a class:
class foo
{
public:
std::atomic<double_word> dword;
};
How do I know foo::dword
is actually aligned on a 16-byte boundary? How do I know a call to dword.load()
would actually be atomic?
Actually, I originally starting asking this question because of an odd thing that happened when I added another data member before foo::dword
. I defined foo
as:
class foo
{
public:
std::uint64_t x;
std::atomic<double_word> dword;
};
When I actually perform an atomic load on foo::dword
, and compile and run using GCC 4.7.2 on an x86_64 machine running Debian Linux, it actually gives me a Segmentation Fault!
Full program:
#include <atomic>
#include <cstdint>
struct double_word
{
std::uint64_t x;
std::uint64_t y;
};
class foo
{
public:
std::uint64_t x;
std::atomic<double_word> dword; // <-- not aligned on 16-byte boundary
};
int main()
{
foo f;
double_word d = f.dword.load(); // <-- segfaults with GCC 4.7.2 !!
}
This actually segfaults on f.dword.load()
. At first I didn't understand why, but then I realized that dword
is not aligned on a 16-byte boundary. So, this leads to a lot of questions like: what should the compiler do if an atomic variable is not aligned and we try to atomically load it? Is it undefined behavior? Why did the program simply segfault?
Secondly, what does the C++11 standard say about this? Should the compiler make sure that double_word
is automatically aligned on a 16-byte boundary? If so, does that mean GCC is simply buggy here? If not - it would seem it is up to the user to ensure alignment, in which case any time we use an std::atomic<T>
larger than one byte, it would seem we'd have to use std::aligned_storage
to ensure it is properly aligned, which (A) seems cumbersome, and (B) is something I've never actually seen done in practice or in any examples/tutorials.
So, how should a programmer using C++11 <atomic>
handle alignment issues like this?
It is a GCC bug https://gcc.gnu.org/bugzilla/show_bug.cgi?id=65147
Just adding alignas(16)
is fixing the problem.
#include <atomic>
#include <cstdint>
struct double_word
{
std::uint64_t x;
std::uint64_t y;
};
class foo
{
public:
std::uint64_t x;
alignas(16) std::atomic<double_word> dword; // <-- not aligned on 16-byte boundary
};
int main()
{
foo f;
double_word d = f.dword.load(); // <-- segfaults with GCC 4.7.2 !!
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With