Is there a reason why an array name is not an lvalue? - c

For example,
int x[10];
int i = 0;
x = &i; //error occurs!
According to C - A Reference Manual, an array name cannot be an lvalue. Thus, x cannot be an lvalue. But, what is the reason the array name cannot be an lvalue? For example, why does an error occur in the third line?

Your reference is incorrect. An array can be an lvalue (but not a modifiable lvalue), and an "array name" (identifier) is always an lvalue.
Take your example:
int x[10];
int i = 0;
x = &i; //error occurs!
Apply C11 6.5.1, paragraph 2:
An identifier is a primary expression, provided it has been declared
as designating an object (in which case it is an lvalue) ...
We see that x is a primary expression and is an lvalue, because it has previously been declared as designating an array object.
However, the C language rules state that an array expression in various contexts, including the left-hand-side of an assignment expression, are converted to a pointer which points at the first element of the array and is not an lvalue, even if the array was. Specifically:
Except when it is the operand of the sizeof operator, the _Alignof 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. If the array object has
register storage class, the behavior is undefined.
(C11 6.3.2.1 paragraph 3).
The pointer which is the result of the conversion specified above is not an lvalue because an lvalue designates an object, and there is no suitable object holding the pointer value; the array object holds the elements of the array, not a pointer to those elements.
The example you use in your question implies that you understand that an array expression decays (is converted to) a pointer value, but I think you are failing to recognize that after the conversion, the pointer value and the array are two different things. The pointer is not an lvalue; the array might be (and in your example, it is). Whether or not arrays are lvalues in fact has no bearing on your example; it is the pointer value that you are trying to assign to.
If you were to ask instead: Why do arrays decay to pointers when they are on the left-hand-side of an assignment operator? - then I suspect that there is no particularly good answer. C just doesn't allow assignment to arrays, historically.

Array names are non-modifiable lvalues in C.:)
Arrays are named extents of memory where their elements are placed. So you may not substitute one extent of memory for another extent of memory. Each extent of memory initially allocated for an array declaration has its own unique name. Each array name is bounded with its own extent of memory.

An array is an lvalue, however it is a non-modifiable lvalue.
It most likely has to do with compatibility of types. For example, you can do this:
struct ss {
char c[10];
};
...
struct ss s1 = { { "hello" } };
struct ss s2 = s1;
But not this:
char s1[10] = "hello";
char s2[10] = s1;

It's true that array names yield pointer values in many contexts. But so does the & operator, and you don't expect that to be assignable.
int i = 42;
int *j = malloc(sizeof *j);
&i = j; /* obviously wrong */
int a[] = {1,2,3};
&a[0] = j; /* also obviously wrong */
a = j; /* same as the previous line! */
So when learning the relationship between arrays and pointers, remember that a is usually the same as &a[0] and then you won't think lvalue-ness is an exception to the rule - it follows the rule perfectly.

Related

Can the pointer dereferencing sign * be called "many"?

I saw that pointer to either a single variable or an array in C is declared and printed using the same syntax. So I thought of * to be called many like in regular expression sign * which means many such as x*=xxx....
int a[3] = { 4, 5, 6 };
int b = 3;
int *p, *q; // same syntax for either single variable or array
p = a; // pointer to many int
q = &b; // pointer to an int
printf("%d %d", *p, *q); // same syntax again
So can I call both p and q as pointer to many int for ease of understanding?
No, that's not a good idea. A pointer can only point to a single variable, although that is often the first element of an array. p = a; is the pointer to the first element in the array a, and only to that one. The pointer in itself can't know if there's more valid data following the address it is currently pointing at.
For example, it's not meaningful to write a function like void print_array(int* p) since that function won't know the size of the array, since p only points at the first element. We would rather write something like void print_array(int* p, int size)
Advanced topic (ignore if beginner): A pointer to "many int" would rather be the more exotic feature called "array pointer", such as int (*)[3] = &a;. You can think of this as "a pointer to the whole array" rather than just to the first element.
Well, not really, the types are not interchangeable.
While used as the RHS of assignment (among many other cases), a variable with type array decays to the type of pointer to the first element of the array.note
So,
p = a;
is the same as
p = &(a[0]);
where both the sides are of type pointer to integer (int *).
NOTE:
Quoting C11, chapter §6.3.2.1
Except when it is the operand of the sizeof operator, the _Alignof 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. [...]

C - Assigning pointers to arrays? Based on four examples

