difference between int a[9] and a[3][3] - c

What is the difference between
#include <stdio.h>
int a[9];
int
main()
{
printf("%d\n", a[1]);
}
and
#include <stdio.h>
int a[3][3];
int
main()
{
printf("%d\n", a[1]);
}
I think both result in placing same 36 byte memory buffer in .bss section, what is the difference? Or is a[3][3] syntactic sugar over a[9] - a[3*3]?

Nope, they are not same, they represent different types. In reference to your code,
In the first case, a is an one dimensional array. Hence, a[1] is of type int.
§) To print the value, %d is fine.
In the second case, however, a is a two-dimensional array. Hence, a[1] is of type int [3].
§) When passed as a function argument, it decays to a pointer to the first element, basically a int *. You'll be needing %p to print that (and cast the pointer to void *, as required by the %p format specifier).
However, if you're bothered about the memory layout for both the variables, you can check the other answer by AnT or another one which details about the memory layout of multi-dimensional arrays.

int a[3][3] is a semantic sugar of sorts over int a[9]. The raw memory layout it the same (i.e. it is a contiguous block of 9 ints), but the language-level access syntax is different. When accessing a[3][3] as a[i][j], the apparent 2D-indexing is converted by the compiler to 1D indexing using i * 3 + j formula. The latter index translation scheme is easily extendable to any number of dimensions.

