11

I know that the following is explicitly allowed in the standard:

int n = 0;
char *ptr = (char *) &n;
cout << *ptr;

What about this?

alignas(int) char storage[sizeof(int)];
int *ptr = (int *) &storage[0];
*ptr = 0;
cout << *ptr;

Essentially, I'm asking if the aliasing rules allow for a sequence of chars to be accessed through a pointer to another type. I'd like references to the portions of the standard that indicate one way or another if possible.

Some parts of the standard have left me conflicted; (3.10.10) seems to indicate it would be undefined behavior on the assumption that the dynamic type of storage is not int. However, the definition of dynamic type is unclear to me, and the existence of std::aligned_storage would lead me to believe that this is possible.

chbaker0
  • 1,758
  • 2
  • 13
  • 27
  • 5
    Can the people downvoting leave comments please? – chbaker0 Aug 09 '16 at 23:49
  • I don't know if you can do this, but it is defined by the standard to access a variable in a [`aligned_storage`](http://en.cppreference.com/w/cpp/types/aligned_storage) in the way you are trying to – DarthRubik Aug 10 '16 at 00:04
  • I didn't vote, but if asking for references to standard, I would expect you to show your own research and cite portions that you think are relevant - and describe your own conclusions that you are unsure about. – eerorika Aug 10 '16 at 00:07
  • @chbaker0 Looking at the link I gave above it looks like the a sample implementation (hopefully standard conforming) does almost the same thing you are doing here – DarthRubik Aug 10 '16 at 00:08
  • 1
    @user2079303 good point, I'll add in my research that left me conflicted – chbaker0 Aug 10 '16 at 00:09
  • @DarthRubik Thanks, I noted the extistence of that in my edit – chbaker0 Aug 10 '16 at 00:15
  • "Dynamic type" is useful for classes, not scalar. – curiousguy Jan 13 '17 at 17:43

3 Answers3

6

The code int *ptr = (int *) &storage[0]; *ptr = 0; causes undefined behaviour by violating the strict aliasing rule (C++14 [basic.lval]/10)

The objects being accessed have type char but the glvalue used for the access has type int.

The "dynamic type of the object" for a char is still char. (The dynamic type only differs from the static type in the case of a derived class). C++ does not have any equivalent of C's "effective type" either, which allows typed objects to be "created" by using the assignment operator into malloc'd space.


Regarding correct use of std::aligned_storage, you're supposed to then use placement-new to create an object in the storage. The use of placement-new is considered to end the lifetime of the char (or whatever) objects, and create a new object (of dynamic storage duration) of the specified type, re-using the same storage. Then there will be no strict aliasing violation.

You could do the same thing with the char array, e.g.:

alignas(int) char storage[sizeof(int)];
int *ptr = new(storage) int;
*ptr = 0;
cout << *ptr;

Note that no pseudo-destructor call or delete is required for built-in type int. You would need to do that if using a class type with non-trivial initialization. Link to further reading

Community
  • 1
  • 1
