1

The array does not have any assigned values, but when subtracting the values in the array, the z value becomes 20.
Can anyone explain this concept?
Any help would be appreciated.

#include <stdio.h>

int main()
{
    int a[10][20][30][40];
    int z = a[6] - a[5];

    printf("%d\n", z); // z value is 20. why?
}
Creek
  • 207
  • 2
  • 12
  • why shouldn't it be 20? the array can have any old random numbers in it because you didn't say you wanted specific numbers – user253751 Feb 16 '23 at 14:56
  • 3
    But you are not subtracting values. – user253751 Feb 16 '23 at 14:56
  • The array is uninitialized and could be filled with anything. It just so happens that `a[6] - a[5] = 20`, but that is absolutely no guarantee. – B Remmelzwaal Feb 16 '23 at 14:57
  • @user253751 I beg to differ. It has 20 because of the way jagged arrays are implemented. The first dim of `a` will hold pointers to the second dim, which holds pointers to the third dim, which hold pointers to the fourth dim which contain the actual data. – norok2 Feb 16 '23 at 14:59
  • 13
    It's pointer subtraction, not value subtraction. – Ian Abbott Feb 16 '23 at 15:00
  • 1
    I used the local vsCode C compiler and obtained a value of 20 for 'z'. I also tried an online C compiler, which also returned 20. – Creek Feb 16 '23 at 15:00
  • 1
    @norok2 that's not how multidimentional arrays in C work. – Yakov Galka Feb 16 '23 at 15:00
  • 1
    @norok2. The example code does not demonstrate "jagged" arrays. There are no pointers stored within. – John Bollinger Feb 16 '23 at 15:01
  • 2
    This code behavior is perfectly well defined. `a[6]` and `a[5]` are *arrays* by themselves, so subtracting them is subject of pointer arithmetic. – Eugene Sh. Feb 16 '23 at 15:01
  • 1
    @norok2 there is no jagged array in this code. Those are true native arrays of arrays (of arrays of arrays, in fact). This is basic, but no-so-subtle, pointer arithmetic. – WhozCraig Feb 16 '23 at 15:02
  • sorry I meant [Iliffe vectors](https://en.wikipedia.org/wiki/Iliffe_vector), I just use them only for jagged arrays but you are right that they are not. – norok2 Feb 16 '23 at 15:07
  • @EugeneSh. is it well defined though? is it guaranteed that `&a[6][0] == &a[5][20]`? – Yakov Galka Feb 16 '23 at 15:17
  • @YakovGalka Well, I guess it boils down to if it is guaranteed that `sizeof( type a[5*20] ) == sizeof (type a[5][20])`. You made me doubt though – Eugene Sh. Feb 16 '23 at 15:23
  • 1
    @YakovGalka Yes, `&a[6][0] == &a[5][20]` always true with `int a[10][20][30][40];`. – chux - Reinstate Monica Feb 16 '23 at 15:45
  • 1
    @Creek Try changing `int a[10][20][30][40];` to `int a[10][7][30][40];`. Is the output now 7? – chux - Reinstate Monica Feb 16 '23 at 15:46
  • @chux-ReinstateMonica what in the standard implies that though? that `sizeof(T[n]) == sizeof(T)*n`? – Yakov Galka Feb 16 '23 at 15:56
  • @YakovGalka It is somewhat involved. Post as a SO question if you like - yet their might be a duplicate. – chux - Reinstate Monica Feb 16 '23 at 16:01
  • @YakovGalka Note that `intptr_t` is a common yet optional type. So [`(intptr_t)(p+i) - (intptr_t)p == sizeof(*p)*i`](https://stackoverflow.com/questions/75474037/how-does-this-array-subtraction-work-without-assignment?noredirect=1#comment133165181_75474112) **not** completely guaranteed. Further, math like `-` only makes sense if one assumes that converting a pointer to an `intptr_t` results in a linear memory model. That is certainly not guaranteed. – chux - Reinstate Monica Feb 16 '23 at 16:05

4 Answers4

4

I believe this is technically undefined behavior. At least in C++, probably in C as well.

In practice it will almost certainly print 20, as explained by other answers, but it violates C11 6.5.6 Additive operators / 9 (and in C++: [expr.add]/4.2):

When two pointers are subtracted, both shall point to elements of the same array object, or one past the last element of the array object ...

Your pointers point to two different arrays: to the first elements of a[6] and a[5] respectively.

One could argue that the first element of a[6] is one past the last element of a[5], but at least in C++, it's not the case, even though they have the same value: notoriously, how a pointer was derived does matter (that's why things like std::launder exist).

A non-UB way of computing the same thing would be:

int z = ((uintptr_t)a[6] - (uintptr_t)a[5]) / sizeof(a[0][0]);

This is technically implementation-defined (because of pointer-to-uintptr_t conversion), but should have an even smaller chance of breaking.

Yakov Galka
  • 70,775
  • 16
  • 139
  • 220
HolyBlackCat
  • 78,603
  • 9
  • 131
  • 207
  • Your point about "pointers point to two different arrays:" is interesting though - they are different arrays _within_ a larger common array, hmmm. – chux - Reinstate Monica Feb 16 '23 at 16:27
  • @chux-ReinstateMonica Mhm, but for the common array to matter, `-` would need to have custom wording for multidimensional arrays, and there's none. – HolyBlackCat Feb 16 '23 at 16:34
  • Good fix, not yet in agreement with _even smaller chance of breaking_. "would need to have custom wording for multidimensional arrays" --> other possibilities exist. I suspect deep in the bowels of the C spec, the fact that array `a[6]` and `a[5]` are themselves part of the larger common array makes `a[6] - a[5]` well defined. Just getting too deep for me today. – chux - Reinstate Monica Feb 16 '23 at 16:38
  • Would `int z = ((char *)a[6] - (char *)a[5]) / sizeof(a[0][0]);` be valid? `a[6]` and `a[5]` are different array objects but both are part of the same, larger object, so it might be OK. If it is valid, it avoids the implementation-defined nature of the pointer-to-`uintptr_t` conversion. – Ian Abbott Feb 16 '23 at 18:27
  • @IanAbbott It's moot. I think it's better than what OP does, and that's probably what I would do, but it could still be undefined. [`std::launder`](https://en.cppreference.com/w/cpp/utility/launder) has some interesting wording for "reachibility through a pointer", but I don't remember if it's clarified anywhere else in the standard. Shouldn't create any problems in the real world. – HolyBlackCat Feb 16 '23 at 18:31
  • 1
    @HolyBlackCat I removed the `sizeof(int) == 4` remark as it has no bearing on the result of pointer subtraction. – Yakov Galka Feb 17 '23 at 18:02
2

Because it is pointer arithmetics.

a[6] is 20 elements of type int[30][40] from a[5].

It happens because arrays decay to pointers

0___________
  • 60,014
  • 4
  • 34
  • 74
  • 1
    I must admit that I'm a bit hazy about whether this subtraction is allowed. Even though it is obvious that `&a[6][0]` and `&a[5][20]` are the same value, does that make `&a[6][0] - &a[5][0]` just as legal as `&a[5][20] - &a[5][0]`? – Ian Abbott Feb 16 '23 at 15:14
  • @IanAbbott That subtraction is legal. `&a[5][20]` is illegal and any expression using it invokes UB – 0___________ Feb 16 '23 at 15:16
  • But does that also make `a[6] - a[4]` (i.e. `&a[6][0] - &a[4][0]`) legal? I think we are on shaky ground here. – Ian Abbott Feb 16 '23 at 15:20
  • @IanAbbott I'm with you here that this is not at all obvious; but I think it follows from `sizeof(T[n]) == sizeof(T)*n` and `(intptr_t)(p+i) - (intptr_t)p == sizeof(*p)*i`, which I believe are guaranteed. – Yakov Galka Feb 16 '23 at 15:22
  • @IanAbbott no, it is ok – 0___________ Feb 16 '23 at 15:22
  • `&a[5][20]` is not illegal. It is the same pointer as `a[5] + 20` (or `&a[5][0] + 20`). – Ian Abbott Feb 16 '23 at 15:23
  • No , the second index is outside the range. It is UB even if it is inside the same array. – 0___________ Feb 16 '23 at 15:24
  • Are you saying that `&a[5][20]` is illegal but `a[5] + 20` and `&a[5][0] + 20` are OK? – Ian Abbott Feb 16 '23 at 15:27
  • 2
    @0___________, (@Ian Abbot) Why assert `&a[5][20]` is bad? The 20 index is one-past the array end - an allowable exception to array calculations. Forming the address of an array is OK. `printf("%p\n", (void *) &a[5][20]);` is fine. – chux - Reinstate Monica Feb 16 '23 at 15:40
  • @YakovGalka I don't there are any such guarantees for `intptr_t`, but perhaps replacing `intptr_t` with `char *` is OK (i.e. `(char *)(p+i) - (char *)p == sizeof(*p)*i`) because any object can be treated as an array of `char` and the two `char` pointers are both pointing to bytes inside the object `a` in this case. – Ian Abbott Feb 16 '23 at 16:00
2

The array does not have any assigned values, but when subtracting the values in the array, the z value becomes 20.

You are not subtracting any values in the array. Not int values, anyway.

This declaration ...

int a[10][20][30][40];

... says that a is an array of 10 (arrays of 20 (arrays of 30 (arrays of 40 int))).

Therefore a[5] and a[6] are each an array of 20 (arrays of 30 (arrays of 40 int)).

When array-valued expressions appear as operands in most kinds of expressions, they are automatically converted to pointers to their first elements, so a[6] - a[5] is equivalent to &a[6][0] - &a[5][0].

Pointer differences are computed in units the size of the pointed-to type, so it doesn't actually matter for this purpose what the type of a[5][0] and a[6][0] is, or what int values are stored in the a[i][j][k][l]. The value of the difference is fully determined by the second dimension of a, which is 20.

John Bollinger
  • 160,171
  • 8
  • 81
  • 157
2

z value is 20. why?

Given int a[10][20][30][40];, a[6] indicates an array, an array of 20 elements.

When used in subtraction like a[6] - a[5], the arrays are converted to the address of the type of their first elements. So it is like &a[6][0] - &a[5][0]. &a[6][0] is a pointer. The pointer type they share is not so important here.

Pointer subtraction returns the difference in elements as an integer of type ptrdiff_t. There are 20 elements between &a[6][0] and &a[5][0].

When two pointers are subtracted, both shall point to elements of the same array object, or one past the last element of the array object; the result is the difference of the subscripts of the two array elements. The size of the result is implementation-defined, and its type (a signed integer type) is ptrdiff_t ... C17dr § 6.5.6 9

Code then assigns a 20 (of type ptrdiff_t) to z, an int.

Ian Abbott
  • 15,083
  • 19
  • 33
chux - Reinstate Monica
  • 143,097
  • 13
  • 135
  • 256