Sequence Points between printf function args; does the sequence point between conversions matter?

后端 未结 3 1365
轻奢々
轻奢々 2020-12-10 14:24

I read here that there is a sequence point:

After the action associated with input/output conversion format specifier. For example, in the expression

3条回答
  •  爱一瞬间的悲伤
    2020-12-10 14:30

    Because this question was asked because of a comment-based discussion here, I'll provide some context:

    first comment: The order of operations is not guaranteed to be the order in which you pass arguments to the function. Some people (wrongly) assume that the arguments will be evaluated right to left, but according to the standard, the behaviour is undefined.

    The OP accepts and understands this. No point in repeating the fact that your_function(++i, ++i) is UB.

    In response to that comment: Thanks to your comment I see that printf may be evaluated in any order, but I understood that to be because printf arguments are part of a va_list. Are you saying that the arguments to any function are executed in an arbitrary order?

    OP asking for clarification, so I elaborated a bit:

    Second comment: Yes, that's exactly what I'm saying. even calling int your_function(int a, int b) { return a - b; } does not guarantee that the expressions you pass will be evaluated left to right. There's no sequence point (a point at which all side effects of previous evaluations are performed). Take this example. The nested call is a sequence point, so the outer call passes i+1 (13), and the return value of the inner call (undefined, in this case -1 because i++, i evaluates to 12, 13 apparently), but there's no guarantee that this will always be the case

    That made it pretty clear that these kinds of constructs trigger UB for all functions.


    Wikipedia confusion

    OP Quotes this:

    After the action associated with input/output conversion format specifier. For example, in the expression printf("foo %n %d", &a, 42), there is a sequence point after the %n is evaluated before printing 42.

    Then applies it to his snippet (prinf("%d - %d - %d\n", i, your_function(++i, ++i), i);) expeciting the format specifiers to serve as sequence points.
    What is being referred to by saying "input/output conversion format specifier" is the %n specifier. The corresponding argument must be a pointer to an unsigned integer, and it will be assigned the number of characters printed thus far. Naturally, %n must be evaluated before the rest of the arguments are printed. However, using the pointer passed for %n in other arguments is still dangerous: it's not UB (well, it isn't, but it can be):

    printf("Foo %n %*s\n", &a, 100-a, "Bar");//DANGER!!
    

    There is a sequence point before the function is called, so the expression 100-a will be evaluated before %n has set &a to the correct value. If a is uninitialized, then 100-a is UB. If a is initialized to 0, for example, the result of the expression will be 100. On the whole, though, this kind of code is pretty much asking for trouble. Treat it as very bad practice, or worse...
    Just look at the output generated by either one of these statements:

    unsigned int a = 90;
    printf("%u %n %*s\n",a,  &a, 10, "Bar");//90         Bar
    printf("%u\n", a);//3
    printf("Foo %u %n %*s\n",a, &a, 10-a, "Bar");//Foo 3      Bar < padding used: 10 - 3, not 10 - 6 
    printf("%u\n", a);//6
    

    In as you can see, n gets reassigned inside of printf, so you can't use its new value in the argument list (because there's a sequence point). If you expect n to be reassigned "in-place" you're essentially expecting C to jump out of the function call, evaluate other arguments, and jump back into the call. That's just not possible. If you were to change unsigned int a = 90; to unsigned int a;, then the behaviour is undefined.


    Concerning the 12's

    Now because the OP read up on sequence points, he correctly notices that this statement:

    printf("%d - %d - %d\n", i, your_function(++i, ++i), i);
    

    Is slightly different: your_function(++i, ++i) is a sequence point, and guarantees that i will be incremented twice. This function call is a sequence point because:

    Before a function is entered in a function call. The order in which the arguments are evaluated is not specified, but this sequence point means that all of their side effects are complete before the function is entered

    That means that, before printf is called, your_function has to be called (because its return value is one of the arguments for the printf call), and i will be incremented twice.
    This could explain the output being "12 - 0 - 12", but is it guaranteed to be the output?

    No

    Technically, although most compilers will evaluate the your_function(++i, ++i); call first, the standard would allow a compiler to evaluate the arguments passed to sprintf left to right (the order isn't specified after all). So this would be an equally valid result:

    10 - 0 - 12
    //or even
    12 - 0 - 10
    //and
    10 - 0 - 10
    //technically, even this would be valid
    12 - 0 - 11
    

    Although the latter output is extremely unlikely (it'd be very inefficient)

提交回复
热议问题