1

You can determine the length of an array in C/C++ with the following:

int arr[5] {1,2,3,4,5};
int len = *(&arr + 1) - *&arr; //5

but i also know that

cout << "(&arr + 1): " << (&arr + 1) << endl; //0x22fe34
cout << "*(&arr + 1): " << *(&arr + 1) << endl;//0x22fe34
cout << "*&arr: " << *&arr << endl; //0x22fe20
cout << "&arr: " << &arr << endl;  //0x22fe20

which means that (&arr + 1) == *(&arr + 1) and *&arr == &arr, but if i do

int len = (&arr + 1) - &arr; //1

the result is 1, not 5. I'm totally confused here. And i think this is related to this

int arr[5] {1,2,3,4,5};
int* p = arr; 
int (*ptr)[5] = &arr; 

Here too arr == &arr, so why can't i do

int arr[5] {1,2,3,4,5};
int *p = &arr; //main.cpp|53|error: cannot convert 'int (*)[5]' to 'int*' in assignment
int (*ptr)[5] = arr; //main.cpp|56|error: cannot convert 'int [5]' to 'int (*)[5]' in assignment

Is there some c/c++ guru who can explain in plain english for me?? I think i understand conceptually, arr refers to the first element of the array whereas &arr refers to the array as a whole, but its the same address!! Is it maybe some compiler magic?

Alan Birtles
  • 32,622
  • 4
  • 31
  • 60
