Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Cast A primitive type pointer to A structure pointer - Alignment and Padding?

Tags:

c++

casting

Just 20 minutes age when I answered a question, I come up with an interesting scenario that I'm not sure of the behavior:

Let me have an integer array of size n, pointed by intPtr;

int* intPtr;

and let me also have a struct like this:

typedef struct {
int val1;
int val2;
//and less or more integer declarations goes on like this(not any other type)
}intStruct;

My question is if I do a cast intStruct* structPtr = (intStruct*) intPtr;

Am I sure to get every element correctly if I traverse the elements of the struct? Is there any possibility of miss-alignment(possible because of padding) in any architecture/compiler?

like image 522
Seçkin Savaşçı Avatar asked Aug 27 '12 08:08

Seçkin Savaşçı


3 Answers

The standard is fairly specific that even a POD-struct (which is, I believe the most restrictive class of structs) can have padding between members. ("There might therefore be unnamed padding within a POD-struct object, but not at its beginning, as necessary to achieve appropriate alignment." -- a non-normative note, but still makes the intent quite clear).

For example, contrast the requirements for a standard-layout struct (C++11, §1.8/4):

An object of trivially copyable or standard-layout type (3.9) shall occupy contiguous bytes of storage."

...with those for an array (§8.3.4/1):

An object of array type contains a contiguously allocated non-empty set of N subobjects of type T.

In the array, the elements themselves are required to be allocated contiguously, whereas in the struct, only the storage is required to be contiguous.

The third possibility that might make the "contiguous storage" requirement make more sense would be to consider a struct/class that is not trivially copyable or standard layout. In this case, it's possible that the storage might might not be contiguous at all. For example, an implementation might set aside one area of memory for holding all the private variables, and an entirely separate area of memory to hold all the public variables. To make that a little more concrete, consider two definitions like:

class A { 
    int a;
public:
    int b;
} a;

class B {
    int x;
public:
    int y;
} b;

With these definitions, the memory might be laid out something like:

a.a;
b.x;

// ... somewhere else in memory entirely:

a.b;
b.y;

In this case, neither the elements nor the storage needs to be contiguous, so interleaving parts of entirely separate structs/classes is allowable.

That said, the first element must be at the same address as the struct as a whole (9.2/17): "A pointer to a POD-struct object, suitably converted using a reinterpret_cast, points to its initial member (or if that member is a bit-field, then to the unit in which it resides) and vice versa."

In your case, you have a POD-struct, so (§9.2/17): "A pointer to a POD-struct object, suitably converted using a reinterpret_cast, points to its initial member (or if that member is a bit-field, then to the unit in which it resides) and vice versa." Since the first member must be aligned, and the remaining members are all of the same type, it's impossible for any padding to be truly necessary between the other members (i.e., except for bit-fields, any type you can put in a struct you can also put in an array, where contiguous allocation of the elements is required). If you have elements smaller than a word, on a word-oriented machine (e.g., early DEC Alphas), it's possible that padding could make access somewhat simpler though. For example, early DEC Alphas (at the hardware level) were only capable of reading/writing an entirely (64-bit) word at a time. As such, let's consider something like a struct of four char elements:

struct foo { 
   char a, b, c, d;
};

If it was required to lay these out in memory so they were contiguous, accessing a foo::b (for example) would require that the CPU load the word, then shift it 8-bits right, then mask to zero-extend that byte to fill the entire register.

Storing would be even worse -- the CPU would have to load the current value of the whole word, mask out the current contents of the appropriate char-sized piece of that, shift the new value to the correct place, OR it into the word, and finally store the result.

By contrast, with padding between the elements, each of those becomes a simple load/store, with no shifting, masking, etc.

At least if memory serves, with DEC's normal compiler for the Alpha, int was 32 bits, and long was 64 bits (it predated long long). As such, with your struct of four ints, you could have expected to see another 32 bits of padding between the elements (and another 32 bits after the last element as well).

Given that you do have a POD-struct, you still have some possibilities though. The one I'd probably prefer would be to use offsetof to get the offsets of the members of the struct, create an array of them, and access the members via those offsets. I showed how to do this in a couple of previous answers.

like image 159
Jerry Coffin Avatar answered Oct 12 '22 00:10

Jerry Coffin


Strictly speaking, such pointer casts aren't allowed and lead to undefined behavior.

The main issue with the cast is however that the compiler is free to add any number of padding bytes anywhere inside a struct, except before the very first element. So whether it will work or not depends on the alignment requirements of the specific system, and also whether struct padding is enabled or not.

int is not necessarily of the same size as the optimal size for an addressable chunk of data, even though this is true for most 32-bit systems. Some 32-bitters don't care about misalignment, some will allow misalignment but produce less efficient code, and some must have the data aligned. In theory, 64-bitters may also want to add padding after an int (which will be 32 bit there) to get a 64-bit chunk, but in practice they support 32-bit instruction sets.

If you write code relying on this cast, you should add something like this:

static_assert (sizeof(intStruct) == 
               sizeof(int) + sizeof(int));
like image 28
Lundin Avatar answered Oct 12 '22 01:10

Lundin


It is guaranteed to be legal, given that the element type is standard-layout. Note: all references in the following are to the c++11 standard.

8.3.4 Arrays [dcl.array]

1 - [...] An object of array type contains a contiguously allocated non-empty set of N subobjects of type T. [...]

Regarding a struct with N members of type T,

9.2 Class members [class.mem]

14 - Nonstatic data members of a (non-union) class with the same access control are allocated so that later members have higher addresses within a class object. [...] Implementation alignment requirements might cause two adjacent members not to be allocated immediately after each other [...]
20 - A pointer to a standard-layout struct object, suitably converted using a reinterpret_cast, points to its initial member [...] and vice versa. [ Note: There might therefore be unnamed padding within a standard-layout struct object, but not at its beginning, as necessary to achieve appropriate alignment. —end note ]

So the question is whether any alignment-required padding within a struct could cause its members not to be contiguously allocated with respect to each other. The answer is:

1.8 The C++ object model [intro.object]

4 - [...] An object of trivially copyable or standard-layout type shall occupy contiguous bytes of storage.

In other words, a standard-layout struct a containing at least two members x, y of the same (standard-layout) type that does not respect the identity &a.y == &a.x + 1 is in violation of 1.8:4.

Note that alignment is defined as (3.11 Alignment [basic.align]) the number of bytes between successive addresses at which a given object can be allocated; it follows that alignment of a type T can be no greater than the distance between adjacent objects in an array of T, and (since 5.3.3 Sizeof [expr.sizeof] specifies that the size of an array of n elements is n times the size of an element) alignof(T) can be no greater than sizeof(T). Thus any additional padding between adjacent elements of a struct of the same type would not be required by alignment and so would not be countenanced by 9.2:14.


With regard to AProgrammer's point, I would interpret the language in 26.4 Complex numbers [complex.numbers] as requiring that the instantiations of std::complex<T> should behave as standard-layout types with regard to the position of their members, without being required to conform to all the requirements of standard-layout types.

like image 24
ecatmur Avatar answered Oct 12 '22 02:10

ecatmur