What is the difference between
int a1[9];
and
int a2[3][3];
(I've changed the names so I can talk about the declarations more easily.)
The difference is that they're of different types. They both have the same underlying memory layout,each consisting of a contiguous region of memory 9 times the size of an int. But a1 is an array of 9 int objects, while a2 is an array of 3 objects each of which is an array of 3 int objects. It's a multidimensional array, which in C is precisely an array of array, nothing more, nothing less.
The difference is not just syntactic sugar. You might get the same generated code for certain operations, for example a1[1] and a2[0][1]. But, for example, a1[3] refers to the 4th element of the array a1, while a2[0][3], though you might think it refers to the same thing, actually has undefined behavior. (Compilers are permitted, but not required, to perform run-time array bound checking, and are permitted to assume that array references do not go past the end of the indexed array object.)
printf("%d\n", a2[1]);
As others have mentioned, this has undefined behavior. a2[1] is an array object of type int[3]. An expression of array type is, in most contexts, converted to an expression of pointer type, so a2[1] ends up being of type int*, and yields a pointer to the initial element of the second row of a2. To print a pointer value, use %p -- which requires an argument of type void*, so you need to cast it:
printf("%p\n", a2[1]);
Recommended reading: Section 6 of the comp.lang.c FAQ.

Related

In C why do I NOT need to specify 2D array size when passing into function when the 2D array is created with malloc?

I'm pretty new with C and just confused with what's really happening when I'm passing 2D arrays allocated in HEAP memory into a function. I've written code which has three functions, A, B, C which demonstrates my question.
Essentially, when I create a 2d array in stack space in function-A, I am able to pass that 2d array pointer to a function-B which requires the parameter (int size, int (*arr)[size]) and that works fine. My understanding is the 'int size' variable is required to let arr pointer now how much space it should jump each increment
However, when I create a 2d array in HEAP space in function-A, passing it to function-B appears to lose the location of the data (see code). However if I pass this HEAP space 2d array to function-C which has the parameter (int **arr), it works fine.
It would be great if someone could try to explain why I don't need to specify size when passing the HEAP space 2d array into function-C. Also, when I pass the 2d array created in STACK space to function-C, it crashes, why is that?
Here is sample code showcasing my question (Output is this):
#include <stdio.h>
#include <stdlib.h>
void function_A(int num)
{
// allocating HEAP space for 2D array
int **arrHEAP = (int **)malloc(2*sizeof(int*));
arrHEAP[0] = (int *)malloc(5*sizeof(int));
arrHEAP[1] = (int *)malloc(5*sizeof(int));
for(int i=0;i<2;i++) // initialising
for(int j=0;j<5;j++)
arrHEAP[i][j] = num++;
function_B(5, arrHEAP); // prints random data
function_C(arrHEAP); // prints correctly, works
// allocating STACK space for 2D array and initialising
int arrSTACK[2][5] = {{100, 200, 300, 400, 500},{600,700,800,900,1000}};
function_B(5, arrSTACK); // prints correctly, works
//function_C(arrSTACK); // if I were to run this it crashes the program, why?
}
void function_B(int size, int (*arr)[size])
{
for(int i=0;i<2;i++)
for(int j=0;j<5;j++)
printf("HEAP row is %d, value is %d:\n", i, arr[i][j]);
}
void function_C(int **arr)
{
for(int i=0;i<2;i++)
for(int j=0;j<5;j++)
printf("HEAP row is %d, value is %d:\n", i, arr[i][j]);
}
int main()
{
function_A(1);
}
Array/Pointer Conversion
The defect in understand you have surrounds the use of arrays and the use of pointers. In C, an array is a distinct type of object. One of which that causes confusion is that an array is converted to a pointer to its first element on access. (array/pointer conversion) This is governed by C11 Standard - 6.3.2.1 Other Operands - Lvalues, arrays, and function designators(p3) (note the 4 exceptions where array/pointer conversion does not occur)
The key here is type. When you declare a 2D array, e.g.
int arrSTACK[2][5] = {{100, 200, 300, 400, 500},{600,700,800,900,1000}};
On access it will be converted to a pointer -- but what type? A 2D array in C is an array of 1D arrays. Array/pointer conversion only applies to the first level of indirection. So on access arrSTACK is converted to a pointer to array int[5]. So its type is int (*)[5]. Since type controls pointer arithmetic arrSTACK + 1 advances five-integer values so that it points to the beginning of the second 1D array that makes up arrSTACK (the second row)
Pointers
int **arrHEAP declares a single pointer. A pointer-to-pointer-to int. It has nothing to do with an array. However a pointer-to-pointer can be indexed as you would index a 2D array to address the individual integers stored in memory. That is the only similarity between the 2D array and the object created by allocating storage for pointers and then allocating storage for integers and assigning the starting address for each block holding integers to one of the pointers you have allocated. Here there is no guarantee that all elements of arrHEAP are contiguous in memory as they are with a 2D array.
So let's look at the difference in how pointer arithmetic works with arrHEAP. When you dereference arrHEAP, a pointer-to-pointer (e.g. arrHEAP[0]) What type results from the dereference? If you had a pointer-to-pointer-to int and you dereference it you are left with pointer-to int. So with the array, the dereference resulted in the type pointer-to int[5], but with arrHEAP[0] the result is simply a pointer-to int (no 5 -- it's just a pointer to int). So how does pointer arithmetic differ? arrSTACK + 1 advances the pointer by 5 * sizeof(int) bytes (20-bytes). With arrHEAP + 1 advances only to the next pointer in your allocated block of pointers (1-pointer 8-bytes).
That is why you cannot pass one to the other function. The function expecting the array understands arrSTACK[0] and arrSTACK[1] being 20-bytes apart, while with the pointer arrHEAP[0] and arrHEAP[1] are only 8-bytes apart. This is the crux of the pointer-incompatibility warnings and errors you generate.
Then there is the lack of guarantee that all values of arrSTACK being sequential in memory. You know that arrSTACK[1] is always 20-byes from the beginning of the array. With arrHEAP the first allocated pointer has no guaranteed relationship with the other from an adjacency standpoint. They can later be replaced or reallocated.
What this means is if you try and provide arrSTACK to function_C(int **arr), the complier will generate a warning for incompatible pointer types -- because they are. Conversely, if you attempt to provide arrHEAP to function_B(int size, int (*arr)[size]) it will likewise issue a warning due to incompatible pointer types again -- because they are.
Even if how the object and the array are used in the other function would seem like it would work because you are essentially indexing both in the same way, the compiler cannot let one incompatible type through -- that's not the compilers job.
The compiler can only base its operation on the promise you made to it when you wrote your code. For function_B(int size, int (*arr)[size]) you promised you were sending a 2D array of 1D arrays containing 5 int. With function_C(int **arr), you promised the compiler you would provide pointer-to-pointer-to int. When the compiler sees you are attempting to pass the wrong object as a parameter, it will warn, and you should heed that warning, because the start of the 2nd block of integers in arrHEAP isn't guaranteed to be 6 int away from the beginning of arrHEAP -- and it won't be found there.
In void function_B(int size (int (*arr)[size]), arr points to a place where there are some number of rows of some number of int. To know where any row is, the compiler needs to know how many int are in each row. For example, with 10 rows of 12 int, row 3 starts after 3•12 int.
In void function_C(int **arr), arr points to a place where there are pointers to rows of int. To know where any row is, the compiler merely loads one of those pointers. For example, row 3 starts where the pointer arr[3] points.

multiArray and multiArray[0] and &multiArray[0] same?

On 6th line instead of multiArray[0], when I write multiArray, program still works. Don't understand why. I was thinking before that multiArray is a pointer to multiArray[0] which is a pointer to multiArray[0][0]. So multiArray alone is a pointer to a pointer. multiArray[0] is a pointer to a 4 element int array. So it seems that multiArray and multiArray[0] must be different. But in below code, both work. Print function I wrote expects a pointer to a 4 element int array. So only multiArray[0] must work and multiArray must not work. But both works. Didn't understand that.
#include <stdio.h>
void printArr(int(*ptr)[4]);
int i, k;
int main(void){
int multiArray[3][4] = { { 1, 5, 2, 4 }, { 0, 6, 3, 14 }, { 132, 4, 22, 5 } };
int(*point)[4] = multiArray[0];
for (k = 0; k < 3; k++)
{
printArr(point++);
}
getchar();
}
void printArr(int(*ptr)[4]){
int *temp = (int *)ptr;
for (i = 0; i < 4; i++)
{
printf("%d ", *temp);
temp++;
}
puts("\n");
}
Someone else wrote "Multi-dimensional arrays are syntactic sugar for 1-D arrays".
This is sort of like saying that int is just syntactic sugar for a unsigned char[4] . You could do away with expressions like 4 + 5 and get the same result by manipulating arrays of 4 bytes.
You could even say that C is just syntactic sugar for a Universal Turing Machine script, if you want to take this concept a bit further.
The reality is that multi-dimensional arrays are a part of the type system in C, and they have syntax associated with them. There's more than one way to skin a cat.
Moving on, the way C arranges what we are calling a multi-dimension array is to say: "Arrays can only have one dimension, but the element type may itself be another array". We say "multi-dimension array" as a matter of convenience, but the syntax and the type system actually reflect the one-dimensional nature of the array.
So, int multiArray[3][4] is an array of 3 elements. Each of those elements is an array of 4 ints.
In memory, an array's elements are stored contiguously -- regardless of what the element type is. So, the memory layout is an array of 4 int, immediately followed by another array of 4 int, and finally another array of 4 int.
There are 12 contiguous int in memory, and in the C type system they are grouped up into 3 groups of 4.
You will note that the first int of the 12 is also the first int of the first group of 4. This is why we find that if we ask "What is the memory location of the first int?", "What is the memory location of the first group of 4 ints?", and "What is the memory location of the entire bloc of 12 ints?", we get the same answer every time. (In C, the memory location of a multi-byte object is considered to start at the location of its first byte).
Now, to talk about the pointer syntax and representation. In C, a pointer tells you where in memory an object can be found. There are two aspects to this: the memory location of the object, and what type of object it is. (The size of the object is a corollary of the type).
Some presentations only focus on the first of those, they will say things like "A pointer is just a number". But that is forgetting about the type information, which is a crucial part of a pointer.
When you print the pointer with %p, you lose the type information. You're just putting out the location in memory of the first byte. So they all look the same, despite the fact that the three pointers are pointing at differently-sized objects (which overlap each other like matruskha dolls).
In most implementations of C, the type information is all computed at compile-time, so if you try to understand C by comparing source code with assembly code (some people do this), you only see the memory-location part of the pointer. This can lead to misunderstanding if you forget that the type information is also crucial.
Footnote: All of this is independent of a couple of syntax quirks that C has; which have caused a lot of confusion over the years (but are also useful sometimes). The expression x is a shortcut for &x[0] if x is an array, except when used as the operand of & or sizeof. (Otherwise this would be a recursive definition!). The second quirk is that if you write what looks like an array declarator in a function formal parameter list, it is actually as if you wrote a pointer declarator. I stress again that these are just syntax oddities, they are not saying anything fundamental about the nature of arrays and pointers, which is actually not that complicated. The language would work just as well without both of these quirks.
Multidiemensional arrays var_t arr[size_y][size_x] provide means of declaring and accessing array elements (memory) in a conveniant manner. But all multidiemensional arrays are internally continuous memory blocks.
You may say that arr[y][x] = arr[y*cols+x].
In terms of pointer-level, the pointers multiArray and multiArray[0] are the same, they're int* - though the formal type for arr will be int (*)[2]. Using that type will allow one to take advantage of all pointer mechanics (++ on such pointer will move the address by 8 bytes, not 4).
Try this:
void t1(int* param)
{
printf("t1: %d\n", *param);
}
void t2(int** param)
{
printf("t2: %d\n", **param);
}
int main(void) {
int arr[2][2] = { { 1, 2 } , { 3, 4 } };
t1(arr); // works ok
t1(arr[0]); // works ok
t2(arr); // seg fault
t2(arr[0]);
}
int(*point)[4] = multiArray[0];
This works because both multiArray[0] and multiArray point to same address, the address of first element of array: multiArray[0][0].
However in this case, you may get a warning from compiler because type of multiArray[0] is int* while of point is int [4]*(pointer to array of 4 integers).

Why do we need to specify the column size when passing a 2D array as a parameter?

Why can't my parameter be
void example(int Array[][]){ /*statements*/}
Why do I need to specify the column size of the array? Say for example, 3
void example(int Array[][3]){/*statements*/}
My professor said its mandatory, but I was coding before school started and I remembered that there was no syntactical or semantic error when I made this my parameter? Or did I miss something?
When it comes to describing parameters, arrays always decay into pointers to their first element.
When you pass an array declared as int Array[3] to the function void foo(int array[]), it decays into a pointer to the beginning of the array i.e. int *Array;. Btw, you can describe a parameter as int array[3] or int array[6] or even int *array - all these will be equivalent and you can pass any integer array without problems.
In case of arrays of arrays (2D arrays), it decays to a pointer to its first element as well, which happens to be a single dimensional array i.e. we get int (*Array)[3].
Specifying the size here is important. If it were not mandatory, there won't be any way for compiler to know how to deal with expression Array[2][1], for example.
To dereference that a compiler needs to compute the offset of the item we need in a contiguous block of memory (int Array[2][3] is a contiguous block of integers), which should be easy for pointers. If a is a pointer, then a[N] is expanded as start_address_in_a + N * size_of_item_being_pointed_by_a. In case of expression Array[2][1] inside a function (we want to access this element) the Array is a pointer to a single dimensional array and the same formula applies. The number of bytes in the last square bracket is required to find size_of_item_being_pointed_by_a. If we had just Array[][] it would be impossible to find it out and hence impossible to dereference an array element we need.
Without the size, pointers arithmetics wouldn't work for arrays of arrays. What address would Array + 2 produce: advance the address in Array 2 bytes ahead (wrong) or advance the pointer 3* sizeof(int) * 2 bytes ahead?
In C/C++, even 2-D arrays are stored sequentially, one row after another in memory. So, when you have (in a single function):
int a[5][3];
int *head;
head = &a[0][0];
a[2][1] = 2; // <--
The element you are actually accessing with a[2][1] is *(head + 2*3 + 1), cause sequentially, that element is after 3 elements of the 0 row, and 3 elements of the 1 row, and then one more index further.
If you declare a function like:
void some_function(int array[][]) {...}
syntactically, it should not be an error. But, when you try to access array[2][3] now, you can't tell which element is supposed to be accessed. On the other hand, when you have:
void some_function(int array[][5]) {...}
you know that with array[2][3], it can be determined that you are actually accessing element at the memory address *(&array[0][0] + 2*5 + 3) because the function knows the size of the second dimension.
There is one other option, as previously suggested, you can declare a function like:
void some_function(int *array, int cols) { ... }
because this way, you are calling the function with the same "information" as before -- the number of columns. You access the array elements a bit differently then: you have to write *(array + i*cols + j) where you would usually write array[i][j], cause array is now a pointer to integer (not to a pointer).
When you declare a function like this, you have to be careful to call it with the number of columns that are actually declared for the array, not only used. So, for example:
int main(){
int a[5][5];
int i, j;
for (i = 0; i < 3; ++i){
for (int j=0; j < 3; ++j){
scanf("%d", &a[i][j]);
}
}
some_function(&a[i][j], 5); // <- correct
some_function(&a[i][j], 3); // <- wrong
return 0;
}
C 2018 6.7.6.2 specifies the semantics of array declarators, and paragraph 1 gives constraints for them, including:
The element type shall not be an incomplete or function type.
In a function declaration such as void example(int Array[][]), Array[] is an array declarator. So it must satisfy the constraint that its element type must not be incomplete. Its element type in that declaration is int [], which is incomplete since the size is not specified.
There is no fundamental reason the C standard could not remove that constraint for parameters that are about to be adjusted to pointers. The resulting type int (*Array)[] is a legal declaration, is accepted by compilers, and can be used in the form (*Array)[j].
However, the declaration int Array[][] suggests that Array is at least associated with a two-dimensional array, and hence is to be used in the form Array[i][j]. Even if the declaration int Array[][] were accepted and were adjusted to int (*Array)[], using it as Array[i][j] would not be possible because the subscript operator requires that its pointer operand be a pointer to a complete type, and this requirement is not avoidable as it is needed to calculate the address of the element. Thus, keeping the constraint on the array declarator makes sense, as it is consistent with the intended expression that the argument will be a two-dimensional array, not just a pointer to one one-dimensional array.
Actually whether it is a 2d array or a 1d array, it is stored in the memory in a single line.So to say the compiler where should it break the row indicating the next numbers to be in the next rows we are supposed to provide the column size. And breaking the rows appropriately will give the size of the rows.
Let's see an example:
int a[][3]={ 1,2,3,4,5,6,7,8,9,0 };
This array a is stored in the memory as:
1 2 3 4 5 6 7 8 9 0
But since we have specified the column size as 3 the memory splits after every 3 numbers.
#include<stdio.h>
int main() {
int a[][3]={1,2,3,4,5,6},i,j;
for(i=0;i<2;i++)
{
for(j=0;j<3;j++)
{
printf("%d ",a[i][j]);
}
printf("\n");
}
}
OUTPUT:
1 2 3
4 5 6
In the other case,
int a[3][]={1,2,3,4,5,6,7,8,9,0};
The compiler only knows that there are 3 rows but it doesn't know the number of elements in each row so it cannot allocate memory and will show an error.
#include<stdio.h>
int main() {
int a[3][]={1,2,3,4,5,6},i,j;
for(i=0;i<3;i++)
{
for(j=0;j<2;j++)
{
printf("%d ",a[i][j]);
}
printf("\n");
}
}
OUTPUT:
c: In function 'main':
c:4:8: error: array type has incomplete element type 'int[]'
int a[3][]={1,2,3,4,5,6},i,j;
^
As we know, we can pass a variable as an argument(s) in a function. Similarly, we can pass two-dimensional arrays in C++.
C++ does not allow us to pass an entire array as an argument to a function. However, we can pass a pointer to an array by specifying the array's name without an index.
We can pass a 2D array to a function by specifying the size of the columns of a 2D array. One of the important things to remember here is that the size of rows is optional but the size of the column should not be left empty else the compiler will show an error. A 2D array is stored in the memory in a single line. So, to say the compiler where should it break the row indicating the following numbers to be in the next rows we are supposed to provide the column size. And breaking the rows appropriately will automatically give the size of the rows.
source: https://www.scaler.com/topics/two-dimensional-array-in-cpp/
There is a similar post regarding this. You can refer below link.
Creating Array in C and passing pointer to said array to function
Hope it helps.
On the other hand, compiler needs to the second dimension so that it can move "Array" from one pointer to next since the whole memory is arranged in a linear fashion
I thought this was a cool approach. If you take this as the formula to calculate the address of an element in the array:
a[i][j] = baseArrayAddress + (i + (colSize + elementSize)) + (j * (elementSize))
Then you can see that the only thing the compiler needs to know (which it can't otherwise infer) is the size of the column, thus you need to provide it as the programmer so the algorithm can run to calculate the offset.
The row number only acts as a multiplier and is provided by the programmer when trying to dereference an array location.
When you create a 2D array, anytype a[3][4], in memory what you actually create is 3 contiguous blocks of 4 anytype objects.
a[0][0] a[0][1] a[0][2] a[0][3] a[1][0] a[1][1] a[1][2] a[1][3] a[2][0] a[2][1] a[2][2] a[2][3]
Now the next question is, why is that so? Because, keeping with the spec and structure of the language, anytype a[3][4] actually expands out into anytype (*a)[4], because arrays decay into pointers. And in fact that also expands out into anytype (*(*a)), however, you've now completely lost the size of the 2D array. So, you must help the compiler out a bit.
If you ask the program for a[2], the program can follow the exact same steps that it does for 1D arrays. It simply can return the 3rd element of sizeof(object pointed to), the object pointed to here is of size 4 anytype objects.

Why don't compilers convert int[][] to int** which is theoretically possible?

In C, two dimensional arrays are stored in the way just like linear arrays, but they are indexed using a double pointer. That is, if we define
int a[3][3] = {{1,2,3},{4,5,6},{7,8,9}}.
a first points to something like
{p1 = 0x7fff5fbffb58 , p2 = 0x7fff5fbffb70, p3 = 0x7fff5fbffb88}
then p1 points to 1, p2 points to 4 and p3 points to 7.
So why don't compilers allow for a conversion from the two dimensional array to the double pointer which is theoretically possible? Although 2-D arrays are stored one by one, but the index information can always be passed to an arbitrary double pointer.
It's not theoretically possible. 2d arrays are not indexed using a double pointer - the compiler convert it to one index. For example, if you have int a[3][5], and you access a[i][j], the compiler convert it to ((int[])a)[5*i+j]. (Your explanation is completely wrong)
Because of all of that, if you want to convert int[][] to int**, you need to allocate memory to save the addresses of all the sub-arrays, and get the address of it. Just (int**)a will not work.
Your example illustrates the reason why compilers cannot do it: the values of p1, p2, and p3 are calculated, not stored. The compiler knows the address of p1, and calculates the other two with the knowledge of the array size in hand. With double pointers, on the other hand, all three pointers would need to be stored in sequential memory locations, forming an array of pointers.
Converting int[][] to int** would require introducing array of int pointers full of data. We love c for not taking such liberties when not explicitly asked for.
Because C says so.
(C99, 6.3.2.1p3) "Except when it is the operand of the sizeof operator or the unary &
operator, or is a string literal used to initialize an array, an expression that has type "array of type" is converted to an expression with type "pointer to type" that points to the initial element of the array object and is not an lvalue."
This conversion rule is not recursive for array of array.
The value of an object of type int [N][M] is of type int (*)[M] after conversion.
Because are two different things. int a[3][3], in the memory is an array of 9 elements.
int** you should have an array of pointers with 3 elements that each one of them will point to another array, that will contains the data.

Pointer address in a C multidimensional array

I'm messing around with multidimensional arrays and pointers. I've been looking at a program that prints out the contents of, and addresses of, a simple array. Here's my array declaration:
int zippo[4][2] = { {2,4},
{6,8},
{1,3},
{5,7} };
My current understanding is that zippo is a pointer, and it can hold the address of a couple of other pointers. By default, zippo holds the address of pointer zippo[0], and it can also hold the addresses of pointers zippo[1], zippo[2], and zippo[3].
Now, take the following statement:
printf("zippo[0] = %p\n", zippo[0]);
printf(" *zippo = %p\n", *zippo);
printf(" zippo = %p\n", zippo);
On my machine, that gives the following output:
zippo[0] = 0x7fff170e2230
*zippo = 0x7fff170e2230
zippo = 0x7fff170e2230
I perfectly understand why zippo[0] and *zippo have the same value. They're both pointers, and they both store the address (by default) of the integer 2, or zippo[0][0]. But what is up with zippo also sharing the same memory address? Shouldn't zippo be storing the address of the pointer zippo[0]? Whaaaat?
When an array expression appears in most contexts, its type is implicitly converted from "N-element array of T" to "pointer to T", and its value is set to point to the first element in the array. The exceptions to this rule are when the array expression is an operand of either the sizeof or address-of (&) operators, or when the array is a string literal being used as an initializer in a declaration.
Thus, the expression zippo "decays" from type int [4][2] (4-element array of 2-element arrays of int) to int (*)[2] (pointer to 2-element array of int). Similarly, the type of zippo[0] is int [2], which is implicitly converted to int *.
Given the declaration int zippo[4][2], the following table shows the types of various array expressions involving zippo and any implicit conversions:
Expression Type Implicitly converted to Equivalent expression
---------- ---- ----------------------- ---------------------
zippo int [4][2] int (*)[2]
&zippo int (*)[4][2]
*zippo int [2] int * zippo[0]
zippo[i] int [2] int *
&zippo[i] int (*)[2]
*zippo[i] int zippo[i][0]
zippo[i][j] int
&zippo[i][j] int *
*zippo[i][j] invalid
Note that zippo, &zippo, *zippo, zippo[0], &zippo[0], and &zippo[0][0] all have the same value; they all point to the base of the array (the address of the array is the same as the address of the first element of the array). The types of the various expressions all differ, though.
When you declare a multidimensional array, the compiler treats it as a single dimensional array. Multidimensional arrays are just an abstraction to make our life easier. You have a misunderstanding: This isn't one array pointing to 4 arrays, its always just a single contigous block of memory.
In your case, doing:
int zippo[4][2]
Is really the same as doing
int zippo[8]
With the math required for the 2D addressing handled for you by the compiler.
For details, see this tutorial on Arrays in C++.
This is very different than doing:
int** zippo
or
int* zippo[4]
In this case, you're making an array of four pointers, which could be allocated to other arrays.
zippo is not a pointer. It's an array of array values. zippo, and zippo[i] for i in 0..4 can "decay" to a pointer in certain cases (particularly, in value contexts). Try printing sizeof zippo for an example of the use of zippo in a non-value context. In this case, sizeof will report the size of the array, not the size of a pointer.
The name of an array, in value contexts, decays to a pointer to its first element. So, in value context, zippo is the same as &zippo[0], and thus has the type "pointer to an array [2] of int"; *zippo, in value context is the same as &zippo[0][0], i.e., "pointer to int". They have the same value, but different types.
I recommend reading Arrays and Pointers for answering your second question. The pointers have the same "value", but point to different amounts of space. Try printing zippo+1 and *zippo+1 to see that more clearly:
#include <stdio.h>
int main(void)
{
int zippo[4][2] = { {2,4}, {6,8}, {1,3}, {5,7} };
printf("%lu\n", (unsigned long) (sizeof zippo));
printf("%p\n", (void *)(zippo+1));
printf("%p\n", (void *)(*zippo+1));
return 0;
}
For my run, it prints:
32
0xbffede7c
0xbffede78
Telling me that sizeof(int) on my machine is 4, and that the second and the third pointers are not equal in value (as expected).
Also, "%p" format specifier needs void * in *printf() functions, so you should cast your pointers to void * in your printf() calls (printf() is a variadic function, so the compiler can't do the automatic conversion for you here).
Edit: When I say an array "decays" to a pointer, I mean that the name of an array in value context is equivalent to a pointer. Thus, if I have T pt[100]; for some type T, then the name pt is of type T * in value contexts. For sizeof and unary & operators, the name pt doesn't reduce to a pointer. But you can do T *p = pt;—this is perfectly valid because in this context, pt is of type T *.
Note that this "decaying" happens only once. So, let's say we have:
int zippo[4][2] = { {2,4}, {6,8}, {1,3}, {5,7} };
Then, zippo in value context decays to a pointer of type: pointer to array[2] of int. In code:
int (*p1)[2] = zippo;
is valid, whereas
int **p2 = zippo;
will trigger an "incompatible pointer assignment" warning.
With zippo defined as above,
int (*p0)[4][2] = &zippo;
int (*p1)[2] = zippo;
int *p2 = zippo[0];
are all valid. They should print the same value when printed using printf("%p\n", (void *)name);, but the pointers are different in that they point to the whole matrix, a row, and a single integer respectively.
The important thing here is that int zippy[4][2] is not the same type of object as int **zippo.
Just like int zippi[5], zippy is the address of a block of memory. But the compiler knows that you want to address the eight memory location starting at zippy with a two dimensional syntax, but want to address the five memory location starting at zippi with a one dimensional syntax.
zippo is a different thing entirely. It holds the address of a a block of memory big enough to contain two pointer, and if you make them point at some arrays of integers, you can dereference them with the two dimensional array access syntax.
Very well explained by Reed, I shall add few more points to make it simpler, when we refer to zippo or zippo[0] or zippo[0][0], we are still referring to the same base address of the array zippo. The reason being arrays are always contiguous block of memory and multidimensional arrays are multiple single dimension arrays continuously placed.
When you have to increment by each row, you need a pointer int *p = &zippo[0][0], and doing p++ increments the pointer by every row.
In your example id its a 4 X 2 array, on doing p++ its, pointer currently points to second set of 4 elements.

Resources