amateur
  • 37
  • 5
  • For C++ why all the effort to understand "C" style arrays. They're likely to cause bugs in your code, because the decay to a pointer and loose all size information. Just use `std::array values;` and then you can use `values.size()` to get the size. This also shows you you should be very specific about "C" or "C++" they are **different** languages so please tag only one. Note : `int *p = &arr;` should have been `int *p = &arr[0];` or `int* p = arr;` (and no arr doesn't refer to the array as a whole, it is just the address of the first value) – Pepijn Kramer Jun 27 '23 at 05:23
  • 2
    @PepijnKramer `arr` does refer to the whole array, its address may be the same as the first value but it's data type is an array, not a pointer to an element – Alan Birtles Jun 27 '23 at 05:34
  • 1
    @AlanBirtles I know the compiler is aware, but runtime it is just a pointer. – Pepijn Kramer Jun 27 '23 at 05:55
  • @PepijnKramer Agree on the `std::array` part, but I dare to contradict the remainder – understanding arrays and pointers is still a good thing for two reasons: 1. One might have to deal with legacy code that still uses them. 2. One might have to interfere with some library with C interface. Writing custom containers might yet be another relevant topic, but that's usually not a coder's daily bread... – Aconcagua Jun 27 '23 at 06:03
  • @Pepijn Kramer Yes you are right arr is just an address, but there is a difference between arr and &arr!! int len1 = *(arr + 1) - *arr; //1 but int len1 = *(&arr + 1) - *&arr; //5 WHYYYY?? – amateur Jun 27 '23 at 06:06
  • @Pepijn Kramer i know about std::array, std::vector but i want to learn about pointers, can i??? – amateur Jun 27 '23 at 06:09
  • 2
    *"all the effort to understand "C" style arrays"* Even if you don't want to use them ([which is arguable by itself](https://quuxplusone.github.io/blog/2022/10/16/prefer-core-over-library/)), would you consider somebody a professional if they don't understand how they work? I wouldn't. – HolyBlackCat Jun 27 '23 at 06:09
  • @amateur Sure you can :) It was just not clear from your question, and there are other people reading this too. And yes my answer is a bit opiniated, knowing lower level stuff is good (but IMO it shouldn't be taught as first thing). And indeed this knowledge is valuable when writing libraries. So to all of you I stand corrected ;) – Pepijn Kramer Jun 27 '23 at 06:13
  • *'Why can't I do `int* p = &arr`* – because they are incompatible pointers! This is just one situation where two pointers of different types can share the same address: `struct S { int n; } s; struct S* p = &s.n;` or `union { int n; double x; } u; int* p = &u.x;` – both assignments won't work due to the same reason even though addresses would be identical. – Aconcagua Jun 27 '23 at 06:14
  • @HolyBlackCat I agree with you. I think many of the so called professionals don't really understand how pointers work, that's why they tell you to use vectors or std::array. Instead i want to understand pointers, after all they are a unique feature of C, and yes c++ too!! – amateur Jun 27 '23 at 06:17
  • @Pepijn Kramer "They're likely to cause bugs in your code" And you know why? Because a few know how they really work. Because if you know how they work you can write performant and bug free programs!! – amateur Jun 27 '23 at 06:23
  • 2
    `*(&arr + 1)` has undefined behavior, at least in C++. You are trying to dereference a one-past-the-object pointer. – user17732522 Jun 27 '23 at 06:41
  • 1
    @user17732522 It has undefined behaviour in both C and C++, for the same reason. – Peter Jun 27 '23 at 06:50
  • Addresses have a value and a type. When you output with `cout` for example you're only seeing the value and not the type. – M.M Jun 27 '23 at 06:57
  • @amateur Based on my 30+ experience with (very) large code bases. They cause bugs because the array and its size must be passed separately (so there are two sources of truth). So at some point someone is going to make them differ (don't ask me how but they manage) – Pepijn Kramer Jun 27 '23 at 08:07
  • 2
    @amateur *I think many of the so called professionals don't really understand how pointers work* -- I can't name you a single programmer that I've worked with, listened to, etc. who didn't know how pointers worked. Of course they know how they work -- they are not going to be bogged down with superfluous things such as your "array length trick" -- they have actual real software to develop. – PaulMcKenzie Jun 27 '23 at 08:08
  • *I'm totally confused here.* It appears you do not really understand how pointers work. In particular, the relationship between a pointer and it's **type**, and pointer decay of an an array. `arr` and `&arr` have the same pointer value, but they do not have the same **type**. – Eljay Jun 27 '23 at 11:43

2 Answers2

5

They do refer to the same address but they are different data types. Arithmetic on pointers gives you the number of elements of that datatype between the two pointers.

So if you take the difference between two int * you get the number of ints between them, if you take the difference between two int (*)[5] then you get the number of 5 element arrays between them.

So your original trick works by &arr + 1 being one whole array after arr as it was performed on a pointer to an array. This is then dereferenced to get you back to int* and *&arr is subtracted which is also int* after the reference and dereference.

It's much simpler to just use std::size to get the number of elements in an array or, if not use the more traditional sizeof(arr)/sizeof(arr[0]) which doesn't rely on any special tricks and is much more readable.

Alan Birtles
  • 32,622
  • 4
  • 31
  • 60
  • int len1 = *(arr + 1) - *arr; //1 int len1 = *(&arr + 1) - *&arr; //5 Why is that? Adding '&' changing everything, but arr == &arr. I dont understand.... – amateur Jun 27 '23 at 06:12
  • '&' is just the address operator, right? It has nothing to do with type. So why arr != &arr? After all they are the same value!! The same address!! – amateur Jun 27 '23 at 06:15
  • They have the same value, but they are different data types, pointer arithmetic is based on the data type otherwise it'd always return the number of bytes which is less useful – Alan Birtles Jun 27 '23 at 06:31
  • So please explain to me why adding '&' to 'arr' in the code int len = *(arr + 1) - *arr; //1 changes the result from 1 to 5. '&' has nothing to do with pointer arithmetic, it's just the address operator. Why adding it to the code above gives me 5 as a result? – amateur Jun 27 '23 at 06:38
  • Because it changes the data type from `int*` to an array pointer – Alan Birtles Jun 27 '23 at 06:40
  • @amateur - Yes, `&` (in this context, at least) is the address-of operator. But the type of the pointer it produces is based on the type of its operand. In your case, `arr` is an array of five int (`int [5]`) so the type of `&arr` is `int (*)[5]`. Which is why `&arr + 1` is also of type `int (*)[5]` (which is distinct from `int *`) and points at a (non-existent) array of five `int`, immediately past the end of `arr`. The fact that `&arr + 1` points at a non-existent array also causes the expression `*(&arr + 1)` to have undefined behaviour. – Peter Jun 27 '23 at 06:56
  • @amateur You might read (though unprecise, not standard conform) pointer difference `p - q` as `(reinterpret_cast(p) - reinterpret_cast(q))/static_cast(sizeof(*p))`, and together with Peter's comment and with `p` and `q` of type `int(*)[5]` you get `sizeof(*p) == sizeof(int[5])`; now guess, if you have a pointer to array and another one past the end and divide the difference by size of the array, what do you get??? – Aconcagua Jun 27 '23 at 07:01
  • *'The original trick works'* – actually it doesn't, it just *seems* to work, but relies on undefined behaviour for dereferencing the one-past-the-end pointer. – Aconcagua Jun 27 '23 at 07:12
  • nitpick: the last paragraph can be misunderstood to suggest prefering `sizeof` to `std::size`, because it would be more readable – 463035818_is_not_an_ai Jun 27 '23 at 07:46
3

Explaining the "length trick"

int arr[5] {1,2,3,4,5};
int len = *(&arr + 1) - *&arr; //5

&arr is the address of the array, of type int(*)[5]. &arr + 1 is a one-past-the-end pointer for this array. Dereferencing it results in int[5], which is implicitly convertible to int* (array-to-pointer conversion). The result of this expression is a one-past-the-end pointer for the elements of the array, of type int*. We can subtract this from *&arr or just arr to obtain the length of the array.

// shorter version of the trick above
int len = *(&arr + 1) - arr;

Note that both versions dereference a one-past-the-end pointer, so they are undefined behavior! You're just seeing a "works on my machine" effect.

There are much more readable and correct ways to obtain the length of an array, such as:

  • std::size(arr) (since C++17)
  • sizeof(arr) / sizeof(*arr) (C-style, avoid if possible)

Other Questions

which means that (&arr + 1) == *(&arr + 1) and *&arr == &arr, but if i do

int len = (&arr + 1) - &arr; //1

the result is 1, not 5. I'm totally confused here.

Both of these equations would fail to compile, you cannot say that *&arr == &arr. Note that a pointer to an array and a pointer to the first element of the array represent the same address, but they are different pointers. Performing + 1 on them will take differently sized steps depending on the type of the pointer.

The result is 1 because one is a pointer to the array arr, and one is a one-past-the-end pointer to the array arr. The difference between them is obviously 1, because they're one step apart. Converting to an int* is crucial for the original trick to work.

int* p = arr; 
int (*ptr)[5] = &arr; 

Here, the first line is performing array-to-pointer conversion implicitly, and the second line is taking the address of the array. These two pointers represent the same address, but they have a different type and cannot be compared or converted between one another.

int *p = &arr;       // error
int (*ptr)[5] = arr; // error

The first line attempts to convert an int(*)[5] to an int* implicitly, which is not allowed. The second line tries to do the opposite, which is also not allowed. Once again, the original trick works because we are dereferencing the pointer to the array, which gives us an int[5], which can be converted to int*:

int (*ptr)[5] = &arr;
int *p = *ptr;
Jan Schultke
  • 17,446
  • 6
  • 47
  • 96
  • *'This works, too'* – actually it doesn't – just as the original form doesn't either, both just *seem* to work, but rely on undefined behaviour for dereferencing a one-past-the-end pointer, see comments to question. – Aconcagua Jun 27 '23 at 07:08
  • @Aconcagua you're right, thanks for pointing that out. I've updated my answer accordingly. https://eel.is/c++draft/expr.unary.op#1 It's interesting that even though we don't perform a value computation, the pointer must still be a pointer to an object when dereferencing, not a one-past-the-end pointer. – Jan Schultke Jun 27 '23 at 08:19
  • I think i' m starting to get it, though my head is still spinning ahahah, anyway thank you to you and all the other people who answered my question – amateur Jun 27 '23 at 08:37