• Re: Undefined behaviour in C23

    From Keith Thompson@3:633/280.2 to All on Sun Aug 24 08:58:46 2025
    Keywords: C, standards

    This discussion started in comp.compilers (which is moderated).
    I've cross-posted to comp.lang.c and set followups there.

    I've snipped some context.

    David Brown <david.brown@hesbynett.no> writes:
    On 23/08/2025 00:11, Keith Thompson wrote:
    comp.lang.c would probably be a better place for this discussion,
    but cross-posting between moderated and unmoderated newsgroups is
    likely to cause problems.

    Yes - but some comments have also wandered slightly from being just applicable to C. Still, it is not really a compiler discussion.

    [FYI, cross-posting to comp.compilers and other groups works because
    your moderator's scripts know how to handle it. -John]

    David Brown <david.brown@hesbynett.no> writes:
    On 21/08/2025 21:53, Keith Thompson wrote:
    [...]
    If you declare and call a function "foo" that is written in fully
    portable C code, but not part of the current translation unit being
    compiled (perhaps it has been separately compiled or included in a
    library), then it would be UB by the section 4 definition (since the C
    standards don't say anything about what "foo" does, nor does your code).

    If the translation unit that defined "foo" is part of your program, then
    your code *does* define its behavior. Linking multiple translation
    units into a program is specified by the C standard; it's translation
    phase 8.

    No.

    Yes.

    The C standard does not define how this linking or combing is done - it
    only covers certain specific aspects of the linking that relate directly
    to C. The behaviour of the function "foo" here is not defined in the C standards, and if the source code is not available when translating a different translation unit, the behaviour of "foo" is undefined.

    It doesn't matter how linking is done, only that it follows the
    semantics of C. The function "foo" is *part of the program*.
    The fact that it's in a different translation unit doesn't affect
    its semantics.

    A C program can be made up of multiple translation units.
    The behavior of a call to foo() is the same whether it's defined
    in the same translation unit or not. Linking, translation phase 8,
    ensures that that's the case.

    [...]

    The C code being translated has code to call the function - the call is defined (assuming declarations and definitions are consistent), but the effect of the call is not defined - it is therefore UB.

    Again, the effect of the call is specified by the definition of foo,
    which is *part of your program*.

    [...]

    Add to that, the C standard has a specific term for features that are
    non-portable but not undefined behaviour - "implementation-defined
    behaviour". Code that relies on "int" being 32-bit is not portable, but >>> it is not UB when compiled on implementations for which "int" /is/ 32-bit. >>
    That's not what "implementation-defined behavior" means in C.
    Cases of implementation-defined behavior are explicitly called out in
    the standard, and an implementation must document how it treats each
    instance of implementation-defined behavior. Each implementation
    must document the range of int. There is no such requirement for
    the behavior of "foo" defined in some non-standard header.

    Yes, exactly - implementation-defined behaviours are things that are not portable, but are not undefined behaviour, because they must be defined
    by the implementation. (The C standard usually also gives some specific options or minimum requirements for those definitions.)

    You're ignoring two critical requirements for behavior to be implementation-defined: it has to be specified by the standard
    as implementation-defined (unlike undefined behavior, there is no
    implicit implementation-defined behavior), and the implementation's
    choice of behavior must be documented. Neither of these applies
    to a function defined outside your program (unless it's part of
    the C standard library).

    [...]

    Section 4 says precisely that behaviour that is not defined by the C standard, is "undefined behaviour" in exactly the same way as things
    that are explicitly labelled "undefined behaviour" in the standard.

    Yes.

    And that, I think, is the root of the problem - the C standard is on the
    one hand trying to classify, define and describe things as "undefined behaviour" as a technical term in the C standard, while on the other
    hand it is also trying to say these are things that have no definition
    or descriptions of their behaviours.

    I see no problem. For any construct whose behavior is undefined, the
    standard says that it provides no definition of its behavior. This is
    equally true whether the standard explicitly says the behavior is
    undefined or it just omits any definition of the behavior.

    It is behavior that
    is not defined *by the C standard*. If I write printf("goodbye\n")
    when I meant to write printf("hello\n"), that's incorrect behavior,
    but it's not undefined behavior.


    I agree that it is not C undefined behaviour, yes.
    [...]

    Excellent.

    It's entirely possible for a construct that has undefined behavior
    according to the C standard to have its behavior defined elsewhere
    (e.g., by POSIX). When discussing C, using the phrase "undefined
    behavior" with a meaning other than the C standard definition will
    only cause confusion.

    --
    Keith Thompson (The_Other_Keith) Keith.S.Thompson+u@gmail.com
    void Void(void) { Void(); } /* The recursive call of the void */

    --- MBSE BBS v1.1.2 (Linux-x86_64)
    * Origin: Compilers Central (3:633/280.2@fidonet)