• gcc and 'include'

    From Bart@3:633/10 to All on Thu Mar 26 22:36:59 2026
    Take this program:

    #include <stdlib.h>

    inline int F(){return rand();}

    int main(void) {
    return F();
    }

    This compiles fine with gcc using -O1 -O2 -O3.

    But compile without optimising, and it fails to link as it can't find a function 'F'.

    Is it supposed to behave like that? I assume no discrete function F is
    being generated because of the 'inline' attribute, but you'd think it
    would ignore that if inlining wasn't enabled.

    It compiles fine with or without 'inline' using tcc and bcc (my product).

    Clang fails too, but clearly only because it has to duplicate gcc's
    behaviour for drop-in compatability, even if it is crass.

    (This is the actual program: https://web.archive.org/web/20010127204000/http://www.bagley.org/~doug/shootout/bench/lists/lists.gcc)

    --- PyGate Linux v1.5.13
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Keith Thompson@3:633/10 to All on Thu Mar 26 16:12:47 2026
    Bart <bc@freeuk.com> writes:
    Take this program:

    #include <stdlib.h>

    inline int F(){return rand();}

    int main(void) {
    return F();
    }

    This compiles fine with gcc using -O1 -O2 -O3.

    But compile without optimising, and it fails to link as it can't find
    a function 'F'.

    Is it supposed to behave like that? I assume no discrete function F is
    being generated because of the 'inline' attribute, but you'd think it
    would ignore that if inlining wasn't enabled.

    It compiles fine with or without 'inline' using tcc and bcc (my product).

    Clang fails too, but clearly only because it has to duplicate gcc's
    behaviour for drop-in compatability, even if it is crass.

    (This is the actual program: https://web.archive.org/web/20010127204000/http://www.bagley.org/~doug/shootout/bench/lists/lists.gcc)

    I don't have an answer to your question, but defining F as
    "static inline" makes the program work. You can also do this:

    inline int F();
    int F(){return rand();}

    (Unless you're using a C23 compiler, I suggest "int F(void)" rather
    than "int F()".)

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

    --- PyGate Linux v1.5.13
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Lawrence D?Oliveiro@3:633/10 to All on Fri Mar 27 00:25:15 2026
    On Thu, 26 Mar 2026 22:36:59 +0000, Bart wrote:

    Take this program:

    #include <stdlib.h>

    inline int F(){return rand();}

    int main(void) {
    return F();
    }

    This compiles fine with gcc using -O1 -O2 -O3.

    But compile without optimising, and it fails to link as it can't
    find a function 'F'.

    Is it supposed to behave like that?

    Yes. Since you didn?t declare it ?static? (internal linkage), it has
    external linkage. Looking at N3220, section 6.7.5, ?Function
    specifiers?:

    An inline definition does not provide an external definition for
    the function and does not forbid an external definition in another
    translation unit. Inline definitions provide an alternative to
    external definitions, which a translator may use to implement any
    call to the function in the same translation unit. It is
    unspecified whether a call to the function uses the inline
    definition or the external definition.

    --- PyGate Linux v1.5.13
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Michael S@3:633/10 to All on Fri Mar 27 04:10:42 2026
    On Thu, 26 Mar 2026 22:36:59 +0000
    Bart <bc@freeuk.com> wrote:

    Take this program:

    #include <stdlib.h>

    inline int F(){return rand();}

    int main(void) {
    return F();
    }

    This compiles fine with gcc using -O1 -O2 -O3.

    But compile without optimising, and it fails to link as it can't find
    a function 'F'.

    Is it supposed to behave like that? I assume no discrete function F
    is being generated because of the 'inline' attribute, but you'd think
    it would ignore that if inlining wasn't enabled.

    It compiles fine with or without 'inline' using tcc and bcc (my
    product).

    Clang fails too, but clearly only because it has to duplicate gcc's behaviour for drop-in compatability, even if it is crass.

    (This is the actual program: https://web.archive.org/web/20010127204000/http://www.bagley.org/~doug/shootout/bench/lists/lists.gcc)


    Sounds like a bug introduced during transition to c99/gnu99
    front end.
    With gcc4.x, which defaults to gnu89 it still works.
    Also works with the latest gcc -std=gnu89
    But there is no hope that gcc maintaines will ever admit that it is a
    bug.

    I never encountered it because I never use 'inline' without 'static'.







    --- PyGate Linux v1.5.13
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Tim Rentsch@3:633/10 to All on Thu Mar 26 19:06:28 2026
    Bart <bc@freeuk.com> writes:

    Take this program:

    #include <stdlib.h>

    inline int F(){return rand();}

    int main(void) {
    return F();
    }

    This compiles fine with gcc using -O1 -O2 -O3.

    But compile without optimising, and it fails to link as it can't find
    a function 'F'.

    Is it supposed to behave like that?

    The C standard allows the observed behavior.

    Here is a different formulation:

    /* ??? */
    inline int F();

    int
    main(void){
    return F();
    }

    inline int
    F(){
    return 0;
    }

    This program shows (under gcc, in my environment) ths same behavior
    as what you describe. A conforming implementation may choose to
    compile and run the program without problem, or it may choose to
    fail the program because there is no definition of F() with
    external linkage (and that statement is true regardless of
    optimization setting).

    If the comment line (with question marks) is replaced by either

    extern

    or

    static

    then the program links and runs without problem.

    --- PyGate Linux v1.5.13
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Tim Rentsch@3:633/10 to All on Thu Mar 26 19:08:16 2026
    Michael S <already5chosen@yahoo.com> writes:

    On Thu, 26 Mar 2026 22:36:59 +0000
    Bart <bc@freeuk.com> wrote:

    Take this program:

    #include <stdlib.h>

    inline int F(){return rand();}

    int main(void) {
    return F();
    }

    This compiles fine with gcc using -O1 -O2 -O3.

    But compile without optimising, and it fails to link as it can't find
    a function 'F'.

    Is it supposed to behave like that? I assume no discrete function F
    is being generated because of the 'inline' attribute, but you'd think
    it would ignore that if inlining wasn't enabled.

    [...]

    Sounds like a bug introduced during transition to c99/gnu99
    front end.
    With gcc4.x, which defaults to gnu89 it still works.
    Also works with the latest gcc -std=gnu89
    But there is no hope that gcc maintaines will ever admit that it is a
    bug.

    It isn't a bug. The C standard allows this behavior.

    --- PyGate Linux v1.5.13
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Bart@3:633/10 to All on Fri Mar 27 10:55:55 2026
    On 26/03/2026 23:12, Keith Thompson wrote:
    Bart <bc@freeuk.com> writes:
    Take this program:

    #include <stdlib.h>

    inline int F(){return rand();}

    int main(void) {
    return F();
    }

    This compiles fine with gcc using -O1 -O2 -O3.

    But compile without optimising, and it fails to link as it can't find
    a function 'F'.

    Is it supposed to behave like that? I assume no discrete function F is
    being generated because of the 'inline' attribute, but you'd think it
    would ignore that if inlining wasn't enabled.

    It compiles fine with or without 'inline' using tcc and bcc (my product).

    Clang fails too, but clearly only because it has to duplicate gcc's
    behaviour for drop-in compatability, even if it is crass.

    (This is the actual program:
    https://web.archive.org/web/20010127204000/http://www.bagley.org/~doug/shootout/bench/lists/lists.gcc)

    I don't have an answer to your question, but defining F as
    "static inline" makes the program work. You can also do this:

    inline int F();
    int F(){return rand();}


    Using 'static' to force an actual function to be generated sounds
    unintuitive. It is when F is exported to be called from another module
    that a tangible F function needs to exist.

    But my example came from some pre-existing legacy code, where I was
    surprised that -O0 made it fail.

    (Unless you're using a C23 compiler, I suggest "int F(void)" rather
    than "int F()".)


    Nobody bothers with that any more. Most seem to assume that () already
    means zero parameters anyway, judging by the incorrect usage I
    constantly saw in open source code.


    --- PyGate Linux v1.5.13
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From David Brown@3:633/10 to All on Fri Mar 27 13:49:43 2026
    On 27/03/2026 11:55, Bart wrote:
    On 26/03/2026 23:12, Keith Thompson wrote:
    Bart <bc@freeuk.com> writes:
    Take this program:

    ˙˙ #include <stdlib.h>

    ˙˙ inline int F(){return rand();}

    ˙˙ int main(void) {
    ˙˙˙˙˙˙ return F();
    ˙˙ }

    This compiles fine with gcc using -O1 -O2 -O3.

    But compile without optimising, and it fails to link as it can't find
    a function 'F'.

    Is it supposed to behave like that? I assume no discrete function F is
    being generated because of the 'inline' attribute, but you'd think it
    would ignore that if inlining wasn't enabled.

    It compiles fine with or without 'inline' using tcc and bcc (my
    product).

    Clang fails too, but clearly only because it has to duplicate gcc's
    behaviour for drop-in compatability, even if it is crass.

    (This is the actual program:
    https://web.archive.org/web/20010127204000/http://www.bagley.org/~doug/shootout/bench/lists/lists.gcc)

    I don't have an answer to your question, but defining F as
    "static inline" makes the program work.˙ You can also do this:

    ˙˙˙˙ inline int F();
    ˙˙˙˙ int F(){return rand();}


    Using 'static' to force an actual function to be generated sounds unintuitive. It is when F is exported to be called from another module
    that a tangible F function needs to exist.

    But my example came from some pre-existing legacy code, where I was surprised that -O0 made it fail.


    I agree with you that this behaviour can be surprising, but it is
    correct according to the C standards. "inline" alone in C means that
    the compiler /may/ use the inline definition in code generation - but it
    may also use a normal external linkage definition found at link time.
    The inline definition and the external definition do not have to be the
    same - it is fine to have a local inline version in a file that is
    different from the external one.

    For example, you could have a normal function in your program called
    "sort" that can sort arrays of any size. But you know that in this one particular C file, you only ever need to sort arrays of size 4. You
    write a specialised version - keeping the same name "sort" and the same function signature, and mark it "inline". The compiler can choose
    either version when generating the code - the local inline version, or
    the external version. Typically you get the local version when
    optimising. (The compiler can always choose the local version even when
    you have used a flag like "-O0" - the C standard does not care about optimisation.)

    But this all also means that even when you have a version of the
    function locally with "inline", the compiler can choose to generate code
    that refers to an externally linked version. This is the normal
    behaviour for compilers when you are not optimising (though again the
    standard does not require this). A common reason for not optimising
    code is because you want to debug it, and that can be easier with a
    single external version of the function - perhaps you want to put a
    breakpoint in it when using a debugger, or add printf statements, or
    whatever.


    Another source of confusion here is that this is all subtly different
    from C++'s "inline", as well as the "gnu89" "inline" that gcc supported
    as an extension before C99 was published. In both these cases, the
    local "inline" function will be used (whether it is actually generated
    inline or as a function call is up to the compiler optimisation). But
    you are free to have an external version of the function as well,
    without conflict.


    Generally, as Keith suggested, it is best to use "static inline" rather
    than plain "inline". Then the code is logically a static function -
    callers will always use the local copy, and nothing is exported. You
    can also use "extern inline" which marks the function as being available
    for local inlining, and the definition is also available for external
    linkage. But "static inline" is by far the most common. Both "static
    inline" and "extern inline" work the same way for C99 inline, gnu89
    inline and C++ inline.


    (Unless you're using a C23 compiler, I suggest "int F(void)" rather
    than "int F()".)


    Nobody bothers with that any more. Most seem to assume that () already
    means zero parameters anyway, judging by the incorrect usage I
    constantly saw in open source code.


    I think almost all C programmers write "int foo(void);", not "int
    foo();". The exception is primarily people who are used to writing C++
    rather than C. You can omit the "void" in the definition of a function
    with no parameters, but I don't see any good reason to do that.





    --- PyGate Linux v1.5.13
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Michael S@3:633/10 to All on Fri Mar 27 16:47:57 2026
    On Thu, 26 Mar 2026 19:08:16 -0700
    Tim Rentsch <tr.17687@z991.linuxsc.com> wrote:

    Michael S <already5chosen@yahoo.com> writes:

    On Thu, 26 Mar 2026 22:36:59 +0000
    Bart <bc@freeuk.com> wrote:

    Take this program:

    #include <stdlib.h>

    inline int F(){return rand();}

    int main(void) {
    return F();
    }

    This compiles fine with gcc using -O1 -O2 -O3.

    But compile without optimising, and it fails to link as it can't
    find a function 'F'.

    Is it supposed to behave like that? I assume no discrete function
    F is being generated because of the 'inline' attribute, but you'd
    think it would ignore that if inlining wasn't enabled.

    [...]

    Sounds like a bug introduced during transition to c99/gnu99
    front end.
    With gcc4.x, which defaults to gnu89 it still works.
    Also works with the latest gcc -std=gnu89
    But there is no hope that gcc maintaines will ever admit that it is
    a bug.

    It isn't a bug. The C standard allows this behavior.

    So, by C standard, there is no situation in which 'inline' with no
    addition qualifuers like 'static' or 'extern' can be used with
    predictabe outcome?







    --- PyGate Linux v1.5.13
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From David Brown@3:633/10 to All on Fri Mar 27 16:43:09 2026
    On 27/03/2026 14:47, Michael S wrote:
    On Thu, 26 Mar 2026 19:08:16 -0700
    Tim Rentsch <tr.17687@z991.linuxsc.com> wrote:

    Michael S <already5chosen@yahoo.com> writes:

    On Thu, 26 Mar 2026 22:36:59 +0000
    Bart <bc@freeuk.com> wrote:

    Take this program:

    #include <stdlib.h>

    inline int F(){return rand();}

    int main(void) {
    return F();
    }

    This compiles fine with gcc using -O1 -O2 -O3.

    But compile without optimising, and it fails to link as it can't
    find a function 'F'.

    Is it supposed to behave like that? I assume no discrete function
    F is being generated because of the 'inline' attribute, but you'd
    think it would ignore that if inlining wasn't enabled.

    [...]

    Sounds like a bug introduced during transition to c99/gnu99
    front end.
    With gcc4.x, which defaults to gnu89 it still works.
    Also works with the latest gcc -std=gnu89
    But there is no hope that gcc maintaines will ever admit that it is
    a bug.

    It isn't a bug. The C standard allows this behavior.

    So, by C standard, there is no situation in which 'inline' with no
    addition qualifuers like 'static' or 'extern' can be used with
    predictabe outcome?


    It is unspecified. If you have a function declared as "inline" without
    either "extern" or "static", then you can predict that the compiler will
    use that definition, or it will use a call to an external definition of
    the function (which may be in the same translation unit, or a different
    one). If you don't like that choice, either use compiler-documented
    methods of forcing the choice or use either "static inline" or "extern
    inline" as you feel appropriate. I think "static inline" is the usual
    course of action.

    (I gave a more complete explanation in another reply in this thread.)


    --- PyGate Linux v1.5.13
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Tim Rentsch@3:633/10 to All on Fri Mar 27 09:03:23 2026
    Michael S <already5chosen@yahoo.com> writes:

    On Thu, 26 Mar 2026 19:08:16 -0700
    Tim Rentsch <tr.17687@z991.linuxsc.com> wrote:

    Michael S <already5chosen@yahoo.com> writes:

    On Thu, 26 Mar 2026 22:36:59 +0000
    Bart <bc@freeuk.com> wrote:

    Take this program:

    #include <stdlib.h>

    inline int F(){return rand();}

    int main(void) {
    return F();
    }

    This compiles fine with gcc using -O1 -O2 -O3.

    But compile without optimising, and it fails to link as it can't
    find a function 'F'.

    Is it supposed to behave like that? I assume no discrete function
    F is being generated because of the 'inline' attribute, but you'd
    think it would ignore that if inlining wasn't enabled.

    [...]

    Sounds like a bug introduced during transition to c99/gnu99
    front end.
    With gcc4.x, which defaults to gnu89 it still works.
    Also works with the latest gcc -std=gnu89
    But there is no hope that gcc maintaines will ever admit that it is
    a bug.

    It isn't a bug. The C standard allows this behavior.

    So, by C standard, there is no situation in which 'inline' with no
    addition qualifuers like 'static' or 'extern' can be used with
    predictabe outcome?

    That depends on just how you mean the question. If we have this
    source file

    inline int
    F(){
    return 0;
    }

    int
    main(void){
    return F();
    }

    then we might get an undefined reference for F(). However if we
    expand that source file just slightly, to

    inline int
    F(){
    return 0;
    }

    int
    main(void){
    return F();
    }

    int F();

    then the program has well-defined behavior. This result obtains
    because the function F() now has a declaration that is (implicitly)
    external, and without specifying 'inline', in addition to the
    original inline definition.

    The usual pattern for providers of inline functions is supply a home
    for any inline functions defined in the .h file, by putting a
    non-line declaration in the corresponding .c file. Here is a simple
    example:


    in inline-client.c:

    #include "f-provider.h"

    int
    main(){
    return F();
    }


    in f-provider.h:

    #ifndef H_f_provider_h_
    #define H_f_provider_h_

    inline int
    F(){
    return 0;
    }

    #endif/*H_f_provider_h_*/


    in f-provider.c:

    #include "f-provider.h"

    int F();


    Following this pattern provides a definition for function F() with
    external linkage -- in f-provider.o -- in case one is needed.

    Note that this pattern can be used regardless of whether the inline
    definition for F() in the .h file specifies 'static' or doesn't.

    For what it's worth, I agree the rules for inline functions that do
    not specify 'static' are kind of weird. I don't know what the
    rationale was for choosing them. However, once one knows the
    pattern for how to deal with such cases, it's easy to provide inline
    functions without the danger of getting undefined references.

    --- PyGate Linux v1.5.13
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Bart@3:633/10 to All on Fri Mar 27 16:20:19 2026
    On 26/03/2026 22:36, Bart wrote:
    Take this program:

    ˙ #include <stdlib.h>

    ˙ inline int F(){return rand();}

    I just noticed that my subject line used 'include' instead of 'inline'.

    Either no one else did, or they were too polite to mention it.



    --- PyGate Linux v1.5.13
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From David Brown@3:633/10 to All on Fri Mar 27 18:07:40 2026
    On 27/03/2026 17:20, Bart wrote:
    On 26/03/2026 22:36, Bart wrote:
    Take this program:

    ˙˙ #include <stdlib.h>

    ˙˙ inline int F(){return rand();}

    I just noticed that my subject line used 'include' instead of 'inline'.

    Either no one else did, or they were too polite to mention it.


    As you know, we are all /very/ polite in this group :-)

    (Personally, I didn't notice.)


    --- PyGate Linux v1.5.13
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Keith Thompson@3:633/10 to All on Fri Mar 27 10:51:27 2026
    Bart <bc@freeuk.com> writes:
    On 26/03/2026 23:12, Keith Thompson wrote:
    [...]
    (Unless you're using a C23 compiler, I suggest "int F(void)" rather
    than "int F()".)

    Nobody bothers with that any more.

    I presume that's meant to be hyperbole. Plenty of C programmers do
    bother with that.

    Most seem to assume that () already
    means zero parameters anyway, judging by the incorrect usage I
    constantly saw in open source code.

    I find it better to write correct code than to look for excuses
    to write poor code. If I define a parameterless function F, I
    absolutely want a diagnostic if I call it with one or more arguments.
    If nothing else, it's an opportunity to set a good example.

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

    --- PyGate Linux v1.5.13
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Lawrence D?Oliveiro@3:633/10 to All on Fri Mar 27 21:27:21 2026
    On Fri, 27 Mar 2026 10:55:55 +0000, Bart wrote:

    Using 'static' to force an actual function to be generated sounds unintuitive.

    Lots of things about C are ?unintuitive?. Coming from Pascal, I found
    its type-definition syntax completely backwards.

    --- PyGate Linux v1.5.13
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Bart@3:633/10 to All on Fri Mar 27 22:05:24 2026
    On 27/03/2026 21:27, Lawrence D?Oliveiro wrote:
    On Fri, 27 Mar 2026 10:55:55 +0000, Bart wrote:

    Using 'static' to force an actual function to be generated sounds
    unintuitive.

    Lots of things about C are ?unintuitive?. Coming from Pascal, I found
    its type-definition syntax completely backwards.

    If only it was simply backwards! Instead it's inside-out and spirular.

    But, yeah, some C aspect being unintuitive is nothing new:

    extern int abc;
    int abc = 123;

    Or:

    static int abc;
    extern int abc;

    Both are valid C, but this isn't:

    extern int abc;
    static int abc;


    I dare not ask what the rules are; I'm sure they exist, but they still
    don't make sense. I can only find out which of these are valid or not by
    trial and error. A HLL shouldn't be like that.

    --- PyGate Linux v1.5.13
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Keith Thompson@3:633/10 to All on Fri Mar 27 17:03:52 2026
    Bart <bc@freeuk.com> writes:
    [...]
    I dare not ask what the rules are; I'm sure they exist, but they still
    don't make sense. I can only find out which of these are valid or not
    by trial and error. A HLL shouldn't be like that.
    [...]

    Fortunately, there's a standard.

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

    --- PyGate Linux v1.5.13
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Janis Papanagnou@3:633/10 to All on Sat Mar 28 05:10:08 2026
    On 2026-03-27 23:05, Bart wrote:
    On 27/03/2026 21:27, Lawrence D?Oliveiro wrote:
    On Fri, 27 Mar 2026 10:55:55 +0000, Bart wrote:

    Using 'static' to force an actual function to be generated sounds
    unintuitive.

    Lots of things about C are ?unintuitive?. Coming from Pascal, I found
    its type-definition syntax completely backwards.

    Is there any [non-C-derived] language that had a similar convoluted
    form?

    If only it was simply backwards! Instead it's inside-out and spirular.

    LOL! - nice formulation. (You made my day.)

    (The "C" declaration syntax somehow reminds me the US date format;
    yeah, neither forwards nor backwards.)

    Janis


    --- PyGate Linux v1.5.13
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Andrey Tarasevich@3:633/10 to All on Fri Mar 27 22:38:24 2026
    On Thu 3/26/2026 3:36 PM, Bart wrote:
    Take this program:

    ˙ #include <stdlib.h>

    ˙ inline int F(){return rand();}

    ˙ int main(void) {
    ˙˙˙˙˙ return F();
    ˙ }

    This compiles fine with gcc using -O1 -O2 -O3.

    But compile without optimising, and it fails to link as it can't find a function 'F'.

    Is it supposed to behave like that?

    Yes.

    C is very different from C++ when it comes to inline functions with
    external linkage. The language stoically sticks to "manual ODR"
    philosophy and refuses to employ the "common section" approach used by
    C++. C will not automatically emit an out-of-line definition for an external-linkage inline function. It is your responsibility to provide
    one (and choose the site for one) by making an `extern inline` declaration.

    If the compiler decides not to inline the call, it will seek an
    out-of-line "body" for your function. If there's no out-of-line "body",
    you will get a linker error. Which is exactly what happened in your case.

    So, you have to choose a site for the out-of-line definition - one and
    only one translation unit, in which you have to declare your function as `extern inline`. This translation unit will host the out-of-line "body"
    for the function. Since in your case you have only one translation unit,
    you can do it as

    inline int F(){return rand();}
    extern inline int F(); /* <- emits an out-of-line definition */

    or simply combine the two into one

    extern inline int F(){return rand();}

    Of course, you could avoid the whole issue altogether by declaring the function with internal linkage, i.e. `static inline`. `static inline` in
    C works exactly as it does in C++ - no extra effort is necessary.

    --
    Best regards,
    Andrey

    --- PyGate Linux v1.5.13
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Tim Rentsch@3:633/10 to All on Sat Mar 28 00:30:15 2026
    Andrey Tarasevich <noone@noone.net> writes:

    On Thu 3/26/2026 3:36 PM, Bart wrote:

    Take this program:

    #include <stdlib.h>

    inline int F(){return rand();}

    int main(void) {
    return F();
    }

    This compiles fine with gcc using -O1 -O2 -O3.

    But compile without optimising, and it fails to link as it can't
    find a function 'F'.

    Is it supposed to behave like that?

    Yes.

    C is very different from C++ when it comes to inline functions with
    external linkage. The language stoically sticks to "manual ODR"
    philosophy and refuses to employ the "common section" approach used by
    C++. C will not automatically emit an out-of-line definition for an external-linkage inline function. It is your responsibility to provide
    one (and choose the site for one) by making an `extern inline`
    declaration.

    If the compiler decides not to inline the call, it will seek an
    out-of-line "body" for your function. If there's no out-of-line
    "body", you will get a linker error. Which is exactly what happened in
    your case.

    So, you have to choose a site for the out-of-line definition - one and
    only one translation unit, in which you have to declare your function
    as `extern inline`. This translation unit will host the out-of-line
    "body" for the function. Since in your case you have only one
    translation unit, you can do it as

    inline int F(){return rand();}
    extern inline int F(); /* <- emits an out-of-line definition */

    Note that both 'extern' and 'inline' here can be dispensed with
    on the declaration. Rather than 'extern inline int F();' it
    suffices to say

    int F();

    to force the compiler to produce a real function body with
    a linker-visible symbol. And doing that doesn't interfere
    with the function being an inline function.


    --- PyGate Linux v1.5.13
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Andrey Tarasevich@3:633/10 to All on Sat Mar 28 10:25:33 2026
    On Fri 3/27/2026 6:47 AM, Michael S wrote:

    So, by C standard, there is no situation in which 'inline' with no
    addition qualifuers like 'static' or 'extern' can be used with
    predictabe outcome?


    The outcome is perfectly predictable, as long as you folow the ODR rules
    of C language.

    The `extern` is not really "additional" in a sense that it is not
    supposed to be present at the site of the actual inline definition of
    the function. Inline definitions with external linkage usually sit in
    header files, and if you turn them into `extern inline` definitions you
    will end up with "multiple definition" error instead of "missing
    definition" error.

    Every time you define an inline function with external linkage, you also
    have to provide an out-of-line definition for that function. As you
    probably understand, the out-of-line definition still has to obey the
    classic ODR: only one instance has to exist in the entire program. The language expects you to provide it separately and explicitly, but offers
    you some convenient tools to make the task easier. You don't have to
    redefine the function from scratch. All you need to do is to place an
    `extern inline` re-declaration of that function in one TU of your
    choice. Done.

    C sticks to its own ("manual") approach to ODR, and yet wants to
    maintain header-file compatibility with C++. The above is the compromise intended to comply with both of these objectives. That way the header
    file looks the same way it would look in C++ - it contains just an
    inline function definition. But to keep the C linker happy, in one of C-specific TU you will have to add an `extern inline` re-declaration of
    the function.

    --
    Best regards,
    Andrey

    --- PyGate Linux v1.5.13
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Michael S@3:633/10 to All on Sat Mar 28 20:37:18 2026
    On Fri, 27 Mar 2026 22:05:24 +0000
    Bart <bc@freeuk.com> wrote:

    On 27/03/2026 21:27, Lawrence D?Oliveiro wrote:
    On Fri, 27 Mar 2026 10:55:55 +0000, Bart wrote:

    Using 'static' to force an actual function to be generated sounds
    unintuitive.

    Lots of things about C are ?unintuitive?. Coming from P
    ascal, I
    found its type-definition syntax completely backwards.

    If only it was simply backwards! Instead it's inside-out and spirular.

    But, yeah, some C aspect being unintuitive is nothing new:

    extern int abc;
    int abc = 123;

    Or:

    static int abc;
    extern int abc;

    Both are valid C, but this isn't:

    extern int abc;
    static int abc;


    I dare not ask what the rules are; I'm sure they exist, but they
    still don't make sense. I can only find out which of these are valid
    or not by trial and error. A HLL shouldn't be like that.

    Why do you care?
    As C programmer, all you have to know is to never use either 2nd or
    3rd, because even if one of them can be legal according to Standard,
    both of them certainly make no sense.

    For your first example, it sometimes make sense, typically as result of #inclyde and/or marcro substitution. What exactly do you find
    unintuitive about it?










    --- PyGate Linux v1.5.13
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Bart@3:633/10 to All on Sat Mar 28 18:33:53 2026
    On 28/03/2026 17:37, Michael S wrote:
    On Fri, 27 Mar 2026 22:05:24 +0000
    Bart <bc@freeuk.com> wrote:

    On 27/03/2026 21:27, Lawrence D?Oliveiro wrote:
    On Fri, 27 Mar 2026 10:55:55 +0000, Bart wrote:

    Using 'static' to force an actual function to be generated sounds
    unintuitive.

    Lots of things about C are ?unintuitive?. Coming from Pascal, I
    found its type-definition syntax completely backwards.

    If only it was simply backwards! Instead it's inside-out and spirular.

    But, yeah, some C aspect being unintuitive is nothing new:

    extern int abc;
    int abc = 123;

    Or:

    static int abc;
    extern int abc;

    Both are valid C, but this isn't:

    extern int abc;
    static int abc;


    I dare not ask what the rules are; I'm sure they exist, but they
    still don't make sense. I can only find out which of these are valid
    or not by trial and error. A HLL shouldn't be like that.

    Why do you care?
    As C programmer,

    I'm also an implementer.

    all you have to know is to never use either 2nd or
    3rd, because even if one of them can be legal according to Standard,
    both of them certainly make no sense.

    For your first example, it sometimes make sense, typically as result of #inclyde and/or marcro substitution. What exactly do you find
    unintuitive about it?

    That you declare 'abc' as something to be imported, yet in the next line
    it is initialised as though it was exported.



    --- PyGate Linux v1.5.13
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From James Kuyper@3:633/10 to All on Sat Mar 28 18:48:39 2026
    On 27/03/2026 17:20, Bart wrote:
    On 26/03/2026 22:36, Bart wrote:
    Take this program:

    ˙˙ #include <stdlib.h>

    ˙˙ inline int F(){return rand();}

    I just noticed that my subject line used 'include' instead of 'inline'.

    Either no one else did, or they were too polite to mention it.
    The subject line is supposed to summarize the main message, so people
    will use a subject line only for deciding whether to bother reading a
    message. Once they start reading the message, they tend to ignore what
    was in the subject line. They will sometimes respond to obvious
    mismatches between the Subject line and the contents, but will more
    often ignore any such discrepancies. That's why it's a bad idea to
    mention anything only in the Subject line.


    --- PyGate Linux v1.5.13
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Michael S@3:633/10 to All on Sun Mar 29 10:37:56 2026
    On Sat, 28 Mar 2026 10:25:33 -0700
    Andrey Tarasevich <noone@noone.net> wrote:

    On Fri 3/27/2026 6:47 AM, Michael S wrote:

    So, by C standard, there is no situation in which 'inline' with no
    addition qualifuers like 'static' or 'extern' can be used with
    predictabe outcome?


    The outcome is perfectly predictable, as long as you folow the ODR
    rules of C language.


    Sometimes it works. Sometimes it does not. The Standard permits both
    behaviors.
    If it is not called 'unpredictable' then I don't know what is.


    --- PyGate Linux v1.5.13
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Michael S@3:633/10 to All on Sun Mar 29 11:46:18 2026
    On Fri, 27 Mar 2026 09:03:23 -0700
    Tim Rentsch <tr.17687@z991.linuxsc.com> wrote:

    Michael S <already5chosen@yahoo.com> writes:

    On Thu, 26 Mar 2026 19:08:16 -0700
    Tim Rentsch <tr.17687@z991.linuxsc.com> wrote:

    Michael S <already5chosen@yahoo.com> writes:

    On Thu, 26 Mar 2026 22:36:59 +0000
    Bart <bc@freeuk.com> wrote:

    Take this program:

    #include <stdlib.h>

    inline int F(){return rand();}

    int main(void) {
    return F();
    }

    This compiles fine with gcc using -O1 -O2 -O3.

    But compile without optimising, and it fails to link as it can't
    find a function 'F'.

    Is it supposed to behave like that? I assume no discrete
    function F is being generated because of the 'inline' attribute,
    but you'd think it would ignore that if inlining wasn't enabled.

    [...]

    Sounds like a bug introduced during transition to c99/gnu99
    front end.
    With gcc4.x, which defaults to gnu89 it still works.
    Also works with the latest gcc -std=gnu89
    But there is no hope that gcc maintaines will ever admit that it
    is a bug.

    It isn't a bug. The C standard allows this behavior.

    So, by C standard, there is no situation in which 'inline' with no
    addition qualifuers like 'static' or 'extern' can be used with
    predictabe outcome?

    That depends on just how you mean the question. If we have this
    source file

    inline int
    F(){
    return 0;
    }

    int
    main(void){
    return F();
    }

    then we might get an undefined reference for F(). However if we
    expand that source file just slightly, to

    inline int
    F(){
    return 0;
    }

    int
    main(void){
    return F();
    }

    int F();

    then the program has well-defined behavior. This result obtains
    because the function F() now has a declaration that is (implicitly)
    external, and without specifying 'inline', in addition to the
    original inline definition.


    It is indeed well-defined. But well-defined behavior does not match the
    most probable intention of programmer, which is to create a body of F()
    in case that it was not actually inlined and to *not* create it
    otherwise.
    For something like MSVC it does not matter, because by default it does
    not link unused functions into executive. But something like gcc by
    default links everything in.







    --- PyGate Linux v1.5.13
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From David Brown@3:633/10 to All on Sun Mar 29 11:24:39 2026
    On 28/03/2026 19:33, Bart wrote:
    On 28/03/2026 17:37, Michael S wrote:
    On Fri, 27 Mar 2026 22:05:24 +0000
    Bart <bc@freeuk.com> wrote:

    On 27/03/2026 21:27, Lawrence D?Oliveiro wrote:
    On Fri, 27 Mar 2026 10:55:55 +0000, Bart wrote:
    Using 'static' to force an actual function to be generated sounds
    unintuitive.

    Lots of things about C are ?unintuitive?. Coming from Pascal, I
    found its type-definition syntax completely backwards.

    If only it was simply backwards! Instead it's inside-out and spirular.

    But, yeah, some C aspect being unintuitive is nothing new:

    ˙˙˙ extern int abc;
    ˙˙˙ int abc = 123;

    Or:

    ˙˙˙ static int abc;
    ˙˙˙ extern int abc;

    Both are valid C, but this isn't:

    ˙˙˙ extern int abc;
    ˙˙˙ static int abc;


    I dare not ask what the rules are; I'm sure they exist, but they
    still don't make sense. I can only find out which of these are valid
    or not by trial and error. A HLL shouldn't be like that.

    Why do you care?
    As C programmer,

    I'm also an implementer.


    When you write code, there are two sane choices :

    extern int abc; // In a header
    int abc = 123; // In one implementation file

    or

    static int abc = 123; // In any implementation file

    So as a C programmer, as Michael says, the oddities of mixing different declaration types does not matter.


    As an implementer, you have two sane choices :

    1.
    Read the standards and implement them. (The rules here are not remotely difficult to understand or follow. You might find them unintuitive or
    plain daft, and you won't want to remember them, but that is no hinder
    to getting them right.)

    2.
    Throw a fatal error message from your compiler if you encounter such
    mixups. The compiler would be non-compliant, but almost no one is going
    to write code mixing extern and static like this, at least not
    intentionally.


    In your case, I'd recommend option 2. Your compiler is niche, primarily
    for compiling code from your own C code generators, or for your own
    personal testing and interest. And presumably they never generate code
    that mixes static and extern.



    all you have to know is to never use either 2nd or
    3rd, because even if one of them can be legal according to Standard,
    both of them certainly make no sense.

    For your first example, it sometimes make sense, typically as result of
    #inclyde and/or marcro substitution. What exactly do you find
    unintuitive about it?

    That you declare 'abc' as something to be imported, yet in the next line
    it is initialised as though it was exported.




    --- PyGate Linux v1.5.13
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From David Brown@3:633/10 to All on Sun Mar 29 11:30:54 2026
    On 29/03/2026 09:37, Michael S wrote:
    On Sat, 28 Mar 2026 10:25:33 -0700
    Andrey Tarasevich <noone@noone.net> wrote:

    On Fri 3/27/2026 6:47 AM, Michael S wrote:

    So, by C standard, there is no situation in which 'inline' with no
    addition qualifuers like 'static' or 'extern' can be used with
    predictabe outcome?


    The outcome is perfectly predictable, as long as you folow the ODR
    rules of C language.


    Sometimes it works. Sometimes it does not. The Standard permits both behaviors.

    If it matters to the correctness of your code, then your code is faulty.

    It is unspecified whether the inline version will be used, or if the
    external linkage version will be used. Correct code has to be correct
    in either case (unless you have some compiler-specific way of forcing
    the choice). When this example "works sometimes", it is because it is
    not correct portable code.

    It is no different in principle from writing code like :

    foobar(foo(), bar());

    that works if "foo" is called before "bar".


    (Note that I am not endorsing the way C handles plain "inline" functions
    - I prefer the C++ model. I am describing how it is, not how I would
    like it to be.)

    If it is not called 'unpredictable' then I don't know what is.


    It is unspecified behaviour. That means the standards give certain
    choices for implementation, but do not say which is taken. So yes,
    there is unpredictability - within very strict limits of which choices
    can be made. (And real-world compilers usually provide ways to predict
    or influence the choice.)




    --- PyGate Linux v1.5.13
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Bart@3:633/10 to All on Sun Mar 29 12:44:04 2026
    On 29/03/2026 10:24, David Brown wrote:
    On 28/03/2026 19:33, Bart wrote:
    On 28/03/2026 17:37, Michael S wrote:
    On Fri, 27 Mar 2026 22:05:24 +0000
    Bart <bc@freeuk.com> wrote:

    On 27/03/2026 21:27, Lawrence D?Oliveiro wrote:
    On Fri, 27 Mar 2026 10:55:55 +0000, Bart wrote:
    Using 'static' to force an actual function to be generated sounds
    unintuitive.

    Lots of things about C are ?unintuitive?. Coming from Pascal, I
    found its type-definition syntax completely backwards.

    If only it was simply backwards! Instead it's inside-out and spirular. >>>>
    But, yeah, some C aspect being unintuitive is nothing new:

    ˙˙˙ extern int abc;
    ˙˙˙ int abc = 123;

    Or:

    ˙˙˙ static int abc;
    ˙˙˙ extern int abc;

    Both are valid C, but this isn't:

    ˙˙˙ extern int abc;
    ˙˙˙ static int abc;


    I dare not ask what the rules are; I'm sure they exist, but they
    still don't make sense. I can only find out which of these are valid
    or not by trial and error. A HLL shouldn't be like that.

    Why do you care?
    As C programmer,

    I'm also an implementer.


    When you write code, there are two sane choices :

    ˙˙˙˙extern int abc;˙˙˙˙˙˙˙ // In a header
    ˙˙˙˙int abc = 123;˙˙˙˙˙˙˙ // In one implementation file

    or

    ˙˙˙˙static int abc = 123;˙˙˙ // In any implementation file

    So as a C programmer, as Michael says, the oddities of mixing different declaration types does not matter.

    As an implementer, you have two sane choices :

    1.
    Read the standards and implement them.˙ (The rules here are not remotely difficult to understand or follow.˙ You might find them unintuitive or
    plain daft, and you won't want to remember them, but that is no hinder
    to getting them right.)

    With my compiler, I need to set up a test then display the symbol table
    to see whether any declared symbol is local, imported or exported, if
    there are multiple, conflicting declarations.

    The choices it makes seem to be adequate for most code that is
    encountered (at least those have rarely been a problem). But I can't
    tell you if it exactly follows the standard.


    2.
    Throw a fatal error message from your compiler if you encounter such
    mixups.˙ The compiler would be non-compliant, but almost no one is going
    to write code mixing extern and static like this, at least not intentionally.


    In your case, I'd recommend option 2.˙ Your compiler is niche, primarily
    for compiling code from your own C code generators, or for your own
    personal testing and interest.

    That is actually is something it always does perfectly! Unfortunately
    that is only for testing. The point of generating C is for when either
    someone else builds my code with their compiler, or I run gcc to make
    use of its optimisations.

    ˙ And presumably they never generate code
    that mixes static and extern.

    Yes, the code generated is very conservative and validated; I don't
    expect compiler errors.

    If I take this program test.m in my language (The backticks preserve case):

    export proc `F = end # export from program
    global proc `G = end # export from module but not program
    proc `H = end # local

    importdll $dummy =
    proc `I # import from external library, not module
    end

    Then the generated C includes these lines:

    void test_F();
    static void test_G();
    static void test_H();
    extern void I();

    void test_F() {
    return;
    }

    static void test_G() {
    return;
    }

    static void test_H() {
    return;
    }

    (This is for whole-program compilation, and there is a single C file generated. There are no include files, not even for the standard library.)

    All locally defined functions have exactly one forward declaration in
    the C. Most will be static.



    --- PyGate Linux v1.5.13
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Andrey Tarasevich@3:633/10 to All on Sun Mar 29 07:22:37 2026
    On Sun 3/29/2026 12:37 AM, Michael S wrote:
    On Sat, 28 Mar 2026 10:25:33 -0700
    Andrey Tarasevich <noone@noone.net> wrote:

    On Fri 3/27/2026 6:47 AM, Michael S wrote:

    So, by C standard, there is no situation in which 'inline' with no
    addition qualifuers like 'static' or 'extern' can be used with
    predictabe outcome?


    The outcome is perfectly predictable, as long as you folow the ODR
    rules of C language.


    Sometimes it works. Sometimes it does not. The Standard permits both behaviors.
    If it is not called 'unpredictable' then I don't know what is.

    It works or it doesn't work depending on whether the compiler decided to substitute/embed each and every call to that function. That is indeed unpredictable. Call inlining is intended to be based on internal
    compiler's heuristics, which are naturally expected to be unpredictable.

    In a way, this is like undefined or unspecified behavior, which
    "sometimes works and sometimes doesn't". Except that in this case, when
    it doesn't work, the diagnostic is better.

    --
    Best regards,
    Andrey

    --- PyGate Linux v1.5.13
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From James Kuyper@3:633/10 to All on Sun Mar 29 13:56:49 2026
    On 2026-03-29 03:37, Michael S wrote:
    On Sat, 28 Mar 2026 10:25:33 -0700
    Andrey Tarasevich <noone@noone.net> wrote:
    ...
    The outcome is perfectly predictable, as long as you folow the ODR
    rules of C language.


    Sometimes it works. Sometimes it does not. The Standard permits both behaviors.
    If it is not called 'unpredictable' then I don't know what is.

    Using external inline gives the implementation a choice. in general, the implementation will choose whichever is better in a given situation,
    which is usually a good thing.
    Sometimes it will call the inline definition, sometimes it will call the external one. In most cases, you handle that by making sure the inline definition is the same as the external one - there's some not very
    difficult techniques using the preprocessor so that you only have to
    write the code once, and the same definition is used for both.
    If, for any reason, it's important to you that the behavior be different
    when calling the external version than the inline one, that's also easy
    to arrange.
    What's the problem?

    --- PyGate Linux v1.5.13
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Michael S@3:633/10 to All on Sun Mar 29 21:39:01 2026
    On Sun, 29 Mar 2026 13:56:49 -0400
    James Kuyper <jameskuyper@alumni.caltech.edu> wrote:

    On 2026-03-29 03:37, Michael S wrote:
    On Sat, 28 Mar 2026 10:25:33 -0700
    Andrey Tarasevich <noone@noone.net> wrote:
    ...
    The outcome is perfectly predictable, as long as you folow the ODR
    rules of C language.


    Sometimes it works. Sometimes it does not. The Standard permits both behaviors.
    If it is not called 'unpredictable' then I don't know what is.

    Using external inline gives the implementation a choice. in general,
    the implementation will choose whichever is better in a given
    situation, which is usually a good thing.
    Sometimes it will call the inline definition, sometimes it will call
    the external one. In most cases, you handle that by making sure the
    inline definition is the same as the external one - there's some not
    very difficult techniques using the preprocessor so that you only
    have to write the code once, and the same definition is used for both.
    If, for any reason, it's important to you that the behavior be
    different when calling the external version than the inline one,
    that's also easy to arrange.
    What's the problem?

    If you don't see situation presented by Bart as a problem then we don't
    have enough of common ground to continue the conversation.

    My own conclusion is simple - my decades-long custom of never using
    inline without static is even wiser than I imagined.


    --- PyGate Linux v1.5.13
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Bart@3:633/10 to All on Sun Mar 29 22:37:38 2026
    On 29/03/2026 00:53, Waldek Hebisch wrote:
    Bart <bc@freeuk.com> wrote:

    That you declare 'abc' as something to be imported, yet in the next line
    it is initialised as though it was exported.

    Well, C is not able to directy express notion of export and import.
    Also, C does not have modules/interfaces etc. Instead C object
    may be global (which is specified by 'extern' or by lack of storage
    class at file scope) or local. C rules allow you to emulate
    modules using header files. Namely, in header file you put
    'extern' declaration for exports of given module. Any user of
    of the module must include the corresponding header file.
    To ensure consistency also implementation of the module should
    include the header file. So everywhere elso you have just
    'extern' declaration, but in implementation there is 'extern'
    declaration first followed by the definition. If such
    sequence was disallowed, then it would be harder to check
    consistency of implementation and corresponding header.

    So in reasonable code 'extern' really means "part of interface"
    without specifying if it is import or export.


    But, the rules are lax, and implementations exploit that by all behaving
    a little differently.

    TBF, the diverse implementations probably came first, and the standard
    had to accommodate existing practice.

    It still means it is lax: you can often get away without using 'extern'
    for example. Or you can define 'int abc' in several modules but the
    linker creates only one definition. (It's stricter now, but tcc for
    example still allows that.)

    Still, there aren't really many combinations, and no need to have
    anything more than one definition, and one forward declaration. The
    rules could therefore be simplified, and a compiler could reject
    anything unusual unless specifically enabled.

    Here's how it works in my ASM synax:

    F: # local symbol (a function or variable definition)
    G:: # exported symbol
    call H* # imported symbol (all instances need the *)

    It can be that simple!

    (However the assembler allows forward references that C lacks. And there
    is no concept of a shared header; a second module that exports H needs
    to use it locally as H, while calling G needs G*.)

    Note that implementers of C decided to use system linker.
    When C was born standard linkers did not have support for
    modules and due to limits on name length emulating such
    support was problematic.

    This is more about namespaces, a separate topic. You can have exported
    and imported symbols without worrying about namespaces, but names may clash.


    So what C did was best thing
    possible. We can now do better, but requirement of
    compatibility means that old approach is still in use.
    To put it differently: any language which decides to
    break compatibility (and a lot did so) can do better than
    C. But due to compatibility C is more popular than
    competitors.

    Suppose you want to create a DLL or .so file which exports function 'F'
    from this program:

    void F() {}
    void G() {}
    void H() {}

    C appears to give no guidance here; the behaviour depends on the compiler:

    gcc: exports F, G, H
    tcc: exports none

    (With some codebases I've seen which don't bother with 'static',
    hundreds of symbols could be exported.)

    This can be changed using an attribute:

    __declspec(dllexport) void F() {}
    void G() {}
    void H() {}

    Now:

    gcc: exports F only (G, H are no longer exported)
    tcc: exports F

    Again, very messy. And ugly.




    --- PyGate Linux v1.5.13
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Andrey Tarasevich@3:633/10 to All on Sun Mar 29 16:15:39 2026
    On Sat 3/28/2026 12:30 AM, Tim Rentsch wrote:

    So, you have to choose a site for the out-of-line definition - one and
    only one translation unit, in which you have to declare your function
    as `extern inline`. This translation unit will host the out-of-line
    "body" for the function. Since in your case you have only one
    translation unit, you can do it as

    inline int F(){return rand();}
    extern inline int F(); /* <- emits an out-of-line definition */

    Note that both 'extern' and 'inline' here can be dispensed with
    on the declaration. Rather than 'extern inline int F();' it
    suffices to say

    int F();

    to force the compiler to produce a real function body with
    a linker-visible symbol. And doing that doesn't interfere
    with the function being an inline function.

    Yes, I see that any kind of `extern` declaration for this function
    (including the implicit `extern`) causes the compiler to emit the
    non-inline definition for the corresponding TU, i.e. any of

    int F();
    extern int F();
    extern inline int F();

    lead to the same effect.

    Personally, if following this approach (i.e. add a re-declaration to one
    .c file), I'd probably stick to `extern inline` version just to express
    the intent better: to make it clear that this declaration was placed
    there specifically for that purpose - to emit a non-inline body for an
    inline function. Otherwise, it would like like an ordinary function declaration sitting in the middle of the file for no apparent reason.
    (Of course, one can also write a comment stating the purpose.)

    P.S. As it has already been mentioned somewhere in the thread, there an alternative, header-only approach: just use the preprocessor to alter
    the header file definition in one chosen TU

    #ifdef MAKE_BODY_
    extern
    #endif /* MAKE_BODY_ */
    inline int F() { /* whatever */ }

    --
    Best regards,
    Andrey

    --- PyGate Linux v1.5.13
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From James Kuyper@3:633/10 to All on Sun Mar 29 20:08:04 2026
    On 2026-03-29 14:39, Michael S wrote:
    On Sun, 29 Mar 2026 13:56:49 -0400
    James Kuyper <jameskuyper@alumni.caltech.edu> wrote:
    ...
    Using external inline gives the implementation a choice. in general,
    the implementation will choose whichever is better in a given
    situation, which is usually a good thing.
    Sometimes it will call the inline definition, sometimes it will call
    the external one. In most cases, you handle that by making sure the
    inline definition is the same as the external one - there's some not
    very difficult techniques using the preprocessor so that you only
    have to write the code once, and the same definition is used for both.
    If, for any reason, it's important to you that the behavior be
    different when calling the external version than the inline one,
    that's also easy to arrange.
    What's the problem?

    If you don't see situation presented by Bart as a problem then we don't
    have enough of common ground to continue the conversation.

    Bart's problem is that he pays too much attention to his preferences for
    how a language should work, and not enough attention to what the
    standard says about how the C language actually works. I can understand preferring different behavior, and switching to a different langauge
    that provides that behavior, even if that means creating the language
    yourself. What I don't understand is why he continues to insist on using
    a language whose design bothers him so much.

    If he wants the behavior that C defines for static inline, use it. If he
    wants the behavior that C defines for inline functions with external
    linkage, use that. But he should know the difference if he insists on
    using inline functions in C.


    --- PyGate Linux v1.5.13
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Bart@3:633/10 to All on Mon Mar 30 01:58:43 2026
    On 30/03/2026 01:08, James Kuyper wrote:
    On 2026-03-29 14:39, Michael S wrote:
    On Sun, 29 Mar 2026 13:56:49 -0400
    James Kuyper <jameskuyper@alumni.caltech.edu> wrote:
    ...
    Using external inline gives the implementation a choice. in general,
    the implementation will choose whichever is better in a given
    situation, which is usually a good thing.
    Sometimes it will call the inline definition, sometimes it will call
    the external one. In most cases, you handle that by making sure the
    inline definition is the same as the external one - there's some not
    very difficult techniques using the preprocessor so that you only
    have to write the code once, and the same definition is used for both.
    If, for any reason, it's important to you that the behavior be
    different when calling the external version than the inline one,
    that's also easy to arrange.
    What's the problem?

    If you don't see situation presented by Bart as a problem then we don't
    have enough of common ground to continue the conversation.

    Bart's problem is that he pays too much attention to his preferences for
    how a language should work, and not enough attention to what the
    standard says about how the C language actually works. I can understand preferring different behavior, and switching to a different langauge
    that provides that behavior, even if that means creating the language yourself. What I don't understand is why he continues to insist on using
    a language whose design bothers him so much.

    If he wants the behavior that C defines for static inline, use it. If he wants the behavior that C defines for inline functions with external
    linkage, use that. But he should know the difference if he insists on
    using inline functions in C.


    Did you read my OP? I provided a link to the original example, which is
    not mine.

    It was part of set of ancient benchmark, poor ones in my view.

    Still, I was mildly interested in how my C compiler (which shares the
    same backend as one compiler for my own language) would perform.

    As it turns on, not too great on this one. Here are timings when the
    argument to that lists program is 10000:

    tcc 6.7
    gcc -O0 5.4 (with 'inlines' removed)
    DMC -o 3.0 (old 32-bit compiler; rest are 64)
    bcc 2.7
    gcc -O2 1.45+
    gcc -O2 1.45- (with 'inlines' reinstated)
    gcc -O2 1.55 (inlined routines in their own module)

    It's reasonable however compared with lesser C compilers, although I
    don't have many around now.

    The inlines may have had a small effect on gcc -O2, but I can't be sure.
    I'd have expected gcc 14.1.0 to do its own inlining, but the last timing suggests it makes only a small difference anyway.



    --- PyGate Linux v1.5.13
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Lawrence D?Oliveiro@3:633/10 to All on Mon Mar 30 07:13:43 2026
    On Fri, 27 Mar 2026 00:25:15 -0000 (UTC), I wrote:

    Since you didn?t declare it ?static? (internal linkage), it has
    external linkage. Looking at N3220, section 6.7.5, ?Function
    specifiers?:

    An inline definition does not provide an external definition for
    the function and does not forbid an external definition in
    another translation unit. Inline definitions provide an
    alternative to external definitions, which a translator may use
    to implement any call to the function in the same translation
    unit. It is unspecified whether a call to the function uses the
    inline definition or the external definition.

    However, I have also been looking at what GCC makes of it <https://gcc.gnu.org/onlinedocs/gcc-15.2.0/gcc/Inline.html>. I?m not
    sure that?s saying the same thing. Or maybe there is some implication
    about how ?inline? is supposed to be used.

    First it says that GCC implements three different types of ?inline?
    semantics; one which predates the official C spec, one which is
    compatible with the official C spec, and the third which is compatible
    with the way C++ does it.

    It also talks about two ?important cases? for how inline is used:

    * when a function is defined as ?static inline?
    * when a function is first declared without ?inline? and then is
    defined with ?inline?.

    The bottom 3 paragraphs define the pre-C-spec way of implementing
    ?inline?.

    --- PyGate Linux v1.5.13
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Tim Rentsch@3:633/10 to All on Mon Mar 30 00:41:22 2026
    Andrey Tarasevich <noone@noone.net> writes:

    On Sat 3/28/2026 12:30 AM, Tim Rentsch wrote:

    So, you have to choose a site for the out-of-line definition - one and
    only one translation unit, in which you have to declare your function
    as `extern inline`. This translation unit will host the out-of-line
    "body" for the function. Since in your case you have only one
    translation unit, you can do it as

    inline int F(){return rand();}
    extern inline int F(); /* <- emits an out-of-line definition */

    Note that both 'extern' and 'inline' here can be dispensed with
    on the declaration. Rather than 'extern inline int F();' it
    suffices to say

    int F();

    to force the compiler to produce a real function body with
    a linker-visible symbol. And doing that doesn't interfere
    with the function being an inline function.

    Yes, I see that any kind of `extern` declaration for this function
    (including the implicit `extern`) causes the compiler to emit the
    non-inline definition for the corresponding TU, i.e. any of

    int F();
    extern int F();
    extern inline int F();

    lead to the same effect.

    Personally, if following this approach (i.e. add a re-declaration to
    one .c file), I'd probably stick to `extern inline` version just to
    express the intent better: to make it clear that this declaration was
    placed there specifically for that purpose - to emit a non-inline body
    for an inline function.

    I wouldn't want to advise you on how to write your own code, however
    to me this choice seems rather backwards. I follow the same rule
    for functions that I do for variables - 'extern' for declarations to
    mean defined somewhere else, and without 'extern' for definitions to
    mean defined here (or at least in this compilation unit). My habit
    for 'static' functions is to give a foward declaration (of course
    with 'static') near the start of the file, and to not use either
    'static' or 'extern' on the actual definition. Following this rule
    lets me change the linkage from internal to external by changing
    only one place (the factoring principle, later to be called "Don't
    Repeat Yourself"). My preference would be to follow the same rule
    for 'static' variables, but unfortunately the C standard doesn't
    allow that, which strikes me as a shortsighted decision, but oh
    well.

    Otherwise, it would like like an ordinary
    function declaration sitting in the middle of the file for no apparent reason. (Of course, one can also write a comment stating the purpose.)

    Right. I would add to that that if there is one inline external
    function then there is likely more than one, and the definitions for
    all those functions could be put together, right at the end of the
    source file, in which case it would be more obvious what they were
    doing (or that only one comment would be needed for all).

    P.S. As it has already been mentioned somewhere in the thread, there
    an alternative, header-only approach: just use the preprocessor to
    alter the header file definition in one chosen TU

    #ifdef MAKE_BODY_
    extern
    #endif /* MAKE_BODY_ */
    inline int F() { /* whatever */ }

    Ugh. This idea is one of those appropriately described as "too
    clever by half". The downsides greatly outweigh the upsides.

    --- PyGate Linux v1.5.13
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Tim Rentsch@3:633/10 to All on Mon Mar 30 05:07:22 2026
    Bart <bc@freeuk.com> writes:

    On 27/03/2026 21:27, Lawrence D?Oliveiro wrote:

    On Fri, 27 Mar 2026 10:55:55 +0000, Bart wrote:

    Using 'static' to force an actual function to be generated sounds
    unintuitive.

    Lots of things about C are ?unintuitive?. Coming from Pascal, I found
    its type-definition syntax completely backwards.

    If only it was simply backwards! Instead it's inside-out and spirular.

    But, yeah, some C aspect being unintuitive is nothing new:

    extern int abc;
    int abc = 123;

    Or:

    static int abc;
    extern int abc;

    Both are valid C, but this isn't:

    extern int abc;
    static int abc;


    I dare not ask what the rules are; I'm sure they exist, but they
    still don't make sense.

    They do make sense, to those who take the time to understand the
    reasoning that went into why the rules are as they are.

    I can only find out which of these are valid or not
    by trial and error.

    I suspect it's because you think of the rules as arbitrary that
    you feel you have to resort to trial and error. Here again the
    key is to understand the reasons for what choices were made. The
    C Rationale document is an excellent resource for doing that.

    --- PyGate Linux v1.5.13
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Tim Rentsch@3:633/10 to All on Mon Mar 30 05:33:06 2026
    Bart <bc@freeuk.com> writes:

    On 29/03/2026 00:53, Waldek Hebisch wrote:

    Bart <bc@freeuk.com> wrote:

    That you declare 'abc' as something to be imported, yet in the next line >>> it is initialised as though it was exported.

    Well, C is not able to directy express notion of export and import.
    Also, C does not have modules/interfaces etc. Instead C object
    may be global (which is specified by 'extern' or by lack of storage
    class at file scope) or local. C rules allow you to emulate
    modules using header files. Namely, in header file you put
    'extern' declaration for exports of given module. Any user of
    of the module must include the corresponding header file.
    To ensure consistency also implementation of the module should
    include the header file. So everywhere elso you have just
    'extern' declaration, but in implementation there is 'extern'
    declaration first followed by the definition. If such
    sequence was disallowed, then it would be harder to check
    consistency of implementation and corresponding header.

    So in reasonable code 'extern' really means "part of interface"
    without specifying if it is import or export.

    But, the rules are lax, and implementations exploit that by all
    behaving a little differently.

    The rules are not lax; they are carefully and precisely defined.
    To see that one needs to know what the rules are, which in turn
    depends on reading and understanding the C standard.

    TBF, the diverse implementations probably came first, and the
    standard had to accommodate existing practice.

    Diverse implementations certainly came first, including especially
    the original description of C in "The C Programming Language", by
    Kernighan and Ritchie. The rules for global symbols in K&R C are
    markedly different than those in the current C standard. There is
    a long explanation of different options considered for standard C,
    given in the C Rationale document.

    --- PyGate Linux v1.5.13
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Bart@3:633/10 to All on Mon Mar 30 14:42:18 2026
    On 30/03/2026 13:33, Tim Rentsch wrote:> Bart <bc@freeuk.com> writes:

    On 29/03/2026 00:53, Waldek Hebisch wrote:

    Bart <bc@freeuk.com> wrote:

    That you declare 'abc' as something to be imported, yet in the
    next line
    it is initialised as though it was exported.

    Well, C is not able to directy express notion of export and import.
    Also, C does not have modules/interfaces etc. Instead C object
    may be global (which is specified by 'extern' or by lack of storage
    class at file scope) or local. C rules allow you to emulate
    modules using header files. Namely, in header file you put
    'extern' declaration for exports of given module. Any user of
    of the module must include the corresponding header file.
    To ensure consistency also implementation of the module should
    include the header file. So everywhere elso you have just
    'extern' declaration, but in implementation there is 'extern'
    declaration first followed by the definition. If such
    sequence was disallowed, then it would be harder to check
    consistency of implementation and corresponding header.

    So in reasonable code 'extern' really means "part of interface"
    without specifying if it is import or export.

    But, the rules are lax, and implementations exploit that by all
    behaving a little differently.

    The rules are not lax; they are carefully and precisely defined.
    To see that one needs to know what the rules are, which in turn
    depends on reading and understanding the C standard.

    The rules may well be precise, but they can also allow for laxity in how
    code is written, and how strictly they are enforced by a compiler.

    That is why exactly the same program can pass with 0 warnings or errors,
    pass with some warnings, or fail with errors, depending on the options provided. So:

    c:\cx>gcc mcc.c

    c:\cx>gcc -Wall -Wextra -Wpedantic -O2 mcc.c 2>errors

    The first invocation compiled fine. The second generated thousands of warnings, for the same language standard. With -Werror, it would also fail.

    Does the standard also say anything about linking? These two modules:

    a.c: int abc; int main(){}
    b.c: int abc;

    both individually compile with no problems. They also link successfully
    using TCC and DMC /C/ compilers.

    But gcc (invoking its linker) fails saying there are multiple
    definitions of 'abc'.

    The point is that a.c + b.c can either form a valid program or not due
    to laxness in the language.

    Here's another example which I believe you brought up:

    static void F();
    void F() {}

    That first 'static' is what determines F's linkage. However someone
    perusing the source code will see 'void F(){...}` and assume it is exported.

    But you can also have:

    static void F(int a);
    static void F(int b);
    static void F(int c);
    extern void F(int d) {}
    static void F(int e);
    void F(int f);
    static void F(int g);

    This is what I call being lax, while still within the rules, whatever
    they are.

    (In my language, F must be defined in exactly one place. No other
    declarations are allowed or needed within the program itself. So such
    multiple instances don't arise.

    It is effectively stricter than C but enforced through better design.
    C's design could also be tightened up, but people don't seem to like
    losing the 'flexibility' shown above.)


    --- PyGate Linux v1.5.13
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Michael S@3:633/10 to All on Mon Mar 30 16:53:36 2026
    On Mon, 30 Mar 2026 14:42:18 +0100
    Bart <bc@freeuk.com> wrote:


    (In my language, F must be defined in exactly one place. No other declarations are allowed or needed within the program itself. So such multiple instances don't arise.


    Do you make distinction between declarations and definitions?


    --- PyGate Linux v1.5.13
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Tim Rentsch@3:633/10 to All on Mon Mar 30 07:20:29 2026
    Michael S <already5chosen@yahoo.com> writes:

    On Fri, 27 Mar 2026 22:05:24 +0000
    Bart <bc@freeuk.com> wrote:

    But, yeah, some C aspect being unintuitive is nothing new:

    extern int abc;
    int abc = 123;

    Or:

    static int abc;
    extern int abc;

    Both are valid C, but this isn't:

    extern int abc;
    static int abc;


    I dare not ask what the rules are; I'm sure they exist, but they
    still don't make sense. I can only find out which of these are
    valid or not by trial and error. A HLL shouldn't be like that.

    Why do you care?
    As C programmer, all you have to know is to never use either 2nd or
    3rd, because even if one of them can be legal according to Standard,
    both of them certainly make no sense.

    There are situations where an 'extern' declaration might reasonably
    be preceded by a 'static' declaration (or definition). These days I
    expect such situations don't arise very often, so people don't think
    about it, but the rule is there for a reason.

    For your first example, it sometimes make sense, typically as result
    of #inclyde and/or marcro substitution. What exactly do you find
    unintuitive about it?

    For global symbols, in 99+ percent of all cases, there should be
    an 'extern' declaration in a .h file, and a definition without
    the 'extern' in exactly one .c file. Informally, 'extern' means
    "is defined somewhere but I'm not saying where", and leaving off
    the 'extern' means "I'm defining it here". Speaking for myself I
    don't find anything hard to understand or unintuitive about that.

    --- PyGate Linux v1.5.13
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Andrey Tarasevich@3:633/10 to All on Mon Mar 30 07:54:18 2026
    On Mon 3/30/2026 12:13 AM, Lawrence D?Oliveiro wrote:

    The bottom 3 paragraphs define the pre-C-spec way of implementing
    ?inline?.

    It is interesting to note that in many respects it is the opposite of
    the modern standard approach. Plain `inline` cases the compiler to emit
    an external definition, while `extern inline` definition is essentially
    a macro - no external definition is generated.

    --
    Best regards,
    Andrey





    --- PyGate Linux v1.5.13
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Tim Rentsch@3:633/10 to All on Mon Mar 30 07:59:01 2026
    Michael S <already5chosen@yahoo.com> writes:

    On Sun, 29 Mar 2026 13:56:49 -0400
    James Kuyper <jameskuyper@alumni.caltech.edu> wrote:

    On 2026-03-29 03:37, Michael S wrote:

    On Sat, 28 Mar 2026 10:25:33 -0700
    Andrey Tarasevich <noone@noone.net> wrote:

    ...

    The outcome is perfectly predictable, as long as you folow the ODR
    rules of C language.

    Sometimes it works. Sometimes it does not. The Standard permits both
    behaviors.
    If it is not called 'unpredictable' then I don't know what is.

    Using external inline gives the implementation a choice. in general,
    the implementation will choose whichever is better in a given
    situation, which is usually a good thing.
    Sometimes it will call the inline definition, sometimes it will call
    the external one. In most cases, you handle that by making sure the
    inline definition is the same as the external one - there's some not
    very difficult techniques using the preprocessor so that you only
    have to write the code once, and the same definition is used for both.
    If, for any reason, it's important to you that the behavior be
    different when calling the external version than the inline one,
    that's also easy to arrange.
    What's the problem?

    If you don't see situation presented by Bart as a problem then we don't
    have enough of common ground to continue the conversation.

    My own conclusion is simple - my decades-long custom of never using
    inline without static is even wiser than I imagined.

    It's important to realize that local and global inline functions
    serve different purposes. In most cases what I think people want
    and expect is a function that is always expanded inline, except
    perhaps in cases where the compiler has reasons to think that is
    not a good idea. These cases should always use 'static'; as far
    as I can tell there is never any reason not to.

    The more unusual case is where we want the possibility that a
    function might be expanded inline, but it is also important that
    there be a single definition in some translation unit. For
    reasons of ease of compilation, C requires that which translation
    unit that is be identified explicitly; that is done by giving
    a non-inline definition in a particular source file. Because
    this situation doesn't come up very much, it isn't used very much
    (except sometimes by accident), so it isn't surprising that most
    people don't know what is meant by a non-static inline function.

    The rule is simple: Always use 'static' for inline functions,
    unless there is a good reason not to. And in those cases it's
    important to understand just how non-static inline functions
    work, and to observe the relevant criteria so that they work
    properly.

    --- PyGate Linux v1.5.13
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Tim Rentsch@3:633/10 to All on Mon Mar 30 08:19:52 2026
    Michael S <already5chosen@yahoo.com> writes:

    On Fri, 27 Mar 2026 09:03:23 -0700
    Tim Rentsch <tr.17687@z991.linuxsc.com> wrote:

    Michael S <already5chosen@yahoo.com> writes:

    On Thu, 26 Mar 2026 19:08:16 -0700
    Tim Rentsch <tr.17687@z991.linuxsc.com> wrote:

    Michael S <already5chosen@yahoo.com> writes:

    On Thu, 26 Mar 2026 22:36:59 +0000
    Bart <bc@freeuk.com> wrote:

    Take this program:

    #include <stdlib.h>

    inline int F(){return rand();}

    int main(void) {
    return F();
    }

    This compiles fine with gcc using -O1 -O2 -O3.

    But compile without optimising, and it fails to link as it
    can't find a function 'F'.

    Is it supposed to behave like that? I assume no discrete
    function F is being generated because of the 'inline'
    attribute, but you'd think it would ignore that if
    inlining wasn't enabled.

    [...]

    Sounds like a bug introduced during transition to c99/gnu99
    front end.
    With gcc4.x, which defaults to gnu89 it still works.
    Also works with the latest gcc -std=gnu89
    But there is no hope that gcc maintaines will ever admit that it
    is a bug.

    It isn't a bug. The C standard allows this behavior.

    So, by C standard, there is no situation in which 'inline'
    with no addition qualifuers like 'static' or 'extern' can be
    used with predictabe outcome?

    That depends on just how you mean the question. If we have this
    source file

    inline int
    F(){
    return 0;
    }

    int
    main(void){
    return F();
    }

    then we might get an undefined reference for F(). However if we
    expand that source file just slightly, to

    inline int
    F(){
    return 0;
    }

    int
    main(void){
    return F();
    }

    int F();

    then the program has well-defined behavior. This result obtains
    because the function F() now has a declaration that is (implicitly)
    external, and without specifying 'inline', in addition to the
    original inline definition.

    It is indeed well-defined. But well-defined behavior does not
    match the most probable intention of programmer, which is to
    create a body of F() in case that it was not actually inlined and
    to *not* create it otherwise.

    I simply disagree. The behavior you describe is what people expect
    for static inline functions. It doesn't make sense that they would
    expect the same thing for non-static inline functions, if they had
    taken some time to think about it.

    For something like MSVC it does not matter, because by default it
    does not link unused functions into executive. But something like
    gcc by default links everything in.

    That depends on what you mean by "by default". gcc does not
    produce an actual function body for any optimization level
    above -O0. Surely the most common practice these days is to
    enable some optimization level above -O0.

    --- PyGate Linux v1.5.13
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Tim Rentsch@3:633/10 to All on Mon Mar 30 08:27:37 2026
    Bart <bc@freeuk.com> writes:

    On 30/03/2026 13:33, Tim Rentsch wrote:> Bart <bc@freeuk.com> writes:

    On 29/03/2026 00:53, Waldek Hebisch wrote:

    Bart <bc@freeuk.com> wrote:

    That you declare 'abc' as something to be imported, yet in the next line >>>>> it is initialised as though it was exported.

    Well, C is not able to directy express notion of export and import.
    Also, C does not have modules/interfaces etc. Instead C object
    may be global (which is specified by 'extern' or by lack of storage
    class at file scope) or local. C rules allow you to emulate
    modules using header files. Namely, in header file you put
    'extern' declaration for exports of given module. Any user of
    of the module must include the corresponding header file.
    To ensure consistency also implementation of the module should
    include the header file. So everywhere elso you have just
    'extern' declaration, but in implementation there is 'extern'
    declaration first followed by the definition. If such
    sequence was disallowed, then it would be harder to check
    consistency of implementation and corresponding header.

    So in reasonable code 'extern' really means "part of interface"
    without specifying if it is import or export.

    But, the rules are lax, and implementations exploit that by all
    behaving a little differently.

    The rules are not lax; they are carefully and precisely defined.
    To see that one needs to know what the rules are, which in turn
    depends on reading and understanding the C standard.

    The rules may well be precise, but they can also allow for laxity in
    how code is written, and how strictly they are enforced by a compiler.

    That is why exactly the same program can pass with 0 warnings or
    errors, pass with some warnings, or fail with errors, depending on the options provided. So:

    c:\cx>gcc mcc.c

    c:\cx>gcc -Wall -Wextra -Wpedantic -O2 mcc.c 2>errors

    The first invocation compiled fine. The second generated thousands of warnings, for the same language standard. With -Werror, it would also
    fail.

    Does the standard also say anything about linking? These two modules:

    a.c: int abc; int main(){}
    b.c: int abc;

    both individually compile with no problems. They also link
    successfully using TCC and DMC /C/ compilers.

    But gcc (invoking its linker) fails saying there are multiple
    definitions of 'abc'.

    The point is that a.c + b.c can either form a valid program or not due
    to laxness in the language.

    Here's another example which I believe you brought up:

    static void F();
    void F() {}

    That first 'static' is what determines F's linkage. However someone
    perusing the source code will see 'void F(){...}` and assume it is
    exported.

    But you can also have:

    static void F(int a);
    static void F(int b);
    static void F(int c);
    extern void F(int d) {}
    static void F(int e);
    void F(int f);
    static void F(int g);

    This is what I call being lax, while still within the rules, whatever
    they are.

    (In my language, F must be defined in exactly one place. No other declarations are allowed or needed within the program itself. So such multiple instances don't arise.

    It is effectively stricter than C but enforced through better
    design. C's design could also be tightened up, but people don't seem
    to like losing the 'flexibility' shown above.)

    It seems to me that your real complaint is not that you don't
    understand the rules but that you don't like them. I'm not able
    to help you with that.

    --- PyGate Linux v1.5.13
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Michael S@3:633/10 to All on Mon Mar 30 20:08:51 2026
    On Mon, 30 Mar 2026 08:19:52 -0700
    Tim Rentsch <tr.17687@z991.linuxsc.com> wrote:

    Michael S <already5chosen@yahoo.com> writes:

    On Fri, 27 Mar 2026 09:03:23 -0700
    Tim Rentsch <tr.17687@z991.linuxsc.com> wrote:

    Michael S <already5chosen@yahoo.com> writes:

    On Thu, 26 Mar 2026 19:08:16 -0700
    Tim Rentsch <tr.17687@z991.linuxsc.com> wrote:

    Michael S <already5chosen@yahoo.com> writes:

    On Thu, 26 Mar 2026 22:36:59 +0000
    Bart <bc@freeuk.com> wrote:

    Take this program:

    #include <stdlib.h>

    inline int F(){return rand();}

    int main(void) {
    return F();
    }

    This compiles fine with gcc using -O1 -O2 -O3.

    But compile without optimising, and it fails to link as it
    can't find a function 'F'.

    Is it supposed to behave like that? I assume no discrete
    function F is being generated because of the 'inline'
    attribute, but you'd think it would ignore that if
    inlining wasn't enabled.

    [...]

    Sounds like a bug introduced during transition to c99/gnu99
    front end.
    With gcc4.x, which defaults to gnu89 it still works.
    Also works with the latest gcc -std=gnu89
    But there is no hope that gcc maintaines will ever admit that it
    is a bug.

    It isn't a bug. The C standard allows this behavior.

    So, by C standard, there is no situation in which 'inline'
    with no addition qualifuers like 'static' or 'extern' can be
    used with predictabe outcome?

    That depends on just how you mean the question. If we have this
    source file

    inline int
    F(){
    return 0;
    }

    int
    main(void){
    return F();
    }

    then we might get an undefined reference for F(). However if we
    expand that source file just slightly, to

    inline int
    F(){
    return 0;
    }

    int
    main(void){
    return F();
    }

    int F();

    then the program has well-defined behavior. This result obtains
    because the function F() now has a declaration that is (implicitly)
    external, and without specifying 'inline', in addition to the
    original inline definition.

    It is indeed well-defined. But well-defined behavior does not
    match the most probable intention of programmer, which is to
    create a body of F() in case that it was not actually inlined and
    to *not* create it otherwise.

    I simply disagree. The behavior you describe is what people expect
    for static inline functions. It doesn't make sense that they would
    expect the same thing for non-static inline functions, if they had
    taken some time to think about it.

    For something like MSVC it does not matter, because by default it
    does not link unused functions into executive. But something like
    gcc by default links everything in.

    That depends on what you mean by "by default". gcc does not
    produce an actual function body for any optimization level
    above -O0. Surely the most common practice these days is to
    enable some optimization level above -O0.


    In the comment above I was talking specifically about that: https://godbolt.org/z/4TazP4sq1






    --- PyGate Linux v1.5.13
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Bart@3:633/10 to All on Mon Mar 30 18:11:49 2026
    On 30/03/2026 14:53, Michael S wrote:
    On Mon, 30 Mar 2026 14:42:18 +0100
    Bart <bc@freeuk.com> wrote:


    (In my language, F must be defined in exactly one place. No other
    declarations are allowed or needed within the program itself. So such
    multiple instances don't arise.


    Do you make distinction between declarations and definitions?


    Yes. Everything within a program will have a definition. No separate declarations are needed thanks to the module scheme.

    The things that can be defined and shared between modules include
    functions, variables, named constants, enumerations, types, structs,
    macros. Definitions can also be in any order.

    Declarations exist, but they are only for things imported from outside
    the program, which will be precompiled external libraries.

    This is different from C, where sharing any entity between the modules
    of a program requires each module to see its declaration, while the
    definition resides in some 'home' module.

    That can present difficulties, for example:

    char data[]; # in shared header

    char data[] = {10,20,30,40,50}; # definition

    You want to do sizeof(data) from another module, but it doesn't work
    because the size is only known when compiling the home module. Full example:

    a.h:
    extern char data[]; // extern is apparently needed

    a.c:
    #include <stdio.h>
    #include "a.h"

    char data[] = {10, 20, 30, 40, 50};
    int F(); // extern is not needed ...

    int main() {printf("%zu\n", F());}

    b.c:
    #include "a.h"
    int F() {return sizeof(data);}

    Compile using 'cc a.c b.c'. It will complain about sizeof on complete type.

    If I try the same thing:

    a.m:
    module b
    global []byte data = (10, 20, 30, 40, 50)
    proc main = println F() end

    b.m:
    global fun F:int = data.len # 'fun' is for one-liners

    I compile using 'mm a' and it Just Works. I don't even need to tell it
    both module names, or the extension for 'a'. Output is '5'.

    Notice here there is one definition for both 'data' and 'F', and no declaration. (That 'module b' directive is outside the main language.)

    --- PyGate Linux v1.5.13
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Keith Thompson@3:633/10 to All on Mon Mar 30 11:54:37 2026
    Bart <bc@freeuk.com> writes:
    [...]
    The rules may well be precise, but they can also allow for laxity in
    how code is written, and how strictly they are enforced by a compiler.

    That is why exactly the same program can pass with 0 warnings or
    errors, pass with some warnings, or fail with errors, depending on the options provided. So:

    c:\cx>gcc mcc.c

    c:\cx>gcc -Wall -Wextra -Wpedantic -O2 mcc.c 2>errors

    The first invocation compiled fine. The second generated thousands of warnings, for the same language standard. With -Werror, it would also
    fail.

    Not the same language. gcc with no "-std=..." option compiles the
    GNU C dialect. The default is "-std=gnuNN", where NN is 17 or 23
    for recent versions of gcc. That's reasonable if your code depends
    on GNU extensions, but not if you want to make a point about the
    C language as defined by the ISO standard(s).

    If you care about the C language as defined by the ISO standard,
    you should be using something like "-std=cNN -pedantic" (or "-pedantic-errors"), where NN is the relevant edition of the
    standard.

    With "-Wall -Wextra", if you get "thousands of warnings", it's
    because you asked for them.

    You're complaining about gcc being lax, not about the C language
    being lax.

    Of course you know all this.

    I think there is some laxity in ISO C's treatment of "inline",
    some cases where one conforming implementation might successfully
    handle a program and another might reject it. I agree that's not
    ideal. But as a programmer, you can avoid it by understanding the
    standard's rules for "inline" and writing portable code. (And as
    an implementer, you can either make a choice within the range of
    options allowed by the standard, or you can make a non-conforming implementation that does whatever you prefer.)

    [...]

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

    --- PyGate Linux v1.5.13
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Bart@3:633/10 to All on Mon Mar 30 21:54:29 2026
    On 30/03/2026 19:54, Keith Thompson wrote:
    Bart <bc@freeuk.com> writes:
    [...]
    The rules may well be precise, but they can also allow for laxity in
    how code is written, and how strictly they are enforced by a compiler.

    That is why exactly the same program can pass with 0 warnings or
    errors, pass with some warnings, or fail with errors, depending on the
    options provided. So:

    c:\cx>gcc mcc.c

    c:\cx>gcc -Wall -Wextra -Wpedantic -O2 mcc.c 2>errors

    The first invocation compiled fine. The second generated thousands of
    warnings, for the same language standard. With -Werror, it would also
    fail.

    Not the same language. gcc with no "-std=..." option compiles the
    GNU C dialect. The default is "-std=gnuNN", where NN is 17 or 23
    for recent versions of gcc. That's reasonable if your code depends
    on GNU extensions, but not if you want to make a point about the
    C language as defined by the ISO standard(s).

    If you care about the C language as defined by the ISO standard,
    you should be using something like "-std=cNN -pedantic" (or "-pedantic-errors"), where NN is the relevant edition of the
    standard.

    With "-Wall -Wextra", if you get "thousands of warnings", it's
    because you asked for them.

    You're complaining about gcc being lax, not about the C language
    being lax.

    It's both. Example:

    signed char* p;
    unsigned char* q;
    p = q;

    This compiles OK with no options. But should it; what does the language
    say about it?

    Another example:

    int F(){}

    Here not even -Wextra touches it. I need -Wpedantic, which tells me a
    lot about C!

    In the specific example of mcc.c, which is generated code, most warnings
    are of things that I don't consider the business of the language.

    I think there is some laxity in ISO C's treatment of "inline",
    some cases where one conforming implementation might successfully
    handle a program and another might reject it. I agree that's not
    ideal. But as a programmer, you can avoid it by understanding the
    standard's rules for "inline" and writing portable code. (And as
    an implementer, you can either make a choice within the range of
    options allowed by the standard, or you can make a non-conforming implementation that does whatever you prefer.)

    'inline' is easy; it is completely ignored.


    --- PyGate Linux v1.5.13
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From James Kuyper@3:633/10 to All on Mon Mar 30 21:06:06 2026
    Bart <bc@freeuk.com> writes:
    [...]
    The rules may well be precise, but they can also allow for laxity in
    how code is written, and how strictly they are enforced by a compiler.

    That is why exactly the same program can pass with 0 warnings or
    errors, pass with some warnings, or fail with errors, depending on the options provided. So:

    c:\cx>gcc mcc.c

    c:\cx>gcc -Wall -Wextra -Wpedantic -O2 mcc.c 2>errors

    The first invocation compiled fine. The second generated thousands of warnings, for the same language standard. With -Werror, it would also
    fail.

    As Keith has pointed out, the rules of C are irrelevant to those
    compilation, which obey the rules of Gnu C, a different language. You
    should raise the issue in a forum devoted to that language.

    But, what is your issue? You explicitly requested additional warnings,
    and seem to be complaining about the fact that, as a result, you get
    additional warnings?!

    It's less than obvious, but the -O2 option also enables additional
    warnings: in order to perform the optimizations enabled by that option,
    the compiler does a deeper analysis of the code than it does without
    that option. That deeper analysis enables it to give you warnings about
    issues it couldn't even identify without that analysis. Note that the
    problems it identifies at higher optimization levels are not necessarily problems at lower optimization levels; it's mainly the optimizations
    that are enabled by that deeper analysis which run into the potential
    problems that can be uncovered by that analysis.

    --- PyGate Linux v1.5.13
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Keith Thompson@3:633/10 to All on Mon Mar 30 18:07:53 2026
    Bart <bc@freeuk.com> writes:
    On 30/03/2026 19:54, Keith Thompson wrote:
    [...]
    You're complaining about gcc being lax, not about the C language
    being lax.

    It's both. Example:

    signed char* p;
    unsigned char* q;
    p = q;

    This compiles OK with no options. But should it; what does the
    language say about it?

    As I think you already know, that's a constraint violation,
    requiring a diagnostic.

    Another example:

    int F(){}

    Here not even -Wextra touches it. I need -Wpedantic, which tells me a
    lot about C!

    "gcc -Wpedantic" doesn't complain about that. There is no syntax
    error and no constraint violation, so the language does not require
    a diagnostic.

    If you have a point, feel free to make it more clearly.

    [...]

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

    --- PyGate Linux v1.5.13
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Lawrence D?Oliveiro@3:633/10 to All on Tue Mar 31 01:46:46 2026
    On Mon, 30 Mar 2026 07:54:18 -0700, Andrey Tarasevich wrote:

    On Mon 3/30/2026 12:13 AM, Lawrence D?Oliveiro wrote:

    <https://gcc.gnu.org/onlinedocs/gcc-15.2.0/gcc/Inline.html>

    The bottom 3 paragraphs define the pre-C-spec way of implementing
    ?inline?.

    It is interesting to note that in many respects it is the opposite
    of the modern standard approach. Plain `inline` cases the compiler
    to emit an external definition, while `extern inline` definition is essentially a macro - no external definition is generated.

    That seems to be a completely counterintuitive use of the ?extern?
    qualifier to me. I would have used ?static inline? for the macro
    usage, and left ?extern? to be essentially a no-op qualifier, as it
    currently is for ordinary functions.

    --- PyGate Linux v1.5.13
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Tim Rentsch@3:633/10 to All on Tue Mar 31 00:26:09 2026
    Michael S <already5chosen@yahoo.com> writes:

    On Mon, 30 Mar 2026 08:19:52 -0700
    Tim Rentsch <tr.17687@z991.linuxsc.com> wrote:

    Michael S <already5chosen@yahoo.com> writes:

    On Fri, 27 Mar 2026 09:03:23 -0700
    Tim Rentsch <tr.17687@z991.linuxsc.com> wrote:

    Michael S <already5chosen@yahoo.com> writes:

    On Thu, 26 Mar 2026 19:08:16 -0700
    Tim Rentsch <tr.17687@z991.linuxsc.com> wrote:

    Michael S <already5chosen@yahoo.com> writes:

    On Thu, 26 Mar 2026 22:36:59 +0000
    Bart <bc@freeuk.com> wrote:

    Take this program:

    #include <stdlib.h>

    inline int F(){return rand();}

    int main(void) {
    return F();
    }

    This compiles fine with gcc using -O1 -O2 -O3.

    But compile without optimising, and it fails to link as it
    can't find a function 'F'.

    Is it supposed to behave like that? I assume no discrete
    function F is being generated because of the 'inline'
    attribute, but you'd think it would ignore that if
    inlining wasn't enabled.

    [...]

    Sounds like a bug introduced during transition to c99/gnu99
    front end.
    With gcc4.x, which defaults to gnu89 it still works.
    Also works with the latest gcc -std=gnu89
    But there is no hope that gcc maintaines will ever admit that it >>>>>>> is a bug.

    It isn't a bug. The C standard allows this behavior.

    So, by C standard, there is no situation in which 'inline'
    with no addition qualifuers like 'static' or 'extern' can be
    used with predictabe outcome?

    That depends on just how you mean the question. If we have this
    source file

    inline int
    F(){
    return 0;
    }

    int
    main(void){
    return F();
    }

    then we might get an undefined reference for F(). However if we
    expand that source file just slightly, to

    inline int
    F(){
    return 0;
    }

    int
    main(void){
    return F();
    }

    int F();

    then the program has well-defined behavior. This result obtains
    because the function F() now has a declaration that is (implicitly)
    external, and without specifying 'inline', in addition to the
    original inline definition.

    It is indeed well-defined. But well-defined behavior does not
    match the most probable intention of programmer, which is to
    create a body of F() in case that it was not actually inlined and
    to *not* create it otherwise.

    I simply disagree. The behavior you describe is what people expect
    for static inline functions. It doesn't make sense that they would
    expect the same thing for non-static inline functions, if they had
    taken some time to think about it.

    For something like MSVC it does not matter, because by default it
    does not link unused functions into executive. But something like
    gcc by default links everything in.

    That depends on what you mean by "by default". gcc does not
    produce an actual function body for any optimization level
    above -O0. Surely the most common practice these days is to
    enable some optimization level above -O0.

    In the comment above I was talking specifically about that: https://godbolt.org/z/4TazP4sq1

    I don't know what you were expecting me to learn by looking at
    that page. As far as I can tell it adds no information beyond
    what has already been presented.

    Also I'm surprised that you didn't say anything in reaction to the
    first paragraph of my comments. No conclusion, just surprised.

    --- PyGate Linux v1.5.13
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Michael S@3:633/10 to All on Tue Mar 31 11:27:12 2026
    On Tue, 31 Mar 2026 00:26:09 -0700
    Tim Rentsch <tr.17687@z991.linuxsc.com> wrote:

    Michael S <already5chosen@yahoo.com> writes:

    On Mon, 30 Mar 2026 08:19:52 -0700
    Tim Rentsch <tr.17687@z991.linuxsc.com> wrote:

    Michael S <already5chosen@yahoo.com> writes:

    On Fri, 27 Mar 2026 09:03:23 -0700
    Tim Rentsch <tr.17687@z991.linuxsc.com> wrote:

    Michael S <already5chosen@yahoo.com> writes:

    On Thu, 26 Mar 2026 19:08:16 -0700
    Tim Rentsch <tr.17687@z991.linuxsc.com> wrote:

    Michael S <already5chosen@yahoo.com> writes:

    On Thu, 26 Mar 2026 22:36:59 +0000
    Bart <bc@freeuk.com> wrote:

    Take this program:

    #include <stdlib.h>

    inline int F(){return rand();}

    int main(void) {
    return F();
    }

    This compiles fine with gcc using -O1 -O2 -O3.

    But compile without optimising, and it fails to link as it
    can't find a function 'F'.

    Is it supposed to behave like that? I assume no discrete
    function F is being generated because of the 'inline'
    attribute, but you'd think it would ignore that if
    inlining wasn't enabled.

    [...]

    Sounds like a bug introduced during transition to c99/gnu99
    front end.
    With gcc4.x, which defaults to gnu89 it still works.
    Also works with the latest gcc -std=gnu89
    But there is no hope that gcc maintaines will ever admit that
    it is a bug.

    It isn't a bug. The C standard allows this behavior.

    So, by C standard, there is no situation in which 'inline'
    with no addition qualifuers like 'static' or 'extern' can be
    used with predictabe outcome?


    <<<<<<<<<<<<<<<<<<
    That depends on just how you mean the question. If we have this
    source file

    inline int
    F(){
    return 0;
    }

    int
    main(void){
    return F();
    }

    then we might get an undefined reference for F(). However if we
    expand that source file just slightly, to

    inline int
    F(){
    return 0;
    }

    int
    main(void){
    return F();
    }

    int F();

    then the program has well-defined behavior. This result obtains
    because the function F() now has a declaration that is
    (implicitly) external, and without specifying 'inline', in
    addition to the original inline definition.


    <<<<<<<<<<<<<<<<<<


    It is indeed well-defined. But well-defined behavior does not
    match the most probable intention of programmer, which is to
    create a body of F() in case that it was not actually inlined and
    to *not* create it otherwise.

    I simply disagree. The behavior you describe is what people expect
    for static inline functions. It doesn't make sense that they would
    expect the same thing for non-static inline functions, if they had
    taken some time to think about it.

    For something like MSVC it does not matter, because by default it
    does not link unused functions into executive. But something like
    gcc by default links everything in.

    That depends on what you mean by "by default". gcc does not
    produce an actual function body for any optimization level
    above -O0. Surely the most common practice these days is to
    enable some optimization level above -O0.

    In the comment above I was talking specifically about that: https://godbolt.org/z/4TazP4sq1

    I don't know what you were expecting me to learn by looking at
    that page. As far as I can tell it adds no information beyond
    what has already been presented.


    The page shows that when, according to your suggestion, source code is
    modified to make it well-defined then body of F() is generated
    regardless of optimization level. As you likely expected.
    It was a point of suggested modification, was not it?

    Also I'm surprised that you didn't say anything in reaction to the
    first paragraph of my comments. No conclusion, just surprised.

    What do you consider 1st paragraph?
    My reading was that lines between <<<<<<<<<<<<<<<<<< marks were meant to
    be read together, as a single item.





    --- PyGate Linux v1.5.13
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Bart@3:633/10 to All on Tue Mar 31 11:39:41 2026
    On 31/03/2026 02:07, Keith Thompson wrote:
    Bart <bc@freeuk.com> writes:
    On 30/03/2026 19:54, Keith Thompson wrote:
    [...]
    You're complaining about gcc being lax, not about the C language
    being lax.

    It's both. Example:

    signed char* p;
    unsigned char* q;
    p = q;

    This compiles OK with no options. But should it; what does the
    language say about it?

    As I think you already know, that's a constraint violation,
    requiring a diagnostic.

    But apparently it doesn't require it to fail the compilation.

    Another example:

    int F(){}

    Here not even -Wextra touches it. I need -Wpedantic, which tells me a
    lot about C!

    "gcc -Wpedantic" doesn't complain about that. There is no syntax
    error and no constraint violation, so the language does not require
    a diagnostic.

    If you have a point, feel free to make it more clearly.

    OK, you suggested that it was a compiler being lax, not the language.

    Here, allowing such code to be written IS being lax. And in the above
    example, the language allowing the compiler to ignore the error or to
    only warn, is also being lax.

    But I expect that you will now redefine 'lax', or 'laxity' in such a way
    that the language cannot ever be accused of being it. It's all under
    control!

    So a language may require a diagnostic but leaves it up to the compiler
    as to how severely it is reported, if at all.

    Or the language specifies that running into the final '}' of a non-void function is UB, thereby washing its hands of it.

    However the end result is that people CAN write sloppy code, and that in
    my book is the language being 'lax'.

    (Both of these examples will fail compilation in my language: the second
    one is 'func F:int = end' and the error message is 'Void
    expression/return value missing', since the syntax is expression based
    and a missing return is a common cause of a statement/expression not
    yielding the expected value.)

    --- PyGate Linux v1.5.13
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From David Brown@3:633/10 to All on Tue Mar 31 15:57:26 2026
    On 29/03/2026 13:44, Bart wrote:
    On 29/03/2026 10:24, David Brown wrote:
    On 28/03/2026 19:33, Bart wrote:
    On 28/03/2026 17:37, Michael S wrote:
    On Fri, 27 Mar 2026 22:05:24 +0000
    Bart <bc@freeuk.com> wrote:

    On 27/03/2026 21:27, Lawrence D?Oliveiro wrote:
    On Fri, 27 Mar 2026 10:55:55 +0000, Bart wrote:
    Using 'static' to force an actual function to be generated sounds >>>>>>> unintuitive.

    Lots of things about C are ?unintuitive?. Coming from Pascal, I
    found its type-definition syntax completely backwards.

    If only it was simply backwards! Instead it's inside-out and spirular. >>>>>
    But, yeah, some C aspect being unintuitive is nothing new:

    ˙˙˙ extern int abc;
    ˙˙˙ int abc = 123;

    Or:

    ˙˙˙ static int abc;
    ˙˙˙ extern int abc;

    Both are valid C, but this isn't:

    ˙˙˙ extern int abc;
    ˙˙˙ static int abc;


    I dare not ask what the rules are; I'm sure they exist, but they
    still don't make sense. I can only find out which of these are valid >>>>> or not by trial and error. A HLL shouldn't be like that.

    Why do you care?
    As C programmer,

    I'm also an implementer.


    When you write code, there are two sane choices :

    ˙˙˙˙˙extern int abc;˙˙˙˙˙˙˙ // In a header
    ˙˙˙˙˙int abc = 123;˙˙˙˙˙˙˙ // In one implementation file

    or

    ˙˙˙˙˙static int abc = 123;˙˙˙ // In any implementation file

    So as a C programmer, as Michael says, the oddities of mixing
    different declaration types does not matter.

    As an implementer, you have two sane choices :

    1.
    Read the standards and implement them.˙ (The rules here are not
    remotely difficult to understand or follow.˙ You might find them
    unintuitive or plain daft, and you won't want to remember them, but
    that is no hinder to getting them right.)

    With my compiler, I need to set up a test then display the symbol table
    to see whether any declared symbol is local, imported or exported, if
    there are multiple, conflicting declarations.

    The choices it makes seem to be adequate for most code that is
    encountered (at least those have rarely been a problem). But I can't
    tell you if it exactly follows the standard.


    2.
    Throw a fatal error message from your compiler if you encounter such
    mixups.˙ The compiler would be non-compliant, but almost no one is
    going to write code mixing extern and static like this, at least not
    intentionally.


    In your case, I'd recommend option 2.˙ Your compiler is niche,
    primarily for compiling code from your own C code generators, or for
    your own personal testing and interest.

    That is actually is something it always does perfectly! Unfortunately
    that is only for testing. The point of generating C is for when either someone else builds my code with their compiler, or I run gcc to make
    use of its optimisations.

    ˙ And presumably they never generate code that mixes static and extern.

    Yes, the code generated is very conservative and validated; I don't
    expect compiler errors.

    If I take this program test.m in my language (The backticks preserve case):

    ˙ export proc `F = end˙˙˙˙ # export from program
    ˙ global proc `G = end˙˙˙˙ # export from module but not program
    ˙˙˙˙˙˙˙˙ proc `H = end˙˙˙˙ # local

    ˙ importdll $dummy =
    ˙˙˙˙˙˙˙˙ proc `I˙˙˙˙˙˙˙˙˙˙ # import from external library, not module
    ˙ end

    Then the generated C includes these lines:

    ˙ void test_F();
    ˙ static void test_G();
    ˙ static void test_H();
    ˙ extern void I();

    ˙ void test_F() {
    ˙˙˙˙˙ return;
    ˙ }

    ˙ static void test_G() {
    ˙˙˙˙˙ return;
    ˙ }

    ˙ static void test_H() {
    ˙˙˙˙˙ return;
    ˙ }

    (This is for whole-program compilation, and there is a single C file generated. There are no include files, not even for the standard library.)

    All locally defined functions have exactly one forward declaration in
    the C. Most will be static.



    So what you are really saying, is that you don't have a problem - either
    when writing C code, or when implementing your C compiler. And no one
    else here has expressed having problems with the way "extern" and
    "static" work in C. No doubt many would have preferred somewhat
    different rules, but that's just personal preference - not something
    that hinders them writing or understanding normal C code.

    And if everyone is okay with C rules here, and can work with them
    without trouble, then why are you complaining yet again?



    --- PyGate Linux v1.5.13
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Keith Thompson@3:633/10 to All on Tue Mar 31 13:56:31 2026
    Bart <bc@freeuk.com> writes:
    On 31/03/2026 02:07, Keith Thompson wrote:
    Bart <bc@freeuk.com> writes:
    On 30/03/2026 19:54, Keith Thompson wrote:
    [...]
    You're complaining about gcc being lax, not about the C language
    being lax.

    It's both. Example:

    signed char* p;
    unsigned char* q;
    p = q;

    This compiles OK with no options. But should it; what does the
    language say about it?
    As I think you already know, that's a constraint violation,
    requiring a diagnostic.

    But apparently it doesn't require it to fail the compilation.

    Apparently?

    ISO C requires a diagnostic for any translation unit that contains
    a violation of any syntax rule or constraint. It never requires
    compilation to fail, with the sole exception of the #error directive.

    Did you really not know that?

    Another example:

    int F(){}

    Here not even -Wextra touches it. I need -Wpedantic, which tells me a
    lot about C!
    "gcc -Wpedantic" doesn't complain about that. There is no syntax
    error and no constraint violation, so the language does not require
    a diagnostic.
    If you have a point, feel free to make it more clearly.

    OK, you suggested that it was a compiler being lax, not the language.

    My point was that your example did not demonstrate that the language
    is lax. gcc by default does not generate all the diagnostics that
    are required by the language. Observing that fact says nothing
    about the language.

    Here, allowing such code to be written IS being lax. And in the above example, the language allowing the compiler to ignore the error or to
    only warn, is also being lax.

    I don't disagree. I would have preferred a requirement that any
    translation unit that violates a syntax rule or constraing must be
    rejected. Most compilers would undoubtedly have provided options
    to turn fatal errors into warnings in a non-conforming mode.

    "int F(){}" is probably a better example of the point you were making
    than the example you provided upthread (which I'm too lazy to look up).

    But I expect that you will now redefine 'lax', or 'laxity' in such a
    way that the language cannot ever be accused of being it. It's all
    under control!

    Do not put words in my mouth. Do not pretend that I have every
    said or implied that C is flawless.

    So a language may require a diagnostic but leaves it up to the
    compiler as to how severely it is reported, if at all.

    The language requires diagnostic for certain errors. It does
    not specify whether those diagnostics are fatal or not. It does
    not permit a conforming compiler to fail to produce a required
    diagnostic. A non-conforming compiler can obviously do anythiung
    it likes; it would be absurd for the standard to try to impose
    requirements on implementations that do not conform to it.

    You know this, but you pretend not to.

    Or the language specifies that running into the final '}' of a
    non-void function is UB, thereby washing its hands of it.

    No, it specifies that if a non-void function doesn't return a value
    (falls off the end without executing a return statement), attempting
    to use that value has undefined behavior. Yes, this is lax.
    The historical reasons for this have been discussed at great length.

    Any language change that would require a diagnostic for
    "int F(void){}" would require more compile-time analysis than is
    currently required. I would support adding such a requirement to a
    new edition of the standard. I'm not aware of any proposal to do so.

    For example:

    int F(void) {
    if (time(NULL) > 0) {
    return 1;
    }
    }

    Last time this was discussed here, I suggested adopting something
    similar to C#'s rules in this area.

    However the end result is that people CAN write sloppy code, and that
    in my book is the language being 'lax'.

    Again, I don't disagree -- with the proviso that it's not possible for
    any non-trivial language to forbid all sloppy code.

    (Both of these examples will fail compilation in my language:
    [snip]

    I might be willing to discuss your language if you talked about it in comp.lang.misc.

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

    --- PyGate Linux v1.5.13
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Tim Rentsch@3:633/10 to All on Mon Apr 6 20:56:46 2026
    Keith Thompson <Keith.S.Thompson+u@gmail.com> writes:

    Bart <bc@freeuk.com> writes:

    On 31/03/2026 02:07, Keith Thompson wrote:

    Bart <bc@freeuk.com> writes:

    On 30/03/2026 19:54, Keith Thompson wrote:

    [...]

    You're complaining about gcc being lax, not about the C
    language being lax.

    It's both. Example:

    signed char* p;
    unsigned char* q;
    p = q;

    This compiles OK with no options. But should it; what does
    the language say about it?

    As I think you already know, that's a constraint violation,
    requiring a diagnostic.

    But apparently it doesn't require it to fail the compilation.

    Apparently?

    ISO C requires a diagnostic for any translation unit that contains
    a violation of any syntax rule or constraint. It never requires
    compilation to fail, with the sole exception of the #error
    directive.

    Did you really not know that?

    Another example:

    int F(){}

    Here not even -Wextra touches it. I need -Wpedantic, which
    tells me a lot about C!

    "gcc -Wpedantic" doesn't complain about that. There is no
    syntax error and no constraint violation, so the language does
    not require a diagnostic.
    If you have a point, feel free to make it more clearly.

    OK, you suggested that it was a compiler being lax, not the
    language.

    My point was that your example did not demonstrate that the
    language is lax. gcc by default does not generate all the
    diagnostics that are required by the language. Observing that
    fact says nothing about the language.

    Here, allowing such code to be written IS being lax. And in the
    above example, the language allowing the compiler to ignore the
    error or to only warn, is also being lax.

    I don't disagree. I would have preferred a requirement that any
    translation unit that violates a syntax rule or constraing must be
    rejected. [...]

    Ridiculous. Such a rule would effectively preclude many useful
    extensions, which would have the effect of rendering the provision
    allowing extensions pointless.

    --- PyGate Linux v1.5.13
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Keith Thompson@3:633/10 to All on Mon Apr 6 23:12:08 2026
    Tim Rentsch <tr.17687@z991.linuxsc.com> writes:
    Keith Thompson <Keith.S.Thompson+u@gmail.com> writes:
    [...]
    I don't disagree. I would have preferred a requirement that any
    translation unit that violates a syntax rule or constraing must be
    rejected. [...]

    Ridiculous. Such a rule would effectively preclude many useful
    extensions, which would have the effect of rendering the provision
    allowing extensions pointless.

    I'll modify my suggestion. Any translation unit that violates a
    syntax rule or constraint must be rejected unless the violation
    is apart of a documented extension. (I'm certain there's a better
    way to word that.)

    And of course compilers could have non-conforming modes in which
    they accept anything they like.

    I do not expect C to adopt such a rule. It's merely what I would
    have preferred.

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

    --- PyGate Linux v1.5.13
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Tim Rentsch@3:633/10 to All on Tue Apr 7 09:45:34 2026
    Michael S <already5chosen@yahoo.com> writes:
    Tim Rentsch <tr.17687@z991.linuxsc.com> wrote:
    Michael S <already5chosen@yahoo.com> writes:
    Tim Rentsch <tr.17687@z991.linuxsc.com> wrote:
    Michael S <already5chosen@yahoo.com> writes:
    Tim Rentsch <tr.17687@z991.linuxsc.com> wrote:

    [..in reference to the follwoing translation unit..]

    inline int
    F(){
    return 0;
    }

    int
    main(void){
    return F();
    }

    int F();


    [..end excerpt..]

    In the comment above I was talking specifically about that:
    https://godbolt.org/z/4TazP4sq1

    I don't know what you were expecting me to learn by looking at
    that page. As far as I can tell it adds no information beyond
    what has already been presented.

    The page shows that when, according to your suggestion, source
    code is modified to make it well-defined then body of F() is
    generated regardless of optimization level. As you likely
    expected.

    I did expect it, because the C standard requires it. I was simply
    trying to point out what the C standard requires.

    It was a point of suggested modification, was not it?

    It was not my intention to offer a suggestion. I was only trying
    to provide information about what the C standard requires.


    Let me now try to address the second portion of your posting.
    Here are some excerpts (from your earlier remarks) for context.
    (The excerpts below may be out of order.)

    [michael-s] It is indeed well-defined. But well-defined behavior
    [michael-s] does not match the most probable intention of programmer, [michael-s] which is to create a body of F() in case that it was not [michael-s] actually inlined and to *not* create it otherwise.

    [michael-s] So, by C standard, there is no situation in which 'inline' [michael-s] with no addition qualifuers like 'static' or 'extern' can [michael-s] be used with predictabe outcome?

    For inline functions, static inline functions and non-static inline
    functions serve different purposes.

    A static inline function is meant to be used to give the compiler
    discretion as to whether to expand the body inline or to produce a
    regular static function body, independently, in each translation
    unit where the function is defined (and presumably called).

    A non-static inline function is meant to be used where the body may
    be expanded inline, but it is also important that there be exactly
    one translation unit where the function is defined as a normal
    (which is to say, not inline) function, and having external linkage.
    One reason this might be important is that the address of function
    will be taken in one or more translation units, and it is important
    that the address be the same throughout the program.

    Any programmer understanding to the contrary of these descriptions
    is at odds with what the C standard says. So I think it's a good
    idea for anyone with a different understanding to be apprised of
    what the C standard prescribes, especially if they have definite
    expectations otherwise.

    --- PyGate Linux v1.5.13
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)