Placement new issue
In this I need C++ array class template, which is fixed-size, stack-based and doesn't require default constructor answer I posted a piece of code, that is using placement new with char array. For me, this is something absolutely normal. But according to comments this code is wrong.
Can anyone explain in more detail?
Specifically what can go wrong with the array. What I understand from the comments is that T x[size];
might not fit into char x[size*sizeof(T)];
. I don't believe this is true.
EDIT:
I'm just more and more confused. I kno开发者_StackOverflow社区w what alignment is in case of structures. Yes, when you have a structure the attributes start on different offsets then you might think.
OK, now we are back to arrays. You are telling me that T x[size];
is the same size as char x[size*sizeof(T)];
, yet I cannot access the char array as T array because there might be some alignment. How can there be alignment when the arrays have the same size?
EDIT 2:
OK I finally get it, it may start on a wrong address.
EDIT 3:
Thx everyone, you can stop posting :-) Phew, this total blew my mind. I just never realized this was possible.
A T x[size]
array will always fit exactly into size * sizeof(T)
bytes, meaning that char buffer[size*sizeof(T)]
is always precisely enough to store such an array.
The problem in that answer, as I understood it, was that your char
array is not guaranteed to be properly aligned for storing the object of type T
. Only malloc
-ed/new
-ed buffers are guaranteed to be aligned properly to store any standard data type of smaller or equal size (or data type composed of standard data types), but if you just explicitly declare a char
array (as a local object or member subobject), there's no such guarantee.
Alignment means that on some platform it might be strictly (or not so strictly) required to allocate, say, all int
objects on, say, a 4-byte boundary. E.g. you can place an int
object at the address 0x1000
or 0x1004
, but you cannot place an int
object at the address0x1001
. Or, more precisely, you can, but any attempts to access this memory location as an object of type int
will result in a crash.
When you create an arbitrary char
array, the compiler does not know what you are planning to use it for. It can decide to place that array at the address 0x1001
. For the above reason, a naive attempt to create an int
array in such an unaligned buffer will fail.
The alignment requirements on some platform are strict, meaning that any attempts to work with misaligned data will result in run-time failure. On some other platforms they are less strict: the code will work, but the performance will suffer.
The need for the proper alignment sometimes means that when you want to create an int
array in an arbitrary char
array, you might have to shift the beginning of an int
array forward from the beginning of the char
array. For example, if the char
array resides at 0x1001
, you have no choice but to start your constructed-in-place int
array from the address 0x1004
(which is the char
element with the index 3). In order to accommodate the tail portion of the shifted int
array, the char
array would have to be 3 bytes larger than what the size * sizeof(T)
evaluates to. This is why the original size might not be enough.
Generally, if your char
array is not aligned in any way, you will really need an array of size * sizeof(T) + A - 1
bytes to accommodate an aligned (i.e. possibly shifted) array of objects of type T
that must be aligned at A-byte boundary.
T
may be aligned different from a char.
also, itanium abi (for example) specifies cookies for non-pod array, so it knows how many elements to walk across at deletion (to call the destructors). the allocation via new is like this (iirc):
size_t elementCount;
// padding to get native alignment for 1st element
T elements[elementCount];
so the allocation for a 16 byte aligned object is:
size_t elementCount; // 4
char padding[16 - sizeof(elementCount)];
T elements[elementCount]; // naturally aligned
char can be aligned to 1 on some systems so... you see where the misalignment and size issues fit in. builtin types don't need their dtors called, but everything else does.
char x[size*sizeof(T)];
might not take alignment into account, where as T x[size]; will. alignment(2) can also be very important when working with SSE types, that require 16 byte alignment
On some systems, memory access must be "aligned". For the sake of simplicity, this means that the address must be a multiple of some integer called the "alignemnt requirement" of the type (see 3.9/5 of the C++ standard).
So, for example, suppose sizeof(int) == 4
:
int *intarray = new int[2]; // 8 bytes
char *charptr = (char *)intarray; // legal reinterpret_cast
charptr += 1; // still 7 bytes available
*((int*)charptr) = 1; // BAD!
The address of charptr is not a multiple of 4, so if int
has an alignment requirement of 4 on your platform, then the program has undefined behavior.
Similarly:
char ra[8];
int *intptr = reinterpret_cast<int*>(ra);
intptr[0] = 1; // BAD!
The address of ra
is not guaranteed to be a multiple of 4.
This is OK, though:
char ra = new char[8];
int *intptr = reinterpret_cast<int*>(ra);
intptr[0] = 1; // NOT BAD!
because new
guarantees that char array allocations are aligned for any type that is small enough to fit in the allocation (5.3.4/10).
Moving away from automatics, it's easy to see why the compiler is free not to align data members. Consider:
struct foo {
char first[1];
char second[8];
char third[3];
};
If the standard guaranteed that second
was 4-aligned (still assuming that int
is 4-aligned), then the size of this struct would have to be at least 16 (and its alignment requirement at least 4). As the standard is actually written, a compiler is permitted to give this struct size 12, with no padding and no alignment requirement.
§5.3.4/10:
A new-expression passes the amount of space requested to the allocation function as the first argument of type
std::size_t
. That argument shall be no less than the size of the object being created; it may be greater than the size of the object being created only if the object is an array. For arrays of char and unsigned char, the difference between the result of the new-expression and the address returned by the allocation function shall be an integral multiple of the most stringent alignment requirement (3.9) of any object type whose size is no greater than the size of the array being created.
This allows using char arrays allocated with new for placement-construction of appropriately sized objects of other types. The pre-allocated buffer has to be allocated on the heap. Otherwise you might run into alignment problems.
精彩评论