Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Misaligned address using virtual inheritance

The following apparently valid code produces a misaligned address runtime error using the UndefinedBehaviorSanitizer sanitiser.

#include <memory>
#include <functional>

struct A{
  std::function<void()> data; // seems to occur only if data is a std::function
} ;

struct B{
  char data; // occurs only if B contains a member variable
};

struct C:public virtual A,public B{

};

struct D:public virtual C{

};

void test(){
  std::make_shared<D>();
}

int main(){
  test();
  return 0;
}

Compiling and executing on a macbook with clang++ -fsanitize=undefined --std=c++11 ./test.cpp && ./a.out produces the output runtime error: constructor call on misaligned address 0x7fe584500028 for type 'C', which requires 16 byte alignment [...].

I would like to understand how and why the error occurs.

like image 560
Lars Melchior Avatar asked Sep 28 '17 16:09

Lars Melchior


1 Answers

Since alignment of std::function<void()> is 16 and size is 48 lets simplify. This code has the same behavior but is easier to understand:

struct alignas(16) A
{ char data[48]; };

struct B
{ char data; };

struct C : public virtual A, public B
{};

struct D : public virtual C
{};

int main()
{
    D();
}

We have the following alignments and sizes:

                     |__A__|__B__|__C__|__D__|
 alignment (bytes):  |  16 |  1  |  16 |  16 |
      size (bytes):  |  48 |  1  |  64 |  80 |

Now lets see how this looks like in memory. More explanation on that can be found in this great answer.

  • A: char[48] + no padding == 48B
  • B: char[1] + no padding == 1B
  • C: A* + B + A + 7 bytes of padding (align to 16) == 64B
  • D: C* + C + 8 bytes of padding (align to 16) == 80B

Now it is easy to see that the offset of C inside D is 8 bytes, but C is aligned to 16. Thus error, which is helpfully accompanied by this pseudo-graphic

00 00 00 00  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  00 00 00 00
             ^ 

Here each zero is 1 byte.

UPDATE: Where and how to place padding is up to a C++ compiler. Standard does not specify it. It looks like with the size of padding it has, clang is unable to align everything in D. One way to mitigate the misalignment is to design your classes carefully so that they have the same alignment (e.g., 8 bytes).

like image 86
AMA Avatar answered Nov 08 '22 07:11

AMA