2

For scalar values, the assignment operator seems to copy the right-hand side value to the left. How does that work for composite data types? Eg, if I have a nested struct

struct inner {
    int b;
};

struct outer {
   struct inner a;
};

int main() {
   struct outer s1 = { .a = {.b=1}};   
   struct outer s2 = s1;
}
  • does the assignment recursively deep copy the values?
  • does the same happen when passing the struct to a function?

By experimenting it seems like it does, but can anyone point to the specification of the behavior?

Lundin
  • 195,001
  • 40
  • 254
  • 396
blue_note
  • 27,712
  • 9
  • 72
  • 90
  • 4
    I wouldn't call what you are doing a "deep copy". I would reserve that term for a data structure containing pointers to things that also need to be copied, and a simple assignment doesn't do that. – Ian Abbott Jun 11 '19 at 10:54
  • 1
    Note that `struct outer s2 = s1;` isn't an assignment but initialization. – KamilCuk Jun 11 '19 at 11:04
  • @KamilCuk: correct. but, isn't equivalent in terms of copying? – blue_note Jun 11 '19 at 11:05
  • @KamilCuk That's not entirely relevant in C though, since we have 6.7.9 Initialization §11: "the same type constraints and conversions as for simple assignment apply". C++ is another story entirely. – Lundin Jun 11 '19 at 11:07

2 Answers2

7

There is no "recursion"; it copies all the (value) bits of the value. Pointers are not magically followed of course, the assignment operator wouldn't know how to duplicate the pointed-to data.

You can think of

a = b;

as shorthand for

memcpy(&a, &b, sizeof a);

The sizeof is misleading of course, since we know the types are the same on both sides but I don't think __typeof__ helps.

The draft C11 spec says (in 6.5.16.1 Simple assignment, paragraph 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.

unwind
  • 391,730
  • 64
  • 469
  • 606
  • The term recursion is often formally used in the standard though, for iterating through a structs members, without it having anything to do with recursive functions. Take the rules of 6.5 pointer aliasing for example: "- an aggregate or union type that includes one of the aforementioned types among its members (including, **recursively**, a member of a subaggregate or contained union)". Or the rules of struct initialization: "if it is an aggregate, every member is initialized (recursively) according to these rules, and any padding is initialized to zero bits;" – Lundin Jun 11 '19 at 10:50
1
  • does the assignment recursively deep copy the values?

    Yes, just as if you would have used memcpy. Pointers are copied, but not what they point at. The term "deep copy" often means: also copy what the pointers point at (for example in a C++ copy constructor).

    Except the values of any padding bytes may hold indeterminate values. (Meaning that memcmp on a struct might be unsafe.)

  • does the same happen when passing the struct to a function?

    Yes. See the reference to 6.5.2.2 below.

  • By experimenting it seems like it does, but can anyone point to the specification of the behavior?

    C17 6.5.16:

    An assignment operator stores a value in the object designated by the left operand. An assignment expression has the value of the left operand after the assignment, but is not an lvalue. The type of an assignment expression is the type the left operand would have after lvalue conversion.

    (Lvalue conversion in this case isn't relevant, since both structs must be of 100% identical and compatible types. Simply put: two structs are compatible if they have exactly the same members.)

    C17 6.5.16.1 Simple assignment:

    • the left operand has an atomic, qualified, or unqualified version of a structure or union type compatible with the type of the right;

    C17 6.5.2.2 Function calls, §7:

    If the expression that denotes the called function has a type that does include a prototype, the arguments are implicitly converted, as if by assignment, ...

Lundin
  • 195,001
  • 40
  • 254
  • 396
  • 1
    Two structure or union types must also have the same tag (or both be untagged) to be compatible. Member correspondence is not enough. In practice, it follows that inside a code block, a structure or union type is compatible only with itself. – John Bollinger Jun 11 '19 at 12:00
  • @JohnBollinger Yep but I didn't want to drag the whole compatible struct definition into this answer since it is complex, hence "simply put". For those who are interested, it's the first wall of text below chapter 6.2.7 in the standard. – Lundin Jun 11 '19 at 15:00
  • I understand what you're doing, and I know you were already aware of the details of struct compatibility. It's not necessary to go into the full details here, but I think it's important to convey that member correspondence is not enough, because that would be an easy misunderstanding for an inexperienced person to take away. – John Bollinger Jun 11 '19 at 15:11
  • 1
    @JohnBollinger To make matters worse, you can have two structs with same members but different tags or names. They are not compatible types, but you can in theory lvalue access their data through pointers by using the "union common initial sequence hack". Which is an obscure rule that has caused various standard DR and compiler bug reports. https://stackoverflow.com/a/54571011/584518 – Lundin Jun 11 '19 at 15:17