Assume following Code:
#include <stdio.h>
#include <stdlib.h>
int main (int argc, char **argv)
{
int arrayXYZ[10];
int i;
int *pi;
int intVar;
for (i=0; i<10; i++){
arrayXYZ[i] = i;
}
pi = arrayXYZ; // Reference 1
pi++; // Reference 2
arrayXYZ++; // Reference 3
arrayXYZ = pi; // Reference 4
}
Reference 1 is correct: pi points to first element in arrayXYZ -> *pi = 0
Reference 2 is correct: element to which pi points is incremented -> *pi = 1
Reference 3 is not correct: I am not completely sure why. Every integer needs 4 bits of memory. Hence, we cannot increment the address of the head of the array by just one? Assume, we had a char array with sizeof(char)=1 -> Would the head of the array point to the next bucket?
Reference 4 is not correct: I am not completely sure why. Why cannot the head of the array point to the address to which pi points?
Thanks for all clarifications!
I am a new member, so if my question doesn't follow the Stackoverflow guidelines, feel free to tell me how I can improve my next questions!
arrayXYZ++;
This is equivalent to:
arrayXYZ += 1;
which is equivalent to:
arrayXYZ = arrayXYZ + 1;
This is not allowed because the C language does not allow it. An array can not be assigned to.
arrayXYZ = pi;
This fails for the same reason. An array can not be assigned to.
The other assignments work because you are allowed to assign to a pointer.
Also keep in mind that arrays and pointers are distinct datatypes. In C, there are circumstances where arrays decay into a pointer to their first element for convenience purposes. Which is why this works:
pi = arrayXYZ;
However, this is just an automatic conversion, so that you don't have to write:
pi = &arrayXYZ[0];
This automatic conversion does not mean that arrays are the same thing as pointers.
From C11 standard §6.3.2.1 (N1570)
An lvalue is an expression (with an object type other than void) that potentially designates an object;64) if an lvalue does not designate an object when it is evaluated, the behavior is undefined. When an object is said to have a particular type, the type is specified by the lvalue used to designate the object. A modifiable lvalue is an lvalue that does not have array type, does not have an incomplete type, does not have a const- qualified type, and if it is a structure or union, does not have any member (including, recursively, any member or element of all contained aggregates or unions) with a const- qualified type.
And also From §6.5.2.4
The operand of the postfix increment or decrement operator shall have atomic, qualified, or unqualified real or pointer type, and shall be a modifiable lvalue.
As pointed out here these are the reasons why those statements are illegal. Same way for assignment operation the left one has to be modifiable. Here it is not. That's why the problem.
Now to explain why the other two works - there is a thing called array decay. Array in most situations (exceptions are when used in operand of &, sizeof etc) are converted to pointer to the first element of the array and that pointer is being assigned to the pi. This is modifiable. And that's why you can easily apply ++ over it.

Are these pointer lvalues?

According to "C - A Reference Manual", an lvalue is an expression that refers to an object in such a way that the object may be examined or altered.
For example,
int ranks[10];
Then are ranks and ranks+3 lvalues? Since ranks(pointer to first element of array) and ranks+3(pointer to fourth element of array) refer to objects, I guess they are lvalues -- or are they not lvalues?
ranks+3 is not an lvalue for the same reason that 1+3 is not an lvalue; the result is simply a numerical (pointer) value, not a reference to an object.
Put another way, if the result of ranks + 3 is 0xdeadbeef, you cannot write
0xdeadbeef = 10;
for the same reason you cannot write
4 = 10;
However, *(ranks+3) is an lvalue, because you are dereferencing that pointer value; it is the 4th element of the ranks array, and is equivalent to ranks[3].
Similarly, ranks is not an lvalue except in the context of sizeof or the unary & operator. Since array expressions "decay" to pointer expressions in most contexts, you again wind up with the situation of
ranks = 10;
being equivalent to
0xdeadbeef = 10;
Again, you'd have to dereference ranks as either *ranks or ranks[0] for it to be an lvalue.
In this context, the names aren't lvalues, but they can be dereferenced with * to form lvalues. For example:
int ranks[10];
// ranks = 42; <--- doesn't compile! error: assignment to expression with array type
*ranks = 42; // OK: same as ranks[0] = 42
//ranks+3 = 42; <--- error: lvalue required as left operand of assignment
*(ranks+3) = 42; //OK: same as ranks[3] = 42
This answer lists the few cases in C when the name of an array doesn't decay to a pointer to the array's first element. On the left-hand side of an assignment isn't one of those cases. Therefore, in ranks = ..., ranks is (decays to, acts as) a pointer to the first element of the array. You have to dereference that pointer (*ranks) to get one of the array elements as an lvalue.
Just don't ask me what an lvalue is. I know it when I see it. :)
Tested on gcc.

