Hacker News new | ask | show | jobs
by halayli 3461 days ago
this is undefined behavior. &arr + 1 can overflow. There's no guarantee &arr isn't near memory end boundary. &arr + 1 is converted at compile time to rbp - X where X is an integer determined by the compiler similarly to how sizeof works.

Basically ptr + integer requires the compiler to determine the sizeof ptr's type.

2 comments

this is undefined behavior. &arr + 1 can overflow

No. From 6.5.6 Additive operators:

7 For the purposes of these operators, a pointer to an object that is not an element of an array behaves the same as a pointer to the first element of an array of length one with the type of the object as its element type.

8 [...] if the expression P points to the last element of an array object, the expression (P)+1 points one past the last element of the array object [...] If both the pointer operand and the result point to elements of the same array object, or one past the last element of the array object, the evaluation shall not produce an overflow; otherwise, the behavior is undefined. If the result points one past the last element of the array object, it shall not be used as the operand of a unary * operator that is evaluated.

So &arr + 2 can overflow, and &arr + 1 cannot be dereferenced, but &arr + 1 shall not overflow and is not undefined behaviour.

But arr != &arr even though they have the same value. #8 applies to arr (P), but in the post OP is using &arr which is a ptr to array[x] and doesn't apply to it.
That's why I quoted paragraph 7: arr is not an element of an array, so &arr (being a pointer to an object which is not an element of an array) behaves like a pointer to the first element of an array of length one with the type of the arr as its element type.

So &arr behaves like it's a pointer to the start of int[5][1].

Yes &arr does behave like arr when it comes to ptr arithmetic but the compiler does not guarantee that &arr + 1 does not overflow. It only guarantees arr + 1. if you have a ptr from heap, ptr + 1 if not alloced previously is UB.

> If both the pointer operand and the result point to elements of the same array object, or one past the last element of the array object, the evaluation shall not produce an overflow;

this point doesn't apply to &arr + 1.

How so? If the array has five elements, you can pass &arr to a function that expects an int[5][], and that function certainly can build a pointer that points one past the last element.

Likewise, the compiler ensures that you can build &arr[5] and that is the same address as &arr+1. &arr+1 cannot overflow.

The compiler guarantees that arr + 1 doesn't overflow by making sure arr's address is small enough to not overflow when accessing one element past the array size. &arr + 1 is not one past the array you asked the compiler to allocate.

if you're on a 16bit system and you define char x[36], the compiler guarantees that x's address is not more than 65500. if you do &x + 1 then you'll overflow, x + 1 won't.

You can pass whatever you want to the functions and apply the operands you want and the compiler will happily comply with you. But when you pass it 65500 and add 72 to it, it's going to overflow.

Can't we declare pointer of type &arr, assign it there and be sure that it points to equivalent of array[1] of &arr? If yes, then is it logically possible to have UB on that?
You can define a pointer of type `int (*)[5]` and assign `(&arr)[1]` to it. That's fine, it's a pointer to the 5-element array just after the one we're sure is valid.

Dereferencing the pointer is UB, but you can create the pointer, assign it to a variable, etc.

I know it's not what you were trying to communicate, but actually "arr != &arr" is false.
So then I guess malloc can't return an allocation which actually goes to the end of the address space, but has to leave at least one extra byte to avoid overflow? That's pretty interesting, though I guess it certainly makes sense.

Edit: Also now that I think about it, I've written code that relied on that behavior...not sure if I'd heard it before and internalized and forgot it, or just was being foolish.

Technically, this needn't impact malloc, because dereferencing the "one past the end" address is still undefined. All you need is logic in your pointer arithmetic that essentially treats the past the end address as a special value (which normally would never need to be represented).
Not just pointer arithmetic; also pointer comparisons. One-past-the-end pointer must compare greater-than any other pointer into that array.

So an implementation that could stick an array at the very end of the address space, and do wraparound for one-past-the-end so that it's represented by all bits zero, would then need to special-case that zero value when performing any pointer comparisons.

Oh, I was assuming a runtime that didn't do wraparound. If you are doing wraparound, you already have a ton of special case work you'd have to do.

But yes, you'd need logic for pointer comparisons as well.

Wouldn't it be very unusual for an environment to not do wraparound? I mean, on assembler level, pointers are just integers, and pretty much everything does wraparound arithmetics on those these days.

But, so far as I can see, this case (allocating at the very end of address space) is the only one where wraparound would matter for pointers.

And what would be the other option? If it's saturation, then your one-past-the-end pointer for the array at the end of address space would compare equal to pointer to last element...

Nope, you have guarantees about checking the address of one element past the end of an array. Think of all the bugs you'd otherwise enjoy...