6

C has no elementsof keyword to get the element count of an array. So this is commonly replaced by calculateing sizeof(Array)/sizeof(Array[0]) but this needs repeating the array variable name. 1[&Array] is the pointer to the first element right after the array, so you could use:

int myArray[12] = {1,2,3};  
int *pElement;

for (pElement = myArray; pElement < 1[&myArray]; pElement++)
{
  ...
}

to replace:

for (pElement = myArray; pElement < &myArray[sizeof(myArray)/sizeof(myArray[0])]; pElement++)
{
  ...
}

do you think this is too obfuscated?

Wai Ha Lee
  • 8,598
  • 83
  • 57
  • 92
notan
  • 359
  • 1
  • 10
  • 4
    Just define a macro _CountElements_ like this: `#define CountElements(Array) sizeof(Array)/sizeof(Array[0])`. Then you can simply use `int nbofelements = CountElements(myArray);`. – Jabberwocky Feb 16 '18 at 08:16
  • Change the array element type to `char` and notice that you can't see the bug anymore. – Hans Passant Feb 16 '18 at 08:16
  • I can see a whole slew of confusion for this and -- you are only 1-byte away from *Undefined Behavior* and saved only because C allows access of a pointer 1-past the end of an array. But hey, if you like "Livin on the edge", that's about as close as it gets. – David C. Rankin Feb 16 '18 at 08:27
  • 1
    @DavidC.Rankin why would this particular pointer past the end be more scary than any other? – Quentin Feb 16 '18 at 08:47
  • Because generally the last element of the array is used. The C-standard does provide that requesting the address of 1-past the array is well-defined, but this may or may not hold true for objects with dynamic storage. So tailoring a comparison scheme that may work "sometimes" (as long as it is an array) seems like a recipe for it to be applied where it is not well-defined if you are not careful. See [C11 - §6.5.6 Additive operators (p8)](http://port70.net/~nsz/c/c11/n1570.html#6.5.6p8) – David C. Rankin Feb 16 '18 at 08:51
  • @David C. Rankin: Yes, it is legal to request the past-the-end address as `array + count`. It is also legal to do so by `&array[count]`, but in this case only because the `&` and the implied `*` "annihilate" each other (a later addition to the language spec). But what about `(&array)[1]`? In this case we obtain the desired pointer by applying "array type decay" to a non-existent array. I understand that it is OK by common sense. But from the formal and pedantic point of view, is this legal? I don't immediately see it. – AnT stands with Russia Feb 16 '18 at 09:07
  • @AnT That is an open question in my mind. We are no longer dealing with the array itself, but rather a quasi-aggregate type derived from particle-physics of both addressing and deferencing the array using operator presence `(&array)[1]` to then hack out an address one past the valid array. I can tell you I don't know the ramifications for all of those pieces, but it seems like buying quite a bit of uncertainty for nothing gained. – David C. Rankin Feb 16 '18 at 09:17

4 Answers4

5

1[&myArray] is non-obvious. I suggest that you use temporary variables:

size_t count = sizeof array / sizeof *array;
int * const end = &array[count];
for (pElement = myArray; pElement < end; pElement++)

Or rather just use standard index variable:

size_t count = sizeof array / sizeof *array;
for(size_t i=0; i<count; ++i) {
    int *pElement = &array[i];

What ever you do, use temporary variables, because you can name them descriptively. It will make reading the code much faster, without affecting runtime performance (unless compiler is braindead).

user694733
  • 15,208
  • 2
  • 42
  • 68
  • 1
    `int * const end = &myArray + 1;` gives a warning "initialization from incompatible pointer type". – notan Feb 16 '18 at 08:22
  • That's because the type for `&myarray` is `type (*)[nelem]` – David C. Rankin Feb 16 '18 at 08:23
  • @notan Whoops, I think you are right. Let me fix that. – user694733 Feb 16 '18 at 08:23
  • Just came around a bug where a star was missing, something like `size_t count = sizeof array / sizeof array'. But you are probably right 1[&array] is too obfuscated (non obvious). – notan Feb 16 '18 at 08:35
  • @notan Yes I agree that the `sizeof` trickery is not optimal either. Wrapping it in reusable macro like MichaelWalz suggested would at least reduce points of failure. – user694733 Feb 16 '18 at 08:41
  • To understand the OP's code, first you need to know that `a[5] == 5[a]`, see [this question](https://stackoverflow.com/questions/381542) for details. Then you need to know that `&myArray` has type `int(*)[12]`. So `1[&myArray]` is of type `int [12]` and points to the address that's just beyond the end of the array. And if you didn't understand that explanation, that's good, because IMO, this is way too obfuscated. And all of the confusion in this thread proves it. – user3386109 Feb 16 '18 at 08:52
  • @user3386109: Still, `1[&myArray]` is an lvalue of `int[12]` type that corresponds to an imaginary "past-the-end" array. I wonder if its is formally legal to let the array-to-pointer conversion happen to it. The `&1[&myArray]` is OK, since `&` and `*` annihilate each other. But what about `1[&myArray]`? – AnT stands with Russia Feb 16 '18 at 08:59
  • @AnT I see no reason why `1[&myArray]` would not decay from `int[12]` to `int *`. §6.3.2.1 p3 lists exceptions when decay doesn't happen, and none those should apply in this case. – user694733 Feb 16 '18 at 09:05
  • @AnT I don't have a good answer to that, so I defer to @ user694733's interpretation. Seems like that would be a good language-lawyer question in its own right. All I know is that the compiler seems perfectly content to do it, even with `-Weverything`. – user3386109 Feb 16 '18 at 09:07
  • @user694733 : Typewise, it would definitely decay. But is it legal to "let" (to "cause") a non-existent array to decay? Remember, that in C89/90, `&array[count]` was formally illegal, since it formally dereferenced the past-the-end pointer. Only C99 saved it by stating that `&*` pair shall "disappear" as if it wasn't there, thus turning `&array[count]` into `array + count`. The `(&array)[1]` form seems to also have some caveats, similar in nature. – AnT stands with Russia Feb 16 '18 at 09:10
  • @AnT I see your point now. I have to admit that I can't decipher standard enough to give definitive answer on that. But I guess it highlights why `1[&myArray]` should not be used. – user694733 Feb 16 '18 at 09:40
4

&myarray has type int(*)[12], so when you write 1[&myarray], which is equivalent to *((&myarray)+1), it is pointing to the element which is just after the last element of the array myarray. It's legal given that we can compare pointers pointing to same array elements or one past the last element of the array (given that they are of same type).

Notice that I won't say that you assign to int* to int(*)[] because there is a loss of type information which the compiler will complain about.

*(&myarray+1) - &myarray has type int(*)[12]. &myarray+1 is one past the last element. Dereferencing it we get the same value but with different type which is int[12] when comparing it decays into pointer to first element which is int* and pointing to the one past last element of the array.

machine_1
  • 4,266
  • 2
  • 21
  • 42
user2736738
  • 30,591
  • 5
  • 42
  • 56
  • Agreed, even though you typically think of the comparisons being between like pointers. (I'll let @coderredoc be the advocate for using this as a new style `:)` – David C. Rankin Feb 16 '18 at 08:28
  • @DavidC.Rankin.: The comparison is not illegal if they are of different type. So yes it's ok. Assigning to `char*` `char(*)[]` is something that we shouldn't do. Compiler will complain. – user2736738 Feb 16 '18 at 08:29
  • No, no, I get that, that's why I agreed with you, but you still do generally think about comparisons between like pointers (even though you can do the pointer arithmetic on addresses which are type agnostic) – David C. Rankin Feb 16 '18 at 08:31
  • That is what we are saying. – David C. Rankin Feb 16 '18 at 08:33
  • @coderredoc: I was wrong. I missed the fact that `[]` performs a dereference. Comparison with `&array + 1` would be wrong. But comparison with `1[&array]` (which is `*(&array + 1)`) is perfectly fine. – AnT stands with Russia Feb 16 '18 at 08:49
  • @AnT.: This is one of the trickiest question I see...infact I also getting weird while thinking. But I guess we get it now. The earlier answer was correct - rolling back to it. But I guess I will give it more thought – user2736738 Feb 16 '18 at 08:53
  • The tricky part is applying `*` to it, followed by immediate decay to pointer. Is this truly legal for a non-existent past-the-end array? – AnT stands with Russia Feb 16 '18 at 08:55
  • @AnT.: Comparing one past end of the array is surely legal. – user2736738 Feb 16 '18 at 08:55
  • @coderredoc: Comparing is clearly legal. The question is: is this method of forming such pointer legal? – AnT stands with Russia Feb 16 '18 at 09:04
  • @AnT.: I can't find any standard section denying that. – user2736738 Feb 16 '18 at 09:04
1

Whilst this suggestion may work, it's far from idiomatic C, and therefore may make your code harder to maintain.

Personally, I'd stick with a conventional ARRAY_SIZE macro:

#define ARRAY_SIZE(a) (sizeof (a) / sizeof (a)[0])

And then use it as

for (p = array;  p < array + ARRAY_SIZE(array);  ++p)
Toby Speight
  • 27,591
  • 48
  • 66
  • 103
  • Is there any (good) reason that you write `(a)[0]` instead of `a[0]`, or perhaps you meant `sizeof(a[0])`? – machine_1 Feb 16 '18 at 11:28
  • 1
    Just standard macro hygiene - especially since the precedence of `[]` is high. It would be possible (if unusual) to write `ARRAY_SIZE(*pa)`, for example (if `pa` is a pointer to an array). Note that the first use of `a` is also parenthesized, for much the same reason. – Toby Speight Feb 16 '18 at 11:33
  • Nontheless i prefere two macros, first something like your `#define ARRAY_SIZE(a)` and then, a macro `#define ENDOF(a) (&((a)[ARRAY_SIZE(a)]))` the `#define ENDOF(Array) (1[&(Array)])` was a joke and not production code, would never get through code review. – notan Feb 16 '18 at 13:01
1

Formally and pedantically, the 1[&array] approach is invalid. It causes undefined behavior.

The expression is equivalent to

(implicit array-to-pointer conversion) *(&array + 1)

This expression contains an application of unary * operator to a pointer that points to an "imaginary" past-the-end object of type int [12]. This application of * is formally evaluated and, per 6.5.6/8, produces undefined behavior

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.

On the other hand, the situation is very similar to the issue that we had in C89/90, where a + 12 was a valid way to form the past-the-end pointer, while at the same time &a[12] was considered undefined. &a[12] is equivalent to &*(a + 12), which also applies * to a past-the-end pointer and thus causes undefined behavior.

C99 legalized the &a[12] method by stating in 6.5.3.2/3 that &* combination should essentially "disappear" from the expression, meaning that * is not evaluated and undefined behavior is not triggered

Similarly, if the operand is the result of a [] operator, neither the & operator nor the unary * that is implied by the [] is evaluated and the result is as if the & operator were removed and the [] operator were changed to a + operator.

Our situation

(implicit array-to-pointer conversion) *(&array + 1)

is rather similar in essense. It would make sense if the language standard said that in the above context the array-to-pointer conversion and the unary * operator should "partially annihilate" each other, leaving behind just a simple implicit (int *) cast. But alas there's nothing like that in the language specification.

AnT stands with Russia
  • 312,472
  • 42
  • 525
  • 765
  • No formally the code is valid and behavior is fully defined. The unary * is dereferencing the pointer to array which is the pointer to the pointer to the first element, therefor after aplying the * we have still a pointer. In this case a pointer to the first element of the int [12] array right after the first int[12] array. The pointer past the end of the first array is never dereferenced therefor there is no undefined behaviour. (nonetheless the code is not obvious and therefore not to be recommended) – notan Feb 17 '18 at 09:06
  • @notan: "The unary * is dereferencing the pointer to array which is the pointer to the pointer to the first element" - this does not make any sense at all. Array is **not** a pointer. Pointer to array is **not** a "pointer to the pointer". The rest of your description is based on this strange assumption. It is incorrect. The result of unary `*` in this case is an *array* (`int [12]`), not a pointer. – AnT stands with Russia Feb 17 '18 at 14:52
  • Yes so far you are right. The result of the unary * is (int [12]). And the standard says that the use of an array results in the pointer to it's first element. – notan Feb 17 '18 at 19:11
  • @notan: Well, when standard says that "use of an array results in the pointer to it's first element", it refers to the well-known implicit array-to-pointer conversion AKA "array type decay" - exactly what I mention above in my answer. However, this conversion does not happen in such contexts as, for example, unary `&` or `sizeof`. Which is why `&array` is not a "pointer to pointer". – AnT stands with Russia Feb 17 '18 at 19:19
  • Yes &array is not exactly a pointer to a pointer but a pointer to an array of 12 int. Otherwise &array+1 would not point to the position after the array. The type of the result is array of int. An array of type expression is converted to pointer to first element. Therefor 1 [&array] is &array [sizeof array/sizeof array [0]]. – notan Feb 17 '18 at 20:48
  • @notan: One more time: It is perfectly legal to form an `int (*)[12]` pointer to the imaginary "array of 12 int" located beyond the original `myArray`. But that pointer can only be used in a number of very limited ways. The language specification explicitly prohibits you from applying the `*` operator to such a pointer. That's all there is to it. – AnT stands with Russia Jan 03 '19 at 20:30