casting pointer to array into pointer

Consider the following C code:
int arr[2] = {0, 0};
int *ptr = (int*)&arr;
ptr[0] = 5;
printf("%d\n", arr[0]);
Now, it is clear that the code prints 5 on common compilers. However, can somebody find the relevant sections in the C standard that specifies that the code does in fact work? Or is the code undefined behaviour?
What I'm essentially asking is why &arr when casted into void * is the same as arr when casted into void *? Because I believe the code is equivalent to:
int arr[2] = {0, 0};
int *ptr = (int*)(void*)&arr;
ptr[0] = 5;
printf("%d\n", arr[0]);
I invented the example while thinking about the question here: Pointer-to-array overlapping end of array ...but this is clearly a distinct question.
For unions and structures, cf. ISO 9899:2011§6.7.2.1/16f:
16 The size of a union is sufficient to contain the largest of its members. The value of at most one of the members can be stored in a union object at any time. A pointer to a union object, suitably converted, points to each of its members (or if a member is a bit-field, then to the unit in which it resides), and vice versa.
17 Within a structure object, the non-bit-field members and the units in which bit-fields reside have addresses that increase in the order in which they are declared. A pointer to a structure object, suitably converted, points to its initial member (or if that member is a bit-field, then to the unit in which it resides), and vice versa. There may be unnamed padding within a structure object, but not at its beginning.
For array types, the situation is a bit more complex. First, observe what an array is, from ISO 9899:2011§6.2.5/20:
An array type describes a contiguously allocated nonempty set of objects with a particular member object type, called the element type. The element type shall be complete whenever the array type is specified. Array types are characterized by their element type and by the number of elements in the array. An array type is said to be derived from its element type, and if its element type is T, the array type is sometimes
called “array of T”. The construction of an array type from an element type is called “array type derivation”.
The wording “contiguously allocated” implies that there is no padding between array members. This notion is affirmed by footnote 109:
Two objects may be adjacent in memory because they are adjacent elements of a larger array or adjacent members of a structure with no padding between them, or because the implementation chose to place them so, even though they are unrelated. If prior invalid pointer operations (such as accesses outside array bounds) produced undefined behavior, subsequent comparisons also produce undefined behavior.
The use of the sizeof operator in §6.5.3.5, Example 2 expresses the intent that there is also no padding before or after arrays:
EXAMPLE 2
Another use of the sizeof operator is to compute the number of elements
in an array:
sizeof array / sizeof array[0]
I therefore conclude that a pointer to an array, converted to a pointer to the element typo of that array, points to the first element in the array. Furthermore, observe what the definition of equality says about pointers (§6.5.9/6f.):
6 Two pointers compare equal if and only if both are null pointers, both are pointers to the same object (including a pointer to an object and a subobject at its beginning) or function, both are pointers to one past the last element of the same array object, or one is a pointer
to one past the end of one array object and the other is a pointer to the start of a different array object that happens to immediately follow the first array object in the address space.109)
7 For the purposes of these operators, a pointer to an object that is not an element of an array behaves the same as a pointer to the first element of an array of length one with the type of the object as its element type.
Since the first element of an array is “a subobject at its beginning,” a pointer to the first element of an array and a pointer to an array compare equal.
Here is a slightly refactored version of your code for easier reference:
int arr[2] = { 0, 0 };
int *p1 = &arr[0];
int *p2 = (int *)&arr;
with the question being: Is p1 == p2 true, or unspecified, or UB?
Firstly: I think that it is intended by the authors of C's abstract memory model that p1 == p2 is true; and if the Standard doesn't actually spell it out then it would be a defect in the Standard.
Moving on; the only relevant piece of text seems to be C11 6.3.2.3/7 (irrelevant text excised):
A pointer to an object type may be converted to a pointer to a different object type. [...] When converted back again, the result shall compare equal to the original pointer.
When a pointer to an object is converted to a pointer to a character type, the result points to the lowest addressed byte of the object. Successive increments of the result, up to the size of the object, yield pointers to the remaining bytes of the object.
It doesn't specifically say what the result of the first conversion is. Ideally it should say ...and the pointer points to the same address, but it doesn't.
However, I argue that it it is implied that the pointer must point to the same address after the conversion. Here is an illustrative example:
void *v1 = malloc( sizeof(int) );
int *i1 = (int *)v1;
If we do not accept "and the pointer points to the same address" then i1 might not actually point into the malloc'd space, which would be ridiculous.
My conclusion is that we should read 6.3.2.3/7 as saying that the pointer cast does not change the address being pointed to. The part about using pointers to character type seems to back this up.
Therefore, since p1 and p2 have the same type and point to the same address, they compare equal.
To answer directly:
Can somebody find the relevant sections in the C standard that specifies that the code does in fact work?
6.3.2.1 Lvalues, arrays, and function designators, paragraph 1
6.3.2.3 Pointers, paragraphs 1,5 and 6
6.5.3.2 Address and indirection operators, paragraph 3
Or is the code undefined behaviour?
The code you posted is not undefined, but it "might" be compiler/implementation specific (per section 6.3.2.3 p5/6)
What I'm essentially asking is why &arr when casted into void * is the same as arr when casted into void *?
This would imply asking why int *ptr = (int*)(void*)&arr gives the same results as int *ptr = (int*)(void*)arr;, but per your code posted, you're actually asking why int *ptr = (int*)(void*)&arr gives the same as int *ptr = (int*)&arr.
Either way I'll expand on what your code is actually doing to help clarify:
Per 6.3.2.1p3:
Except when it is the operand of the sizeof operator, the _Alignof 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. If the array object has register storage class, the behavior is undefined.
and per 6.5.3.2p3:
The unary & operator yields the address of its operand. If the operand has type ‘‘type’’, the result has type ‘‘pointer to type’’.
So in your first declaration
int arr[2] = {0, 0};
arr is initialized to an array type containing 2 elements of type int both equal to 0. Then per 6.3.2.1p3 it is "decayed" into a pointer type pointing to the first element anywhere it is called in scope (except when it's used like sizeof(arr), &arr, ++arr or --arr).
So in your next line, you could simply just do the following:
int *ptr = arr; or int *ptr = &*arr; or int *ptr = &arr[0];
and ptr is now a pointer to an int type that points to the first element of the array arr (i.e. &arr[0]).
Instead you declare it as such:
int *ptr = (int*)&arr;
Lets break this down into it's parts:
&arr -> triggers the exception to 6.3.2.1p3 so instead of getting &arr[0], you get the address to arr which is an int(*)[2] type (not an int* type), so you are not getting a pointer to an int, you are getting a pointer to an int array
(int*)&arr, (i.e. the cast to int*) -> per 6.5.3.2p3, &arr takes the address of the variable arr returning a pointer to the type of it, so simply saying int* ptr = &arr will give a warning of "incompatible pointer types" (since ptr is of type int* and &arr is of type int(*)[2]) which is why you need to cast to an int*.
Further per 6.3.2.3p1: "a pointer to void may be converted to or from a pointer to any object type. A pointer to any object type may be converted to a pointer to void and back again; the result shall compare equal to the original pointer".
So, you're declaration of int* ptr = (int*)(void*)&arr; would produce the same results as int* ptr = (int*)&arr; because of the types you are using and converting to/from. Also as a note: ptr[0] = 5; is the same as *ptr = 5, where ptr[1] = 5; would also be the same as *++ptr = 5;
Some of the references:
6.3.2.1 Lvalues, arrays, and function designators
1. An lvalue is an expression (with an object type other than void) that potentially designates an object (*see note); if an lvalue does not designate an object when it is evaluated, the behavior is undefined. When an object is said to have a particular type, the type is specified by the lvalue used to designate the object. A modifiable lvalue is an lvalue that does not have array type, does not have an incomplete type, does not have a constqualified type, and if it is a structure or union, does not have any member (including, recursively, any member or element of all contained aggregates or unions) with a constqualified type.
*The name ‘‘lvalue’’ comes originally from the assignment expression E1 = E2, in which the left operand E1 is required to be a (modifiable) lvalue. It is perhaps better considered as representing an object ‘‘locator value’’. What is sometimes called ‘‘rvalue’’ is in this International Standard described as the ‘‘value of an expression’’. An obvious example of an lvalue is an identifier of an object. As a further example, if E is a unary expression that is a pointer to an object, *E is an lvalue that designates the object to which E points.
2. Except when it is the operand of the sizeof operator, the _Alignof operator, the unary & operator, the ++ operator, the -- operator, or the left operand of the . operator or an assignment operator, an lvalue that does not have array type is converted to the value stored in the designated object (and is no longer an lvalue); this is called lvalue conversion. If the lvalue has qualified type, the value has the unqualified version of the type of the lvalue; additionally, if the lvalue has atomic type, the value has the non-atomic version of the type of the lvalue; otherwise, the value has the type of the lvalue. If the lvalue has an incomplete type and does not have array type, the behavior is undefined. If the lvalue designates an object of automatic storage duration that could have been declared with the register storage class (never had its address taken), and that object is uninitialized (not declared with an initializer and no assignment to it has been performed prior to use), the behavior is undefined.
3. Except when it is the operand of the sizeof operator, the _Alignof 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. If the array object has register storage class, the behavior is undefined.
6.3.2.3 Pointers
1. A pointer to void may be converted to or from a pointer to any object type. A pointer to any object type may be converted to a pointer to void and back again; the result shall compare equal to the original pointer.
5. An integer may be converted to any pointer type. Except as previously specified, the result is implementation-defined, might not be correctly aligned, might not point to an entity of the referenced type, and might be a trap representation (the mapping functions for converting a pointer to an integer or an integer to a pointer are intended to be consistent with the addressing structure of the execution environment).
6. Any pointer type may be converted to an integer type. Except as previously specified, the result is implementation-defined. If the result cannot be represented in the integer type, the behavior is undefined. The result need not be in the range of values of any integer type.
6.5.3.2 Address and indirection operators
1. The operand of the unary & operator shall be either a function designator, the result of a [] or unary * operator, or an lvalue that designates an object that is not a bit-field and is not declared with the register storage-class specifier.
3. The unary & operator yields the address of its operand. If the operand has type ‘‘type’’, the result has type ‘‘pointer to type’’. If the operand is the result of a unary * operator, neither that operator nor the & operator is evaluated and the result is as if both were omitted, except that the constraints on the operators still apply and the result is not an lvalue. Similarly, if the operand is the result of a [] operator, neither the & operator nor the unary * that is implied by the [] is evaluated and the result is as if the & operator were removed and the [] operator were changed to a + operator. Otherwise, the result is a pointer to the object or function designated by its operand.
4. The unary * operator denotes indirection. If the operand points to a function, the result is a function designator; if it points to an object, the result is an lvalue designating the object. If the operand has type ‘‘pointer to type’’, the result has type ‘‘type’’. If an invalid value has been assigned to the pointer, the behavior of the unary * operator is undefined (*see note).
*Thus, &*E is equivalent to E (even if E is a null pointer), and &(E1[E2]) to ((E1)+(E2)). It is always true that if E is a function designator or an lvalue that is a valid operand of the unary & operator, *&E is a function designator or an lvalue equal to E. If *P is an lvalue and T is the name of an object pointer type, *(T)P is an lvalue that has a type compatible with that to which T points. Among the invalid values for dereferencing a pointer by the unary * operator are a null pointer, an address inappropriately aligned for the type of object pointed to, and the address of an object after the end of its lifetime.
6.5.4 Cast operators
5. Preceding an expression by a parenthesized type name converts the value of the expression to the named type. This construction is called a cast (a cast does not yield an lvalue; thus, a cast to a qualified type has the same effect as a cast to the unqualified version of the type). A cast that specifies no conversion has no effect on the type or value of an expression.
6. If the value of the expression is represented with greater range or precision than required by the type named by the cast (6.3.1.8), then the cast specifies a conversion even if the type of the expression is the same as the named type and removes any extra range and precision.
6.5.16.1 Simple assignment
2. In simple assignment (=), the value of the right operand is converted to the type of the assignment expression and replaces the value stored in the object designated by the left operand.
6.7.6.2 Array declarators
1. In addition to optional type qualifiers and the keyword static, the [ and ] may delimit an expression or *. If they delimit an expression (which specifies the size of an array), the expression shall have an integer type. If the expression is a constant expression, it shall have a value greater than zero. The element type shall not be an incomplete or function type. The optional type qualifiers and the keyword static shall appear only in a declaration of a function parameter with an array type, and then only in the outermost array type derivation.
3. If, in the declaration ‘‘T D1’’, D1 has one of the forms:
D[ type-qualifier-listopt assignment-expressionopt ]
D[ static type-qualifier-listopt assignment-expression ]
D[ type-qualifier-list static assignment-expression ]
D[ type-qualifier-listopt * ]
and the type specified for ident in the declaration ‘‘T D’’ is ‘‘derived-declarator-type-list T’’, then the type specified for ident is ‘‘derived-declarator-type-list array of T’’.142) (See 6.7.6.3 for the meaning of the optional type qualifiers and the keyword static.)
4. If the size is not present, the array type is an incomplete type. If the size is * instead of being an expression, the array type is a variable length array type of unspecified size, which can only be used in declarations or type names with function prototype scope;143) such arrays are nonetheless complete types. If the size is an integer constant expression and the element type has a known constant size, the array type is not a variable length array type; otherwise, the array type is a variable length array type. (Variable length arrays are a conditional feature that implementations need not support; see 6.10.8.3.)
5. If the size is an expression that is not an integer constant expression: if it occurs in a declaration at function prototype scope, it is treated as if it were replaced by *; otherwise, each time it is evaluated it shall have a value greater than zero. The size of each instance of a variable length array type does not change during its lifetime. Where a size expression is part of the operand of a sizeof operator and changing the value of the size expression would not affect the result of the operator, it is unspecified whether or not the size expression is evaluated.
6. For two array types to be compatible, both shall have compatible element types, and if both size specifiers are present, and are integer constant expressions, then both size specifiers shall have the same constant value. If the two array types are used in a context which requires them to be compatible, it is undefined behavior if the two size specifiers evaluate to unequal values.
P.S. As a side note, given the following code:
#include <stdio.h>
int main(int argc, char** argv)
{
int arr[2] = {10, 20};
X
Y
printf("%d,%d\n", arr[0],arr[1]);
return 0;
}
where X was one of the following:
int *ptr = (int*)(void*)&arr;
int *ptr = (int*)&arr;
int *ptr = &arr[0];
and Y was one of the following:
ptr[0] = 15;
*ptr = 15;
When compiled on OpenBSD with gcc version 4.2.1 20070719 and providing the -S flag, the assembler output for all files was exactly the same.

