Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it safe to assert(sizeof(A) == sizeof(B)) when A and B are "the same"?

Suppose I have two classes that I would expect to have exact same memory layout:

struct A {
    int x;
    int y;
};

/* possibly more code */

struct B {
    int a;
    int b;
};

Is there anything in the standard that guarantees that I can safely static_assert(sizeof(A) == sizeof(B)) ?

As a weaker variant consider

struct C {
    int a;
};

static_assert( sizeof(A) >= sizeof(C) );   // can this ever fail?
static_assert( sizeof(A) >  sizeof(C) );   // can this ever fail?

The question was triggered by this one. Naively I would not expect any of the asserts to fail, but is this guaranteed?

like image 987
463035818_is_not_a_number Avatar asked Mar 08 '19 18:03

463035818_is_not_a_number


3 Answers

A contrived counter-example:

#include <stdint.h>

struct A {
    int32_t a;
    int64_t b;
};

#pragma pack(4)

struct B {
    int32_t a;
    int64_t b;
};

static_assert(sizeof(A) == sizeof(B));

Compilation with g++ in 64-bit Linux yields:

a.cc:15:1: error: static assertion failed
static_assert(sizeof(A) == sizeof(B));
like image 74
atomsymbol Avatar answered Oct 23 '22 16:10

atomsymbol


Nothing in the Standard would forbid an implementation which identified all the structures that are ever used as parts of unions, and added a random amount of padding after each element of any structure that was not used in such fashion. On the other hand, nothing would forbid an implementation from behaving in arbitrary fashion if the number of tags an implementation can handle, nor would anything forbid an implementation from imposing a limit of one.

All of those things fall into the category of things that the Standard would allow a conforming implementation to do, but which quality implementations should generally be expected to refrain from doing even if allowed by the Standard. The Standard makes no effort to forbid implementations from doing silly things, nor to guess at whether some specialized implementations might have a good reasons for processing something in an atypical fashion. Instead, it expects that compiler writers will try to fulfill the needs of their customers whether or not the Standard requires them to do so.

like image 25
supercat Avatar answered Oct 23 '22 16:10

supercat


Yes, the standard guarantees that it is safe to assert that. The relevant term here is layout compatible.

The standard defines that term in two parts. First it defines what a common initial sequence of data members is (but only for standard-layout structs): It is the sequence of data members that are equivalent between the two structs. The standard includes an example, but I'll use a slightly different one to avoid some technicalities:

struct A { int a; char b; };
struct B { int b1; char b2; };
struct C { int x; int y; };

In that example, the common initial layout of A and B is both of their members, while for A and C it is only their first respective members. It then defines structs as layout compatible if the common initial layout is simply the whole class.

If you have instances of two different layout-compatible types, like A and B in the above example, you *can* assume they have the same size:

static_assert(sizeof(A) == sizeof(B));

However, you *cannot* (in theory) cast between them without invoking undefined behaviour, because that violates aliasing rules:

A a{1, 'a'};
B* b = reinterpret_cast<B*>(&a); // undefined behaviour!
do_something_with(b);

What you can do, subject to the usual const/volatile rules as well as rules about data members being trivial (see When is a type in c++11 allowed to be memcpyed?), is use memcpy to get data between layout-compatible structs. Of course, that wouldn't be possible of padding between members could be randomly different, as the current top answer suggests.

A a{1, 'a'};
B b;
memcpy(&b, &a, sizeof(b)); // copy from a to b
do_something_with(b);

If do_something_with takes its argument by reference and modifies it then you would then need to copy back from b to a to reflect the effect there. In practice this would usually be optimised to be what you would expect the above cast to do.

The answer by atomsymbol gives an example that appears to contradict everything above. But you asked about what was in the standard, and a #pragma that affects padding is outside of what is covered by the standard.

like image 20
Jim Oldfield Avatar answered Oct 23 '22 16:10

Jim Oldfield