Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is `*((*(&array + 1)) - 1)` safe to use to get the last element of an automatic array?

Suppose I want to get the last element of an automatic array whose size is unknown. I know that I can make use of the sizeof operator to get the size of the array and get the last element accordingly.

Is using *((*(&array + 1)) - 1) safe?

Like:

char array[SOME_SIZE] = { ... };
printf("Last element = %c", *((*(&array + 1)) - 1));
int array[SOME_SIZE] = { ... };
printf("Last element = %d", *((*(&array + 1)) - 1));

etc

like image 847
Spikatrix Avatar asked Sep 12 '15 09:09

Spikatrix


People also ask

What does an asterisk (*) indicate?

What is an asterisk? An asterisk is a star-shaped symbol (*) that has a few uses in writing. It is most commonly used to signal a footnote, but it is sometimes also used to clarify a statement or to censor inappropriate language.

What is a * used for?

An asterisk in a math formula In a math formula, an asterisk is used to represent a multiplication (times).

What is * used for in writing?

When you're writing something and need to add a quick footnote, an easy way to mark the place where you want to include the extra comment is to use an asterisk, a star-shaped symbol. An asterisk is a punctuation mark that you can use to note something in writing, or to stand in for something you've left out.

What is the star key called?

The asterisk (/ˈæst(ə)rɪsk/ *), from Late Latin asteriscus, from Ancient Greek ἀστερίσκος, asteriskos, "little star", is a typographical symbol. It is so called because it resembles a conventional image of a heraldic star. *

What is the difference between “was” and “were?

Well, here it is! See how well you can differentiate between the uses of "was" vs. "were" in this quiz. “Was” is used for the indicative past tense of “to be,” and “were” is only used for the subjunctive past tense. as is. as 1 (def. 23).

What is the difference between is and are?

Is vs. Are. When deciding whether to use is or are, look at whether the noun is plural or singular. If the noun is singular, use is. If it is plural or there is more than one noun, use are. The cat is eating all of his food. The cats are eating all of their food. The cat and the dog are eating as fast as they can.

How do you know when to use is or are?

When deciding whether to use is or are, look at whether the noun is plural or singular. If the noun is singular, use is. If it is plural or there is more than one noun, use are. The cat is eating all of his food.


4 Answers

No, it is not.

&array is of type pointer to char[SOME_SIZE] (in the first example given). This means &array + 1 points to memory immediately past the end of array. Dereferencing that (as in (*(&array+1)) gives undefined behaviour.

No need to analyse further. Once there is any part of an expression that gives undefined behaviour, the whole expression does.

like image 121
Peter Avatar answered Oct 24 '22 11:10

Peter


I don't think it is safe.

From the standard as @dasblinkenlight quoted in his answer (now removed) there is also something I would like to add:

C99 Section 6.5.6.8 -

[...]
if the expression P points to the last element of an array object, the expression (P)+1 points [...]
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 as it says , we should not do this *(&array + 1) as it will go one past the last element of array and so * should not be used.

As also it is well known that dereferencing pointers pointing to an unauthorized memory location leads to undefined behaviour .

like image 22
ameyCU Avatar answered Oct 24 '22 10:10

ameyCU


I believe it's undefined behavior for the reasons Peter mentions in his answer.

There is a huge debate going on about *(&array + 1). On the one hand, dereferencing &array + 1 seems to be legal because it's only changing the type from T (*)[] back to T [], but on the other hand, it's still a pointer to uninitialized, unused and unallocated memory.

My answer relies on the following:

C99 6.5.6.7 (Semantics of additive operators)

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.

Since &array is not a pointer to an object that is an element of an array, then according to this, it means that the code is equivalent to:

char array_equiv[1][SOME_SIZE] = { ... };
/* ... */
printf("Last element = %c", *((*(&array_equiv[0] + 1)) - 1));

That is, &array is a pointer to an array of 10 chars, so it behaves the same as a pointer to the first element of an array of length 1 where each element is an array of 10 chars.

Now, that together with the clause that follows (already mentioned in other answers; this exact excerpt is blatantly stolen from ameyCU's answer):

C99 Section 6.5.6.8 -

[...]
if the expression P points to the last element of an array object, the expression (P)+1 points [...]
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.

Makes it pretty clear that it is UB: it's equivalent to dereferencing a pointer that points one past the last element of array_equiv.

Yes, in real world, it probably works, as in reality the original code doesn't really dereference a memory location, it's mostly a type conversion from T (*)[] to T [], but I'm pretty sure that from a strict standard-compliance point of view, it is undefined behavior.

like image 13
Filipe Gonçalves Avatar answered Oct 24 '22 09:10

Filipe Gonçalves


It is probably safe, but there are some caveats.

Suppose we have

T array[LEN];

Then &array is of type T(*)[LEN].

Next, &array + 1 is again of type T(*)[LEN], pointing just past the end of the original array.

Next, *(&array + 1) is of type T[LEN], which may be implicitly converted to T*, still pointing just past the end of the original array. (So we did NOT dereference an invalid memory location: the * operator is not evaluated).

Next, *(&array + 1) - 1 is of type T*, pointing at the last array location.

Finally, we dereference this (which is legitimate if the array length is not zero): *(*(&array + 1) - 1) gives the last array element, a value of type T.

Note that the only time we actually dereference a pointer is in this last step.

Now, the potential caveats.

First, *(&array + 1) formally appears like an attempt to dereference a pointer that points to an invalid memory location. But it really isn't. That's the nature of array pointers: this formal dereference only changes the type of the pointer, does not actually result in an attempt to retrieve value from the referenced location. That is, array is of type T[LEN] but it may be implicitly converted to type &T, pointing to the first element of the array; &array is a pointer to type T[LEN], pointing at the beginning of the array; *(&array+1) is again of type T[LEN] which may be implicitly converted to type &T. At no point is a pointer actually dereferenced.

Second, &array + 1 may in fact be an invalid address, but it really isn't: My C++11 reference manual tells me explicitly that "Taking a pointer to the element one beyond the end of an array is guaranteed to work", and a similar statement is also made in K&R, so I believe it has always been standard behavior.

Finally, in case of a zero-length array, the expression dereferences the memory location just before the array, which may be unallocated/invalid. But this issue would also arise if one used a more conventional approach using sizeof() without testing for nonzero length first.

In short, I do not believe there is anything undefined or implementation-dependent about this expression's behavior.

like image 2
Viktor Toth Avatar answered Oct 24 '22 09:10

Viktor Toth