C: transitive (double) assignments

后端 未结 6 1161
半阙折子戏
半阙折子戏 2021-01-20 07:11

I have used such construct in C:

list->head = list->tail = NULL;

and now I consider whether this really mean what I suppose.

<
相关标签:
6条回答
  • 2021-01-20 07:30

    With all the incomplete answers, I have to clarify this a bit.

    First, the expression is evaluated right to left:

    list->head = (list->tail = NULL);
    

    The standard defines the behaviour of the assignment operator as:

    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,111) but is not an lvalue. The type of an assignment expression is the type the left operand would have after lvalue conversion. The side effect of updating the stored value of the left operand is sequenced after the value computations of the left and right operands. The evaluations of the operands are unsequenced.

    Problem is now, the standard leaves two ways how to get the value of an assignment.**

    // variant 1 (read the left-hand side after writing it)
    list->tail = NULL;
    list->head = list->tail;
    

    That is, list->tail is read after it got its value and that value is assigned to list->head.

    // variant 2 (use temporary storage)
    typeof(list->tail) temp = NULL;  // get the value/type of the RHS of the inner assignment
    list->tail = temp;
    list->head = temp;
    

    (RHS: right hand side, the term to the right of an operator). Thanks, to @chux, the two assignments could also be swapped.

    See footnote 111:

    The implementation is permitted to read the object to determine the value but is not required to, even when the object has volatile-qualified type.


    The variants behave equivalent with respect of the abstract machine - unless list->tail is qualified volatile (more general: any but the leftmost object). Briefly, volatile tells the compiler the access to an object has side-effects. It is typically used for peripheral hardware registers, e.g. USART. While it is rarely (and if, then often wrongly) used in desktop applications, it is typically used in OS kernel-drivers and bare-metal embedded systems.

    Writes and reads to such registers typically pass the data written to the (external) hardware, resp. a read yields the value of such a hardware register. Worse, these registers can be write-only or read-only. For write-only, reading yields indeterminate values which is not related to what was written. Relevant here is that accesses to these objects have to be very carefully controlled and sequenced. There mut be no unexpected read or write.

    So, consider the code above. Then the variants will generate different accesses to the hardware:

    • Variant 1 will result in a write, followed by a read. If the register is write-only, the read yields unrelated data. That is likely not what we want.
    • Variant 2 will only write, but reuse the value written for the second assignment.
    • Variant 2 has indeterminate sequence of the order the writes are performed, wich is another concern with volatile lvalues.

    Note which variant is used is not detrmined by the user, but the compiler. It might even vary for different expressions in the code.

    As a consequence, chained assignments are a no-go once volatile is involved.

    0 讨论(0)
  • 2021-01-20 07:33

    it's multiple assignment as your first option is the right one

    list->head = list->tail = NULL;
    

    it's a magic of flow.

    initially ,tail is set to NULL ,starting from right to left

    list->tail = NULL;
    

    then list->head = list->tail ;

    now tail is NULL so the head will also assigned a NULL value

    0 讨论(0)
  • 2021-01-20 07:36

    Associativity of assignment operator = is right to left. This means that if there are more than one = operator in a statement, then the rightmost is evaluated first, then the one left to it, and in that order, untill the leftmost = operator is evaluated at last.

    This means that when you do

    list->head = list->tail = NULL;
    

    the right most assignment, i.e. list->tail = NULL is evaluated first. So list->tail will be NULL.

    After that list->head = list->tail will be evaluated. And since list->tail is NULL by now (because of previous evaluation- i.e. list->tail = NULL), now list->head is also NULL.

    Based on how you are representing in your question, it is like

    list->tail = NULL; list->head = list->tail;
    
    0 讨论(0)
  • 2021-01-20 07:37

    Neither of them. It mean

    list->tail = NULL;
    list->head = list->tail;
    

    1 is incorrect because in case the type of list->tail is not a pointer, it may yield different result because the value will be converted to the type of the destination of the assignment.

    The order of two statements in 2 is incorrect.

    0 讨论(0)
  • 2021-01-20 07:41

    The assignment operator is right to left associative.

    Thus this expression statement

    list->head = list->tail = NULL;
    

    is equivalent to

    list->tail = NULL;
    list->head = list->tail;
    
    0 讨论(0)
  • 2021-01-20 07:46

    Neither of those is correct.

    Since the simple assignment = operator is right-to-left associative, your expression is identical to:

    list->head = (list->tail = NULL);
    

    NULL is assigned to tail, and then tail, which has the value of a null pointer, to head.

    0 讨论(0)
提交回复
热议问题