Increment a vector adress and assign it to a pointer

Why can't I do p=numbers++ or p=++numbers? The compiler shows the message: "lvalue required as increment operand" but isn't the pointer p a left value?
int main(int argc, char *argv[]){
int numbers[] = {1,2,3,4,5,6}, *p;
p=numbers++;
printf("%d ",*p);
return 0;
}
When you declare and define an array, the name of the array is an expression that evaluates to the address of the first element of the array. Think of the name as a constant that holds the address of the first element.
Once memory has been allocated for the array, the address of the array cannot be changed. Consequently, the value of the array identifier (name) cannot be changed either.
In your code, when you have
p = numbers++;
you are asking that numbers, which has the constant value of the address of the first element of the array, be incremented and point to the second element instead. This is not a legal operation. The increment operand requires a modifiable lvalue, and the name of the array is not a modifiable lvalue.
What is an lvalue? It is a locator value: something that identifies an area of memory that can hold another value. So when you declare:
int a = 5;
a signifies an area of memory large enough to hold an int, and the int held in that area of memory is 5. You can change that int by assignment:
a = 7;
but the identifier a still signifies the same area of memory. This means a is a modifiable lvalue.
When you declare and define your array:
int numbers[] = { 1, 2, 3, 4, 5, 6 };
numbers is an lvalue, in that it specifies an area of memory that holds the array of specified integers, but it is not modifiable. You can change the value of the elements in numbers, but you cannot change the value of numbers itself. numbers always evaluates to &numbers[0].
If you want to change where p points so that it points to the second element instead of the first, use:
p = numbers + 1;
This does not change the value of numbers itself, only that of p. numbers is still &numbers[0] and p will be &numbers[1].
Hope this is clear!
numbers++ is the equivalent of numbers = numbers + 1
However, numbers here is an array and you cannot modify the address of an array.
lvalue doesn't mean a "left value"
First I recommend that you read the C FAQ's section on Arrays and Pointers it is one of the better general references.
To directly address your error, if we look at the draft C99 standard we see that in sections 6.5.2.4 Postfix increment and decrement operators and
6.5.3.1 Prefix increment and decrement operators say in paragraph 1:
The operand of the prefix increment or decrement operator shall have qualified or
unqualified real or pointer type and shall be a modifiable lvalue.
section 6.3.2.1 Lvalues, arrays, and function designators paragraph 3 says:
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. [...]
and paragraph 1 says:
[...] A modifiable lvalue is an lvalue that does not have array type, [...]
so even though an array will decay to a pointer to the first element in many circumstances it is not a lvalue.
numbers is NOT a pointer. It is an array with this definition int numbers[] = {1,2,3,4,5,6}, and a rvalue. So the compiler shows the message: "lvalue required as increment operand".
Difference between array and pointer. Pointers - Difference between Array and Pointer

Resources