M.M
  • 138,810
  • 21
  • 208
  • 365
  • The second part got to the core of my misunderstanding; I didn't realize that the object type was established through use of placement `new`. – chbaker0 Aug 10 '16 at 02:27
  • 1
    So this would mean that using `malloc` then using resulting memory as an `int` or any other primitive other than `char` is also undefined unless you do placement new? – chbaker0 Aug 10 '16 at 02:29
  • 1
    @chbaker0 yeah it is not very well known; omitting the placement-new and doing strict aliasing violations appears to work most (or all) of the time so many people just do that either unaware of the problem, or not concerned with the Standard. – M.M Aug 10 '16 at 02:30
  • 1
    According to the standard - that's correct, you must use placement-new on malloc'd space before writing into it. – M.M Aug 10 '16 at 02:30
  • Re the "end the lifetime" part - see P0137R1 for some tweaks (and a special case for arrays of unsigned chars) – T.C. Aug 13 '16 at 16:02
  • @T.C. yes, hopefully this can all be improved so that common constructs like we are discussing is actually made to be well-defined – M.M Aug 14 '16 at 04:41
  • "_The objects being accessed have type char_" No. You made that up. The code is fine. – curiousguy Jan 13 '17 at 17:44
  • @curiousguy `char storage[sizeof(int)];` creates an array of `char` objects. – M.M Jan 14 '17 at 06:30
  • @M.M And also every POD object that fits in this region of memory! – curiousguy Jan 14 '17 at 11:08
  • 2
    @curiousguy The standard does not support your claim . Really, it is preposterous to say that declaring a char array makes effectively an infinite number of objects. This thread is about what the standard actually says, not what you wish it says. – M.M Jan 14 '17 at 12:41
  • @M.M Please explain what is problematic with an infinity of objects at the same address, beside you not liking the idea. – curiousguy Nov 09 '17 at 07:00
  • 1) Why pass the address of `storage` as a `char*` (`storage/&storage[0]`) instead of a `char (*)[sizeof(int)]` (`&storage`) to the `new` operator? I assume it doesn't matter and is simply easier to write, or are there other nuances involved? 2) Since the lifetime of `storage` ended after placement new, accessing it would lead to UB, correct? – 303 Jan 22 '22 at 13:25
  • @303 the argument is converted to `void *` – M.M Jan 22 '22 at 22:53
  • Sorry for asking such a silly question... :/ – 303 Jan 22 '22 at 23:35
-1

The union construct might be useful here.

union is similar to struct, except that all of the elements of a union occupy the same area of storage.

They are, in other words, "different ways to view the same thing," just like FORTRAN's EQUIVALENCE declaration. Thus, for instance:

union {
  int   foo;
  float bar;
  char  bletch[8];
}

offers three entirely-different ways to consider the same area of storage. (The storage-size of a union is the size of its longest component.) foo, bar, and bletch are all synonyms for the same storage.

union is often used with typedef, as illustrated in this StackOverflow article: C: typedef union.

Community
  • 1
  • 1
Mike Robinson
  • 8,490
  • 5
  • 28
  • 41
  • The question has the C++ tag and according to http://stackoverflow.com/questions/25664848/unions-and-type-punning this is not legal in C++ (but it is in C). – Jerry Jeremiah Aug 10 '16 at 01:51
  • And this one http://stackoverflow.com/questions/28521188/type-punning-a-struct-in-c-and-c-via-a-union says the same thing. – Jerry Jeremiah Aug 10 '16 at 01:59
  • 1
    C++ does not allow type punning via unions . You can only read out of the most recently assigned-to member. – M.M Aug 10 '16 at 02:11
  • 1
    Oh, foo ... how right you are ... me bad. **:*(** ... (*"type punning"* ... somehow I hadn't heard this term before. But, I like it.) – Mike Robinson Aug 10 '16 at 02:37
  • @M.M "_You can only read out of the most recently assigned-to member_" That isn't true either. – curiousguy Jan 14 '17 at 13:17
-3
*ptr = 0;

writes to an int, so it is an access to int, with an lvalue of type int, so that part of the code is fine.

The cast is morally fine, but the C/C++ standard texts don't clearly describe casts, or pointers, or anything fundamental.

curiousguy
  • 8,038
  • 2
  • 40
  • 58
  • 1
    They do clearly describe it, you just seem to disagree with the implications of what the standard says so you pretend it doesn't say that. – M.M Jan 14 '17 at 12:44
  • @M.M "_They do clearly describe it_" I am please to learn that. Please see and answer my many questions regarding pointers: http://stackoverflow.com/q/32100245/963864 – curiousguy Jan 14 '17 at 14:46
  • @M.M The implication here that any use of union was undefined until recently is beyond silly. – curiousguy Nov 09 '17 at 07:00