Why are there sequence point guarantees for C library functions?
Asked Answered
C

0

2

I am looking at the C23 draft standard, but I think this would apply in C11 as well. There are several guarantees about sequence points in relation to function calls in C, such as before the return of any library function and between sorting and search algorithm calls to comparison functions. I feel like I am missing something, as my understanding says we don't need these guarantees because we have 6.5.2.2.8:

"There is a sequence point after the evaluations of the function designator and the actual arguments but before the actual call. Every evaluation in the calling function (including other function calls) that is not otherwise specifically sequenced before or after the execution of the body of the called function is indeterminately sequenced with respect to the execution of the called function."

This implies as I understand it a sequence point (or the same effect) before the return of any library function and between any comparison function calls. So we don't need those specific guarantees.

Is this a (somewhat confusing) redundancy, or am I missing something?

EDIT: The comments below give two good reasons for the guarantee for library function returns: a library function may be a macro or may be written in assembly. However, the question still stands in relation to the guarantee that comparison function calls are sequence point separated from eachother (and from element movements in the case of sorting). This is only one redundant assurance at this point, so maybe it is just redundant and there is nothing more to it.

Champaigne answered 23/5, 2024 at 4:17 Comment(17)
Standard library routines may be implemented as function-like macros, so a specific guarantee that a “standard library routine” finishes with a sequence point may cover the macro case when the generic guarantee for expressions in return statements in functions does not. (Semi-speculating, do not have my references conveniently at hand.)Artichoke
I think the reason is function-like macros as well. C23 7.1.4 lists a number of requirements that such macros are guaranteed to fulfill and next to that they say (7.1.4 §3) "There is a sequence point immediately before a library function returns."Salve
Some of the library functions might be written in assembly, and not have any return statement in them.Chagrin
I would read the "sequence point between comparison function calls" promise as ruling out parallel sort/search algorithms that might make concurrent calls to the comparison function (e.g. from multiple threads), or might call it concurrently with rearranging the array. So it is safe for your comparison function to write to static variables, or inspect other elements of the array, without potentially causing a data race.Bushire
Similarly, "sequence point before return of the library function" could be taken as a promise that the effects are actually complete when the call returns, as opposed to still running in some background thread. I'm not sure if that's the main intent, though.Bushire
@EricPostpischil: That's an interesting point. If we imagine the traditional macro implementation for getchar() that expands to something like buf_empty ? refill() : buf[i++], then doing getchar() + getchar() would appear to be UB as i is modified twice without an intervening sequence point. So unless I'm missing something, either the library implementers have to know that their compiler handles this safely, or use some other method to insert a sequence point. How could the latter be done?Bushire
Nate: ? : includes a sequence point, so getchar() + getchar() would be fine.Artichoke
@EricPostpischil: There are sequence points between each buf_empty and the corresponding i++, but none in between the two i++. gcc and clang both warn about such code as being unsequenced modifications, see godbolt.org/z/d79f6jTqP. I guess the simplest way to fix it for a modern implementation would be an inline function, but I wonder how old implementations did it.Bushire
@NateEldredge: Yes, you are correct. They could induce a sequence point with (real stuff, 0).Artichoke
@EricPostpischil: clang still doesn't like that, see godbolt.org/z/WWh7P3vsd, but gcc doesn't complain. Interesting.Bushire
@NateEldredge In addition, even if there aren't multiple side effects/value computations of the same object between sequence points, there is the C11 addition "If there are multiple allowable orderings of the subexpressions of an expression, the behavior is undefined if such an unsequenced side effect occurs in any of the orderings." gcc in particular likes to abuse this. So stuff like (0,i++,0) + (0,i++,0) will still result in UB even though each comma operator adds a sequence point, because the two i++ aren't sequenced in relation to each other.Salve
@Lundin: This is rather interesting. Peeking at openbsd as an example, their macro implementations of getc/putc don't seem to include a sequence point (and as I mentioned, I'm not sure how they could except by switching to inline functions). So getchar() + getchar() on OpenBSD looks like it actually would cause UB, which I suppose must be a bug.Bushire
Oh, I just found footnote 190 in C17: "Such macros might not contain the sequence points that the corresponding function calls do." So as a result, getchar() + getchar() actually is allowed to be UB. I would need to do (getchar)() + (getchar)().Bushire
Which has other interesting implications: I suppose that the very innocent looking sqrt(2) + sqrt(3) is also potentially UB because sqrt could be a macro with side effects (on errno, for instance).Bushire
@NateEldredge But the standard does guarantee a sequence point and not only that but as I mentioned in my first comment, a lot of other stuff in 7.1.4 like "Any invocation of a library function that is implemented as a macro shall expand to code that evaluates each of its arguments exactly once, fully protected by parentheses where necessary, so it is generally safe to use arbitrary expressions as arguments."Salve
@Lundin: My reading of footnote 190 is that the sequence point is only guaranteed if the library function is actually a function and not a macro, which we can't be sure of unless we #undef getchar or use (getchar)(). I know about the stuff with arguments being evaluated exactly once, etc, but that doesn't relate to this point. Maybe I should ask a new question about it.Bushire
I asked a related question: #78558464Bushire

© 2022 - 2025 — McMap. All rights reserved.