Suppose we have:
typedef struct {
uint8_t someVal;
} Entry
typedef struct {
Entry grid[3][3];
} Matrix
//Make a 3x3 matrix of all 0s
Matrix emptyMatrix(void) {
Entry zero = {.value = 0}
Matrix matrix;
for (int i = 0; i < 3; i++)
for (int j = 0; j < 3; j++) {
//Shallow copy empty struct
matrix.grid[i][j] = zero;
}
return matrix;
}
Matrix myMatrix = emptyMatrix();
I understand that in C we're allowed to return a struct from a function so this works and I've tested it. However, it's unclear to me HOW the assignment works.
Does the compiler allocate the memory for myMatrix then copy each Entry element of the array in the Matrix struct returned by emptyMatrix()?
I guess it would also be helpful to know the memory map of Matrix - I assumed that since grid is an array that Matrix's memory would contain pointers. However, it apparently stores the value. If this is the case, my guess for how the assignment works makes much more sense to me.
Edit: It seems like people are answering the question incompletely. I want to know whether my guess of how the assignment works is correct.
Each instance of Matrix will contain a 3x3 array of Entry. When you assign one instance of Matrix to another, the contents of the source matrix will be copied to the destination matrix.
Arrays are not pointers. Array expressions will "decay" to pointers if the expression is not the operand of the sizeof or unary & operators, or is not a string literal used to initialize a character array in a declaration.
For example, if you had a function call like
printMatrix( myMatrix.grid );
the expression myMatrix.grid has type "3-element array of 3-element array of Entry"; since it's not the operand of the sizeof or unary & operator, it "decays" to an expression of type "pointer to 3-element array of Entry" (Entry (*)[3]) and the value of the expression is the address of the first element of grid (which will also be the address of the whole Matrix instance).
The ABI for each environment defines how structures are passed and returned by value. A common choice is this:
small structures, up to the size of 2 or 4 registers are returned in registers.
for larger objects, the caller allocates space on its stack frame for the return value and passes a pointer to the function. When returning, the function copies whatever object is being returned into the space to which it received a pointer for the return value. That's it. This simple method allows for recursive calls.
the optimizer tries to minimize the amount of copying, especially if it can expand the function inline or if the returned value is stored into an object, as opposed to discarded or passed by value to another function.
It does not matter if the structure has one or more member arrays. The same method applies to unions as well.
Identifiers bound to arrays may be interpreted as pointers, but that doesn't mean that the array variables are pointers.
The storage for the array is part of the memory layout of the struct itself. The same way as arrays declared on the stack are on the stack itself.
You can test this by yourself by checking sizeof(Matrix).
So in short:
Does the compiler allocate the memory for myMatrix then copy each Entry element of the array in the Matrix struct returned by emptyMatrix()?
Yes
Matrix is a data type struct struct {Entry grid[3][3];}. For every object of the type Metrix memory of size Entry grid[3][3] will be allocated.
Function emptyMatrix is returning an object of type Matrix and the object will be returned by value just like any other object.
Related
I’m taking a C class on Udemy. Unfortunately the instructor isn’t replying to my question so I thought I’d try this site. My assumption is that it is probably fairly common when developing a program to not know how many elements may be part of an array. When initializing an array the instructor recommends not specifying a size but to let the compiler do it.
Example: int array[ ] = {2,3,4,5,6,7,8};
Obviously, using this method there is no index to use to terminate looping. According to “C Primer Plus” by Stephen Prata the element after the last element in the array is a valid pointer location:
(pg. 406) - C guarantees that when it allocates space for an array, a
pointer to the first location after the end of the array is a valid
pointer.
If I’m using pointer notation (array++) to loop through the array, what condition can I use to terminate the looping? Is there a value in that location after the final element that I can use? Is that value always the same or does it change depending on the type of array?
In C pointers are signed. That has consequences dealing with array-like data structures where you might:
while (a <= a+last) {
...
a++;
}
if the index one beyond the end of a could have a change of sign, then that code could fail. Idiomatic C does not suggest the above; but it needs to be preserved, thus this limitation.
In system code, it is possible that you deal with allocations that do not conform to this, thus you should try to work with the idiomatic:
while (a < a+len) {
...
a++
}
So, for your exact question:
for (size_t i = 0; i < sizeof array/sizeof array[0]; i++) {
...
}
or
for (int *p = array; p < array + sizeof array / sizeof array[0]; p++) {
...
}
Your basic idea (looping through an array using pointers) is sound; however, there are a number of points in your question that need some attention.
Is there a value in that location after the final element that I can use? Is that value always the same or does it change depending on the type of array?
Yes, there is a (almost certainly) some value in that location, but it's not something you can ever use! The pointer to the 'one-past-the-end' element is valid only for use in pointer arithmetic or comparison operations; attempting to dereference it (to read the value at that address) is undefined behaviour.
You can get that 'one-past-the-end' pointer by adding the number of elements in the array to the address of the array's first element (or the array 'name' itself). The idiomatic way to get the number of elements in an array is to divide the size of the entire array by the size of its first element. So, for your array, we can declare and initialize our "end pointer" like this, using the sizeof operator:
int* end = array + sizeof(array) / sizeof(*array);
// int* end = array + sizeof array / sizeof *array; // Alternative form: "()" optional
Another important point: In your question you mention using array++ to loop through your array variable. You cannot do this, because array isn't actually a (modifiable) pointer variable – it's the name of a variable (an array) whose location is fixed at the point when main (or whatever function it is declared inside) is entered. Instead, you will need to copy the address of the array into another int* pointer, and increment that in the loop.
With those points in mind, here's an illustrative example of how you can loop through your array using a pointer:
#include <stdio.h>
int main(void)
{
int array[] = { 2,3,4,5,6,7,8 };
int* end = array + sizeof(array) / sizeof(*array);
for (int* p = array; p < end; ++p) {
// Note that, when we reach p == end, the loop will not run, so ...
printf("%d\n", *p); // ...we never attempt the *p operation on that
}
return 0;
}
A couple of other points of clarification:
The int* p = array assignment works (and is perfectly valid C) because an array variable name can readily decay into a pointer to its first element (as it will if you pass that array as an argument to a function, for example). See: What is array to pointer decay?
Because of that last point above, you cannot use the sizeof(a)/sizeof(*a) paradigm to determine the size of an array in a function it is passed to as an argument; in such cases, you need to pass the array's size as an additional argument. See: How do I determine the size of my array in C?
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.
Here consider the following sample of code:
int *a = malloc(sizeof(int) * n);
Can this code be used to define an array a containing n integers?
int *a = malloc(sizeof(int) * n);
Can this code be used to define an array a containing n integers?
That depends on what you mean by "define an array".
A declaration like:
int arr[10];
defines a named array object. Your pointer declaration and initialization does not.
However, the malloc call (if it succeeds and returns a non-NULL result, and if n > 0) will create an anonymous array object at run time.
But it does not "define an array a". a is the name of a pointer object. Given that the malloc call succeeds, a will point to the initial element of an array object, but it is not itself an array.
Note that, since the array object is anonymous, there's nothing to which you can apply sizeof, and no way to retrieve the size of the array object from the pointer. If you need to know how big the array is, you'll need to keep track of it yourself.
(Some of the comments suggest that the malloc call allocates memory that can hold n integer objects, but not an array. If that were the case, then you wouldn't be able to access the elements of the created array object. See N1570 6.5.6p8 for the definition of pointer addition, and 7.22.3p1 for the description of how a malloc call can create an accessible array.)
int *a = malloc(sizeof(int) * n);
Assuming malloc() call succeeds, you can use the pointer a like an array using the array notation (e.g. a[0] = 5;). But a is not an array itself; it's just a pointer to an int (and it may be a block of memory which can store multiple ints).
Your comment
But I can use an array a in my program with no declaration otherwise
suggests this is what you are mainly asking about.
In C language,
p[i] == *(p + i) == *(i + p) == i[p]
as long as one of i or p is of pointer type (p can an array as well -- as it'd be converted into a pointer in any expression). Hence, you'd able to index a like you'd access an array. But a is actually a pointer.
Yes. That is exactly what malloc() does.
The important distinction is that
int array[10];
declares array as an array object with enough room for 10 integers. In contrast, the following:
int *pointer;
declares pointer as a single pointer object.
It is important to distiguinsh that one of them is a pointer and that the other as an actual array, and that arrays and pointers are closely related but are different things. However, saying that there is no array in the following is also incorrect:
pointer = malloc(sizeof (int) * 10);
Because what this piece of code does is precisely to allocate an array object with room for 10 integers. The pointer pointer contains the address of the first element of that array.(C99 draft, section 7.20.3 "Memory management functions")
Interpreting your question very literally, the answer is No: To "define an array" means something quite specific; an array definition looks something like:
int a[10];
Whereas what you have posted is a memory allocation. It allocates a space suitable for holding an array of 10 int values, and stores a pointer to the first element within this space - but it doesn't define an array; it allocates one.
With that said, you can use the array element access operator, [], in either case. For instance the following code snippets are legal:
int a[10];
for (int i = 0; i < 10; i++) a[i] = 0;
and
int *a = malloc(sizeof(int) * n);
for (int i = 0; i < n; i++) a[i] = 0;
There is a subtle difference between what they do however. The first defines an array, and sets all its elements to 0. The second allocates storage which can hold an equivalently-typed array value, and uses it for this purpose by initialising each element to 0.
It is worth pointing out that the second example does not check for an allocation error, which is generally considered bad practice. Also, it constitutes a potential memory leak if the allocated storage is not later freed.
In the language the Standard was written to describe (as distinct from the language that would be described by a pedantic literal reading of it), the intention was that malloc(n) would return a pointer that would, if cast to a T*, could be treated as a pointer to the first element of a T[n/sizeof T*]. Per N1570 7.22.3:
The
pointer returned if the allocation succeeds is suitably aligned so that it may be assigned to
a pointer to any type of object with a fundamental alignment requirement and then used
to access such an object or an array of such objects in the space allocated (until the space
is explicitly deallocated).
The definition of pointer addition and subtraction, however, do not speak of acting upon pointers that are "suitably aligned" to allow access to arrays of objects, but rather speak of pointers to elements of actual array objects. If a program accesses space for 20 int objects, I don't think the Standard does actually says that the resulting pointer would behave in all respects as though it were a pointer to element [0] of an int[20], as distinct from e.g. a pointer to element [0][0] of an int[4][5]. An implementation would have to be really obtuse not to allow it to be used as either, of course, but I don't think the Standard actually requires such treatment.
I have been following some examples that declare an int pointer
int *myInt;
and then turn that pointer into an array
myInt = (int*)malloc(1024);
this checks out
myInt[0] = 5;
cout << myInt[0]; // prints 5
myInt[1] = 7;
cout << myInt[1]; // prints 7
I thought an int pointer was a pointer to an int and never anything else. I know that pointers to strings just point to the first character of the string but it looks like the same sort of thing is happening here with an array of ints. But then if what we want is an array of ints why not just create an array of ints instead of a pointer to an int?
By the way I am interested in how this works in C not C++. This is in a C++ file but the relevant code is in C.
Is an int pointer an array of ints?
No.
I thought an int pointer was a pointer to an int and never anything else
That's right. Pointers are pointers, arrays are arrays.
What confuses you is that pointers can point to the first element of arrays, and arrays can decay into pointers to their first element. And what's even more confusing: pointers have the same syntax for dereferencing and pointer arithmetic that arrays utilize for indexing. Namely,
ptr[i]
is equivalent with
*(ptr + i)
if ptr is a pointer. Of course, similarly, arr[i] is the ith element of the arr array too. The similarity arises out of the common nature of pointers and arrays: they are both used to access (potentially blocks of) memory indirectly.
The consequence of this strong relation is that in some situations (and with some constraints), arrays and pointers can be used as if they were interchangeable. This still doesn't mean that they are the same, but they exhibit enough common properties so that their usage often appears to be "identical".
There is an alternative syntax for accessing items pointed by a pointer - the square brackets. This syntax lets you access data through pointers as if the pointer were an array (of course, pointers are not arrays). An expression a[i] is simply an alternative form of writing *(a+i)* .
When you allocate dynamic storage and assign it to myInt, you can use the pointer like a dynamic array that can change size at runtime:
myInt = malloc(1024*sizeof(int)); // You do not need a cast in C, only in C++
for (int i = 0 ; i != 1024 ; i++) {
myInt[i] = i; // Use square bracket syntax
}
for (int i = 0 ; i != 1024 ; i++) {
printf("%d ", *(myInt+i)); // Use the equivalent pointer syntax
}
* Incidentally, commutativity of + lets you write 4[array] instead of array[4]; don't do that!
Sort of, and technically no. An int pointer does point to the int. But an array of ints is contiguous in memory, so the next int can be referenced using *(myInt+1). The array notation myInt[1] is equivalent, in that it uses myInt pointer, adds 1 unit to it (the size of an int), and reference that new address.
So in general, this is true:
myInt[i] == *(myint + i)
So you can use an int pointer to access the array. Just be careful to look out for the '\0' character and stop.
An int pointer is not an array of ints. But your bigger question seems to be why both arrays and pointers are needed.
An array represents the actual storage in memory of data. Once that storage is allocated, it makes no significant difference whether you refer to the data stored using array notation or pointer notation.
However, this storage can also be allocated without using array notation, meaning that arrays are not necessarily needed. The main benefit of arrays is convenient allocation of small blocks of memory, i.e., int x[20] and the slightly more convenient notation array[i] rather than *(array+i). Thankfully, this more convenient notation can be used regardless of whether array came from an array declaration or is just a pointer. (Essentially, once an array has been allocated, its variable name from that point onwards is no different than a pointer that has been assigned to point to the location in memory of the first value in the array.)
Note that the compiler will complain if you try to directly allocate too big of a block of memory in an array.
Arrays:
represent the actual memory that is allocated
the variable name of the array is the same as a pointer that references the point in memory where the array begins (and the variable name + 1 is the same as a pointer that references the point in memory where the second element of the array begins (if it exists), etc.)
values in the array can be accessed using array notation like array[i]
Pointers:
are a place to store the location of something in memory
can refer to the memory that is allocated in an array
or can refer to memory that has been allocated by functions like malloc
the value stored in the memory pointed to by the pointer can be accessed by dereferencing the pointer, i.e., *pointer.
since the name of the array is also a pointer, the value of the first element in the array can be accessed by *array, the second element by *(array+1), etc.
an integer can be added or subtracted to a pointer to create a new pointer that points to other values within the same block of memory your program has allocated. For example, array+5 points to the place in memory where the value array[5] is stored.
a pointer can be incremented or decremented to point to other values with the same block of memory.
In many situations one notation will be more convenient than the other, so it is extremely beneficial that both notations are available and so easily interchanged with each other.
They are not the same. Here is the visible difference.
int array[10];
int *pointer;
printf ("Size of array = %d\nSize of pointer = %d\n",
sizeof (array), sizeof (pointer));
The result is,
Size of array = 40
Size of pointer = 4
If You do "array + 1", the resulting address will be address of array[0] + 40. If You do "pointer + 1", resulting address will be address of pointer[0] + 4.
Array declaration results in compile time memory allocation. Pointer declaration does not result in compile time memory allocation and dynamic allocation is needed using calloc() or malloc()
When you do following assignment, it is actually implicit type cast of integer array to integer pointer.
pointer = array;
Sanity-check questions:
I did a bit of googling and discovered the correct way to return a one-dimensional integer array in C is
int * function(args);
If I did this, the function would return a pointer, right? And if the return value is r, I could find the nth element of the array by typing r[n]?
If I had the function return the number "3", would that be interpreted as a pointer to the address "3?"
Say my function was something like
int * function(int * a);
Would this be a legal function body?
int * b;
b = a;
return b;
Are we allowed to just assign arrays to other arrays like that?
If pointers and arrays are actually the same thing, can I just declare a pointer without specifying the size of the array? I feel like
int a[10];
conveys more information than
int * a;
but aren't they both ways of declaring an array? If I use the latter declaration, can I assign values to a[10000000]?
Main question:
How can I return a two-dimensional array in C? I don't think I could just return a pointer to the start of the array, because I don't know what dimensions the array has.
Thanks for all your help!
Yes
Yes but it would require a cast: return (int *)3;
Yes but you are not assigning an array to another array, you are assigning a pointer to a pointer.
Pointers and arrays are not the same thing. int a[10] reserves space for ten ints. int *a is an uninitialized variable pointing to who knows what. Accessing a[10000000] will most likely crash your program as you are trying to access memory you don't have access to or doesn't exist.
To return a 2d array return a pointer-to-pointer: int ** f() {}
Yes; array indexing is done in terms of pointer arithmetic: a[i] is defined as *(a + i); we find the address of the i'th element after a and dereference the result. So a could be declared as either a pointer or an array.
It would be interpreted as an address, yes (most likely an invalid address). You would need to cast the literal 3 as a pointer, because values of type int and int * are not compatible.
Yes, it would be legal. Pointless, but legal.
Pointers and arrays are not the same thing; in most circumstances, an expression of array type will be converted ("decay") to an expression of pointer type and its value will be the address of the first element of the array. Declaring a pointer by itself is not sufficient, because unless you initialize it to point to a block of memory (either the result of a malloc call or another array) its value will be indeterminate, and may not point to valid memory.
You really don't want to return arrays; remember that an array expression is converted to a pointer expression, so you're returning the address of the first element. However, when the function exits, that array no longer exists and the pointer value is no longer valid. It's better to pass the array you want to modify as an argument to the function, such as
void foo (int *a, size_t asize)
{
size_t i;
for (i = 0; i < asize; i++)
a[i] = some_value();
}
Pointers contain no metadata about the number of elements they point to, so you must pass that as a separate parameter.
For a 2D array, you'd do something like
void foo(size_t rows, size_t columns, int (*a)[columns])
{
size_t i, j;
for (i = 0; i < rows; i++)
for (j = 0; j < columns; j++)
a[i][j] = some_value;
}
This assumes you're using a C99 compiler or a C2011 compiler that supports variable length arrays; otherwise the number of columns must be a constant expression (i.e., known at compile time).
These answers certainly call for a bit more depth. The better you understand pointers, the less bad code you will write.
An array and a pointer are not the same, EXCEPT when they are. Off the top of my head:
int a[2][2] = { 1, 2, 3, 4 };
int (* p)[2] = a;
ASSERT (p[1][1] == a[1][1]);
Array "a" functions exactly the same way as pointer "p." And the compiler knows just as much from each, specifically an address, and how to calculate indexed addresses. But note that array a can't take on new values at run time, whereas p can. So the "pointer" aspect of a is gone by the time the program runs, and only the array is left. Conversely, p itself is only a pointer, it can point to anything or nothing at run time.
Note that the syntax for the pointer declaration is complicated. (That is why I came to stackoverflow in the first place today.) But the need is simple. You need to tell the compiler how to calculate addresses for elements past the first column. (I'm using "column" for the rightmost index.) In this case, we might assume it needs to increment the address ((2*1) + 1) to index [1][1].
However, there are a couple of more things the compiler knows (hopefully), that you might not.
The compiler knows two things: 1) whether the elements are stored sequentially in memory, and 2) whether there really are additional arrays of pointers, or just one pointer/address to the start of the array.
In general, a compile time array is stored sequentially, regardless of dimension(s), with no extra pointers. But to be sure, check the compiler documentation. Thus if the compiler allows you to index a[0][2] it is actually a[1][0], etc. A run time array is however you make it. You can make one dimensional arrays of whatever length you choose, and put their addresses into other arrays, also of whatever length you choose.
And, of course, one reason to muck with any of these is because you are choosing from using run time multiplies, or shifts, or pointer dereferences to index the array. If pointer dereferences are the cheapest, you might need to make arrays of pointers so there is no need to do arithmetic to calculate row addresses. One downside is it requires memory to store the addtional pointers. And note that if the column length is a power of two, the address can be calculated with a shift instead of a multiply. So this might be a good reason to pad the length up--and the compiler could, at least theoretically, do this without telling you! And it might depend on whether you select optimization for speed or space.
Any architecture that is described as "modern" and "powerful" probably does multiplies as fast as dereferences, and these issues go away completely--except for whether your code is correct.