2
#include <stdio.h>
int main()
{
int a[3][2]={10,11,12,13,14,15};
printf("%d %d",a[2][-2],a[2][-1]);
}

I get the output as 12,13 but I don't understand why? Does a pointer exist to a multidimensional array like a[1] is same as *(a+1) in single dimension??

Vlad from Moscow
  • 301,070
  • 26
  • 186
  • 335
Vishal
  • 59
  • 2
  • 9
  • 5
    Negative indexes, in your case yield undefined behavior. You reach outside the array boundary. – StoryTeller - Unslander Monica Apr 14 '17 at 19:33
  • Why do you use negative indices? – ForceBru Apr 14 '17 at 19:34
  • Also why do you declare a 2D array but make it equal to a 1D array? – TheQAGuy Apr 14 '17 at 19:34
  • Yeah, this is undefined. It could be explained, with references to assembly code, but explaining it is pointless because different compilers could do this differently, even crashing the program or reading entirely different memory areas. – Zan Lynx Apr 14 '17 at 19:35
  • Draw the array out on squared paper, where each square is an element in the array. Then think about that for any array or pointer `a` and index `i` the expression `a[i]` is equal to `*(a + i)`. Now think about what a negative index means in that context. – Some programmer dude Apr 14 '17 at 19:36
  • 1
    @StoryTeller It's not undefined in this case, because 2-dimensional arrays are stored in row-major order. A negative second index goes to a previous row, and it's still inside the array boundary. – Barmar Apr 14 '17 at 19:37
  • For example of what I mean imagine a segmented architecture with 8 segment bits and 16 address bits, if the array element was big enough the compiler would have to place one element per segment, and a negative address would not be the previous segment, but it would wrap around to the end of the current segment. – Zan Lynx Apr 14 '17 at 19:38
  • 1
    @ZanLynx I don't think that's allowed for 2-dimensional arrays. They have to be contiguous (or it has to act like they are). – Barmar Apr 14 '17 at 19:39
  • @Barmar of course it would "act like it" as long as you use defined C code. – Zan Lynx Apr 14 '17 at 19:40
  • @ZanLynx And there's nothing undefined in this C code, since he's not going outside the array bounds. – Barmar Apr 14 '17 at 19:40
  • 5
    @Barmar - According to the C standard. `a[2]` is an expression that yields an array `a[2][-2]` reaches out side the bounds of that array. undefined behavior. The fact `a[2]` is part of an array of arrays, doesn't mean `a[1]` and `a[2]` are the same array. – StoryTeller - Unslander Monica Apr 14 '17 at 19:40
  • @StoryTeller isn't a 2-D array guaranteed to be contiguous? This is not an array of pointers to 1-D. – Weather Vane Apr 14 '17 at 19:41
  • 1
    @WeatherVane - It is. But it's immaterial. The C standard only requires pointer arithmetic to be valid within the same array. `a[1]` and `a[2]` are separate array objects, despite being contiguous in memory. I do concur that most if not all implementations usually do the sane thing, however. – StoryTeller - Unslander Monica Apr 14 '17 at 19:43
  • In a non-flat, segmented memory layout, you have the choice of weird insane things, or be limited to 64K arrays. – Zan Lynx Apr 14 '17 at 19:45
  • @StoryTeller there is only one array object. – Weather Vane Apr 14 '17 at 19:46
  • @ZanLynx See http://stackoverflow.com/questions/7269099/may-i-treat-a-2d-array-as-a-contiguous-1d-array – Barmar Apr 14 '17 at 19:46
  • 1
    @WeatherVane. no there are 3 sub-arrays. `a[0]`, `a[1]` and `a[2]` are all expressions yielding an array rvalue. – StoryTeller - Unslander Monica Apr 14 '17 at 19:47
  • @StoryTeller But for purposes of determining whether you're inside the array, only the main `a` array matters, not the sub-arrays. – Barmar Apr 14 '17 at 19:47
  • 1
    @Barmar - Surely you can quote the standard on this. `a` is an array of `int[2]`, not `int`. – StoryTeller - Unslander Monica Apr 14 '17 at 19:48
  • @StoryTeller [this answer](http://stackoverflow.com/a/24318220/4142924) in Barmar's link. Ways of indexing one object. – Weather Vane Apr 14 '17 at 19:48
  • @WeatherVane - Did you read the comments to that answer? [Ben is wrong](http://stackoverflow.com/questions/7269099/may-i-treat-a-2d-array-as-a-contiguous-1d-array/24318220#comment37587312_24318220). – StoryTeller - Unslander Monica Apr 14 '17 at 19:49
  • @StoryTeller and so they (`a[0]`, `a[1]` and `a[2]` are all expressions yielding an array rvalue) might be. But that does not diminish the debate in the interesting linked question. The indexing does not exceed the bounds of this single object, even if the compiler *can* treat it as separate arrays. – Weather Vane Apr 14 '17 at 20:13
  • 1
    @WeatherVane- It exceeds the limit of each single `a[i]`. You can use pointer arithmetic with `int*` inside each `a[i]`, but to be standard compliant you can only use `int(*)[2]` for arithmetic inside `a`. The standard is quite explicit about this. – StoryTeller - Unslander Monica Apr 14 '17 at 20:16
  • @StoryTeller then submit an answer and quote the standard chapter and verse, or link to a previous. – Weather Vane Apr 14 '17 at 20:19
  • @WeatherVane - Do the same for your claim it's okay – StoryTeller - Unslander Monica Apr 14 '17 at 20:19
  • @StoryTeller you are the one claiming the standard is "explicit". So back it up, please. My claim is that the indexing is within the bounds of the object, and arithmetically it obviously is, as others have said. – Weather Vane Apr 14 '17 at 20:22
  • 1
    @WeatherVane - [here](http://port70.net/~nsz/c/c11/n1570.html#6.5.2.1p3) and [here](http://port70.net/~nsz/c/c11/n1570.html#6.5.6p8). Happy reading. – StoryTeller - Unslander Monica Apr 14 '17 at 20:28
  • 1
    Nice comment battle that is not helping the OP. – takendarkk Apr 14 '17 at 20:53
  • It doesn't matter either way. The negative-index code is rubbish whether it works, doesn't work or whether or no it's backed up by C standards. It's of negative use to other SO users and future visitors. – ThingyWotsit Apr 14 '17 at 20:56

4 Answers4

2

Your example is officially undefined behavior. I'll refer to the C standard:

[C11 §6.5.2.1 ¶3]

Successive subscript operators designate an element of a multidimensional array object. If E is an n-dimensional array (n >= 2) with dimensions i x j x . . . x k, then E (used as other than an lvalue) is converted to a pointer to an (n - 1)-dimensional array with dimensions j x . . . x k. If the unary * operator is applied to this pointer explicitly, or implicitly as a result of subscripting, the result is the referenced (n - 1)-dimensional array, which itself is converted into a pointer if used as other than an lvalue. It follows from this that arrays are stored in row-major order (last subscript varies fastest).

[C11 §6.5.6 ¶8]

When an expression that has integer type is added to or subtracted from a pointer, the result has the type of the pointer operand. If the pointer operand points to an element of an array object, and the array is large enough, the result points to an element offset from the original element such that the difference of the subscripts of the resulting and original array elements equals the integer expression. In other words, if the expression P points to the i-th element of an array object, the expressions (P)+N (equivalently, N+(P)) and (P)-N (where N has the value n) point to, respectively, the i+n-th and i-n-th elements of the array object, provided they exist. Moreover, 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, and if the expression Q points one past the last element of an array object, the expression (Q)-1 points to 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.

The emphasis above is mine. The expression a[2][-2] carries a lot of meaning per the above two paragraphs. According to the first paragraphs a[2] will refer to an array, specifically the third int[2] contained in a. At this point, any subscript operator applied further needs to be valid in regards to this array of 2 integers.

Since [-2] is now applied to an int[2], the resulting pointer arithmetic goes outside the aggregate it's applied to, and according to the second paragraph, is undefined behavior.

Having said that, most implementations I'm aware of do the thing one may expect, and the other answers document well how it is that you got those values.

StoryTeller - Unslander Monica
  • 165,132
  • 21
  • 377
  • 458
  • I like your arguments, and - if I understand you right - you argue that the interim expression `a[2]` is interpreted as some sort of "new array context"; However, what if this interim expression is interpreted as a pointer in the context of the initial array, as statement "... itself is converted into a pointer if used as other than an lvalue" could indicate? – Stephan Lechner Apr 14 '17 at 21:21
  • @StephanLechner - I would argue that it is only the standard being explicitly recursive. Furthermore, subscripting is defined as `*(a + i)`, i.e pointer decay. Since different pointer types are in use when accessing `a` as opposed to `a[2]` (`int(*)[2]` vs `int*`), I think it's safe to say the context switch is warranted. – StoryTeller - Unslander Monica Apr 14 '17 at 21:30
  • Hm - another answer supporting your point of view (though when exceeding the interim array in the other direction): http://stackoverflow.com/a/6293364/2630032; So, if I were the OP, I'd vote for your answer and stopped using negative indexes in two-dimensional arrays. – Stephan Lechner Apr 14 '17 at 21:59
  • @StoryTeller There is neither undefined behavior. Your answer is totally wrong. – Vlad from Moscow Apr 15 '17 at 01:02
  • @VladfromMoscow - If you can present a standard quote that indicates `a[0][0]` and `a[2][1]` are considered part of the same array object, by virtue of `a[0]` and `a[2]` being part of the same array, then please do. If you cannot, then no amount of wishful thinking makes my quoting of the standard "totally wrong". – StoryTeller - Unslander Monica Apr 15 '17 at 07:07
1

A two-dimensional array declared like

int a[3][2] = { 10, 11, 12, 13, 14, 15 };

you can interpret as a one dimensional array declared like

int a[3 * 2] = { 10, 11, 12, 13, 14, 15 };

Between indices of the two-dimensional array and indices of the one-dimensional array there is the following relation

a[i][j] corresponds to a[2 * i + j]

So the element a[2][-2] of the two dimensional array corresponds to the element a[2 * 2 - 2] of the one-dimensional array that is to a[2] The value of a[2] is equal to 12. And the value of the element a[2][-1] of the two-dimensional array corresponds to the value of the element a[2 * 2 - 1] of the one-dimensional array that is to a[3] equal to 13.

And the reverse calculation. If you have an element of a one-dimensional array a[i] then it has the following corresponding element of a two-dimensional array

a[i / nCols][i % nCols ]

where nCols is the number of columns in the two-dimensional array.

Vlad from Moscow
  • 301,070
  • 26
  • 186
  • 335
0

I don't think that it is undefined behaviour; The C standard at paragraph 6.5.2.1 Array subscripting describes how array subscripting is interpreted. I applied the paragraph defined there to the example asked by the OP:

int a[3][2];

Here a is a 3 × 2 array of ints; more precisely, a is an array of three element objects, each of which is an array of two ints. In the expression a[i], which is equivalent to (*((a)+(i))), a is first converted to a pointer to the initial array of two ints. Then i is adjusted according to the type of a, which conceptually entails multiplying i by the size of the object to which the pointer points, namely an array of two int objects. Then, an expression a[i][j], is equivalent to (*(*((a)+(i)))+(j)), where j conceptually is multiplied by the size of int.

In terms of memory addresses, and assuming 4 as the sizeof(int), this means a + (i*sizeof(int[2])) + (j*sizeof(int)); for i=2 and j=-2 this means a + 16 - 8, i.e. a + 2*sizeof(int). In the memory layout of int a[3][2], dereferencing this memory address achieves 12.

Stephan Lechner
  • 34,891
  • 4
  • 35
  • 58
0

Your array looks like this:

10 | 11  
---+---  
12 | 13  
---+---  
14 | 15

but in the memory it looks like this:

10  
--  
11  
--  
.
.  
.  
--  
15

That means that if you want to access the element a[1][1] (= 13) you don't have to access the 1 + 1 = 2nd element because you have to skip the entire first row with 2 elements so you have to access the 1 * 2 + 1 = 3rd (10 would be the 0th) element.

So if you have an array:

int a[nRows][nCols];

accessing a[i][j] would look like this:

*(a + nCols * i + j) = val;

So in your example it would be:

*(a + 2 * 2 + (-2)) = *(a + 2)

and

*(a + 2 * 2 + (-1)) = *(a + 3)