Something like that is fairly simple to check (a):
#include <stdio.h>
int main (void) {
int a[2][3][4][5];
// Ignore incorrect format specifiers for now.
printf ("%d\n", sizeof(int));
printf ("%d\n", &(a[0][0][0][0]));
printf ("%d\n", &(a[1][1][1][1]));
printf ("%d\n", (int)&(a[1][1][1][1])
- (int)&(a[0][0][0][0])
+ 1000);
return 0;
}
and the output of that is:
4
2665056
2665400
1344
Note the conversions of the pointers to int values in that final printf. Without this, the 1000 would be scaled as an int *, giving the wrong value.
So, yes, bottom line, your reasoning is correct.
(a) That's not always the case since some aspects of the C language can differ across implementations (implementation-specified behaviour) or in any way they want (undefined behaviour).
Happily, layout of arrays is specified specifically by the standard, in C11 6.5.2.1 Array subscripting:
2/ A postfix expression followed by an expression in square brackets [] is a subscripted designation of an element of an array object. The definition of the subscript operator [] is that E1[E2] is identical to (*((E1)+(E2))). Because of the conversion rules that apply to the binary + operator, if E1 is an array object (equivalently, a pointer to the initial element of an array object) and E2 is an integer, E1[E2] designates the E2-th element of E1 (counting from zero).
3/ Successive subscript operators designate an element of a multidimensional array object. If E is an n-dimensional array (n >= 2) with dimensions i * j * ... * k, then E (used as other than an lvalue) is converted to a pointer to an (n − 1)-dimensional array with dimensions j * ... * 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).