This question already has answers here:
Why do C and C++ support memberwise assignment of arrays within structs, but not generally?
(5 answers)
Closed 8 years ago.
What is the difference between the two array assignments, one inside a struct and one outside a struct?
struct A
{
char s[4];
};
int main(int argc, char *argv[])
{
char s[4];
char d[4];
d = s; // 'invalid array assignment'
A a, b;
b = a; // compiles without problems
return 0;
}
The default operator = is supposed to invoke member-by-member assignment operators. If so, then there should exist an array assignment operator, but the compiler does not want to invoke it explicitly. Why?
I think this is why... Struct is a class object and there is this special rule for assignment of member its an array, not for an array itself (c++14 draft):
12.8 Copying and moving class objects
12.8.28. The implicitly-defined copy/move assignment operator for a non-union class X performs memberwise copy-
/move assignment of its subobjects. The direct base classes of X are assigned first, in the order of their
declaration in the base-specifier-list, and then the immediate non-static data members of X are assigned, in
the order in which they were declared in the class definition. Let x be either the parameter of the function
or, for the move operator, an xvalue referring to the parameter. Each subobject is assigned in the manner
appropriate to its type:
— if the subobject is an array, each element is assigned, in the manner appropriate to the element type;
So the copy procedure is not defined for an array (because it's an unmodifiable variable type), but for a member type that is an array. There is no definition for implicit operator= for an array.
The value of an array name (e.g., s) is the starting address of the array. Once the array is allocated in memory, that address should be fixed. d = s is saying to assign the starting address of s[4] to d[4], which obviously can't be done.
A simple structure like yours is just a chuck of bits. In your case, an instance of A occupies 4 bytes. When you do a = b, it copies the bits of b to that of a.
To illustrate the difference, I don't think you can do a.s = b.s. You can try.
Related
In the following class:
struct S {
S() : B{} {}
const uint8_t B[32];
};
Are all 32 bytes of the B array guaranteed to be initialized to zero by the default constructor?
Is there any way to create an object of type S such that any element of the B array is not zero? (without const casting or reinterpretting memory). Do all forms of initialization of S lead to a zeroed B array?
Are all 32 bytes of the B array guaranteed to be initialized to zero by the default constructor?
Yes, B is value-initialized which for an array means each member is value-initialized - primitive types are value-initialized to 0.
Is there any way to create an object of type S such that any element of the B array is not zero?
Not as far as I know, although S still has the default copy constructor so if somehow you got an S with non-zero B, you can clone those objects.
const member guarantees the values cannot be changed throughout the lifetime, so any non-zero value must be set at initialization which leads to the third question...
Do all forms of initialization of S lead to a zeroed B array?
Yes, S is not an aggregate (due to user-provided ctor) so there is no way how to initialize the members directly.
I understand that memberwise assignment of arrays is not supported, such that the following will not work:
int num1[3] = {1,2,3};
int num2[3];
num2 = num1; // "error: invalid array assignment"
I just accepted this as fact, figuring that the aim of the language is to provide an open-ended framework, and let the user decide how to implement something such as the copying of an array.
However, the following does work:
struct myStruct { int num[3]; };
struct myStruct struct1 = {{1,2,3}};
struct myStruct struct2;
struct2 = struct1;
The array num[3] is member-wise assigned from its instance in struct1, into its instance in struct2.
Why is member-wise assignment of arrays supported for structs, but not in general?
edit: Roger Pate's comment in the thread std::string in struct - Copy/assignment issues? seems to point in the general direction of the answer, but I don't know enough to confirm it myself.
edit 2: Many excellent responses. I choose Luther Blissett's because I was mostly wondering about the philosophical or historical rationale behind the behavior, but James McNellis's reference to the related spec documentation was useful as well.
Here's my take on it:
The Development of the C Language offers some insight in the evolution of the array type in C:
http://cm.bell-labs.com/cm/cs/who/dmr/chist.html
I'll try to outline the array thing:
C's forerunners B and BCPL had no distinct array type, a declaration like:
auto V[10] (B)
or
let V = vec 10 (BCPL)
would declare V to be a (untyped) pointer which is initialized to point to an unused region of 10 "words" of memory. B already used * for pointer dereferencing and had the [] short hand notation, *(V+i) meant V[i], just as in C/C++ today. However, V is not an array, it is still a pointer which has to point to some memory. This caused trouble when Dennis Ritchie tried to extend B with struct types. He wanted arrays to be part of the structs, like in C today:
struct {
int inumber;
char name[14];
};
But with the B,BCPL concept of arrays as pointers, this would have required the name field to contain a pointer which had to be initialized at runtime to a memory region of 14 bytes within the struct. The initialization/layout problem was eventually solved by giving arrays a special treatment: The compiler would track the location of arrays in structures, on the stack etc. without actually requiring the pointer to the data to materialize, except in expressions which involve the arrays. This treatment allowed almost all B code to still run and is the source of the "arrays convert to pointer if you look at them" rule. It is a compatiblity hack, which turned out to be very handy, because it allowed arrays of open size etc.
And here's my guess why array can't be assigned: Since arrays were pointers in B, you could simply write:
auto V[10];
V=V+5;
to rebase an "array". This was now meaningless, because the base of an array variable was not a lvalue anymore. So this assigment was disallowed, which helped to catch the few programs that did this rebasing on declared arrays. And then this notion stuck: As arrays were never designed to be first class citized of the C type system, they were mostly treated as special beasts which become pointer if you use them. And from a certain point of view (which ignores that C-arrays are a botched hack), disallowing array assignment still makes some sense: An open array or an array function parameter is treated as a pointer without size information. The compiler doesn't have the information to generate an array assignment for them and the pointer assignment was required for compatibility reasons. Introducing array assignment for the declared arrays would have introduced bugs though spurious assigments (is a=b a pointer assignment or an elementwise copy?) and other trouble (how do you pass an array by value?) without actually solving a problem - just make everything explicit with memcpy!
/* Example how array assignment void make things even weirder in C/C++,
if we don't want to break existing code.
It's actually better to leave things as they are...
*/
typedef int vec[3];
void f(vec a, vec b)
{
vec x,y;
a=b; // pointer assignment
x=y; // NEW! element-wise assignment
a=x; // pointer assignment
x=a; // NEW! element-wise assignment
}
This didn't change when a revision of C in 1978 added struct assignment ( http://cm.bell-labs.com/cm/cs/who/dmr/cchanges.pdf ). Even though records were distinct types in C, it was not possible to assign them in early K&R C. You had to copy them member-wise with memcpy and you could pass only pointers to them as function parameters. Assigment (and parameter passing) was now simply defined as the memcpy of the struct's raw memory and since this couldn't break exsisting code it was readily adpoted. As a unintended side effect, this implicitly introduced some kind of array assignment, but this happended somewhere inside a structure, so this couldn't really introduce problems with the way arrays were used.
Concerning the assignment operators, the C++ standard says the following (C++03 §5.17/1):
There are several assignment operators... all require a modifiable lvalue as their left operand
An array is not a modifiable lvalue.
However, assignment to a class type object is defined specially (§5.17/4):
Assignment to objects of a class is defined by the copy assignment operator.
So, we look to see what the implicitly-declared copy assignment operator for a class does (§12.8/13):
The implicitly-defined copy assignment operator for class X performs memberwise assignment of its subobjects. ... Each subobject is assigned in the manner appropriate to its type:
...
-- if the subobject is an array, each element is assigned, in the manner appropriate to the element type
...
So, for a class type object, arrays are copied correctly. Note that if you provide a user-declared copy assignment operator, you cannot take advantage of this, and you'll have to copy the array element-by-element.
The reasoning is similar in C (C99 §6.5.16/2):
An assignment operator shall have a modifiable lvalue as its left operand.
And §6.3.2.1/1:
A modifiable lvalue is an lvalue that does not have array type... [other constraints follow]
In C, assignment is much simpler than in C++ (§6.5.16.1/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.
For assignment of struct-type objects, the left and right operands must have the same type, so the value of the right operand is simply copied into the left operand.
In this link: http://www2.research.att.com/~bs/bs_faq2.html there's a section on array assignment:
The two fundamental problems with arrays are that
an array doesn't know its own size
the name of an array converts to a pointer to its first element at the slightest provocation
And I think this is the fundamental difference between arrays and structs. An array variable is a low level data element with limited self knowledge. Fundamentally, its a chunk of memory and a way to index into it.
So, the compiler can't tell the difference between int a[10] and int b[20].
Structs, however, do not have the same ambiguity.
I know, everyone who answered are experts in C/C++. But I thought, this is the primary reason.
num2 = num1;
Here you are trying to change the base address of the array, which is not permissible.
and of course,
struct2 = struct1;
Here, object struct1 is assigned to another object.
Another reason no further efforts were made to beef up arrays in C is probably that array assignment would not be that useful. Even though it can be easily achieved in C by wrapping it in a struct (and the struct's address can be simply cast to the array's address or even the array's first element's address for further processing) this feature is rarely used. One reason is that arrays of different sizes are incompatible which limits the benefits of assignment or, related, passing to functions by value.
Most functions with array parameters in languages where arrays are first-class types are written for arrays of arbitrary size. The function then usually iterates over the given number of elements, an information that the array provides. (In C the idiom is, of course, to pass a pointer and a separate element count.) A function which accepts an array of just one specific size is not needed as often, so not much is missed. (This changes when you can leave it to the compiler to generate a separate function for any occurring array size, as with C++ templates; this is the reason why std::array is useful.)
I have 3 questions:
Can std::move move a built-in type ?
int a = 10;
int b = std::move(a);
Will a be an invalid value?
Can std::move move a pointer
int *a = new int[10];
int *b = std::move(a);
Will a become an invalid pointer or nullptr?
can std::move move a c array?
struct S {
int array[10];
}
S a;
for(int i=0; i<10; i++)
a.array[i]=1;
S b;
b = std::move(a);
Will a.array become an invalid array?
move itself does nothing rather than a static_cast. According to cppreference.com
It is exactly equivalent to a static_cast to an rvalue reference type.
Thus, it depends on the type of the variable you assign to after the move, if the type has constructors or assign operators that takes a rvalue parameter, it may or may not steal the content of the original variable, so, it may leave the original variable to be in an unspecified state:
Unless otherwise specified, all standard library objects that have been moved from are placed in a valid but unspecified state.
Back to your questions, because there is no special move constructor or move assign operator for built-in literal types such as integers and raw pointers, so, it will be just a simple copy for these types. Thus, for question 1, and question 2, a is still valid and not changed after the move. Because C++ does not allow direct assignment for C array, so the code in question 3 does not compile.
--- edit ---
Your original 3rd question is assigning a plain C array to another, which is not allowed by the language.
Your updated 3rd question is different, it embeds a C array in a struct, and now you are assigning a struct to another, which can compile now. Because you does not have move assign operator for your struct, the default assignment operator is called, which does a byte level copying, thus, a is unchanged after the move and assignment.
This happens only if all member of the struct are POD (Plain Old Data). If you add a non-POD member into your struct, the assignment statement will not compile, because it can not use the default "byte level copy" assign operator to copy non-POD member (for example, a string object).
struct S {
std::string something; // non-POD member
int array[100];
};
S a, b;
b = std::move(a); // this line will not compile
I have recently fixed a bug in an application of mine: the problem was that an object that resides on the stack had a field left uninitialized.
The object had a class declaration of this type:
struct A{
int somefield, someotherfield;
A(): someotherfield(0) {}
}
and when declaring a local variable (like A var; in a function), somefield was left uninitialized, and so a read of it would return a randomish value.
I was certain that fields of a class, which don't appear in the constructor initialization list, would always get initialized by a synthesized trivial constructor (in the case of an int, a zero value). Evidently I am wrong.
So what are the general rules about implicit field initialization?
classes and structs are initialized by contructor
Basic types int double char short ... are not initialized and contain random numbers
Pointers are not initialized and point to random positions
arrays of classes or structs cause each element to be initialized by its constructor
arrays of basic types or pointers are random.
I understand that memberwise assignment of arrays is not supported, such that the following will not work:
int num1[3] = {1,2,3};
int num2[3];
num2 = num1; // "error: invalid array assignment"
I just accepted this as fact, figuring that the aim of the language is to provide an open-ended framework, and let the user decide how to implement something such as the copying of an array.
However, the following does work:
struct myStruct { int num[3]; };
struct myStruct struct1 = {{1,2,3}};
struct myStruct struct2;
struct2 = struct1;
The array num[3] is member-wise assigned from its instance in struct1, into its instance in struct2.
Why is member-wise assignment of arrays supported for structs, but not in general?
edit: Roger Pate's comment in the thread std::string in struct - Copy/assignment issues? seems to point in the general direction of the answer, but I don't know enough to confirm it myself.
edit 2: Many excellent responses. I choose Luther Blissett's because I was mostly wondering about the philosophical or historical rationale behind the behavior, but James McNellis's reference to the related spec documentation was useful as well.
Here's my take on it:
The Development of the C Language offers some insight in the evolution of the array type in C:
http://cm.bell-labs.com/cm/cs/who/dmr/chist.html
I'll try to outline the array thing:
C's forerunners B and BCPL had no distinct array type, a declaration like:
auto V[10] (B)
or
let V = vec 10 (BCPL)
would declare V to be a (untyped) pointer which is initialized to point to an unused region of 10 "words" of memory. B already used * for pointer dereferencing and had the [] short hand notation, *(V+i) meant V[i], just as in C/C++ today. However, V is not an array, it is still a pointer which has to point to some memory. This caused trouble when Dennis Ritchie tried to extend B with struct types. He wanted arrays to be part of the structs, like in C today:
struct {
int inumber;
char name[14];
};
But with the B,BCPL concept of arrays as pointers, this would have required the name field to contain a pointer which had to be initialized at runtime to a memory region of 14 bytes within the struct. The initialization/layout problem was eventually solved by giving arrays a special treatment: The compiler would track the location of arrays in structures, on the stack etc. without actually requiring the pointer to the data to materialize, except in expressions which involve the arrays. This treatment allowed almost all B code to still run and is the source of the "arrays convert to pointer if you look at them" rule. It is a compatiblity hack, which turned out to be very handy, because it allowed arrays of open size etc.
And here's my guess why array can't be assigned: Since arrays were pointers in B, you could simply write:
auto V[10];
V=V+5;
to rebase an "array". This was now meaningless, because the base of an array variable was not a lvalue anymore. So this assigment was disallowed, which helped to catch the few programs that did this rebasing on declared arrays. And then this notion stuck: As arrays were never designed to be first class citized of the C type system, they were mostly treated as special beasts which become pointer if you use them. And from a certain point of view (which ignores that C-arrays are a botched hack), disallowing array assignment still makes some sense: An open array or an array function parameter is treated as a pointer without size information. The compiler doesn't have the information to generate an array assignment for them and the pointer assignment was required for compatibility reasons. Introducing array assignment for the declared arrays would have introduced bugs though spurious assigments (is a=b a pointer assignment or an elementwise copy?) and other trouble (how do you pass an array by value?) without actually solving a problem - just make everything explicit with memcpy!
/* Example how array assignment void make things even weirder in C/C++,
if we don't want to break existing code.
It's actually better to leave things as they are...
*/
typedef int vec[3];
void f(vec a, vec b)
{
vec x,y;
a=b; // pointer assignment
x=y; // NEW! element-wise assignment
a=x; // pointer assignment
x=a; // NEW! element-wise assignment
}
This didn't change when a revision of C in 1978 added struct assignment ( http://cm.bell-labs.com/cm/cs/who/dmr/cchanges.pdf ). Even though records were distinct types in C, it was not possible to assign them in early K&R C. You had to copy them member-wise with memcpy and you could pass only pointers to them as function parameters. Assigment (and parameter passing) was now simply defined as the memcpy of the struct's raw memory and since this couldn't break exsisting code it was readily adpoted. As a unintended side effect, this implicitly introduced some kind of array assignment, but this happended somewhere inside a structure, so this couldn't really introduce problems with the way arrays were used.
Concerning the assignment operators, the C++ standard says the following (C++03 §5.17/1):
There are several assignment operators... all require a modifiable lvalue as their left operand
An array is not a modifiable lvalue.
However, assignment to a class type object is defined specially (§5.17/4):
Assignment to objects of a class is defined by the copy assignment operator.
So, we look to see what the implicitly-declared copy assignment operator for a class does (§12.8/13):
The implicitly-defined copy assignment operator for class X performs memberwise assignment of its subobjects. ... Each subobject is assigned in the manner appropriate to its type:
...
-- if the subobject is an array, each element is assigned, in the manner appropriate to the element type
...
So, for a class type object, arrays are copied correctly. Note that if you provide a user-declared copy assignment operator, you cannot take advantage of this, and you'll have to copy the array element-by-element.
The reasoning is similar in C (C99 §6.5.16/2):
An assignment operator shall have a modifiable lvalue as its left operand.
And §6.3.2.1/1:
A modifiable lvalue is an lvalue that does not have array type... [other constraints follow]
In C, assignment is much simpler than in C++ (§6.5.16.1/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.
For assignment of struct-type objects, the left and right operands must have the same type, so the value of the right operand is simply copied into the left operand.
In this link: http://www2.research.att.com/~bs/bs_faq2.html there's a section on array assignment:
The two fundamental problems with arrays are that
an array doesn't know its own size
the name of an array converts to a pointer to its first element at the slightest provocation
And I think this is the fundamental difference between arrays and structs. An array variable is a low level data element with limited self knowledge. Fundamentally, its a chunk of memory and a way to index into it.
So, the compiler can't tell the difference between int a[10] and int b[20].
Structs, however, do not have the same ambiguity.
I know, everyone who answered are experts in C/C++. But I thought, this is the primary reason.
num2 = num1;
Here you are trying to change the base address of the array, which is not permissible.
and of course,
struct2 = struct1;
Here, object struct1 is assigned to another object.
Another reason no further efforts were made to beef up arrays in C is probably that array assignment would not be that useful. Even though it can be easily achieved in C by wrapping it in a struct (and the struct's address can be simply cast to the array's address or even the array's first element's address for further processing) this feature is rarely used. One reason is that arrays of different sizes are incompatible which limits the benefits of assignment or, related, passing to functions by value.
Most functions with array parameters in languages where arrays are first-class types are written for arrays of arbitrary size. The function then usually iterates over the given number of elements, an information that the array provides. (In C the idiom is, of course, to pass a pointer and a separate element count.) A function which accepts an array of just one specific size is not needed as often, so not much is missed. (This changes when you can leave it to the compiler to generate a separate function for any occurring array size, as with C++ templates; this is the reason why std::array is useful.)