int data[3][5];
is a 3-element array of 5-element arrays.
Why? Intuitively for me if int[3] is a 3-element array and int[3][5] Should be a 5-element array of 3-elements arrays.
The intuition should come from the indexing convention - since it is an array of arrays, first index is selecting the element which is an array, the second index is selecting the element of the selected array. That is:
data[2][4] will select element number 4 of the array number 2 (mind the zero-basing).
Now the definition of such an array seems to be a bit counter-intuitive as you noted, but apparently it is this way just to be consistent with indexing syntax, otherwise it will be much more confusing.
C doesn't always work in an intuitive way because of things like the spiral rule, though maybe you're mis-applying it here.
As with any language, you need to accept the syntax for what it is, not what you think it is, or you'll constantly be fighting with the language on a semantic level.
Tools like cdecl explain it as:
declare data as array 3 of array 5 of int
This falls out of C's concept of declarators. The pointer-ness, array-ness, or function-ness of a declaration is specified in the declarator, while the type-ness is specified with a type specifier1:
int *p; // *p is the declarator
double arr[N][M]; // arr[N][M] is the declarator
char *foo( int x ); // *foo( int x ) is the declarator
This allows you to create arbitrarily complex types in a compact manner:
int *(*foo(void))[M][N];
foo is a function taking no parameters, returning a pointer to an M-element array of N-element arrays of pointer to int.
Thus, the actual type of an object or function is specified through the combination of the type specifier (and any qualifiers) and the declarator.
Unfortunately, "compact" is just another way of saying "eye-stabby". Declarations like that can be hard to read and understand. It does mean that things like multi-dimensional array declarations read kind of "backwards":
+---------------------------------+
| |
v |
type arr -> array-of -> array-of -+
^
|
start here
But, if you work it through, it does make sense. Let's start with some arbitrary type T. We declare an array of T as
T arr[N];
Thus, arr is an N-element array of T. Now we replace T with an array type R [M]. This gives us
R arr[N][M];
arr is still an N-element array of something, and that something is R [M], which is why we write arr[N][M] instead of arr[M][N].
And there are also storage-class qualifiers, type qualifiers, etc., but we won't go into those here.
Related
It is stated here that
The term modifiable lvalue is used to emphasize that the lvalue allows the designated object to be changed as well as examined. The following object types are lvalues, but not modifiable lvalues:
An array type
An incomplete type
A const-qualified type
A structure or union type with one of its members qualified as a const type
Because these lvalues are not modifiable, they cannot appear on the left side of an assignment statement.
Why array type object is not modifiable? Isn't it correct to write
int i = 5, a[10] = {0};
a[i] = 1;
?
And also, what is an incomplete type?
Assume the declaration
int a[10];
then all of the following are true:
the type of the expression a is "10-element array of int"; except when a is the operand of the sizeof or unary & operators, the expression will be converted to an expression of type "pointer to int" and its value will be the address of the first element in the array;
the type of the expression a[i] is int; it refers to the integer object stored as the i'th element of the array;
The expression a may not be the target of an assignment because C does not treat arrays like other variables, so you cannot write something like a = b or a = malloc(n * sizeof *a) or anything like that.
You'll notice I keep emphasizing the word "expression". There's a difference between the chunk of memory we set aside to hold 10 integers and the symbols (expressions) we use to refer to that chunk of memory. We can refer to it with the expression a. We can also create a pointer to that array:
int (*ptr)[10] = &a;
The expression *ptr also has type "10-element array of int", and it refers to the same chunk of memory that a refers to.
C does not treat array expressions (a, *ptr) like expressions of other types, and one of the differences is that an expression of array type may not be the target of an assignment. You cannot reassign a to refer to a different array object (same for the expression *ptr). You may assign a new value to a[i] or (*ptr)[i] (change the value of each array element), and you may assign ptr to point to a different array:
int b[10], c[10];
.....
ptr = &b;
.....
ptr = &c;
As for the second question...
An incomplete type lacks size information; declarations like
struct foo;
int bar[];
union bletch;
all create incomplete types because there isn't enough information for the compiler to determine how much storage to set aside for an object of that type. You cannot create objects of incomplete type; for example, you cannot declare
struct foo myFoo;
unless you complete the definition for struct foo. However, you can create pointers to incomplete types; for example, you could declare
struct foo *myFooPtr;
without completing the definition for struct foo because a pointer just stores the address of the object, and you don't need to know the type's size for that. This makes it possible to define self-referential types like
struct node {
T key; // for any type T
Q val; // for any type Q
struct node *left;
struct node *right;
};
The type definition for struct node isn't complete until we hit that closing }. Since we can declare a pointer to an incomplete type, we're okay. However, we could not define the struct as
struct node {
... // same as above
struct node left;
struct node right;
};
because the type isn't complete when we declare the left and right members, and also because each left and right member would each contain left and right members of their own, each of which would contain left and right members of their own, and on and on and on.
That's great for structs and unions, but what about
int bar[];
???
We've declared the symbol bar and indicated that it will be an array type, but the size is unknown at this point. Eventually we'll have to define it with a size, but this way the symbol can be used in contexts where the array size isn't meaningful or necessary. Don't have a good, non-contrived example off the top of my head to illustrate this, though.
EDIT
Responding to the comments here, since there isn't going to be room in the comments section for what I want to write (I'm in a verbose mood this evening). You asked:
Does it mean every variables are expression?
It means that any variable can be an expression, or part of an expression. Here's how the language standard defines the term expression:
6.5 Expressions
1 An expression is a sequence of operators and operands that specifies computation of a
value, or that designates an object or a function, or that generates side effects, or that
performs a combination thereof.
For example, the variable a all by itself counts as an expression; it designates the array object we defined to hold 10 integer values. It also evaluates to the address of the first element of the array. The variable a can also be part of a larger expression like a[i]; the operator is the subscript operator [] and the operands are the variables a and i. This expression designates a single member of the array, and it evaluates to the value currectly stored in that member. That expression in turn can be part of a larger expression like a[i] = 0.
And also let me clear that, in the declaration int a[10], does a[] stands for array type
Yes, exactly.
In C, declarations are based on the types of expressions, rather than the types of objects. If you have a simple variable named y that stores an int value, and you want to access that value, you simply use y in an expression, like
x = y;
The type of the expression y is int, so the declaration of y is written
int y;
If, on the other hand, you have an array of int values, and you want to access a specific element, you would use the array name and an index along with the subscript operator to access that value, like
x = a[i];
The type of the expression a[i] is int, so the declaration of the array is written as
int arr[N]; // for some value N.
The "int-ness" of arr is given by the type specifier int; the "array-ness" of arr is given by the declarator arr[N]. The declarator gives us the name of the object being declared (arr) along with some additional type information not given by the type specifier ("is an N-element array"). The declaration "reads" as
a -- a
a[N] -- is an N-element array
int a[N]; -- of int
EDIT2
And after all that, I still haven't told you the real reason why array expressions are non-modifiable lvalues. So here's yet another chapter to this book of an answer.
C didn't spring fully formed from the mind of Dennis Ritchie; it was derived from an earlier language known as B (which was derived from BCPL).1 B was a "typeless" language; it didn't have different types for integers, floats, text, records, etc. Instead, everything was simply a fixed length word or "cell" (essentially an unsigned integer). Memory was treated as a linear array of cells. When you allocated an array in B, such as
auto V[10];
the compiler allocated 11 cells; 10 contiguous cells for the array itself, plus a cell that was bound to V containing the location of the first cell:
+----+
V: | | -----+
+----+ |
... |
+----+ |
| | <----+
+----+
| |
+----+
| |
+----+
| |
+----+
...
When Ritchie was adding struct types to C, he realized that this arrangement was causing him some problems. For example, he wanted to create a struct type to represent an entry in a file or directory table:
struct {
int inumber;
char name[14];
};
He wanted the structure to not just describe the entry in an abstract manner, but also to represent the bits in the actual file table entry, which didn't have an extra cell or word to store the location of the first element in the array. So he got rid of it - instead of setting aside a separate location to store the address of the first element, he wrote C such that the address of the first element would be computed when the array expression was evaluated.
This is why you can't do something like
int a[N], b[N];
a = b;
because both a and b evaluate to pointer values in that context; it's equivalent to writing 3 = 4. There's nothing in memory that actually stores the address of the first element in the array; the compiler simply computes it during the translation phase.
1. This is all taken from the paper The Development of the C Language
The term "lvalue of array type" literally refers to the array object as an lvalue of array type, i.e. array object as a whole. This lvalue is not modifiable as a whole, since there's no legal operation that can modify it as a whole. In fact, the only operations you can perform on an lvalue of array type are: unary & (address of), sizeof and implicit conversion to pointer type. None of these operations modify the array, which is why array objects are not modifiable.
a[i] does not work with lvalue of array type. a[i] designates an int object: the i-th element of array a. The semantics of this expression (if spelled out explicitly) is: *((int *) a + i). The very first step - (int *) a - already converts the lvalue of array type into an rvalue of type int *. At this point the lvalue of array type is out of the picture for good.
Incomplete type is a type whose size is not [yet] known. For example: a struct type that has been declared but not defined, an array type with unspecified size, the void type.
An incomplete type is a type which is declared but not defined, for example struct Foo;.
You can always assign to individual array elements (assuming they are not const). But you cannot assign something to the whole array.
C and C++ are quite confusing in that something like int a[10] = {0, 1, 2, 3}; is not an assignment but an initialization even though it looks pretty much like an assignment.
This is OK (initialization):
int a[10] = {0, 1, 2, 3};
This is does not work in C/C++:
int a[10];
a = {0, 1, 2, 3};
Assuming a is an array of ints, a[10] isn't an array. It is an int.
a = {0} would be illegal.
Remember that the value of an array is actually the address (pointer) of its first element. This address can't be modified. So
int a[10], b[10];
a = b
is illegal.
It has of course nothing to do with modifying the content of the array as in a[1] = 3
I have heard many people saying that when we want to pass a 1D array to a function fun the following prototypes are equivalent:
1.
int fun(int a[]);
int fun(int a[10]);
int fun(int *a);
I have even heard people say that the 1st and 2nd one are internally converted to the 3rd one in C. I guess that this is true because doing something as sizeof(a) in the definition of the function declared in 2 gives the size of a pointer in bytes (and not 10*sizeof(int)).
Now that being said, I have seen texts claiming that to pass a 2D array to a function the following are equivalent:
1.
int fun(int a[][10]);
int fun(int (*a)[10]);
And here again I have heard people say that in C the 1st one is internally converted to the second one. If that is true, the following should have been equivalent right?
1.
int fun(int a[][]);
int fun(int (*a)[]);
But unfortunately the first one puts forth a compilation error but the second one does not:
1 | int fun(int a[][]);
| ^
t.c:2:13: note: declaration of ‘a’ as multidimensional array must have bounds for all dimensions except the first
This makes me feel that C is treating a in the first as a multidimensional array each of whose element is an integer array but their type is not complete (int[] namely).
While in the second one, a is just a pointer to an array of integers (with size not specified or incomplete type). And the two are indeed different and one format is not equivalent to the other...
Can anyone guide me in details as to what actually happens in C, in each of these cases?
First, the rules for declarations say that the element type of an array must be complete, per C 2018 6.7.6.2 1. So int a[][] gets a compiler error since the first [] specifies an array whose elements would be int [], which is incomplete.
After the declaration is analyzed, a declaration of a function parameter to be an array is adjusted to be a pointer, per C 2018 6.7.6.3 7.
int (*a)[] is allowed with no error because there is no rule that a pointer must point to a complete type. (If arithmetic is performed on the pointer, then the pointed-to type must be complete, per C 2018 6.5.6 2 and 3.)
The relevant rule is found in C17 6.7.6.3/7:
A declaration of a parameter as ‘‘array of type’’ shall be adjusted to ‘‘qualified pointer to type’’, where the type qualifiers (if any) are those specified within the [ and ] of the array type derivation
This is sometimes informally referred to as "array decay" and is similar to the rule where an array identifier is used in an expression.
I have even heard people say that the 1st and 2nd one are internally converted to the 3rd one in C
Yes that is correct, as per the above quoted rule. All 3 declarations in your first example are 100% equivalent.
Now that being said, I have seen texts claiming that to pass a 2D array to a function the following are equivalent: /--/
And here again I have heard people say that in C the 1st one is internally converted to the second one.
Correct.
int fun(int a[][]); is an invalid declaration because this is actually an array of incomplete array types, where all items of each array is an incomplete type of int. C does not allow declarations of arrays with elements of incomplete type.
C17 6.7.6.2/1
The element type shall not be an incomplete or function type.
We can however leave the outermost dimension with incomplete array type since it gets adjusted to a pointer anyway, making its size irrelevant. But that can't be done for the inner dimensions as per the above rule that elements of arrays must be of a complete type. (We can't have arrays of incomplete struct types either.)
int (*a)[] is valid since it's a pointer to an array of incomplete type. Similarly C allows us to use pointers to incomplete struct types, but we cannot declare objects of an incomplete struct type.
My intuition when I see this int array_name[x][y]; is an array of y arrays i.e. array_name[x] is one element out of y such elements. But turns out it's not so [in fact it's the opposite(?) ]
The guides/tutorials seem hellbent on bring matrices to explain this which makes it specific to 2D arrays. I'm looking to understand a general array_name[w][x] ... [n] syntax.
Note: fine, syntax is syntax, and this is how C defines it, okay. Then, is it true that array_name[w][x] ... [n] is simply an array of w elements each of which is array_name[x] ... [n]? But even this is not entirely correct because int a[][3] = {1,2,3,4,5,6,7}; is valid even though the RHS contains a number of elements not divisible by 3.
int x[5][3];
declares x as an array with 5 elements. Each of these elements is an array with 3 int. You're correct so far.
But you should compile with -Wall -Wextra. Look here:
k.c:2:16: warning: missing braces around initializer [-Wmissing-braces]
2 | int a[][3] = {1,2,3,4,5,6,7};
| ^
| { }{ }{}
It's valid to initialize it this way, but the more proper way of initializing it is:
int a[][3] = {{1,2,3},{4,5,6},{7,0,0}};
This is much more readable. The zeros are not needed. If you initialize one single element, all other elements will be zeroed.
One more thing is that you can go out of bounds without actually going out of bounds with multi dimensional arrays. DO NOTE THAT EVEN IF THIS IS LIKELY TO WORK, IT'S UNDEFINED BEHAVIOR, SO DON'T DO IT!
a[1][4] = (*a+1)[4]=*(*a+1)+4)
This is because [] is simply syntactic sugar for pointer arithmetic. So if you have declared T x[5][3]; for some type T, then x[1][1] will point to the same element as x[0][4]
But as I said, it's UB. Read more about it here
The way to read a multidimensional array declaration like
int arr[N][M];
is as an N-element array of M-element arrays of int. Each arr[i] has type int [M].
We can get there using substitution. Let's start with a simple object declaration:
T a;
a is an instance of something which we call T. Now replace T with the array type R [N]:
R a[N];
Important thing to note - since the [] operator is postfix in both expressions and declarations, when we substitute T with R [N] the [N] goes to the rightmost side of the declarator a, giving us R a[N]; the importance of this will be clear on the next round of substitution.
So now a is an N-element array of something, and that something is type R. Now we replace R with another array type, int [M]:
int a[N][M];
Again, since the [] operator is postfix, we need to add it to the rightmost side of the declarator a[N] when doing the substitution, giving us a[N][M]. a is still an N-element array of something, it's just now that something is "M-element array of int". Hence, a is an N-element array of M-element arrays of int.
But even this is not entirely correct because int a[][3] = {1,2,3,4,5,6,7}; is valid even though the RHS contains a number of elements not divisible by 3.
That's covered here:
6.7.9 Initialization
...
21 If there are fewer initializers in a brace-enclosed list than there are elements or members
of an aggregate, or fewer characters in a string literal used to initialize an array of known
size than there are elements in the array, the remainder of the aggregate shall be
initialized implicitly the same as objects that have static storage duration.
C 2011 Online Draft
So that initializer is interpreted as:
int a[][3] = {{1,2,3},{4,5,6},{7,0,0}};
It is stated here that
The term modifiable lvalue is used to emphasize that the lvalue allows the designated object to be changed as well as examined. The following object types are lvalues, but not modifiable lvalues:
An array type
An incomplete type
A const-qualified type
A structure or union type with one of its members qualified as a const type
Because these lvalues are not modifiable, they cannot appear on the left side of an assignment statement.
Why array type object is not modifiable? Isn't it correct to write
int i = 5, a[10] = {0};
a[i] = 1;
?
And also, what is an incomplete type?
Assume the declaration
int a[10];
then all of the following are true:
the type of the expression a is "10-element array of int"; except when a is the operand of the sizeof or unary & operators, the expression will be converted to an expression of type "pointer to int" and its value will be the address of the first element in the array;
the type of the expression a[i] is int; it refers to the integer object stored as the i'th element of the array;
The expression a may not be the target of an assignment because C does not treat arrays like other variables, so you cannot write something like a = b or a = malloc(n * sizeof *a) or anything like that.
You'll notice I keep emphasizing the word "expression". There's a difference between the chunk of memory we set aside to hold 10 integers and the symbols (expressions) we use to refer to that chunk of memory. We can refer to it with the expression a. We can also create a pointer to that array:
int (*ptr)[10] = &a;
The expression *ptr also has type "10-element array of int", and it refers to the same chunk of memory that a refers to.
C does not treat array expressions (a, *ptr) like expressions of other types, and one of the differences is that an expression of array type may not be the target of an assignment. You cannot reassign a to refer to a different array object (same for the expression *ptr). You may assign a new value to a[i] or (*ptr)[i] (change the value of each array element), and you may assign ptr to point to a different array:
int b[10], c[10];
.....
ptr = &b;
.....
ptr = &c;
As for the second question...
An incomplete type lacks size information; declarations like
struct foo;
int bar[];
union bletch;
all create incomplete types because there isn't enough information for the compiler to determine how much storage to set aside for an object of that type. You cannot create objects of incomplete type; for example, you cannot declare
struct foo myFoo;
unless you complete the definition for struct foo. However, you can create pointers to incomplete types; for example, you could declare
struct foo *myFooPtr;
without completing the definition for struct foo because a pointer just stores the address of the object, and you don't need to know the type's size for that. This makes it possible to define self-referential types like
struct node {
T key; // for any type T
Q val; // for any type Q
struct node *left;
struct node *right;
};
The type definition for struct node isn't complete until we hit that closing }. Since we can declare a pointer to an incomplete type, we're okay. However, we could not define the struct as
struct node {
... // same as above
struct node left;
struct node right;
};
because the type isn't complete when we declare the left and right members, and also because each left and right member would each contain left and right members of their own, each of which would contain left and right members of their own, and on and on and on.
That's great for structs and unions, but what about
int bar[];
???
We've declared the symbol bar and indicated that it will be an array type, but the size is unknown at this point. Eventually we'll have to define it with a size, but this way the symbol can be used in contexts where the array size isn't meaningful or necessary. Don't have a good, non-contrived example off the top of my head to illustrate this, though.
EDIT
Responding to the comments here, since there isn't going to be room in the comments section for what I want to write (I'm in a verbose mood this evening). You asked:
Does it mean every variables are expression?
It means that any variable can be an expression, or part of an expression. Here's how the language standard defines the term expression:
6.5 Expressions
1 An expression is a sequence of operators and operands that specifies computation of a
value, or that designates an object or a function, or that generates side effects, or that
performs a combination thereof.
For example, the variable a all by itself counts as an expression; it designates the array object we defined to hold 10 integer values. It also evaluates to the address of the first element of the array. The variable a can also be part of a larger expression like a[i]; the operator is the subscript operator [] and the operands are the variables a and i. This expression designates a single member of the array, and it evaluates to the value currectly stored in that member. That expression in turn can be part of a larger expression like a[i] = 0.
And also let me clear that, in the declaration int a[10], does a[] stands for array type
Yes, exactly.
In C, declarations are based on the types of expressions, rather than the types of objects. If you have a simple variable named y that stores an int value, and you want to access that value, you simply use y in an expression, like
x = y;
The type of the expression y is int, so the declaration of y is written
int y;
If, on the other hand, you have an array of int values, and you want to access a specific element, you would use the array name and an index along with the subscript operator to access that value, like
x = a[i];
The type of the expression a[i] is int, so the declaration of the array is written as
int arr[N]; // for some value N.
The "int-ness" of arr is given by the type specifier int; the "array-ness" of arr is given by the declarator arr[N]. The declarator gives us the name of the object being declared (arr) along with some additional type information not given by the type specifier ("is an N-element array"). The declaration "reads" as
a -- a
a[N] -- is an N-element array
int a[N]; -- of int
EDIT2
And after all that, I still haven't told you the real reason why array expressions are non-modifiable lvalues. So here's yet another chapter to this book of an answer.
C didn't spring fully formed from the mind of Dennis Ritchie; it was derived from an earlier language known as B (which was derived from BCPL).1 B was a "typeless" language; it didn't have different types for integers, floats, text, records, etc. Instead, everything was simply a fixed length word or "cell" (essentially an unsigned integer). Memory was treated as a linear array of cells. When you allocated an array in B, such as
auto V[10];
the compiler allocated 11 cells; 10 contiguous cells for the array itself, plus a cell that was bound to V containing the location of the first cell:
+----+
V: | | -----+
+----+ |
... |
+----+ |
| | <----+
+----+
| |
+----+
| |
+----+
| |
+----+
...
When Ritchie was adding struct types to C, he realized that this arrangement was causing him some problems. For example, he wanted to create a struct type to represent an entry in a file or directory table:
struct {
int inumber;
char name[14];
};
He wanted the structure to not just describe the entry in an abstract manner, but also to represent the bits in the actual file table entry, which didn't have an extra cell or word to store the location of the first element in the array. So he got rid of it - instead of setting aside a separate location to store the address of the first element, he wrote C such that the address of the first element would be computed when the array expression was evaluated.
This is why you can't do something like
int a[N], b[N];
a = b;
because both a and b evaluate to pointer values in that context; it's equivalent to writing 3 = 4. There's nothing in memory that actually stores the address of the first element in the array; the compiler simply computes it during the translation phase.
1. This is all taken from the paper The Development of the C Language
The term "lvalue of array type" literally refers to the array object as an lvalue of array type, i.e. array object as a whole. This lvalue is not modifiable as a whole, since there's no legal operation that can modify it as a whole. In fact, the only operations you can perform on an lvalue of array type are: unary & (address of), sizeof and implicit conversion to pointer type. None of these operations modify the array, which is why array objects are not modifiable.
a[i] does not work with lvalue of array type. a[i] designates an int object: the i-th element of array a. The semantics of this expression (if spelled out explicitly) is: *((int *) a + i). The very first step - (int *) a - already converts the lvalue of array type into an rvalue of type int *. At this point the lvalue of array type is out of the picture for good.
Incomplete type is a type whose size is not [yet] known. For example: a struct type that has been declared but not defined, an array type with unspecified size, the void type.
An incomplete type is a type which is declared but not defined, for example struct Foo;.
You can always assign to individual array elements (assuming they are not const). But you cannot assign something to the whole array.
C and C++ are quite confusing in that something like int a[10] = {0, 1, 2, 3}; is not an assignment but an initialization even though it looks pretty much like an assignment.
This is OK (initialization):
int a[10] = {0, 1, 2, 3};
This is does not work in C/C++:
int a[10];
a = {0, 1, 2, 3};
Assuming a is an array of ints, a[10] isn't an array. It is an int.
a = {0} would be illegal.
Remember that the value of an array is actually the address (pointer) of its first element. This address can't be modified. So
int a[10], b[10];
a = b
is illegal.
It has of course nothing to do with modifying the content of the array as in a[1] = 3
Are
int (*x)[10];
and
int x[10];
equivalent?
According to the "Clockwise Spiral" rule, they parse to different C declarations.
For the click-weary:
The ``Clockwise/Spiral Rule'' By David
Anderson
There is a technique known as the
``Clockwise/Spiral Rule'' which
enables any C programmer to parse in
their head any C declaration!
There are three simple steps to follow:
1. Starting with the unknown element, move in a spiral/clockwise direction;
when ecountering the following elements replace them with the
corresponding english statements:
[X] or []
=> Array X size of... or Array undefined size of...
(type1, type2)
=> function passing type1 and type2 returning...
*
=> pointer(s) to...
2. Keep doing this in a spiral/clockwise direction until all tokens have been covered.
3. Always resolve anything in parenthesis first!
Follow this simple process when reading declarations:
Start at the variable name (or
innermost construct if no identifier
is present. Look right without jumping
over a right parenthesis; say what you
see. Look left again without jumping
over a parenthesis; say what you see.
Jump out a level of parentheses if
any. Look right; say what you see.
Look left; say what you see. Continue
in this manner until you say the
variable type or return type.
So:
int (*x)[10];
x is a pointer to an array of 10 ints
int x[10];
x is an array of 10 ints
int *x[10];
x is an array of 10 pointers to ints
They are not equal. in the first case x is a pointer to an array of 10 integers, in the second case x is an array of 10 integers.
The two types are different. You can see they're not the same thing by checking sizeof in the two cases.
I tend to follow The Precedence Rule for Understanding C Declarations which is given very nicely in the book Expert C Programming - Deep C Secrets by Peter van der Linden
A - Declarations are read by starting with the name and then reading in
precedence order.
B - The precedence, from high to low, is:
B.1 parentheses grouping together parts of a declaration
B.2 the postfix operators:
parentheses () indicating a function, and
square brackets [] indicating an array.
B.3 the prefix operator: the asterisk denoting "pointer to".
C If a const and/or volatile keyword is next to a type specifier (e.g. int,
long, etc.) it applies to the type specifier.
Otherwise the const and/or volatile keyword
applies to the pointer asterisk on its immediate left.
For me, it's easier to remember the rule as absent any explicit grouping, () and [] bind before *. Thus, for a declaration like
T *a[N];
the [] bind before the *, so a is an N-element array of pointer. Breaking it down in steps:
a -- a
a[N] -- is an N-element array
*a[N] -- of pointer
T *a[N] -- to T.
For a declaration like
T (*a)[N];
the parens force the * to bind before the [], so
a -- a
(*a) -- is a pointer
(*a)[N] -- to an N-element array
T (*a)[N] -- of T
It's still the clockwise/spiral rule, just expressed in a more compact manner.
No. First one declares an array of 10 int pointers and second one declares an array of 10 ints.