问题
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?
回答1:
Assume the declaration
int a[10];
then all of the following are true:
- the type of the expression
a
is "10-element array ofint
"; except whena
is the operand of thesizeof
or unary&
operators, the expression will be converted to an expression of type "pointer toint
" and its value will be the address of the first element in the array; - the type of the expression
a[i]
isint
; it refers to the integer object stored as thei
'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 likea = b
ora = 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
回答2:
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.
回答3:
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};
回答4:
Assuming a
is an array of ints, a[10]
isn't an array. It is an int
.
a = {0}
would be illegal.
回答5:
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
来源:https://stackoverflow.com/questions/17687429/why-array-type-object-is-not-modifiable