Is it legal to alias a char array through a pointer to int?
Asked Answered
B

3

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.

Blinker answered 9/8, 2016 at 23:39 Comment(7)
Can the people downvoting leave comments please?Blinker
I don't know if you can do this, but it is defined by the standard to access a variable in a aligned_storage in the way you are trying toChamomile
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.Nudity
@Blinker 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 hereChamomile
@user2079303 good point, I'll add in my research that left me conflictedBlinker
@Chamomile Thanks, I noted the extistence of that in my editBlinker
"Dynamic type" is useful for classes, not scalar.Messing
B
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

Bathtub answered 10/8, 2016 at 2:20 Comment(14)
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.Blinker
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?Blinker
@Blinker 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.Bathtub
According to the standard - that's correct, you must use placement-new on malloc'd space before writing into it.Bathtub
Re the "end the lifetime" part - see P0137R1 for some tweaks (and a special case for arrays of unsigned chars)Opposable
@Opposable yes, hopefully this can all be improved so that common constructs like we are discussing is actually made to be well-definedBathtub
"The objects being accessed have type char" No. You made that up. The code is fine.Messing
@Messing char storage[sizeof(int)]; creates an array of char objects.Bathtub
@Bathtub And also every POD object that fits in this region of memory!Messing
@Messing 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.Bathtub
@Bathtub Please explain what is problematic with an infinity of objects at the same address, beside you not liking the idea.Messing
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?Semicentennial
@Semicentennial the argument is converted to void *Bathtub
Sorry for asking such a silly question... :/Semicentennial
S
-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.

Schmid answered 10/8, 2016 at 1:41 Comment(5)
The question has the C++ tag and according to https://mcmap.net/q/23993/-is-it-allowed-to-use-unions-for-type-punning-and-if-not-why this is not legal in C++ (but it is in C).Vere
And this one #28521688 says the same thing.Vere
C++ does not allow type punning via unions . You can only read out of the most recently assigned-to member.Bathtub
Oh, foo ... how right you are ... me bad. :*( ... ("type punning" ... somehow I hadn't heard this term before. But, I like it.)Schmid
@Bathtub "You can only read out of the most recently assigned-to member" That isn't true either.Messing
M
-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.

Messing answered 13/1, 2017 at 17:46 Comment(3)
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.Bathtub
@Bathtub "They do clearly describe it" I am please to learn that. Please see and answer my many questions regarding pointers: https://mcmap.net/q/668195/-dereferencing-a-50-out-of-bound-pointer-array-of-array/963864Messing
@Bathtub The implication here that any use of union was undefined until recently is beyond silly.Messing

© 2022 - 2024 — McMap. All rights reserved.