• Prioritize Performance over Correctness

    From Janis Papanagnou@3:633/10 to All on Wednesday, July 22, 2026 01:38:59
    There was some recent longish thread with discussions addressing
    Undefined Behavior. I just stumbled across a paper from Russ Cox
    on that topic with a provoking title.[*] I don't recall whether
    that had already been referenced anywhere in that thread; if not
    it might be of interest to some. A couple arguments and examples
    look familiar already, but I think it may be valuable anyway.

    Janis

    [*] https://research.swtch.com/ub


    --- PyGate Linux v1.5.18
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Keith Thompson@3:633/10 to All on Tuesday, July 21, 2026 17:26:41
    Janis Papanagnou <janis_papanagnou+ng@hotmail.com> writes:
    There was some recent longish thread with discussions addressing
    Undefined Behavior. I just stumbled across a paper from Russ Cox
    on that topic with a provoking title.[*] I don't recall whether
    that had already been referenced anywhere in that thread; if not
    it might be of interest to some. A couple arguments and examples
    look familiar already, but I think it may be valuable anyway.

    Janis

    [*] https://research.swtch.com/ub

    For those who don't follow the link, the full title of the paper is
    "C and C++ Prioritize Performance over Correctness". (Your
    paraphrase could be interpreted as advice, which I'm sure is not
    how you intended it.)

    --
    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.18
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Dan Cross@3:633/10 to All on Wednesday, July 22, 2026 01:50:00
    In article <113p2o1$2es3m$1@kst.eternal-september.org>,
    Keith Thompson <Keith.S.Thompson+u@gmail.com> wrote:
    Janis Papanagnou <janis_papanagnou+ng@hotmail.com> writes:
    There was some recent longish thread with discussions addressing
    Undefined Behavior. I just stumbled across a paper from Russ Cox
    on that topic with a provoking title.[*] I don't recall whether
    that had already been referenced anywhere in that thread; if not
    it might be of interest to some. A couple arguments and examples
    look familiar already, but I think it may be valuable anyway.

    Janis

    [*] https://research.swtch.com/ub

    For those who don't follow the link, the full title of the paper is
    "C and C++ Prioritize Performance over Correctness". (Your
    paraphrase could be interpreted as advice, which I'm sure is not
    how you intended it.)

    From the post:

    ```
    term% cat eraseall.c
    #include <stdio.h>
    #include <stdlib.h>

    typedef void (*Function)(void);

    static Function Do;

    static void
    baz(void)
    {
    printf("baz invoked.\n");
    }

    static void
    bar(void)
    {
    baz();
    }

    void
    foo(void)
    {
    Do = bar;
    }

    int
    main(void)
    {
    Do();
    return 0;
    }

    term% clang -O1 -Wall -Wextra -pedantic -std=c23 -o eraseall eraseall.c
    term% ./eraseall
    baz invoked.
    term%
    ```

    Something similar came up a month or so ago; recall my "what.c"
    program:

    ```
    term% cat what.c
    #include <stdio.h>
    int main(void) { for (unsigned int k = 0; k != 1; k += 2){} return 0; }
    void hello(void) { printf("Hello, World!\n"); }
    term% clang -O1 -Wall -Werror -pedantic -pedantic-errors -std=c23 -o what what.c
    term% ./what
    Hello, World!
    term%
    ```

    It's notable that the quoted text for the ANSI C standard also
    says that an implementation is free to abort translation if it
    encounters UB, which I recall was something that was a point of
    concention in the earlier discussion. Kuyper posted a link to
    a defect report that suggested that the compiler had to
    translate the program if it could prove that (in that case,
    manifest) UB was never triggered; however, that was from 1994,
    and not normative anyhow. The committee has had ample time to
    tune up the writing about this in the standard, and has chosen
    not to: Cox's post is on-point; the current standard is deeply
    flawed.

    - Dan C.


    --- PyGate Linux v1.5.18
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Keith Thompson@3:633/10 to All on Tuesday, July 21, 2026 20:25:38
    cross@spitfire.i.gajendra.net (Dan Cross) writes:
    [...]
    ```
    term% cat eraseall.c
    #include <stdio.h>
    #include <stdlib.h>

    typedef void (*Function)(void);

    static Function Do;

    static void
    baz(void)
    {
    printf("baz invoked.\n");
    }

    static void
    bar(void)
    {
    baz();
    }

    void
    foo(void)
    {
    Do = bar;
    }

    int
    main(void)
    {
    Do();
    return 0;
    }

    term% clang -O1 -Wall -Wextra -pedantic -std=c23 -o eraseall eraseall.c
    term% ./eraseall
    baz invoked.
    term%
    ```

    In that program, Do is a function pointer that's initialized to a null
    pointer value (because it's static), so the call `Do()` has undefined
    behavior.

    Something similar came up a month or so ago; recall my "what.c"
    program:

    ```
    term% cat what.c
    #include <stdio.h>
    int main(void) { for (unsigned int k = 0; k != 1; k += 2){} return 0; }
    void hello(void) { printf("Hello, World!\n"); }
    term% clang -O1 -Wall -Werror -pedantic -pedantic-errors -std=c23 -o what what.c
    term% ./what
    Hello, World!
    term%
    ```

    That's an example of something more specific. N3220 6.8.6.1p4
    gives explicit permission for an implementation to "assume" that
    certain iteration statements will terminate. (I've said before
    that I dislike this rule for several reasons.)

    It's notable that the quoted text for the ANSI C standard also
    says that an implementation is free to abort translation if it
    encounters UB, which I recall was something that was a point of
    concention in the earlier discussion. Kuyper posted a link to
    a defect report that suggested that the compiler had to
    translate the program if it could prove that (in that case,
    manifest) UB was never triggered; however, that was from 1994,
    and not normative anyhow.

    C89 and all later editions of the C standard say (in a non-normative
    note) that undefined behavior can include "terminating a
    translation or execution (with the issuance of a diagnostic
    message)". But almost all undefined behavior occurs at run time.
    That could have been worded more clearly, but my interpretation
    is that an implementation that detects that a source construct
    has undefined behavior can terminate translation only if it can
    prove that it's always executed. `if (0) 1/0;` does not permit
    terminating translation.

    The committee has had ample time to
    tune up the writing about this in the standard, and has chosen
    not to: Cox's post is on-point; the current standard is deeply
    flawed.

    How would you suggest fixing it?

    --
    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.18
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Janis Papanagnou@3:633/10 to All on Wednesday, July 22, 2026 07:33:29
    On 2026-07-22 02:26, Keith Thompson wrote:
    Janis Papanagnou <janis_papanagnou+ng@hotmail.com> writes:
    There was some recent longish thread with discussions addressing
    Undefined Behavior. I just stumbled across a paper from Russ Cox
    on that topic with a provoking title. [...]

    For those who don't follow the link, the full title of the paper is
    "C and C++ Prioritize Performance over Correctness". (Your
    paraphrase could be interpreted as advice, which I'm sure is not
    how you intended it.)

    It was actually a (deliberate) "marketing trick" to provoke interest
    by reducing the title to an (IMO) yet more aggravating formulation.

    Yes, it was not my intention to suggest performance over correctness.
    (Would anyone?)

    Janis


    --- PyGate Linux v1.5.18
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From David Brown@3:633/10 to All on Wednesday, July 22, 2026 10:41:15
    On 22/07/2026 07:33, Janis Papanagnou wrote:
    On 2026-07-22 02:26, Keith Thompson wrote:
    Janis Papanagnou <janis_papanagnou+ng@hotmail.com> writes:
    There was some recent longish thread with discussions addressing
    Undefined Behavior. I just stumbled across a paper from Russ Cox
    on that topic with a provoking title. [...]

    For those who don't follow the link, the full title of the paper is
    "C and C++ Prioritize Performance over Correctness".ÿ (Your
    paraphrase could be interpreted as advice, which I'm sure is not
    how you intended it.)

    It was actually a (deliberate) "marketing trick" to provoke interest
    by reducing the title to an (IMO) yet more aggravating formulation.

    Yes, it was not my intention to suggest performance over correctness.
    (Would anyone?)


    Unfortunately, the answer to your final question is yes - and that is
    one of the reasons UB can be a problem. If by "correctness" you mean
    "code does what it should", people do sometimes release code that they
    know has bugs, but they know that fixing them would slow down the code - perhaps it is still good enough for their purposes at the time.

    More relevant to this discussion, if by "correct" code you mean code
    that has fully defined or appropriate implementation-defined behaviour
    (most code does not need to be fully portable) according to the C
    standards and/or platform or compiler extended semantics, then it is absolutely the case that programmers regularly prioritise performance
    over correctness by relying on UB. The result is code that works
    efficiently and as intended at the time, using tools tested by the
    developer. Then the UB bites the next person down the road that uses
    the same code in good faith, but with a different compiler or different options.

    If the reliance on the UB was unintentional, it's a bug - programmers
    are humans, they don't always know everything, and people make mistakes.
    But some programmers do so /knowing/ that the code is UB. Sometimes
    it is even stated in comments. The most common cases, IME, are reliance
    on two's complement wrapping, and messing with pointer casts to access
    data as different types, possibly with incorrect alignment.


    As for the linked paper, I am not particularly impressed by its arguments.

    I think it is fair enough to have different opinions about what should
    or should not be UB in language standards - and it is inarguably the
    case that some things that are UB could have defined behaviour at quite
    minor performance costs. (Note, however, that studies looking at the performance cost of disabling certain optimisations - such as "-fno-strict-aliasing" or "-fwrapv" - almost invariably use only x86
    targets. x86 processors are designed to get the best from poorly
    optimised code, because that's what is often run on them, while most
    other processors except compilers to do more of the work. Compiler optimisations often have a much bigger impact on non-x86 targets and on processors with smaller relative memory latencies.)

    Fundamentally, the idea of "UB" is that it describes code that does not
    make sense, where there is no correct answer or meaningful specification
    of behaviour. What should it mean to dereference a pointer that does
    not point to a valid object of the type you are using? What should it
    mean to store a large result in a target type that is too small? Code
    that does this kind of thing is buggy - it cannot give correct answers. Nothing the compiler can do could make the code correct.

    The compiler's logic then goes like this:

    1. UB is incorrect code.
    2. C is a "trust the programmer" language.
    3. The compiler assumes the programmer has written correct code.
    4. Therefore, the programmer has not written code with UB.
    5. Therefore, UB cannot happen.
    6. Therefore the compiler can optimise on that assumption, such as by
    skipping some code generation.

    The only question is, is it a good idea for the compiler to be allowed
    to make the program "more wrong" ? I don't think it matters as much as
    people often think. It's easy to find or create examples where compiler optimisations on UB can amplify errors. Equally, however, you can find
    or create examples where assuming some kind of na‹ve "obvious"
    implementation of the UB code will also lead to bad results. Programs
    adding two positive integers are going to expect a positive result - a
    wrapped negative result is going to lead to tears. You need the same
    kind of care in development and testing no matter how the compiler
    treats its UB - and if you have done that job, these kinds of compiler optimisations do not significantly (IMHO) affect the risk of later problems.

    The killer point, however, is when you have received some code that you
    can justifiably assume is correct and well tested, but contains reliance
    on how certain types of UB happen to be implemented. It is not really
    any different than code that makes other undocumented assumptions, such
    as implementation-dependent details, reliance on certain files or OS
    features, timing requirements, etc. And compiling with "-fwrapv -fno-strict-aliasing" (if you are using a compiler that supports such features) will mitigate the most common cases of reliance on UB.


    As for the paper itself, I have a few points.

    It follows the time-worn path of complaining that "modern" compilers
    have distorted the "original" idea of UB and abuse it for optimisation
    in ways that are dangerous to the uninitiated. First, optimisation
    using UB is not new - I first used a compiler that assumed integer
    overflow does not wrap over thirty years ago. Second, compilers already default to "-O0" - if you know so little about your tools that you can't enable warnings, you won't have optimisation either. Third,
    improvements in compiler optimisation have gone hand-in-hand with
    improvements in compiler static error checking. Using "-Wall" along
    with optimisation will eliminate the solid majority of the risks and
    worries of the author. Fourth, the development of "sanitizers" not only allows far more UB to be found during testing, but it allows far more
    bugs to be found than would be possible if the mistakes were /not/ UB - "-fsanitize=signed-integer-overflow" catches overflow bugs precisely
    because it is UB in C, whereas if it were defines as wrapping then the
    mistake in the programmer's code would be correct as far as the language
    is concerned.

    Compilers are, of course, not perfect. But they are usually pretty
    good. Most of the paper could be scraped and replaced with the advice
    of always using "-Wall", possibly with "-Wextra" and/or fine-tuning of
    other warning flags, regardless of optimisation. And make use of
    sanitizers while testing. And always test with solid optimisations
    enabled (like "-O2"), though lower optimisations can be handy for fault-finding and debugging some aspects of code.

    It repeats the myths about signed integer overflow being UB because some computers have different representations of signed integers - C uses implementation-defined behaviour to handle implementation differences,
    and only resorts to UB when there is no sensible definition of "correct" behaviour, or where implementation-defined behaviour could have
    significant overhead costs.

    Both C++ (from C++20, IIRC) and C (from C23) allow only two's complement signed integer representations. Both committees made it clear that
    signed arithmetic overflow is still UB, because it does not lead to a
    sensible answer and allows optimisations and additional error checking.
    It is not hard to get wrapping behaviour if you want it (using
    conversions to and from unsigned types), and C23 introduced standardised checked arithmetic functions.


    As for infinite loops, loops with a constant controlling expression
    (like "for (;;)" ) are exempt from the "assumed to terminate" clause in
    C. I don't know if the example given was a bug in Clang or if it used
    C++ and C++ has different rules here.

    And in the next example, merging two loops into one, the merge won't
    introduce new bugs or race conditions - if another thread is using
    "count2", then there needs to be some kind of coordination (like
    atomics), and then the loops could not be merged anyway. I am not
    convinced that allowing the compiler to assume termination of certain
    types of loop is a particularly useful feature - it seems to me to be an
    added complication with very few potential benefits. But I can't see it
    being a problem for sensible working code either.

    Don't dereference null pointers. It's a bad idea.

    Read the specifications of the functions you want to use. std::sort
    requires a comparison function with a strict weak ordering - give it
    something else, and it's a user bug. Perhaps std::sort could have been specified in a nicer way, but that is hardly a compiler problem.










    --- PyGate Linux v1.5.18
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Dan Cross@3:633/10 to All on Wednesday, July 22, 2026 17:37:01
    In article <113pd7i$2hns9$1@kst.eternal-september.org>,
    Keith Thompson <Keith.S.Thompson+u@gmail.com> wrote: >cross@spitfire.i.gajendra.net (Dan Cross) writes:
    [...]
    ```
    term% cat eraseall.c
    #include <stdio.h>
    #include <stdlib.h>

    typedef void (*Function)(void);

    static Function Do;

    static void
    baz(void)
    {
    printf("baz invoked.\n");
    }

    static void
    bar(void)
    {
    baz();
    }

    void
    foo(void)
    {
    Do = bar;
    }

    int
    main(void)
    {
    Do();
    return 0;
    }

    term% clang -O1 -Wall -Wextra -pedantic -std=c23 -o eraseall eraseall.c
    term% ./eraseall
    baz invoked.
    term%
    ```

    In that program, Do is a function pointer that's initialized to a null >pointer value (because it's static), so the call `Do()` has undefined >behavior.

    Yup. UB gives the compiler permission to go wild.

    Something similar came up a month or so ago; recall my "what.c"
    program:

    ```
    term% cat what.c
    #include <stdio.h>
    int main(void) { for (unsigned int k = 0; k != 1; k += 2){} return 0; }
    void hello(void) { printf("Hello, World!\n"); }
    term% clang -O1 -Wall -Werror -pedantic -pedantic-errors -std=c23 -o what what.c
    term% ./what
    Hello, World!
    term%
    ```

    That's an example of something more specific. N3220 6.8.6.1p4
    gives explicit permission for an implementation to "assume" that
    certain iteration statements will terminate. (I've said before
    that I dislike this rule for several reasons.)

    Yup. This behavior indeed allowed by the standard, but that it
    is allowed by the standard is, itself, an indictment of the
    standard.

    It's notable that the quoted text for the ANSI C standard also
    says that an implementation is free to abort translation if it
    encounters UB, which I recall was something that was a point of
    concention in the earlier discussion. Kuyper posted a link to
    a defect report that suggested that the compiler had to
    translate the program if it could prove that (in that case,
    manifest) UB was never triggered; however, that was from 1994,
    and not normative anyhow.

    C89 and all later editions of the C standard say (in a non-normative
    note) that undefined behavior can include "terminating a
    translation or execution (with the issuance of a diagnostic
    message)". But almost all undefined behavior occurs at run time.
    That could have been worded more clearly, but my interpretation
    is that an implementation that detects that a source construct
    has undefined behavior can terminate translation only if it can
    prove that it's always executed. `if (0) 1/0;` does not permit
    terminating translation.

    Unfortunately, the standard does not say that in normative text.

    The committee has had ample time to
    tune up the writing about this in the standard, and has chosen
    not to: Cox's post is on-point; the current standard is deeply
    flawed.

    How would you suggest fixing it?

    Honestly? I don't think that it can be fixed. There are too
    many competing interests, and C is the language that it is. I
    do not think we're going to get away from that.

    That said, a thing that they _could_ do is be more explicit
    about when termination is allowed and so forth. The UB working
    group is trying to reign in the undue dominance of UB, and I
    give them credit for that; I don't know how far they will get,
    though.

    In the meanwhile, _most_ programmers should not be writing in C.

    - Dan C.


    --- PyGate Linux v1.5.18
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Chris M. Thomasson@3:633/10 to All on Wednesday, July 22, 2026 13:26:45
    On 7/21/2026 6:50 PM, Dan Cross wrote:
    #include <stdio.h>
    #include <stdlib.h>

    typedef void (*Function)(void);

    static Function Do;

    static void
    baz(void)
    {
    printf("baz invoked.\n");
    }

    static void
    bar(void)
    {
    baz();
    }

    void
    foo(void)
    {
    Do = bar;
    }

    int
    main(void)
    {
    Do();
    return 0;
    }

    That should be a seg fault? Anyway:

    _______________
    #include <stdio.h>
    #include <stdlib.h>

    typedef void (*Function)(void);

    static Function Do = NULL;

    static void
    baz(void)
    {
    printf("baz invoked.\n");
    }

    static void
    bar(void)
    {
    baz();
    }

    void
    foo(void)
    {
    Do = bar;
    }

    int
    main(void)
    {
    foo(); // :^)
    Do();
    return 0;
    }
    _______________


    Better? ;^)

    --- PyGate Linux v1.5.18
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From BGB@3:633/10 to All on Wednesday, July 22, 2026 18:08:07
    On 7/22/2026 3:41 AM, David Brown wrote:
    On 22/07/2026 07:33, Janis Papanagnou wrote:
    On 2026-07-22 02:26, Keith Thompson wrote:
    Janis Papanagnou <janis_papanagnou+ng@hotmail.com> writes:
    There was some recent longish thread with discussions addressing
    Undefined Behavior. I just stumbled across a paper from Russ Cox
    on that topic with a provoking title. [...]

    For those who don't follow the link, the full title of the paper is
    "C and C++ Prioritize Performance over Correctness".ÿ (Your
    paraphrase could be interpreted as advice, which I'm sure is not
    how you intended it.)

    It was actually a (deliberate) "marketing trick" to provoke interest
    by reducing the title to an (IMO) yet more aggravating formulation.

    Yes, it was not my intention to suggest performance over correctness.
    (Would anyone?)


    Unfortunately, the answer to your final question is yes - and that is
    one of the reasons UB can be a problem.ÿ If by "correctness" you mean
    "code does what it should", people do sometimes release code that they
    know has bugs, but they know that fixing them would slow down the code - perhaps it is still good enough for their purposes at the time.


    Or, the cliche: "It's not a bug, it's a feature..."


    More relevant to this discussion, if by "correct" code you mean code
    that has fully defined or appropriate implementation-defined behaviour
    (most code does not need to be fully portable) according to the C
    standards and/or platform or compiler extended semantics, then it is absolutely the case that programmers regularly prioritise performance
    over correctness by relying on UB.ÿ The result is code that works efficiently and as intended at the time, using tools tested by the developer.ÿ Then the UB bites the next person down the road that uses
    the same code in good faith, but with a different compiler or different options.



    Well, or for example, this pile of wonk:

    #define gfxedit_getu16(ptr) (*(vol_u16p)(ptr))
    #define gfxedit_getu32(ptr) (*(vol_u32p)(ptr))
    #define gfxedit_getu64(ptr) (*(vol_u64p)(ptr))
    #define gfxedit_setu16(ptr,val) (*(vol_u16p)(ptr)=(val))
    #define gfxedit_setu32(ptr,val) (*(vol_u32p)(ptr)=(val))
    #define gfxedit_setu64(ptr,val) (*(vol_u64p)(ptr)=(val))

    Insert other versions for different platform constraints.

    But, say:
    u64 gfxedit_getu64(void *ptr)
    { u64 v; memcpy(&v, ptr, 8); return(v); }
    Being also possible, but a significant performance penalty on some targets.

    The situation is potentially much worse for big endian machines, but
    these are rare at this point.

    Say:
    u16 gfxedit_getu16(void *ptr)
    { u16 v; v=((byte *)ptr)[0]|
    (((u16)((byte *)ptr)[1]))<<8); return(v); }
    u32 gfxedit_getu32(void *ptr)
    { u32 v; v=gfxedit_getu16(ptr)|
    (((u32)gfxedit_getu16(((byte *)ptr)+2))<<16); return(v); }
    u64 gfxedit_getu64(void *ptr)
    { u64 v; v=gfxedit_getu32(ptr)|
    (((u64)gfxedit_getu32(((byte *)ptr)+4))<<32); return(v); }

    Though, for BE machines, one wouldn't want to overuse these.

    ...


    And, the function:

    byte *gfxedit_memlzcpyf(byte *dst, byte *src, int len)
    {
    byte *cs, *ct, *cte;
    u64 v0, v1;
    int d;
    int i;

    d=dst-src;

    if(d>len)
    {
    cs=src; ct=dst; cte=dst+len;
    v0=gfxedit_getu64(cs); v1=gfxedit_getu64(cs+8);
    gfxedit_setu64(ct, v0); gfxedit_setu64(ct+8, v1);
    cs+=16; ct+=16;
    if(ct<cte)
    {
    v0=gfxedit_getu64(cs); v1=gfxedit_getu64(cs+8);
    gfxedit_setu64(ct, v0); gfxedit_setu64(ct+8, v1);
    cs+=16; ct+=16;
    if(ct<cte)
    {
    v0=gfxedit_getu64(cs); v1=gfxedit_getu64(cs+8);
    gfxedit_setu64(ct, v0); gfxedit_setu64(ct+8, v1);
    cs+=16; ct+=16;
    while(ct<cte)
    {
    v0=gfxedit_getu64(cs); v1=gfxedit_getu64(cs+8);
    gfxedit_setu64(ct, v0); gfxedit_setu64(ct+8, v1);
    cs+=16; ct+=16;
    }
    }
    }
    return(cte);
    }
    if(d<=8)
    {
    if(d==1)
    {
    v0=*(byte *)src; v0|=(v0<<8); v0|=(v0<<16); v0|=(v0<<32);
    ct=dst; cte=dst+len;
    while(ct<cte)
    { gfxedit_setu64(ct, v0); gfxedit_setu64(ct+8, v0); ct+=16; }
    return(cte);
    }
    if(d==2)
    {
    v0=gfxedit_getu16(src); v0|=(v0<<16); v0|=(v0<<32);
    ct=dst; cte=dst+len;
    while(ct<cte)
    { gfxedit_setu64(ct, v0); gfxedit_setu64(ct+8, v0); ct+=16; }
    return(cte);
    }
    if(d==4)
    {
    v0=gfxedit_getu32(src); v0|=(v0<<32);
    ct=dst; cte=dst+len;
    while(ct<cte)
    { gfxedit_setu64(ct, v0); gfxedit_setu64(ct+8, v0); ct+=16; }
    return(cte);
    }
    if(d==8)
    {
    v0=gfxedit_getu64(src);
    ct=dst; cte=dst+len;
    while(ct<cte)
    { gfxedit_setu64(ct, v0); gfxedit_setu64(ct+8, v0); ct+=16; }
    return(cte);
    }

    if(d<0)
    {
    cs=src; ct=dst; cte=dst+len;
    while(ct<cte)
    {
    v0=gfxedit_getu64(cs); v1=gfxedit_getu64(cs+8);
    gfxedit_setu64(ct, v0); gfxedit_setu64(ct+8, v1);
    cs+=16; ct+=16;
    }
    return(cte);
    }

    if(d==3)
    {
    v0=gfxedit_getu32(src); v0=v0<<40; v0=(v0>>16)|(v0>>40);
    ct=dst; cte=dst+len;
    while(ct<cte)
    {
    gfxedit_setu64(ct+ 0, v0);
    gfxedit_setu64(ct+ 6, v0);
    gfxedit_setu64(ct+12, v0);
    ct+=18;
    }
    return(cte);
    }
    if(d==5)
    {
    v0=gfxedit_getu64(src);
    ct=dst; cte=dst+len;
    while(ct<cte)
    { gfxedit_setu64(ct, v0); ct+=5; }
    return(cte);
    }
    if(d==7)
    {
    v0=gfxedit_getu64(src);
    ct=dst; cte=dst+len;
    while(ct<cte)
    { gfxedit_setu64(ct, v0); ct+=7; }
    return(cte);
    }
    cs=src; ct=dst; cte=dst+len;
    while(ct<cte)
    { *ct++=*cs++; }
    return(cte);
    }
    if(d>=16)
    {
    cs=src; ct=dst; cte=dst+len;
    while(ct<cte)
    {
    v0=gfxedit_getu64(cs); v1=gfxedit_getu64(cs+8);
    gfxedit_setu64(ct, v0); gfxedit_setu64(ct+8, v1);
    cs+=16; ct+=16;
    }
    return(cte);
    }
    else
    {
    cs=src; ct=dst; cte=dst+len;
    while(ct<cte)
    { v0=gfxedit_getu64(cs); gfxedit_setu64(ct, v0); cs+=8; ct+=8; }
    return(cte);
    }
    }


    Where one might ask, why not just:
    byte *gfxedit_memlzcpyf(byte *dst, byte *src, int len)
    {
    int i, d;
    d=dst-src;
    if(d>len)
    { memcpy(dst, src, len); return(dst+len); }
    if(d<0)
    { memmove(dst, src, len); return(dst+len); }
    if(d==1)
    { memset(dst, *src, len); return(dst+len); }
    for(i=0; i<len; i++)
    dst[i]=src[i];
    return(dst+len);
    }

    But, on the relevant implementation, trying to do the latter being
    around an order of magnitude slower...

    Note: Not on my target... Where I had added _memlzcpyf() as an extension
    to the C library to avoid having to endlessly re-implement this stuff.
    But, for other targets, it is still necessary.

    Where, the 'f' variant is basically the variant that prioritizes speed
    on the allowance that it may copy a little extra.


    Well, except on SiFive chips where this in not the fastest option.

    The fastest case for a SiFive style CPU being or similar, say:
    byte *gfxedit_memlzcpyf(byte *dst, byte *src, int len)
    {
    byte *cs, *ct, *cte;
    int i, d;
    cs=src; ct=dst; cte=dst+len;
    while(ct<cte)
    { *ct++=*cs++; }
    return(cte);
    }

    But, for the original example, this works better on a CPU with higher instruction latency, but the ability to use misaligned loads and stores
    with zero added cost (whereas, the SiFive chips have lower
    per-instruction latency but a very steep cost for misaligned memory
    access; basically in the form of hidden trap-and-emulate).

    Where, say, both CPU's can run RISc-V code, but have significant
    differences in performance characteristics...

    One might also want to use the latter naive byte copy for big endian
    RISC machines (like on PowerPC or similar), ...

    So, it can all turn into a big mess of cruft and ifdef's.


    And, No:
    You can't just use memmove() here...

    In this case the typical repeating bytes on self-overlap behavior being required for correct operation.


    But, while small and elegant and portable, the above version would be undesirable due to being around 10x slower.

    One could try to detect alignment and then have separate aligned vs
    unaligned loops, but in the use-case it gains very little and may end up slower than always assuming misaligned (where it is a sort of chaos
    where most of the copies are both misaligned and fairly short).



    If the reliance on the UB was unintentional, it's a bug - programmers
    are humans, they don't always know everything, and people make mistakes.
    ÿBut some programmers do so /knowing/ that the code is UB.ÿ Sometimes
    it is even stated in comments.ÿ The most common cases, IME, are reliance
    on two's complement wrapping, and messing with pointer casts to access
    data as different types, possibly with incorrect alignment.


    As for the linked paper, I am not particularly impressed by its arguments.

    I think it is fair enough to have different opinions about what should
    or should not be UB in language standards - and it is inarguably the
    case that some things that are UB could have defined behaviour at quite minor performance costs.ÿ (Note, however, that studies looking at the performance cost of disabling certain optimisations - such as "-fno- strict-aliasing" or "-fwrapv" - almost invariably use only x86 targets.
    x86 processors are designed to get the best from poorly optimised code, because that's what is often run on them, while most other processors
    except compilers to do more of the work.ÿ Compiler optimisations often
    have a much bigger impact on non-x86 targets and on processors with
    smaller relative memory latencies.)


    The idea is that compilers should still try to produce efficient code
    even with these semantic limitations.


    Fundamentally, the idea of "UB" is that it describes code that does not
    make sense, where there is no correct answer or meaningful specification
    of behaviour.ÿ What should it mean to dereference a pointer that does
    not point to a valid object of the type you are using?ÿ What should it
    mean to store a large result in a target type that is too small?ÿ Code
    that does this kind of thing is buggy - it cannot give correct answers. Nothing the compiler can do could make the code correct.

    The compiler's logic then goes like this:

    1. UB is incorrect code.
    2. C is a "trust the programmer" language.
    3. The compiler assumes the programmer has written correct code.
    4. Therefore, the programmer has not written code with UB.
    5. Therefore, UB cannot happen.
    6. Therefore the compiler can optimise on that assumption, such as by skipping some code generation.

    The only question is, is it a good idea for the compiler to be allowed
    to make the program "more wrong" ?ÿ I don't think it matters as much as people often think.ÿ It's easy to find or create examples where compiler optimisations on UB can amplify errors.ÿ Equally, however, you can find
    or create examples where assuming some kind of na‹ve "obvious" implementation of the UB code will also lead to bad results.ÿ Programs adding two positive integers are going to expect a positive result - a wrapped negative result is going to lead to tears.ÿ You need the same
    kind of care in development and testing no matter how the compiler
    treats its UB - and if you have done that job, these kinds of compiler optimisations do not significantly (IMHO) affect the risk of later
    problems.

    The killer point, however, is when you have received some code that you
    can justifiably assume is correct and well tested, but contains reliance
    on how certain types of UB happen to be implemented.ÿ It is not really
    any different than code that makes other undocumented assumptions, such
    as implementation-dependent details, reliance on certain files or OS features, timing requirements, etc.ÿ And compiling with "-fwrapv -fno- strict-aliasing" (if you are using a compiler that supports such
    features) will mitigate the most common cases of reliance on UB.


    In some cases, it makes sense to assume that some things formally
    considered UB would be better considered as Implementation Defined.

    In practice, it likely wouldn't make that big of a difference; since
    many cases the compiler still treats these cases is implementation
    defined rather than true UB.


    In this case, this leaves "true UB" as mostly cases where no sensible or consistent behavior can be expected even within a given machine, say:
    reliance on the values of uninitialized local variables;
    going out of bounds on a conventional array (*1).

    *1: I consider out-of-bounds within an array inside a struct to be a
    partial exception, as one can reasonably assume that going OOB would
    access other members within the same struct.

    But, the relative ordering and layout of stack and global variables I considered to be outside the scope of where valid assumptions can be
    made in C.


    However, such assumptions could be made for assembler...

    Granted, in premise one could do, say:
    extern u64 arr1[4];
    extern u64 arr2[4];

    __asm {
    .section .data
    .global arr1
    .global arr2
    arr1:
    .quad 0
    .quad 0
    .quad 0
    .quad 0
    arr2:
    .quad 0
    .quad 0
    .quad 0
    .quad 0
    };

    And, arguably in this case one can argue that it may be valid to assume
    that arr1 overflows into arr2.

    But, then again, one also can't rely on the portability of __asm or
    __asm__ blocks between targets. So, like with the structs, this would
    exist more as a partial exception.

    This example being partly N/A for GCC or similar, which uses a different syntax for ASM blocks (well, and some compilers, like 64-bit MSVC,
    having dropped support for inline ASM).


    But, I had seen non-zero code which had relied on the assumption of
    overflows carrying over consistently between global arrays.

    In my compiler, such assumptions are not safe to be made, as the
    compiler likes to shuffle the declarations around. Partly balancing randomization and efficiency; as it can be more efficient to arrange
    variables by usage frequency, and functions by a combination of
    frequency and proximity. Say, ideally keeping commonly called functions
    closer together, and if possible also clustering callers and callees
    closer together. Even in the absence of profiler feedback, it tends to
    make it such that the "cold path" is less likely to be part of the core working set (and reducing the TLB miss frequency).


    Did run into a little bit of a hassle recently that my compilers' output
    tends to have a section alignment smaller than the page alignment, which turned into a bit of pain trying to set up page access permissions for
    the kernel binary (moving away from just setting the whole thing as RWX).

    Ended up adding a few special case flags to the implementation of the "mprotect" functionality:
    INNER: Only applies to completely covered pages,
    ignores partial pages.
    Default: covers all pages in range.
    ALLOW: Only allows new access, does not remove access.
    DENY: Only removes access.
    Default: New permissions fully replace old permissions.

    These allowed dealing better with the partial page scenarios.
    Can initially set the whole image READ|NOUSER;
    Then use ALLOW to add WRITE/EXEC/USER to the relevant sections.
    Maybe questionable, but there does exist a R|X|USER section.
    Mostly has stuff for Syscalls and COM.
    It is likely this part could be migrated into Userland.
    Mostly it is a vestige of an earlier developmental stage.



    As for the paper itself, I have a few points.

    It follows the time-worn path of complaining that "modern" compilers
    have distorted the "original" idea of UB and abuse it for optimisation
    in ways that are dangerous to the uninitiated.ÿ First, optimisation
    using UB is not new - I first used a compiler that assumed integer
    overflow does not wrap over thirty years ago.ÿ Second, compilers already default to "-O0" - if you know so little about your tools that you can't enable warnings, you won't have optimisation either.ÿ Third,
    improvements in compiler optimisation have gone hand-in-hand with improvements in compiler static error checking.ÿ Using "-Wall" along
    with optimisation will eliminate the solid majority of the risks and
    worries of the author.ÿ Fourth, the development of "sanitizers" not only allows far more UB to be found during testing, but it allows far more
    bugs to be found than would be possible if the mistakes were /not/ UB - "-fsanitize=signed-integer-overflow" catches overflow bugs precisely
    because it is UB in C, whereas if it were defines as wrapping then the mistake in the programmer's code would be correct as far as the language
    is concerned.


    For keeping old code working, it makes sense to assume wrapping.

    But, maybe it could make sense to have a semantic distinction for cases
    where wrapping is assumed vs where it is likely unintentional.

    My proposal, if any, would be, say:
    If "signed" is used explicitly, it means wrap-on-overflow is expected.

    So, say:
    int i;
    signed int v;
    If 'i' overflows, one can question whether or not it was intended; but
    if 'v' overflows, assume it is intentional (as with 'unsigned').

    This being in part because an explicit "signed" keyword is uncommon to
    be used with normal counting/index type variables.



    Compilers are, of course, not perfect.ÿ But they are usually pretty
    good.ÿ Most of the paper could be scraped and replaced with the advice
    of always using "-Wall", possibly with "-Wextra" and/or fine-tuning of
    other warning flags, regardless of optimisation.ÿ And make use of
    sanitizers while testing.ÿ And always test with solid optimisations
    enabled (like "-O2"), though lower optimisations can be handy for fault- finding and debugging some aspects of code.

    It repeats the myths about signed integer overflow being UB because some computers have different representations of signed integers - C uses implementation-defined behaviour to handle implementation differences,
    and only resorts to UB when there is no sensible definition of "correct" behaviour, or where implementation-defined behaviour could have
    significant overhead costs.

    Both C++ (from C++20, IIRC) and C (from C23) allow only two's complement signed integer representations.ÿ Both committees made it clear that
    signed arithmetic overflow is still UB, because it does not lead to a sensible answer and allows optimisations and additional error checking.
    It is not hard to get wrapping behaviour if you want it (using
    conversions to and from unsigned types), and C23 introduced standardised checked arithmetic functions.


    As for infinite loops, loops with a constant controlling expression
    (like "for (;;)" ) are exempt from the "assumed to terminate" clause in
    C.ÿ I don't know if the example given was a bug in Clang or if it used
    C++ and C++ has different rules here.

    And in the next example, merging two loops into one, the merge won't introduce new bugs or race conditions - if another thread is using
    "count2", then there needs to be some kind of coordination (like
    atomics), and then the loops could not be merged anyway.ÿ I am not
    convinced that allowing the compiler to assume termination of certain
    types of loop is a particularly useful feature - it seems to me to be an added complication with very few potential benefits.ÿ But I can't see it being a problem for sensible working code either.

    Don't dereference null pointers.ÿ It's a bad idea.


    Dereferencing NULL is one of those cases where the most sensible option
    is to treat it as a fault...

    Most sensible option is to assume that trying to dereference NULL is unintentional (given the main purpose of NULL being as a "this object
    doesn't exist" pointer).

    But:
    Assuming it doesn't happen and then removing the offending code path
    entirely is also bad IMO. However, turning the code-path into a break instruction or similar (following any other externally visible side
    effects) would still likely be acceptable IMO (or, at least, less bad in
    this case than just continuing down the other path as if nothing had
    ever happened).



    Read the specifications of the functions you want to use.ÿ std::sort requires a comparison function with a strict weak ordering - give it something else, and it's a user bug.ÿ Perhaps std::sort could have been specified in a nicer way, but that is hardly a compiler problem.


    --- PyGate Linux v1.5.18
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From David Brown@3:633/10 to All on Thursday, July 23, 2026 10:31:04
    On 23/07/2026 01:08, BGB wrote:
    On 7/22/2026 3:41 AM, David Brown wrote:
    On 22/07/2026 07:33, Janis Papanagnou wrote:
    On 2026-07-22 02:26, Keith Thompson wrote:
    Janis Papanagnou <janis_papanagnou+ng@hotmail.com> writes:
    There was some recent longish thread with discussions addressing
    Undefined Behavior. I just stumbled across a paper from Russ Cox
    on that topic with a provoking title. [...]

    For those who don't follow the link, the full title of the paper is
    "C and C++ Prioritize Performance over Correctness".ÿ (Your
    paraphrase could be interpreted as advice, which I'm sure is not
    how you intended it.)

    It was actually a (deliberate) "marketing trick" to provoke interest
    by reducing the title to an (IMO) yet more aggravating formulation.

    Yes, it was not my intention to suggest performance over correctness.
    (Would anyone?)


    Unfortunately, the answer to your final question is yes - and that is
    one of the reasons UB can be a problem.ÿ If by "correctness" you mean
    "code does what it should", people do sometimes release code that they
    know has bugs, but they know that fixing them would slow down the code
    - perhaps it is still good enough for their purposes at the time.


    Or, the cliche: "It's not a bug, it's a feature..."


    Possibly.

    But it is always important to remember that different types of software require different levels of quality control - it can be acceptable to
    have known bugs ("undocumented features" or "undocumented limitations")
    in some software. If video games were coded with the same level of care
    used for jet engine controllers, they would never make it to market.


    More relevant to this discussion, if by "correct" code you mean code
    that has fully defined or appropriate implementation-defined behaviour
    (most code does not need to be fully portable) according to the C
    standards and/or platform or compiler extended semantics, then it is
    absolutely the case that programmers regularly prioritise performance
    over correctness by relying on UB.ÿ The result is code that works
    efficiently and as intended at the time, using tools tested by the
    developer.ÿ Then the UB bites the next person down the road that uses
    the same code in good faith, but with a different compiler or
    different options.



    Well, or for example, this pile of wonk:

    #define gfxedit_getu16(ptr)ÿÿÿÿÿÿ (*(vol_u16p)(ptr))
    #define gfxedit_getu32(ptr)ÿÿÿÿÿÿ (*(vol_u32p)(ptr))
    #define gfxedit_getu64(ptr)ÿÿÿÿÿÿ (*(vol_u64p)(ptr))
    #define gfxedit_setu16(ptr,val)ÿÿ (*(vol_u16p)(ptr)=(val))
    #define gfxedit_setu32(ptr,val)ÿÿ (*(vol_u32p)(ptr)=(val))
    #define gfxedit_setu64(ptr,val)ÿÿ (*(vol_u64p)(ptr)=(val))

    Insert other versions for different platform constraints.

    While details of volatile accesses are implementation-dependent, they
    can be a successful way of accessing the underlying memory for data.
    It's easy to get things wrong, however, if you are not consistent about
    it. And too much use can lead to inefficiencies as well.


    But, say:
    ÿ u64 gfxedit_getu64(void *ptr)
    ÿÿÿ { u64 v; memcpy(&v, ptr, 8); return(v); }
    Being also possible, but a significant performance penalty on some targets.


    That's the challenge. memcpy() is often a good, safe and correct way to access data as though it were a different type, but not all compilers
    optimise it well in such cases. Making code that is correct on a range
    of implementations, and also efficient on a range of implementations, is
    the hard part. So sometimes people end up with code that is efficienct
    on the implementations they use, works on those, but is reliant on UB
    and fails on other implementations.

    The situation is potentially much worse for big endian machines, but
    these are rare at this point.

    Say:
    ÿ u16 gfxedit_getu16(void *ptr)
    ÿÿÿ { u16 v; v=((byte *)ptr)[0]|
    ÿÿÿÿÿÿÿ (((u16)((byte *)ptr)[1]))<<8); return(v); }
    ÿ u32 gfxedit_getu32(void *ptr)
    ÿÿÿ { u32 v; v=gfxedit_getu16(ptr)|
    ÿÿÿÿÿÿÿ (((u32)gfxedit_getu16(((byte *)ptr)+2))<<16); return(v); }
    ÿ u64 gfxedit_getu64(void *ptr)
    ÿÿÿ { u64 v; v=gfxedit_getu32(ptr)|
    ÿÿÿÿÿÿÿ (((u64)gfxedit_getu32(((byte *)ptr)+4))<<32); return(v); }

    Though, for BE machines, one wouldn't want to overuse these.


    On good modern compilers, that sort of thing is usually fine, and you
    would expect it to reduce to a single "load 64-bit with endian swap" instruction or minimal similar sequence.

    As I see it, the main problem of UB and compilers is /not/ as commonly believed, overly-enthusiastic compilers that optimise on assumptions
    about UB. The problem is poorer or older compilers that don't optimise
    the correct code well, forcing people who need efficient results to
    spend a lot of effort finding alternative solutions that might be
    reliant on UB. It also doesn't help that some developers don't enable optimisation or otherwise fail to use their tools well.


    <snip code>


    But, on the relevant implementation, trying to do the latter being
    around an order of magnitude slower...

    It sounds like it might make more sense to write improved memcpy, memove
    and memset implementations for the implementation in question!


    So, it can all turn into a big mess of cruft and ifdef's.


    Maximum efficiency is /always/ going to be implementation and target dependent. It is fine, for low-level functions where the speed matters,
    to have a variety of implementations tuned to different targets, with fall-back to something that is correct portable code. The use of
    #ifdef's (or separate files) means you can also happily use
    compiler-specific extensions, pragmas, attributes, builtins, etc., or
    even inline assembly.

    The killer point, however, is when you have received some code that
    you can justifiably assume is correct and well tested, but contains
    reliance on how certain types of UB happen to be implemented.ÿ It is
    not really any different than code that makes other undocumented
    assumptions, such as implementation-dependent details, reliance on
    certain files or OS features, timing requirements, etc.ÿ And compiling
    with "-fwrapv -fno- strict-aliasing" (if you are using a compiler that
    supports such features) will mitigate the most common cases of
    reliance on UB.


    In some cases, it makes sense to assume that some things formally
    considered UB would be better considered as Implementation Defined.

    No, never. You might want that to be the case (and I am sure we all
    have our personal favourite UB's from the C standards that we would
    rather have as IB, or unspecified, or the up-coming "erroneous
    behaviour" - though we would not agree entirely on which UB's). But you
    can't just say "I think left-shift of negative numbers should be IB" and assume it to be the case!


    In practice, it likely wouldn't make that big of a difference; since
    many cases the compiler still treats these cases is implementation
    defined rather than true UB.


    Particular implementations can, of course, document the way a particular
    type of UB is implemented - then when you are using that compiler, you
    can rely on that behaviour.

    Relying on undocumented treatment of UB is where you get in trouble.
    The code might work as you want with this version of the compiler, and
    fail with the next version.


    But, I had seen non-zero code which had relied on the assumption of overflows carrying over consistently between global arrays.


    Relying on the details of how generated assembly is organised is a
    high-risk game. I have had occasions when I needed to do so, but it's
    tightly tied to exact tool versions and flags, and needs checking for
    every run of the build.



    As for the paper itself, I have a few points.

    It follows the time-worn path of complaining that "modern" compilers
    have distorted the "original" idea of UB and abuse it for optimisation
    in ways that are dangerous to the uninitiated.ÿ First, optimisation
    using UB is not new - I first used a compiler that assumed integer
    overflow does not wrap over thirty years ago.ÿ Second, compilers
    already default to "-O0" - if you know so little about your tools that
    you can't enable warnings, you won't have optimisation either.ÿ Third,
    improvements in compiler optimisation have gone hand-in-hand with
    improvements in compiler static error checking.ÿ Using "-Wall" along
    with optimisation will eliminate the solid majority of the risks and
    worries of the author.ÿ Fourth, the development of "sanitizers" not
    only allows far more UB to be found during testing, but it allows far
    more bugs to be found than would be possible if the mistakes were /
    not/ UB - "-fsanitize=signed-integer-overflow" catches overflow bugs
    precisely because it is UB in C, whereas if it were defines as
    wrapping then the mistake in the programmer's code would be correct as
    far as the language is concerned.


    For keeping old code working, it makes sense to assume wrapping.

    To be clear - you mean that if you don't know the old code, it makes
    sense to assume the code might rely on wrapping behaviour, and use
    appropriate compiler flags (or add appropriate pragmas) ?


    But, maybe it could make sense to have a semantic distinction for cases where wrapping is assumed vs where it is likely unintentional.

    My proposal, if any, would be, say:
    ÿ If "signed" is used explicitly, it means wrap-on-overflow is expected.

    That makes no sense at all.


    So, say:
    ÿ int i;
    ÿ signed int v;
    If 'i' overflows, one can question whether or not it was intended; but
    if 'v' overflows, assume it is intentional (as with 'unsigned').

    You can't just invent a personal convention like that - at least, not if
    you ever use other people's code or compilers written by someone else.
    You can't change the meaning of existing code.

    If C were ever to have support for something akin to that, I would
    expect to see new names involved. It would be "_Wrapping int v;", or
    types "int_wrapping32_t" in <stdint.h>.

    More likely, functions like "wrapping_add" could be added to <stdckdint.h>.


    This being in part because an explicit "signed" keyword is uncommon to
    be used with normal counting/index type variables.


    Some people use it - and they do not do so with the expectation of
    different semantics than when it is omitted. Some people write "signed" rather than "int" for declarations.


    Don't dereference null pointers.ÿ It's a bad idea.


    Dereferencing NULL is one of those cases where the most sensible option
    is to treat it as a fault...

    It's a bug.


    Most sensible option is to assume that trying to dereference NULL is unintentional (given the main purpose of NULL being as a "this object doesn't exist" pointer).


    Yes.

    There is the exception that on some hardware, 0 is a valid memory
    address and you might want to access it. On microcontrollers, it is not uncommon for code flash to start there, so something like an integrity
    check of the program would have to read that address. Then you are dereferencing a pointer that happens to have value 0, rather than a null pointer.

    But:
    Assuming it doesn't happen and then removing the offending code path entirely is also bad IMO. However, turning the code-path into a break instruction or similar (following any other externally visible side
    effects) would still likely be acceptable IMO (or, at least, less bad in this case than just continuing down the other path as if nothing had
    ever happened).


    You are thinking of code like :

    int x = *p;
    if (!p) return someone_made_a_mistake;
    ...

    Some compilers will skip the check here, because they assume the
    hardware / OS will have caught the attempt to access address 0. That is
    fair enough - /if/ the target is guaranteed to have such a hardware
    catch. If it is not guaranteed, then the check can't be skipped. Even better, of course, would be a compiler warning that the programmer is
    doing something silly - whether or not the check is skipped.

    I agree that sometimes a "break" (or "trap", or similar) instruction can
    be a good thing for compilers to generate when they are clearly
    executing undefined behaviour. But I am not convinced it is always a
    good idea (I would not want it for this case), and it would be hard to
    specify and guarantee the circumstances without it ruining the
    possibility of optimisation from assuming code has defined behaviour.

    Compilers with sanitizers do support such traps - gcc with the options "-fsanitize-trap=all -fsanitize=null" will trap if the pointer "p" is
    null in this code.



    --- PyGate Linux v1.5.18
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Janis Papanagnou@3:633/10 to All on Thursday, July 23, 2026 11:18:23
    On 2026-07-22 10:41, David Brown wrote:
    On 22/07/2026 07:33, Janis Papanagnou wrote:
    On 2026-07-22 02:26, Keith Thompson wrote:
    Janis Papanagnou <janis_papanagnou+ng@hotmail.com> writes:
    There was some recent longish thread with discussions addressing
    Undefined Behavior. I just stumbled across a paper from Russ Cox
    on that topic with a provoking title. [...]

    For those who don't follow the link, the full title of the paper is
    "C and C++ Prioritize Performance over Correctness".ÿ (Your
    paraphrase could be interpreted as advice, which I'm sure is not
    how you intended it.)

    It was actually a (deliberate) "marketing trick" to provoke interest
    by reducing the title to an (IMO) yet more aggravating formulation.

    Yes, it was not my intention to suggest performance over correctness.
    (Would anyone?)


    Unfortunately, the answer to your final question is yes -

    In that stripped down and generalized formulation that is true; after
    posting there immediately formed an image in my mind of companies that
    have a fast time-to-market strategy as a primary goal on their agenda,
    and with the help of a strong marketing divisions, and with a crowd of believers in their software products that obviously works (as a couple
    of prominent vendors proved over decades). My own views on correctness
    may be (too?) altruistic here, and "peephole" performance tweaks (from aggressively "optimizing" compilers) were rarely an issue.[*]

    and that is one of the reasons UB can be a problem.

    I took Keith's hint as me not having intended to _generally_ suggest performance over correctness. (And he was right, as I said.)

    In my OP I wrote: "A couple arguments and examples look familiar
    already, [...].", so it's not that surprising that in your thorough
    reply you cover arguments previously discussed already. My own opinion
    on "C" and "its UB" is meanwhile consolidated by the various statements
    in the previous thread already so - please bear with me - I'll abstain
    from commenting on details of your post, but thanks for the write-up.

    Janis

    [*] We used to approach performance topics primarily by the choice of algorithms and by appropriate data structuring, and in rare cases by
    optimized isolated modules written in another language (mainly "C"),
    or in some specific cases also by mathematical or logical functional transformations of algorithms.

    [...]


    --- PyGate Linux v1.5.18
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Keith Thompson@3:633/10 to All on Thursday, July 23, 2026 14:31:10
    David Brown <david.brown@hesbynett.no> writes:
    [...]
    You are thinking of code like :

    int x = *p;
    if (!p) return someone_made_a_mistake;
    ...

    Some compilers will skip the check here, because they assume the
    hardware / OS will have caught the attempt to access address 0. That
    is fair enough - /if/ the target is guaranteed to have such a hardware
    catch. If it is not guaranteed, then the check can't be skipped.

    Yes, it can. A conforming compiler can omit the (!p) test because the
    behavior is undefined, not (necessarily) because of the behavior of the
    target system.

    Even better, of course, would be a compiler warning that the
    programmer is doing something silly - whether or not the check is
    skipped.

    Certainly a warning would be nice. The problem is that unexpected optimizations in the presence of undefined behavior don't always occur
    because the compiler *knows* that the behavior is undefined.

    [...]

    --
    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.18
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Keith Thompson@3:633/10 to All on Thursday, July 23, 2026 14:48:28
    Keith Thompson <Keith.S.Thompson+u@gmail.com> writes:
    David Brown <david.brown@hesbynett.no> writes:
    [...]
    You are thinking of code like :

    int x = *p;
    if (!p) return someone_made_a_mistake;
    ...

    Some compilers will skip the check here, because they assume the
    hardware / OS will have caught the attempt to access address 0. That
    is fair enough - /if/ the target is guaranteed to have such a hardware
    catch. If it is not guaranteed, then the check can't be skipped.

    Yes, it can. A conforming compiler can omit the (!p) test because the behavior is undefined, not (necessarily) because of the behavior of the target system.

    Sorry, I was imprecise.

    If p is a null pointer, the behavior is undefined. If p is a
    valid non-null pointer, the behavior is well defined, and the
    `return someone_made_a_mistake;` statement will be executed. Since
    returning `someone_made_a_mistake` is a valid example of undefined
    behavior, a compiler can simplify the code to an unconditional
    `return someone_made_a_mistake;`.

    It can't just omit checks because the behavior *might* be undefined.

    Even better, of course, would be a compiler warning that the
    programmer is doing something silly - whether or not the check is
    skipped.

    Certainly a warning would be nice. The problem is that unexpected optimizations in the presence of undefined behavior don't always occur because the compiler *knows* that the behavior is undefined.

    [...]

    --
    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.18
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Keith Thompson@3:633/10 to All on Thursday, July 23, 2026 15:11:49
    Keith Thompson <Keith.S.Thompson+u@gmail.com> writes:
    Keith Thompson <Keith.S.Thompson+u@gmail.com> writes:
    David Brown <david.brown@hesbynett.no> writes:
    [...]
    You are thinking of code like :

    int x = *p;
    if (!p) return someone_made_a_mistake;
    ...

    Some compilers will skip the check here, because they assume the
    hardware / OS will have caught the attempt to access address 0. That
    is fair enough - /if/ the target is guaranteed to have such a hardware
    catch. If it is not guaranteed, then the check can't be skipped.

    Yes, it can. A conforming compiler can omit the (!p) test because the
    behavior is undefined, not (necessarily) because of the behavior of the
    target system.

    Sorry, I was imprecise.

    Sorry, I was also *wrong*.

    If p is a null pointer, the behavior is undefined. If p is a
    valid non-null pointer, the behavior is well defined, and the
    `return someone_made_a_mistake;` statement will be executed. Since
    returning `someone_made_a_mistake` is a valid example of undefined
    behavior, a compiler can simplify the code to an unconditional
    `return someone_made_a_mistake;`.

    Correction: if p is a valid non-null pointer, the behavior is well
    defined, and the `return someone_made_a_mistake;` statement will *NOT*
    be executed. A conforming compiler can emit code that does nothing.

    For that matter, the behavior is undefined if p is an invalid non-null
    pointer, so that dereferencing it has UB.

    It can't just omit checks because the behavior *might* be undefined.

    Even better, of course, would be a compiler warning that the
    programmer is doing something silly - whether or not the check is
    skipped.

    Certainly a warning would be nice. The problem is that unexpected
    optimizations in the presence of undefined behavior don't always occur
    because the compiler *knows* that the behavior is undefined.

    [...]

    --
    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.18
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From David Brown@3:633/10 to All on Friday, July 24, 2026 09:23:47
    On 24/07/2026 00:11, Keith Thompson wrote:
    Keith Thompson <Keith.S.Thompson+u@gmail.com> writes:
    Keith Thompson <Keith.S.Thompson+u@gmail.com> writes:
    David Brown <david.brown@hesbynett.no> writes:
    [...]
    You are thinking of code like :

    int x = *p;
    if (!p) return someone_made_a_mistake;
    ...

    Some compilers will skip the check here, because they assume the
    hardware / OS will have caught the attempt to access address 0. That
    is fair enough - /if/ the target is guaranteed to have such a hardware >>>> catch. If it is not guaranteed, then the check can't be skipped.

    Yes, it can. A conforming compiler can omit the (!p) test because the
    behavior is undefined, not (necessarily) because of the behavior of the
    target system.

    See below for more discussion.

    In the case of gcc's "-fdelete-null-pointer-checks" flag, the compiler documentation says that look-after-you-leap null pointer checks can be eliminated because the compiler assumes that any dereferencing of
    address zero results in a trap. The big fuss when this flag was
    introduced (and enabled by default) in gcc was because the Linux kernel contained such mistakes in the code, and by default address 0 was
    accessible without causing a trap.


    Sorry, I was imprecise.

    Sorry, I was also *wrong*.

    If p is a null pointer, the behavior is undefined. If p is a
    valid non-null pointer, the behavior is well defined, and the
    `return someone_made_a_mistake;` statement will be executed. Since
    returning `someone_made_a_mistake` is a valid example of undefined
    behavior, a compiler can simplify the code to an unconditional
    `return someone_made_a_mistake;`.

    Correction: if p is a valid non-null pointer, the behavior is well
    defined, and the `return someone_made_a_mistake;` statement will *NOT*
    be executed. A conforming compiler can emit code that does nothing.


    (Caveat - what I am about to write is the best of my understanding, but
    it might not be correct. If I am wrong, I am happy to be corrected!)

    The trouble with your argument here is that a pointer with value 0 is
    not necessarily a null pointer (and I'm assuming an implementation where
    null pointers have a value 0, not anything weirder than that).

    Null pointers are generated from null pointer constants - 0, (void*) 0,
    and nullptr. If you have a pointer that happens to contain the value 0
    but it did not come from a null pointer constant, it is not a null
    pointer and dereferencing it is not inherently UB. (Of course it is
    still UB if it does not point to a valid object of appropriate type.)

    The evaluation of "!p" is based on a comparison of "p" to the value 0 -
    it will be true if p contains the value 0. (This point is perhaps the
    weak link in my reasoning - maybe "!p" is only true if "p" is actually a
    null pointer. However, I am confident that all real implementations,
    where null pointers are value 0, act by comparison of the value.)

    As far as I can see, therefore, "p" can be a valid pointer to an object
    of appropriate type, not be a null pointer, and still have value 0 and
    have "!p" evaluate to 1.


    For that matter, the behavior is undefined if p is an invalid non-null pointer, so that dereferencing it has UB.


    Agreed.

    Of course, a compiler can only optimise away UB code if it knows it is
    UB - the compiler usually cannot know that a general pointer is invalid.

    It can't just omit checks because the behavior *might* be undefined.

    Even better, of course, would be a compiler warning that the
    programmer is doing something silly - whether or not the check is
    skipped.

    Certainly a warning would be nice. The problem is that unexpected
    optimizations in the presence of undefined behavior don't always occur
    because the compiler *knows* that the behavior is undefined.


    Yes, indeed. And compilers are built up with lots of passes and do not
    hold all information throughout the chain (otherwise they would be a lot bigger, a lot slower, and use a lot more memory). Often by the time
    code is eliminated or re-arranged in later passes, the original reason
    is lost. Older versions of gcc had a warning you could enable to tell
    you when code was eliminated - the warning was removed because it was
    too noisy in the face of more advanced optimisations (like code folding, constant propagation, and function cloning), and there was no practical reliable way to give just the helpful cases. We can always wish for
    better warnings, and keep asking for them from our compiler vendors, but
    the compiler writers are unfortunately limited by practical realities.



    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Keith Thompson@3:633/10 to All on Friday, July 24, 2026 03:14:06
    David Brown <david.brown@hesbynett.no> writes:
    On 24/07/2026 00:11, Keith Thompson wrote:
    Keith Thompson <Keith.S.Thompson+u@gmail.com> writes:
    Keith Thompson <Keith.S.Thompson+u@gmail.com> writes:
    David Brown <david.brown@hesbynett.no> writes:
    [...]
    You are thinking of code like :

    int x = *p;
    if (!p) return someone_made_a_mistake;
    ...

    Some compilers will skip the check here, because they assume the
    hardware / OS will have caught the attempt to access address 0. That >>>>> is fair enough - /if/ the target is guaranteed to have such a hardware >>>>> catch. If it is not guaranteed, then the check can't be skipped.

    Yes, it can. A conforming compiler can omit the (!p) test because the >>>> behavior is undefined, not (necessarily) because of the behavior of the >>>> target system.

    See below for more discussion.

    In the case of gcc's "-fdelete-null-pointer-checks" flag, the compiler documentation says that look-after-you-leap null pointer checks can be eliminated because the compiler assumes that any dereferencing of
    address zero results in a trap. The big fuss when this flag was
    introduced (and enabled by default) in gcc was because the Linux
    kernel contained such mistakes in the code, and by default address 0
    was accessible without causing a trap.

    That's the gcc documentation's rationale for its behavior --
    and that's perfectly valid. My point is that evaluating *p
    if p is a null pointer has undefined behavior in the language
    (in the abstract machine), and therefore the condition !p
    can be true when it's evaluated only if the previous line has
    already had undefined behavior. A conforming C compiler could
    implement the optimization (of not generating code for `return someone_made_a_mistake;) regardless of the target system's actual
    behavior on defererencing a null pointer -- even if, for example,
    evaluating `*p` on a null pointer always yielded a consistent value
    (say, 0 of p is of type int*).

    If a null pointer is represented as all-bits-zero and dereferencing
    an all-bits-zero pointer is likely to be a sensible thing to do,
    then a *useful* compiler is likely -- but a *standard-conforming*
    compiler isn't required to do so.

    Sorry, I was imprecise.
    Sorry, I was also *wrong*.

    If p is a null pointer, the behavior is undefined. If p is a
    valid non-null pointer, the behavior is well defined, and the
    `return someone_made_a_mistake;` statement will be executed. Since
    returning `someone_made_a_mistake` is a valid example of undefined
    behavior, a compiler can simplify the code to an unconditional
    `return someone_made_a_mistake;`.
    Correction: if p is a valid non-null pointer, the behavior is well
    defined, and the `return someone_made_a_mistake;` statement will *NOT*
    be executed. A conforming compiler can emit code that does nothing.

    (Caveat - what I am about to write is the best of my understanding,
    but it might not be correct. If I am wrong, I am happy to be
    corrected!)

    The trouble with your argument here is that a pointer with value 0 is
    not necessarily a null pointer (and I'm assuming an implementation
    where null pointers have a value 0, not anything weirder than that).

    Null pointers are generated from null pointer constants - 0, (void*)
    0, and nullptr. If you have a pointer that happens to contain the
    value 0 but it did not come from a null pointer constant, it is not a
    null pointer and dereferencing it is not inherently UB. (Of course it
    is still UB if it does not point to a valid object of appropriate
    type.)

    So you're restricting the discussion to systems where a null pointer is represented as all-bits-zero. I don't think that makes any difference
    to the abstract semantics, but I'll accept it.

    A null pointer constant, when converted to a pointer type, yields
    a null pointer value, so that's *one* way to obtain a null pointer
    value -- but it's not the only one. malloc(SIZE_MAX) or
    fopen("", "") will almost certainly return a null pointer value
    as well, and that's just as null as one obtained by evaluating
    `nullptr` or `(void*)0`. In particular, `malloc(SIZE_MAX) == nullptr`
    will very probably evaluate to 1.

    On the other hand, there are issues of "provenance", such that a pointer
    value obtained by `memset(ptr_obj, 0, sizeof ptr_obj)` has the same representation as a null pointer but might not qualify as one in certain circumstances. But I don't understand that issue well enough to discuss
    it.

    The evaluation of "!p" is based on a comparison of "p" to the value 0
    - it will be true if p contains the value 0. (This point is perhaps
    the weak link in my reasoning - maybe "!p" is only true if "p" is
    actually a null pointer. However, I am confident that all real implementations, where null pointers are value 0, act by comparison of
    the value.)

    The evaluation of `!p` yields 0 if p compares unequal to 0, 1 if
    it compares equal to 0. It's equivalent to `p == 0`. In that
    comparison, the right operand is implicitly converted to a null
    pointer of the type of p. In other words, `!p` means precisely
    "the value of p is a null pointer value", or "p is a null pointer".

    As far as I can see, therefore, "p" can be a valid pointer to an
    object of appropriate type, not be a null pointer, and still have
    value 0 and have "!p" evaluate to 1.

    Only in the presence of undefined behavior, in which case the value
    of "p" can be a lovely shade of blue.

    For that matter, the behavior is undefined if p is an invalid non-null
    pointer, so that dereferencing it has UB.


    Agreed.

    Of course, a compiler can only optimise away UB code if it knows it is
    UB - the compiler usually cannot know that a general pointer is
    invalid.

    It can't just omit checks because the behavior *might* be undefined.

    Even better, of course, would be a compiler warning that the
    programmer is doing something silly - whether or not the check is
    skipped.

    Certainly a warning would be nice. The problem is that unexpected
    optimizations in the presence of undefined behavior don't always occur >>>> because the compiler *knows* that the behavior is undefined.


    Yes, indeed. And compilers are built up with lots of passes and do
    not hold all information throughout the chain (otherwise they would be
    a lot bigger, a lot slower, and use a lot more memory). Often by the
    time code is eliminated or re-arranged in later passes, the original
    reason is lost. Older versions of gcc had a warning you could enable
    to tell you when code was eliminated - the warning was removed because
    it was too noisy in the face of more advanced optimisations (like code folding, constant propagation, and function cloning), and there was no practical reliable way to give just the helpful cases. We can always
    wish for better warnings, and keep asking for them from our compiler
    vendors, but the compiler writers are unfortunately limited by
    practical realities.

    Often the optimizations and other transformations are performed on
    intermediate forms that can't easily be described in terms of the
    source code.

    --
    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.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Janis Papanagnou@3:633/10 to All on Friday, July 24, 2026 14:47:47
    On 2026-07-23 23:31, Keith Thompson wrote:
    David Brown <david.brown@hesbynett.no> writes:
    [...]
    You are thinking of code like :

    int x = *p;
    if (!p) return someone_made_a_mistake;
    ...

    Some compilers will skip the check here, because they assume the
    hardware / OS will have caught the attempt to access address 0. That
    is fair enough - /if/ the target is guaranteed to have such a hardware
    catch. If it is not guaranteed, then the check can't be skipped.

    Yes, it can. A conforming compiler can omit the (!p) test because the behavior is undefined, not (necessarily) because of the behavior of the target system.

    (I've seen you posted two followups on above. - No comment on
    all that.)

    Even better, of course, would be a compiler warning that the
    programmer is doing something silly - whether or not the check is
    skipped.

    Certainly a warning would be nice. The problem is that unexpected optimizations in the presence of undefined behavior don't always occur because the compiler *knows* that the behavior is undefined.

    This thought or reasoning sounds completely perverted. - If the
    compiler *knows* that there's undefined behavior (that may lead
    to non-equivalent (=illegitimate) [dangerous] transformations!)
    a warning (or error message) should be *mandated* in any case!
    Without such a notice any "optimizations" (based on bizarre and
    hazardous assumptions) should never be performed! - Certainly a
    warning would not only be "nice" but should be mandated. - YMMV.

    It probably just boils down to the fact that the C-folks seem to
    imply another meaning of "optimization", meaning more something
    like "mutation of the code to something functionally different".

    Or, as it appear to me, that (with the knowledge of the inherent
    problems to overcome the issues) accommodated to the situation.

    Given what "C" actually is (and what it can *not* become) I don't
    think it makes sense to continue arguing about what I consider to
    be just the crazy facts of ("C"-)reality.

    Janis


    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From David Brown@3:633/10 to All on Friday, July 24, 2026 16:27:14
    On 24/07/2026 14:47, Janis Papanagnou wrote:
    On 2026-07-23 23:31, Keith Thompson wrote:
    David Brown <david.brown@hesbynett.no> writes:
    [...]
    You are thinking of code like :

    ÿÿÿÿint x = *p;
    ÿÿÿÿif (!p) return someone_made_a_mistake;
    ÿÿÿÿ...

    Some compilers will skip the check here, because they assume the
    hardware / OS will have caught the attempt to access address 0.ÿ That
    is fair enough - /if/ the target is guaranteed to have such a hardware
    catch.ÿ If it is not guaranteed, then the check can't be skipped.

    Yes, it can.ÿ A conforming compiler can omit the (!p) test because the
    behavior is undefined, not (necessarily) because of the behavior of the
    target system.

    (I've seen you posted two followups on above. - No comment on
    all that.)

    Even better, of course, would be a compiler warning that the
    programmer is doing something silly - whether or not the check is
    skipped.

    Certainly a warning would be nice.ÿ The problem is that unexpected
    optimizations in the presence of undefined behavior don't always occur
    because the compiler *knows* that the behavior is undefined.

    This thought or reasoning sounds completely perverted. - If the
    compiler *knows* that there's undefined behavior (that may lead
    to non-equivalent (=illegitimate) [dangerous] transformations!)

    Compilers don't (baring bugs in the compiler or misunderstandings by the compiler writers) do transformations that are illegitimate or lead to different semantics. There's no need for warnings about them. All
    defined behaviours before the transformation will have the same defined behaviour after the transformation.

    The only way to get different effects is if the behaviour /before/ the transformation was undefined. And then there is no way, in general, for
    the compiler to know if the behaviour was changed by the transformation
    as it had no definition previously.

    a warning (or error message) should be *mandated* in any case!
    Without such a notice any "optimizations" (based on bizarre and
    hazardous assumptions) should never be performed! - Certainly a
    warning would not only be "nice" but should be mandated. - YMMV.

    It probably just boils down to the fact that the C-folks seem to
    imply another meaning of "optimization", meaning more something
    like "mutation of the code to something functionally different".

    This is all standard computer science theory. You start with code that
    has a precondition and a postcondition - the code guarantees that as
    long as the precondition is fulfilled, running the code will fulfill the postcondition. A transformation - optimisation, implementation, code generation, whatever - is valid as long as the precondition is not strengthened and the postcondition is not weakened. At no point does
    the implementation, or transformed code, or the transformer (compiler / optimiser) have to check the precondition - it can assume it is true.

    The C statement "int x = *p;" has the precondition that "p" is a valid
    pointer pointing to an int object. The postcondition is that "x"
    contains the value of the int at the address contained in "p". The code
    does not say anything about what will happen if "p" does not point to a
    valid int object. So the transformed code can do anything it likes in
    such a case.

    Optimisations don't change the functionality of code. But you have to remember that the functionality of the code is only defined when the precondition is satisfied - code has no functionality outside of that.


    Or, as it appear to me, that (with the knowledge of the inherent
    problems to overcome the issues) accommodated to the situation.

    Given what "C" actually is (and what it can *not* become) I don't
    think it makes sense to continue arguing about what I consider to
    be just the crazy facts of ("C"-)reality.

    Janis



    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Scott Lurndal@3:633/10 to All on Friday, July 24, 2026 14:56:25
    David Brown <david.brown@hesbynett.no> writes:
    On 24/07/2026 00:11, Keith Thompson wrote:
    Keith Thompson <Keith.S.Thompson+u@gmail.com> writes:
    Keith Thompson <Keith.S.Thompson+u@gmail.com> writes:
    David Brown <david.brown@hesbynett.no> writes:
    [...]
    You are thinking of code like :

    int x = *p;
    if (!p) return someone_made_a_mistake;
    ...

    Some compilers will skip the check here, because they assume the
    hardware / OS will have caught the attempt to access address 0. That >>>>> is fair enough - /if/ the target is guaranteed to have such a hardware >>>>> catch. If it is not guaranteed, then the check can't be skipped.

    Yes, it can. A conforming compiler can omit the (!p) test because the >>>> behavior is undefined, not (necessarily) because of the behavior of the >>>> target system.

    See below for more discussion.

    In the case of gcc's "-fdelete-null-pointer-checks" flag, the compiler >documentation says that look-after-you-leap null pointer checks can be >eliminated because the compiler assumes that any dereferencing of
    address zero results in a trap. The big fuss when this flag was
    introduced (and enabled by default) in gcc was because the Linux kernel >contained such mistakes in the code, and by default address 0 was
    accessible without causing a trap.

    It's worth pointing out one of the big differences between System V
    and BSD unix operating systems - where the latter would map a read-only
    page of zeros at address zero on the VAX - in which case loads via
    a NULL pointer would return zeros, and stores would fault. System V
    would fault on such reads.

    This bit more than a few programmers at the time.

    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Johann 'Myrkraverk' Oskarsson@3:633/10 to All on Saturday, July 25, 2026 00:52:05
    On 24/07/2026 8:47 PM, Janis Papanagnou wrote:
    On 2026-07-23 23:31, Keith Thompson wrote:


    Certainly a warning would be nice.ÿ The problem is that unexpected
    optimizations in the presence of undefined behavior don't always occur
    because the compiler *knows* that the behavior is undefined.

    This thought or reasoning sounds completely perverted. - If the
    compiler *knows* that there's undefined behavior (that may lead
    to non-equivalent (=illegitimate) [dangerous] transformations!)
    a warning (or error message) should be *mandated* in any case!
    Without such a notice any "optimizations" (based on bizarre and
    hazardous assumptions) should never be performed! - Certainly a
    warning would not only be "nice" but should be mandated. - YMMV.

    It probably just boils down to the fact that the C-folks seem to
    imply another meaning of "optimization", meaning more something
    like "mutation of the code to something functionally different".

    Or, as it appear to me, that (with the knowledge of the inherent
    problems to overcome the issues) accommodated to the situation.

    Given what "C" actually is (and what it can *not* become) I don't
    think it makes sense to continue arguing about what I consider to
    be just the crazy facts of ("C"-)reality.

    Sounds like you want to read /What every compiler writer should know
    about programmers/ by Anton Ertl, if you haven't already. Link at

    https://c9x.me/compile/bib/

    and more for everyone who wants to write his own C compiler.

    What some people have done, including me, is to simply lose faith in
    the C ISO committee, and started to write our own C compilers. This
    will make Mr. Singapore from the noise earlier very happy.

    Now, I decide how to react to this /Undefined behaviour/ means, and I
    disagree with the people behind the "big three" C compilers.


    Happy C coding!
    --
    Johann | email: invalid -> com | www.myrkraverk.com/blog/
    I'm not from the Internet, I just work there. | twitter: @myrkraverk

    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From BGB@3:633/10 to All on Friday, July 24, 2026 16:50:24
    On 7/24/2026 7:47 AM, Janis Papanagnou wrote:
    On 2026-07-23 23:31, Keith Thompson wrote:
    David Brown <david.brown@hesbynett.no> writes:
    [...]
    You are thinking of code like :

    ÿÿÿÿint x = *p;
    ÿÿÿÿif (!p) return someone_made_a_mistake;
    ÿÿÿÿ...

    Some compilers will skip the check here, because they assume the
    hardware / OS will have caught the attempt to access address 0.ÿ That
    is fair enough - /if/ the target is guaranteed to have such a hardware
    catch.ÿ If it is not guaranteed, then the check can't be skipped.

    Yes, it can.ÿ A conforming compiler can omit the (!p) test because the
    behavior is undefined, not (necessarily) because of the behavior of the
    target system.

    (I've seen you posted two followups on above. - No comment on
    all that.)

    Even better, of course, would be a compiler warning that the
    programmer is doing something silly - whether or not the check is
    skipped.

    Certainly a warning would be nice.ÿ The problem is that unexpected
    optimizations in the presence of undefined behavior don't always occur
    because the compiler *knows* that the behavior is undefined.

    This thought or reasoning sounds completely perverted. - If the
    compiler *knows* that there's undefined behavior (that may lead
    to non-equivalent (=illegitimate) [dangerous] transformations!)
    a warning (or error message) should be *mandated* in any case!
    Without such a notice any "optimizations" (based on bizarre and
    hazardous assumptions) should never be performed! - Certainly a
    warning would not only be "nice" but should be mandated. - YMMV.


    Mostly agree...

    In a lot of cases, there is UB, but:
    Either the UB needs to be treated not as UB,
    because real-world software expects particular behaviors.
    Or, the UB should likely be warning/error + breakpoint.

    Had been a little caught off guard by the latter, in the case of GCC +
    C++ mode + missing return values. Some code that worked before started crashing, eventually identified as a missing return value in an "Init"
    type function:
    Typically one returns 'int', but the return values are typically never
    used and whether or not a function does a "return(0);" is a bit hit or miss.

    Newer GCC versions seem to enforce this case, generating a break-point
    rather than random garbage.

    Though, yes, one can just use 'void' as the return type.



    Did make the recent observation in my compiler a "TODO: Fix" type item
    in that if one has a function returning void, and then tries to use the
    value (such as returning it again, or assigning it to a variable); the compiler just triggers and internal break-point and unceremoniously crashes.

    While, yes, this is one possible way to deal with UB, better to have compilation fail with an error message or something.

    Generally the breakpoints were used for cases where "we shouldn't get
    here, if we do, probably something has gone wrong in the compiler itself".

    But, sometimes this sort of thing means going and finding good places to
    put code to check for issues and print an error message and escape;
    rather than continue on the normal path and have some deeper code be
    like "YO, WTF".

    Compiler internals being a type of code where the best strategy is
    generally to be pedantic and trigger a break-point or similar when
    something is unexpected (with an outer layer than needs to deal with
    whatever weirdness the user throws at it, either diverting the logic, or printing warning/error messages, ...).


    Sometimes errors might be harder to handle in a sensible way if they
    emerge at a later/deeper stage (which is, from what I heard, partly
    where some of the UB opts were coming from in Clang and similar, and
    less as a deliberate design feature).

    Granted, in some of these cases, would still prefer the compiler to
    insert an explicit break-point into the generated code than to quietly
    remove the offending code path.



    It probably just boils down to the fact that the C-folks seem to
    imply another meaning of "optimization", meaning more something
    like "mutation of the code to something functionally different".


    Yeah.

    There are several classes of things that could be classes as optimizations:
    Code generation features:
    Register allocation;
    Choosing sensible instruction sequences;
    Culling unused local variables;
    ...
    Modification/Sequencing:
    Trying to infer how to schedule instructions to best fit pipeline;
    Mostly not needed for OoO CPUs, but needed for in-order.
    ...
    Mild Transformations:
    Constant folding;
    Caching array/member loads;
    Delaying stores to arrays/members;
    ...
    Stronger transformations:
    Loop unrolling;
    Function inlining;
    Pattern matching and replacing expressions;
    ...


    My compiler mostly focuses on the former classes, but largely ignores
    the latter. In the latter case, a lot of complex pattern matching can
    come up, and a lot of ways that things can go horribly wrong.

    There are a few special cases, like my compiler will pattern match:
    (x>>SHIFT)&MASK
    And similar, as there are special CPU instructions that may convert this
    into a single operation; and the fallback case of decomposing it back
    into a shift and mask being no-loss (or, on targets like RISC-V, it
    being often cheaper turn this case into a "shift-left;shift-right" pair
    than to a "shift-and-and" mostly because RISC-V suffers hard for
    immediate values outside the range of -2048..2047; or my ISAs needing to
    use a larger 64-bit encoding if outside the range of -512..511).

    So, for example:
    y=(x>>32)&0xFFFF;
    Being cheaper on RV64 to behave as-if it were:
    y=(x<<16)>>32;
    As it requires 2 instructions rather than 4.
    On my ISA, this may be 1 instruction (if the feature is enabled),
    else it also needs 2 instructions.
    ...


    The latter class is one that GCC seems to lean more heavily into, with
    MSVC sort of in-between.

    The former classes for GCC depend highly on target, but had noted that
    for targets like RISC-V, that GCC has some room for improvement in these areas. Some fair bit of cleverness, but also a bit of "meh".


    Though, for MSVC seems to depend on era, where it seems like MSVC
    behavior changed considerably in the 2010s: More aggressive high-level optimizations, auto vectorization, etc, but also support for newer
    versions on the C standard (before this, it was mostly frozen at C90/C95 levels).


    Decided to leave off going into a big detour about register allocation strategies and similar; it got long and probably no one would care.



    Or, as it appear to me, that (with the knowledge of the inherent
    problems to overcome the issues) accommodated to the situation.

    Given what "C" actually is (and what it can *not* become) I don't
    think it makes sense to continue arguing about what I consider to
    be just the crazy facts of ("C"-)reality.


    Yeah, for the most part, C works well.

    Just, compilers can't go quite as wild as what could be inferred by the standard, as going out of the scope of typical behavior will break
    existing code, and people generally don't like when their code breaks.

    Even when maybe faster could be possible if code were broken by relying
    on things outside of those granted by a strict reading of the standard.

    A person writing code may need to be more conservative, but then it is a balancing act:
    What is needed for performance;
    What is needed for portability across the targets of interest;
    Usually smaller than: "everything that could exist"
    What behaviors the existing ranges of compilers will actually do;
    ...


    Maximum portability at the expense of performance is not always the best strategy.

    Even as much as there may be hassle from moving non-portable code to a different machine with different tradeoffs.

    Though, among modern targets, there tends to be some level of
    convergence as well:
    Twos complement, little endian, supports misaligned memory access, ...
    Signed right shift is near universally sign-extending;
    ...

    As the big-ending and aligned-only machines tend to be in steady decline.

    There is still a bit of variation of what happens when shifts are out-of
    range or negative.
    More common option for "too large" is to make it modulo the width of the
    type, or modulo 32/64 bits;
    A minority simulate a larger space, or range-clamp the shift;
    Some will treat negative shifts like large positive shifts, others will
    treat it as a shift in the opposite direction, ...


    Though, there isn't much precedent for code to rely on the behavior of out-of-range shifts.

    And, seemingly this was one area where compilers would differ between
    constant and non-constant shifts (say, non-constant shifts following the
    Mod-N behavior, but constant shifts following a
    negative-reverses-direction and saturate to maximum value) approach
    (with newer compilers typically generating a warning about the shift
    being too large or negative).



    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From bart@3:633/10 to All on Friday, July 24, 2026 23:08:47
    On 24/07/2026 17:52, Johann 'Myrkraverk' Oskarsson wrote:
    On 24/07/2026 8:47 PM, Janis Papanagnou wrote:
    On 2026-07-23 23:31, Keith Thompson wrote:


    Certainly a warning would be nice.ÿ The problem is that unexpected
    optimizations in the presence of undefined behavior don't always occur
    because the compiler *knows* that the behavior is undefined.

    This thought or reasoning sounds completely perverted. - If the
    compiler *knows* that there's undefined behavior (that may lead
    to non-equivalent (=illegitimate) [dangerous] transformations!)
    a warning (or error message) should be *mandated* in any case!
    Without such a notice any "optimizations" (based on bizarre and
    hazardous assumptions) should never be performed! - Certainly a
    warning would not only be "nice" but should be mandated. - YMMV.

    It probably just boils down to the fact that the C-folks seem to
    imply another meaning of "optimization", meaning more something
    like "mutation of the code to something functionally different".

    Or, as it appear to me, that (with the knowledge of the inherent
    problems to overcome the issues) accommodated to the situation.

    Given what "C" actually is (and what it can *not* become) I don't
    think it makes sense to continue arguing about what I consider to
    be just the crazy facts of ("C"-)reality.

    Sounds like you want to read /What every compiler writer should know
    about programmers/ by Anton Ertl, if you haven't already.ÿ Link at

    ÿ https://c9x.me/compile/bib/



    This is an extract from that PDF:

    -------------------------------
    int d[16];
    int SATD (void) {
    int satd = 0, dd, k;
    for (dd=d[k=0]; k<16; dd=d[++k]) {
    satd += (dd < 0 ? -dd : dd);
    }
    return satd;
    }

    "This was ?optimized? by a pre-release of gcc-4.8 into the following
    infinite loop:

    SATD:
    .L2:
    jmp .L2

    What happened? The compiler assumed that no out-of-bounds access to d
    would happen, and from that derived that k is at most 15 after the
    access, so the following test k<16 can be ?optimized? to 1 (true),
    resulting in an endless loop. Then the compiler sees that the return is
    now unreachable, that satd is dead, that dd is dead, and k is dead, and optimizes the rest away."

    -------------------------------

    This is pretty crazy!

    and more for everyone who wants to write his own C compiler.

    What some people have done, including me, is to simply lose faith in
    the C ISO committee, and started to write our own C compilers.

    Me too. But a big constraint is the language, and also existing
    practice, if the aim is to be able to compile existing code.

    Because 'big' compilers are gcc are lax by default, it means poor habits
    are perpetuated over the years, and your compiler therefore needs to be
    lax too, although at least you can make your own stricter by default
    stricter.

    By 'the language', I mean that C allows lots of things which in my own
    systems language for example, that I maintain, would be impossible to
    write as they are nonsensical.

    With C, what I found bizarre was that it was easy to write a C program
    where with one of these results when compiled:

    * It passes with no errors
    * It passes but with warnings (and still generates an executable)
    * It fails with errors

    All depending on which options have been chosen. Was the program correct
    or not; who knows? I think here it is the language standard giving too
    much scope to compilers, in part in order to be able to compile poor
    quality legacy code.

    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Keith Thompson@3:633/10 to All on Friday, July 24, 2026 16:13:47
    Janis Papanagnou <janis_papanagnou+ng@hotmail.com> writes:
    On 2026-07-23 23:31, Keith Thompson wrote:
    David Brown <david.brown@hesbynett.no> writes:
    [...]
    You are thinking of code like :

    int x = *p;
    if (!p) return someone_made_a_mistake;
    ...

    Some compilers will skip the check here, because they assume the
    hardware / OS will have caught the attempt to access address 0. That
    is fair enough - /if/ the target is guaranteed to have such a hardware
    catch. If it is not guaranteed, then the check can't be skipped.
    Yes, it can. A conforming compiler can omit the (!p) test because
    the
    behavior is undefined, not (necessarily) because of the behavior of the
    target system.

    (I've seen you posted two followups on above. - No comment on
    all that.)

    Even better, of course, would be a compiler warning that the
    programmer is doing something silly - whether or not the check is
    skipped.
    Certainly a warning would be nice. The problem is that unexpected
    optimizations in the presence of undefined behavior don't always occur
    because the compiler *knows* that the behavior is undefined.

    This thought or reasoning sounds completely perverted. - If the
    compiler *knows* that there's undefined behavior (that may lead
    to non-equivalent (=illegitimate) [dangerous] transformations!)
    a warning (or error message) should be *mandated* in any case!
    Without such a notice any "optimizations" (based on bizarre and
    hazardous assumptions) should never be performed! - Certainly a
    warning would not only be "nice" but should be mandated. - YMMV.

    In the case we're discussing:

    int x = *p;
    if (!p) return someone_made_a_mistake;

    the compiler can't know that there's undefined behavior, because
    if p is a valid pointer the behavior is undefined.

    If a compiler actually proves that the behavior of a construct is
    undefined, then I agree that it should issue a warning (perhaps
    controlled by options) -- though the standard never requires warnings,
    other than for the new #warning directive. I've seen warnings about
    constructs with undefined behavior, for example for `i++ + ++i`.

    Here, if the value of p is not known at compile time, a compiler
    may *assume* that the code has well-defined behavior, in this case
    that p is a valid non-null pointer. If p is actually non-null and
    valid at run time, there's no problem; the resulting code will
    behave correctly. If p is null (due to a bug), the behavior is
    undefined, and any actual behavior is "correct".

    Testing whether p is a null pointer *after* attempting to dereference
    it is clearly a programming bug. Of course I want my compiler
    to warn me about any bugs in my code and not waste my time with
    warnings I don't care about, but that's not always practical.

    It probably just boils down to the fact that the C-folks seem to
    imply another meaning of "optimization", meaning more something
    like "mutation of the code to something functionally different".

    Or, as it appear to me, that (with the knowledge of the inherent
    problems to overcome the issues) accommodated to the situation.

    Given what "C" actually is (and what it can *not* become) I don't
    think it makes sense to continue arguing about what I consider to
    be just the crazy facts of ("C"-)reality.

    --
    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.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Keith Thompson@3:633/10 to All on Friday, July 24, 2026 16:29:56
    Johann 'Myrkraverk' Oskarsson <johann@myrkraverk.invalid> writes:
    [...]
    Sounds like you want to read /What every compiler writer should know
    about programmers/ by Anton Ertl, if you haven't already. Link at

    https://c9x.me/compile/bib/

    and more for everyone who wants to write his own C compiler.

    I haven't read it yet, but I intend to (even though I'm not planning to
    write my own C compiler).

    What some people have done, including me, is to simply lose faith in
    the C ISO committee, and started to write our own C compilers. This
    will make Mr. Singapore from the noise earlier very happy.

    Now, I decide how to react to this /Undefined behaviour/ means, and I disagree with the people behind the "big three" C compilers.

    Do you mean that your own compiler will not conform to the C
    standard, or merely that it will conform in different ways than the
    "big three"?

    If you think a compiler should not generate code based on the
    assumption of defined behavior (for example, that it should generate
    code for a statement even if that statement can be executed only if
    undefined behavior has already occurred), that's fine, and nothing
    in the C standard prevents it. There are plenty of things the C
    standard permits compilers but does not require compilers to do.

    --
    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.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From BGB@3:633/10 to All on Friday, July 24, 2026 18:50:27
    On 7/24/2026 5:08 PM, bart wrote:
    On 24/07/2026 17:52, Johann 'Myrkraverk' Oskarsson wrote:
    On 24/07/2026 8:47 PM, Janis Papanagnou wrote:
    On 2026-07-23 23:31, Keith Thompson wrote:


    Certainly a warning would be nice.ÿ The problem is that unexpected
    optimizations in the presence of undefined behavior don't always occur >>>> because the compiler *knows* that the behavior is undefined.

    This thought or reasoning sounds completely perverted. - If the
    compiler *knows* that there's undefined behavior (that may lead
    to non-equivalent (=illegitimate) [dangerous] transformations!)
    a warning (or error message) should be *mandated* in any case!
    Without such a notice any "optimizations" (based on bizarre and
    hazardous assumptions) should never be performed! - Certainly a
    warning would not only be "nice" but should be mandated. - YMMV.

    It probably just boils down to the fact that the C-folks seem to
    imply another meaning of "optimization", meaning more something
    like "mutation of the code to something functionally different".

    Or, as it appear to me, that (with the knowledge of the inherent
    problems to overcome the issues) accommodated to the situation.

    Given what "C" actually is (and what it can *not* become) I don't
    think it makes sense to continue arguing about what I consider to
    be just the crazy facts of ("C"-)reality.

    Sounds like you want to read /What every compiler writer should know
    about programmers/ by Anton Ertl, if you haven't already.ÿ Link at

    ÿÿ https://c9x.me/compile/bib/



    This is an extract from that PDF:

    -------------------------------
    ÿ int d[16];
    ÿ int SATD (void) {
    ÿÿÿÿÿ int satd = 0, dd, k;
    ÿÿÿÿÿ for (dd=d[k=0]; k<16; dd=d[++k]) {
    ÿÿÿÿÿÿÿÿÿ satd += (dd < 0 ? -dd : dd);
    ÿÿÿÿÿ }
    ÿÿÿÿÿ return satd;
    ÿ }

    "This was ?optimized? by a pre-release of gcc-4.8 into the following infinite loop:

    ÿ SATD:
    ÿ .L2:
    ÿ jmp .L2

    What happened? The compiler assumed that no out-of-bounds access to d
    would happen, and from that derived that k is at most 15 after the
    access, so the following test k<16 can be ?optimized? to 1 (true),
    resulting in an endless loop. Then the compiler sees that the return is
    now unreachable, that satd is dead, that dd is dead, and k is dead, and optimizes the rest away."

    -------------------------------

    This is pretty crazy!


    Yeah...

    This sort of thing is a danger of performing high-level optimizations...


    Ironically, I was getting mostly good results in my compiler not doing
    any of these sorts of optimizations. But, it does depend some on the code.

    Code that assumes high-level code-rewriting transformations will,
    granted, not perform well...



    and more for everyone who wants to write his own C compiler.

    What some people have done, including me, is to simply lose faith in
    the C ISO committee, and started to write our own C compilers.

    Me too. But a big constraint is the language, and also existing
    practice, if the aim is to be able to compile existing code.

    Because 'big' compilers are gcc are lax by default, it means poor habits
    are perpetuated over the years, and your compiler therefore needs to be
    lax too, although at least you can make your own stricter by default stricter.

    By 'the language', I mean that C allows lots of things which in my own systems language for example, that I maintain, would be impossible to
    write as they are nonsensical.


    There are some things I would do different from C as well...

    But, harder to bring enough merit over just using C, to justify the
    added cost of it being "not C".


    Like, in an area where I had one of my own languages (BS2) sharing
    basically all of the compiler infrastructure, ABI, etc, with C (and
    being able to import most BS2 features as C extensions), made it harder
    to justify using my own language.

    That said, there are things I might have done different in BS2 as well
    in retrospect:
    Making 'char' 16-bit (like Java and C#) was a mistake;
    Should have leaned more towards C# syntax than Java syntax;
    ...


    But, then it would have mostly been "kinda like C++, but different".

    Part of this was because BS2 syntax had evolved partly from a later form
    of BGBScript, which had started based on JavaScript and evolved in a
    similar direction to ActionScript3 and Haxe; but with BS2 being a reboot towards a Java-like syntax...

    It is supported in BGBCC, mostly sharing pretty much everything with my
    C compiler (both with produce native code for the same target ISAs, etc).


    There was at one point to use the same basic stuff to implement a C++
    like mode, but C++ was different enough (and added enough complexity of
    its own) to mostly kill off this effort.

    It does (in theory) support a C++ dialect partway between EC++ and C++97
    in terms of feature-set:
    Core is a single-inheritance object system with interfaces via abstract base-classes;
    Supports namespaces;
    Some (not well tested) support for templates.

    But, not really close enough...

    Also there was some wonk as well:
    Like in C#, classes and structs are treated as two different types of
    thing; where classes are always treated internally as being "by-reference";
    To mimic C++ style by-value classes, it essentially needs to clone
    objects on assignment and "delete" them as they go out of scope, which
    is "not very good" (though, the basic mechanism already exists, was used
    for "alloca" and C99 style VLAs).



    There are some other things I might want to change though:
    Make BS2 classes structurally equivalent to COM interfaces;
    Maybe devise a high-level way for inter-process COM handlers;
    ...

    As-is, the BS2 classes don't match up exactly with COM interfaces, but
    there could arguably be more merit to using the language if a COM object
    could simply be cast to a class and used directly, or if a class object
    could be exported as a COM object.

    But, then there are still limits, for example, had thought "what if I
    could export VFS interfaces from userland to kernel space via COM
    interfaces?" but then ran into the issue that this would likely create
    some difficult to resolve "paradox level" issues with the system design.

    So, as-is, interfaces are still either strictly User->Kernel or
    User->User, but Kernel->User can't currently be done (nor can any
    recursive or re-entrant call paths).


    Where, say, COM interfaces are usually expressed in C land sorta like:
    struct IFoo_vt_s {
    void *reserved1;
    void *reserved2;
    int (*Method1)(IFoo_vt **clz, int arg1);
    int (*Method2)(IFoo_vt **clz, char *arg1);
    ...
    };

    IFoo_vt **obj;
    (*obj)->Method1(obj, 42);

    And, there might be some metit if, say:
    __interface IFoo {
    int Method1(int arg1);
    int Method2(char *arg1);
    };

    Were structurally equivalent.

    But, this isn't a hard limit, but possibly an eventual TODO.

    Main difference being that the current implementation typically reserves
    the first 4 VTable entries and uses a "thiscall" which passes the 'this' pointer in a separate register rather than as the first argument.


    With C, what I found bizarre was that it was easy to write a C program
    where with one of these results when compiled:

    * It passes with no errors
    * It passes but with warnings (and still generates an executable)
    * It fails with errors

    All depending on which options have been chosen. Was the program correct
    or not; who knows? I think here it is the language standard giving too
    much scope to compilers, in part in order to be able to compile poor
    quality legacy code.


    I would narrow the scope as well, but admittedly more in terms of trying
    to move more of the "undefined behavior" into "defined behavior".

    But, if one is like:
    Integers are twos complement and little endian in memory;
    Misaligned pointers are allowed;
    Integers are wrap on overflow;
    Signed right shift is sign-extending;
    Programmers are free to cast and de-reference pointers however;
    Required to work so long as the target is "knowable".
    ...

    There may exist some who would balk as they are maintaining
    implementations where these assumptions may fail.


    One may be like, "but we have SIMD load/stores that don't work if the
    pointer is misaligned...", but then one can be like, "figure out how to
    infer whether or not the pointer is aligned before deciding on an
    aligned-only SIMD load...".

    Like, for example, I have an ISA where the 128-bit load instruction is
    also aligned-only (*), but I am not going to use it as an excuse to
    forbid "*(__int128 *)someptr", rather merely that the inability of the compiler to prove alignment requires a fallback to less efficient
    load/store in this case.


    *: In a CPU design, it is easier to make it so that all of the "non-maximum-sized" types are unaligned safe, but cost tradeoffs may
    mean needing to make the largest loads/stores respect alignment constraints.

    ...



    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Lawrence D?Oliveiro@3:633/10 to All on Saturday, July 25, 2026 01:54:45
    On Fri, 24 Jul 2026 23:08:47 +0100, bart wrote:

    On 24/07/2026 17:52, Johann 'Myrkraverk' Oskarsson wrote:

    ÿ https://c9x.me/compile/bib/

    This is an extract from that PDF:

    -------------------------------
    int d[16];
    int SATD (void) {
    int satd = 0, dd, k;
    for (dd=d[k=0]; k<16; dd=d[++k]) {
    satd += (dd < 0 ? -dd : dd);
    }
    return satd;
    }

    "This was ?optimized? by a pre-release of gcc-4.8 into the following
    infinite loop:

    SATD:
    .L2:
    jmp .L2

    What happened? The compiler assumed that no out-of-bounds access to
    d would happen, and from that derived that k is at most 15 after the
    access, so the following test k<16 can be ?optimized? to 1 (true),
    resulting in an endless loop. Then the compiler sees that the return
    is now unreachable, that satd is dead, that dd is dead, and k is
    dead, and optimizes the rest away."

    -------------------------------

    This is pretty crazy!

    Would you rather it segfaulted instead?

    Because I can?t see any other reasonable interpretation of that code.

    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Johann 'Myrkraverk' Oskarsson@3:633/10 to All on Saturday, July 25, 2026 15:20:21
    On 25/07/2026 9:54 AM, Lawrence D?Oliveiro wrote:
    On Fri, 24 Jul 2026 23:08:47 +0100, bart wrote:

    On 24/07/2026 17:52, Johann 'Myrkraverk' Oskarsson wrote:

    ÿ https://c9x.me/compile/bib/

    This is an extract from that PDF:

    -------------------------------
    int d[16];
    int SATD (void) {
    int satd = 0, dd, k;
    for (dd=d[k=0]; k<16; dd=d[++k]) {
    satd += (dd < 0 ? -dd : dd);
    }
    return satd;
    }

    "This was ?optimized? by a pre-release of gcc-4.8 into the following
    infinite loop:

    SATD:
    .L2:
    jmp .L2

    What happened? The compiler assumed that no out-of-bounds access to
    d would happen, and from that derived that k is at most 15 after the
    access, so the following test k<16 can be ?optimized? to 1 (true),
    resulting in an endless loop. Then the compiler sees that the return
    is now unreachable, that satd is dead, that dd is dead, and k is
    dead, and optimizes the rest away."

    -------------------------------

    This is pretty crazy!

    Would you rather it segfaulted instead?

    Because I can?t see any other reasonable interpretation of that code.

    The code is perfectly reasonable. Did you forget globals are zero
    initialized? d[] is a global, it has zeros.

    satd is initialized to zero, and the for loop initializes dd and k.
    I see no way for the loop to make an out of bound access at first
    glance.

    That said, I didn't step through the code. Is satd /saturated d/?
    --
    Johann | email: invalid -> com | http://www.myrkraverk.com/blog/
    I'm not from the Internet, I just work there. | via Easynews.com

    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Lawrence D?Oliveiro@3:633/10 to All on Saturday, July 25, 2026 07:24:43
    On Sat, 25 Jul 2026 15:20:21 +0800, Johann 'Myrkraverk' Oskarsson
    wrote:

    The code is perfectly reasonable.

    If the code were ?perfectly reasonable?, then there would be a
    compiler bug. There isn?t.

    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Johann 'Myrkraverk' Oskarsson@3:633/10 to All on Saturday, July 25, 2026 15:25:07
    On 25/07/2026 7:29 AM, Keith Thompson wrote:
    Johann 'Myrkraverk' Oskarsson <johann@myrkraverk.invalid> writes:
    [...]
    Sounds like you want to read /What every compiler writer should know
    about programmers/ by Anton Ertl, if you haven't already. Link at

    https://c9x.me/compile/bib/

    and more for everyone who wants to write his own C compiler.

    I haven't read it yet, but I intend to (even though I'm not planning to
    write my own C compiler).

    What some people have done, including me, is to simply lose faith in
    the C ISO committee, and started to write our own C compilers. This
    will make Mr. Singapore from the noise earlier very happy.

    Now, I decide how to react to this /Undefined behaviour/ means, and I
    disagree with the people behind the "big three" C compilers.

    Do you mean that your own compiler will not conform to the C
    standard, or merely that it will conform in different ways than the
    "big three"?

    I'm going to leave my disagreement with the "big three" as a surprise
    until release.

    If you think a compiler should not generate code based on the
    assumption of defined behavior (for example, that it should generate
    code for a statement even if that statement can be executed only if
    undefined behavior has already occurred), that's fine, and nothing
    in the C standard prevents it. There are plenty of things the C
    standard permits compilers but does not require compilers to do.


    Indeed. If I remember correctly, loading the source code is
    /implementation defined/, so the compiler can perfectly well
    read NNTP instead of files, and strictly parse only code posted
    to comp.lang.c.


    I wonder if Mr. Singapore thought of that angle?
    --
    Johann | email: invalid -> com | http://www.myrkraverk.com/blog/
    I'm not from the Internet, I just work there. | via Easynews.com

    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Johann 'Myrkraverk' Oskarsson@3:633/10 to All on Saturday, July 25, 2026 15:30:09
    On 25/07/2026 3:24 PM, Lawrence D?Oliveiro wrote:
    On Sat, 25 Jul 2026 15:20:21 +0800, Johann 'Myrkraverk' Oskarsson
    wrote:

    The code is perfectly reasonable.

    If the code were ?perfectly reasonable?, then there would be a
    compiler bug. There isn?t.

    That's a simple matter of debate. I make the point it's a compiler
    bug. You don't, so you must be a member of the "big three" developer
    teams. Therefore all your opinions on my way of compiling C are
    completely invalid.
    --
    Johann | email: invalid -> com | http://www.myrkraverk.com/blog/
    I'm not from the Internet, I just work there. | via Easynews.com

    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From David Brown@3:633/10 to All on Saturday, July 25, 2026 10:23:22
    On 25/07/2026 00:08, bart wrote:
    On 24/07/2026 17:52, Johann 'Myrkraverk' Oskarsson wrote:
    On 24/07/2026 8:47 PM, Janis Papanagnou wrote:
    On 2026-07-23 23:31, Keith Thompson wrote:


    Certainly a warning would be nice.ÿ The problem is that unexpected
    optimizations in the presence of undefined behavior don't always occur >>>> because the compiler *knows* that the behavior is undefined.

    This thought or reasoning sounds completely perverted. - If the
    compiler *knows* that there's undefined behavior (that may lead
    to non-equivalent (=illegitimate) [dangerous] transformations!)
    a warning (or error message) should be *mandated* in any case!
    Without such a notice any "optimizations" (based on bizarre and
    hazardous assumptions) should never be performed! - Certainly a
    warning would not only be "nice" but should be mandated. - YMMV.

    It probably just boils down to the fact that the C-folks seem to
    imply another meaning of "optimization", meaning more something
    like "mutation of the code to something functionally different".

    Or, as it appear to me, that (with the knowledge of the inherent
    problems to overcome the issues) accommodated to the situation.

    Given what "C" actually is (and what it can *not* become) I don't
    think it makes sense to continue arguing about what I consider to
    be just the crazy facts of ("C"-)reality.

    Sounds like you want to read /What every compiler writer should know
    about programmers/ by Anton Ertl, if you haven't already.ÿ Link at

    ÿÿ https://c9x.me/compile/bib/



    This is an extract from that PDF:

    -------------------------------
    ÿ int d[16];
    ÿ int SATD (void) {
    ÿÿÿÿÿ int satd = 0, dd, k;
    ÿÿÿÿÿ for (dd=d[k=0]; k<16; dd=d[++k]) {
    ÿÿÿÿÿÿÿÿÿ satd += (dd < 0 ? -dd : dd);
    ÿÿÿÿÿ }
    ÿÿÿÿÿ return satd;
    ÿ }

    "This was ?optimized? by a pre-release of gcc-4.8 into the following infinite loop:

    ÿ SATD:
    ÿ .L2:
    ÿ jmp .L2

    What happened? The compiler assumed that no out-of-bounds access to d
    would happen, and from that derived that k is at most 15 after the
    access, so the following test k<16 can be ?optimized? to 1 (true),
    resulting in an endless loop. Then the compiler sees that the return is
    now unreachable, that satd is dead, that dd is dead, and k is dead, and optimizes the rest away."

    -------------------------------

    This is pretty crazy!

    To me, the story of that example shows that the gcc folks have pretty
    good development practices. The critical point, IMHO, is that this was
    a /pre-release/ version of the compiler. They added some new stuff in
    gcc 4.8, they tested with all their internal test suites and examples,
    then they made a pre-release to encourage other people to test it and
    see if there are any issues - either bugs in the compiler (this was not
    a bug in gcc), or bugs in common existing code where people had relied
    on particular behaviour despite the (presumably unnoticed) bugs in their
    code.

    That's what pre-release testing is for. It works for gcc. It found
    this issue (a bug in the popular C benchmark code, no less). It finds
    many other issues when pre-release gcc's are used for full re-builds of
    big Linux distributions. Of course you won't find every bug in every
    bit of C code when doing such pre-release testing, nor will you find
    every bug in gcc itself. But it is helpful nonetheless.

    gcc - the compiler and the development team - are the good guys in this
    story.

    And by gcc 4.9, the compiler was issuing warnings about the UB in the
    source code.





    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From David Brown@3:633/10 to All on Saturday, July 25, 2026 10:37:41
    On 25/07/2026 09:30, Johann 'Myrkraverk' Oskarsson wrote:
    On 25/07/2026 3:24 PM, Lawrence D?Oliveiro wrote:
    On Sat, 25 Jul 2026 15:20:21 +0800, Johann 'Myrkraverk' Oskarsson
    wrote:

    The code is perfectly reasonable.

    If the code were ?perfectly reasonable?, then there would be a
    compiler bug. There isn?t.

    That's a simple matter of debate.ÿ I make the point it's a compiler
    bug.ÿ You don't, so you must be a member of the "big three" developer
    teams.ÿ Therefore all your opinions on my way of compiling C are
    completely invalid.

    It is not a matter of debate - the code is buggy. But it's not entirely obvious that it is buggy - the folks who wrote the code were pretty good
    and experienced C programmers, and /they/ didn't notice the bug.
    Lawrence is not (AFAIK) a developer for the "big three" compiler groups,
    but perhaps he works in support for a big IT company - his answer was technically entirely correct, while being entirely unhelpful.


    If you look carefully at the code again, consider the final iteration -
    when k is 15 (the test "k < 16" still passes) there is the expression
    "dd = d[++k]". "k" is incremented to 16, and then we read "d[16]" to
    assign to "dd". But "d[16]" does not exist - the array "d" runs from
    "d[0]" to "d[15]". Attempting to read "d[16]" is an out-of-bounds
    access, and that's UB. It's a bug.

    So given that the input code has no defined behaviour, generated code
    cannot be wrong no matter what it does - there is nothing to compare it
    to for correctness. The compiler is has no bug. (Not here, anyway -
    gcc is not bug-free!)

    That does not mean the compiler is being as helpful a development tool
    as it could have been (the warning generated by gcc 4.9 and later is
    more useful). But it is worth noting that if compilers had done as the pre-release gcc 4.8 compiler did when the SPEC code was written, then
    the bug in the source would have been identified and fixed eons ago.

    Look at the following godbolt link, and compare the warning messages
    when the declaration of "d" is changed between "int d[16]" and "int d[17]".

    <https://godbolt.org/z/75WGof54G>



    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Waldek Hebisch@3:633/10 to All on Saturday, July 25, 2026 16:30:58
    David Brown <david.brown@hesbynett.no> wrote:
    On 24/07/2026 00:11, Keith Thompson wrote:

    The trouble with your argument here is that a pointer with value 0 is
    not necessarily a null pointer (and I'm assuming an implementation where null pointers have a value 0, not anything weirder than that).

    Null pointers are generated from null pointer constants - 0, (void*) 0,
    and nullptr. If you have a pointer that happens to contain the value 0
    but it did not come from a null pointer constant, it is not a null
    pointer and dereferencing it is not inherently UB. (Of course it is
    still UB if it does not point to a valid object of appropriate type.)

    The evaluation of "!p" is based on a comparison of "p" to the value 0 -
    it will be true if p contains the value 0. (This point is perhaps the
    weak link in my reasoning - maybe "!p" is only true if "p" is actually a null pointer. However, I am confident that all real implementations,
    where null pointers are value 0, act by comparison of the value.)

    As far as I can see, therefore, "p" can be a valid pointer to an object
    of appropriate type, not be a null pointer, and still have value 0 and
    have "!p" evaluate to 1.

    I did not check the standard, but if the standard allows this, then
    this is clearly a bug in the standard. To say it differently,
    any C compiler when '!p' can evaluate to 1 for pointer p to a valid
    object is broken. Implementing '!p' via machine level comparison
    to 0 works only when null pointer is represented by 0, otherwise
    compiler must to whatever is necessary to get right result of the
    comparison.

    --
    Waldek Hebisch

    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Lawrence D?Oliveiro@3:633/10 to All on Saturday, July 25, 2026 22:32:11
    On Sat, 25 Jul 2026 15:30:09 +0800, Johann 'Myrkraverk' Oskarsson wrote:

    On 25/07/2026 3:24 PM, Lawrence D?Oliveiro wrote:

    On Sat, 25 Jul 2026 15:20:21 +0800, Johann 'Myrkraverk' Oskarsson
    wrote:

    The code is perfectly reasonable.

    If the code were ?perfectly reasonable?, then there would be a
    compiler bug. There isn?t.

    That's a simple matter of debate.

    Back in the early days of computing, we had a principle called ?GIGO?
    -- ?Garbage In, Garbage Out?. That meant that, if you fed crap into
    the computer, don?t expect to get meaningful results out.

    Do they still teach that in ?IT Studies?, or ?Digital Technologies?,
    or whatever passes for CompSci courses these days?

    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Keith Thompson@3:633/10 to All on Saturday, July 25, 2026 15:33:49
    Johann 'Myrkraverk' Oskarsson <johann@myrkraverk.invalid> writes:
    On 25/07/2026 7:29 AM, Keith Thompson wrote:
    Johann 'Myrkraverk' Oskarsson <johann@myrkraverk.invalid> writes:
    [...]
    Sounds like you want to read /What every compiler writer should know
    about programmers/ by Anton Ertl, if you haven't already. Link at

    https://c9x.me/compile/bib/

    and more for everyone who wants to write his own C compiler.
    I haven't read it yet, but I intend to (even though I'm not planning
    to
    write my own C compiler).

    What some people have done, including me, is to simply lose faith in
    the C ISO committee, and started to write our own C compilers. This
    will make Mr. Singapore from the noise earlier very happy.

    Now, I decide how to react to this /Undefined behaviour/ means, and I
    disagree with the people behind the "big three" C compilers.
    Do you mean that your own compiler will not conform to the C
    standard, or merely that it will conform in different ways than the
    "big three"?

    I'm going to leave my disagreement with the "big three" as a surprise
    until release.

    That's nice.

    If you're not going to answer my question, why bother posting a followup
    at all?

    If you think a compiler should not generate code based on the
    assumption of defined behavior (for example, that it should generate
    code for a statement even if that statement can be executed only if
    undefined behavior has already occurred), that's fine, and nothing
    in the C standard prevents it. There are plenty of things the C
    standard permits compilers but does not require compilers to do.

    Indeed. If I remember correctly, loading the source code is
    /implementation defined/, so the compiler can perfectly well
    read NNTP instead of files, and strictly parse only code posted
    to comp.lang.c.

    That's completely irrelevant to what I wrote.

    I wonder if Mr. Singapore thought of that angle?

    Who?

    --
    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.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Keith Thompson@3:633/10 to All on Saturday, July 25, 2026 15:58:34
    Lawrence D?Oliveiro <ldo@nz.invalid> writes:
    On Sat, 25 Jul 2026 15:30:09 +0800, Johann 'Myrkraverk' Oskarsson wrote:
    On 25/07/2026 3:24 PM, Lawrence D?Oliveiro wrote:
    On Sat, 25 Jul 2026 15:20:21 +0800, Johann 'Myrkraverk' Oskarsson
    wrote:
    The code is perfectly reasonable.

    If the code were ?perfectly reasonable?, then there would be a
    compiler bug. There isn?t.

    That's a simple matter of debate.

    Back in the early days of computing, we had a principle called ?GIGO?
    -- ?Garbage In, Garbage Out?. That meant that, if you fed crap into
    the computer, don?t expect to get meaningful results out.

    Do they still teach that in ?IT Studies?, or ?Digital Technologies?,
    or whatever passes for CompSci courses these days?

    The code has a fairly subtle bug (one that gcc 13.3.0 diagnoses with
    "gcc -O1" or higher).

    It seems clear that Johann simply missed the bug. Telling us that

    If the code were ?perfectly reasonable?, then there would be a
    compiler bug. There isn?t.

    is not helpful. Pointing out the actual bug (which others have
    done by now) would have been helpful.

    --
    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.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Johann 'Myrkraverk' Oskarsson@3:633/10 to All on Monday, July 27, 2026 00:35:54
    On 25/07/2026 4:37 PM, David Brown wrote:
    On 25/07/2026 09:30, Johann 'Myrkraverk' Oskarsson wrote:
    On 25/07/2026 3:24 PM, Lawrence D?Oliveiro wrote:
    On Sat, 25 Jul 2026 15:20:21 +0800, Johann 'Myrkraverk' Oskarsson
    wrote:

    The code is perfectly reasonable.

    If the code were ?perfectly reasonable?, then there would be a
    compiler bug. There isn?t.

    That's a simple matter of debate.ÿ I make the point it's a compiler
    bug.ÿ You don't, so you must be a member of the "big three" developer
    teams.ÿ Therefore all your opinions on my way of compiling C are
    completely invalid.

    It is not a matter of debate - the code is buggy.ÿ But it's not entirely obvious that it is buggy - the folks who wrote the code were pretty good
    and experienced C programmers, and /they/ didn't notice the bug.
    Lawrence is not (AFAIK) a developer for the "big three" compiler groups,
    but perhaps he works in support for a big IT company - his answer was technically entirely correct, while being entirely unhelpful.

    Don't worry about Lawrence. I just told him to buy CorelDRAW in comp. unix.shell. He's being very stubborn about being unhelpful. I'm
    enjoying teasing him about it. He'll learn eventually that I don't
    care at all about his opinions about anything.


    If you look carefully at the code again, consider the final iteration -
    when k is 15 (the test "k < 16" still passes) there is the expression
    "dd = d[++k]".ÿ "k" is incremented to 16, and then we read "d[16]" to
    assign to "dd".ÿ But "d[16]" does not exist - the array "d" runs from
    "d[0]" to "d[15]".ÿ Attempting to read "d[16]" is an out-of-bounds
    access, and that's UB.ÿ It's a bug.

    Right, I did not notice that; thank you. I'll add it as a test case
    for my compiler. I'm still working on preliminaries, so it might take
    a year or two, or a decade before I'll show how I treat this code.


    Happy compiling!
    --
    Johann | email: invalid -> com | http://www.myrkraverk.com/blog/
    I'm not from the Internet, I just work there. | via Easynews.com

    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Johann 'Myrkraverk' Oskarsson@3:633/10 to All on Monday, July 27, 2026 00:38:37
    On 26/07/2026 6:33 AM, Keith Thompson wrote:
    Johann 'Myrkraverk' Oskarsson <johann@myrkraverk.invalid> writes:
    On 25/07/2026 7:29 AM, Keith Thompson wrote:
    Johann 'Myrkraverk' Oskarsson <johann@myrkraverk.invalid> writes:
    [...]
    Sounds like you want to read /What every compiler writer should know
    about programmers/ by Anton Ertl, if you haven't already. Link at

    https://c9x.me/compile/bib/

    and more for everyone who wants to write his own C compiler.
    I haven't read it yet, but I intend to (even though I'm not planning
    to
    write my own C compiler).

    What some people have done, including me, is to simply lose faith in
    the C ISO committee, and started to write our own C compilers. This
    will make Mr. Singapore from the noise earlier very happy.

    Now, I decide how to react to this /Undefined behaviour/ means, and I
    disagree with the people behind the "big three" C compilers.
    Do you mean that your own compiler will not conform to the C
    standard, or merely that it will conform in different ways than the
    "big three"?

    I'm going to leave my disagreement with the "big three" as a surprise
    until release.

    That's nice.

    If you're not going to answer my question, why bother posting a followup
    at all?

    If you think a compiler should not generate code based on the
    assumption of defined behavior (for example, that it should generate
    code for a statement even if that statement can be executed only if
    undefined behavior has already occurred), that's fine, and nothing
    in the C standard prevents it. There are plenty of things the C
    standard permits compilers but does not require compilers to do.

    Indeed. If I remember correctly, loading the source code is
    /implementation defined/, so the compiler can perfectly well
    read NNTP instead of files, and strictly parse only code posted
    to comp.lang.c.

    That's completely irrelevant to what I wrote.

    Right, you said something about defined, then undefined behaviour. I
    chose not to rise to the bait. If you word your question differently
    enough not to require either of those terms, I'll reply. Until then,
    happy C coding.


    I wonder if Mr. Singapore thought of that angle?

    Who?

    Message-ID: <113lv5q$3goh8$1@paganini.bofh.team>

    --
    Johann | email: invalid -> com | http://www.myrkraverk.com/blog/
    I'm not from the Internet, I just work there. | via Easynews.com

    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Cóilín Nioclásín Glostéir@3:633/10 to All on Sunday, July 26, 2026 17:03:16
    More resources for creating a compiler -
    news:comp.compilers

    (S. HTTP://Gloucester.Insomnia247.NL/ fuer Kontaktdaten!)

    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From BGB@3:633/10 to All on Sunday, July 26, 2026 12:39:37
    On 7/26/2026 11:35 AM, Johann 'Myrkraverk' Oskarsson wrote:
    On 25/07/2026 4:37 PM, David Brown wrote:
    On 25/07/2026 09:30, Johann 'Myrkraverk' Oskarsson wrote:
    On 25/07/2026 3:24 PM, Lawrence D?Oliveiro wrote:
    On Sat, 25 Jul 2026 15:20:21 +0800, Johann 'Myrkraverk' Oskarsson
    wrote:

    The code is perfectly reasonable.

    If the code were ?perfectly reasonable?, then there would be a
    compiler bug. There isn?t.

    That's a simple matter of debate.ÿ I make the point it's a compiler
    bug.ÿ You don't, so you must be a member of the "big three" developer
    teams.ÿ Therefore all your opinions on my way of compiling C are
    completely invalid.

    It is not a matter of debate - the code is buggy.ÿ But it's not
    entirely obvious that it is buggy - the folks who wrote the code were
    pretty good and experienced C programmers, and /they/ didn't notice
    the bug. Lawrence is not (AFAIK) a developer for the "big three"
    compiler groups, but perhaps he works in support for a big IT company
    - his answer was technically entirely correct, while being entirely
    unhelpful.

    Don't worry about Lawrence.ÿ I just told him to buy CorelDRAW in comp. unix.shell.ÿ He's being very stubborn about being unhelpful.ÿ I'm
    enjoying teasing him about it.ÿ He'll learn eventually that I don't
    care at all about his opinions about anything.


    If you look carefully at the code again, consider the final iteration
    - when k is 15 (the test "k < 16" still passes) there is the
    expression "dd = d[++k]".ÿ "k" is incremented to 16, and then we read
    "d[16]" to assign to "dd".ÿ But "d[16]" does not exist - the array "d"
    runs from "d[0]" to "d[15]".ÿ Attempting to read "d[16]" is an out-of-
    bounds access, and that's UB.ÿ It's a bug.

    Right, I did not notice that; thank you.ÿ I'll add it as a test case
    for my compiler.ÿ I'm still working on preliminaries, so it might take
    a year or two, or a decade before I'll show how I treat this code.



    Well, decided to see what my compiler would do...

    So, to recap:
    int d[16];
    int SATD (void) {
    int satd = 0, dd, k;
    for (dd=d[k=0]; k<16; dd=d[++k]) {
    satd += (dd < 0 ? -dd : dd);
    }
    return satd;
    }

    Gives:

    SATD:
    // tst_random1.c:2 int SATD (void) {
    ADD RD0, RQ0, RD13
    // tst_random1.c:4 for (dd=d[k=0]; k<16; dd=d[++k]) {
    ADD RD0, RQ0, RD12
    MOV d, RQ11
    MOV.L (RQ11, 0), RD10
    .L00800000:
    MOV 16, RD11
    BRGE.L RD11, RD12, .L00800002
    BRGE.L R0, RD10, .L00800003
    RSUBS.L RD10, 0, RQ11
    ADD RQ11, RQ0, RQ17
    BSR .L00800004, R0
    .L00800003:
    ADD RD10, RQ0, RQ17
    .L00800004:
    ADDS.L RD13, RQ17, RD13
    ADDS.L RD12, 1, RD12
    MOV d, RQ16
    MOV.L (RQ16, RD12), RD10
    BSR .L00800000, R0
    .L00800002:
    // tst_random1.c:6 }
    ADDS.L RD13, RQ0, RD10
    .L00C000F9:
    JSR R1, 0, R0

    Not exactly the way that is good to write ASM, just what my compiler
    outputs when dumping ASM...


    Decided not to really try to explain it, or what sort of ISA it is
    aiming for.

    Not the cleverest compiler around either, but gets the job done...



    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From BGB@3:633/10 to All on Sunday, July 26, 2026 15:26:31
    On 7/23/2026 3:31 AM, David Brown wrote:
    On 23/07/2026 01:08, BGB wrote:
    On 7/22/2026 3:41 AM, David Brown wrote:
    On 22/07/2026 07:33, Janis Papanagnou wrote:
    On 2026-07-22 02:26, Keith Thompson wrote:
    Janis Papanagnou <janis_papanagnou+ng@hotmail.com> writes:
    There was some recent longish thread with discussions addressing
    Undefined Behavior. I just stumbled across a paper from Russ Cox
    on that topic with a provoking title. [...]

    For those who don't follow the link, the full title of the paper is
    "C and C++ Prioritize Performance over Correctness".ÿ (Your
    paraphrase could be interpreted as advice, which I'm sure is not
    how you intended it.)

    It was actually a (deliberate) "marketing trick" to provoke interest
    by reducing the title to an (IMO) yet more aggravating formulation.

    Yes, it was not my intention to suggest performance over correctness.
    (Would anyone?)


    Unfortunately, the answer to your final question is yes - and that is
    one of the reasons UB can be a problem.ÿ If by "correctness" you mean
    "code does what it should", people do sometimes release code that
    they know has bugs, but they know that fixing them would slow down
    the code - perhaps it is still good enough for their purposes at the
    time.


    Or, the cliche: "It's not a bug, it's a feature..."


    Possibly.

    But it is always important to remember that different types of software require different levels of quality control - it can be acceptable to
    have known bugs ("undocumented features" or "undocumented limitations")
    in some software.ÿ If video games were coded with the same level of care used for jet engine controllers, they would never make it to market.


    Possibly...

    Video Games are more:
    Looks about right;
    Lacks obvious/glaring faults from the user POV;
    Timing is "fast and consistent enough that user doesn't get annoyed".

    Perfection is unrealistic to achieve in some spaces.



    More relevant to this discussion, if by "correct" code you mean code
    that has fully defined or appropriate implementation-defined
    behaviour (most code does not need to be fully portable) according to
    the C standards and/or platform or compiler extended semantics, then
    it is absolutely the case that programmers regularly prioritise
    performance over correctness by relying on UB.ÿ The result is code
    that works efficiently and as intended at the time, using tools
    tested by the developer.ÿ Then the UB bites the next person down the
    road that uses the same code in good faith, but with a different
    compiler or different options.



    Well, or for example, this pile of wonk:

    #define gfxedit_getu16(ptr)ÿÿÿÿÿÿ (*(vol_u16p)(ptr))
    #define gfxedit_getu32(ptr)ÿÿÿÿÿÿ (*(vol_u32p)(ptr))
    #define gfxedit_getu64(ptr)ÿÿÿÿÿÿ (*(vol_u64p)(ptr))
    #define gfxedit_setu16(ptr,val)ÿÿ (*(vol_u16p)(ptr)=(val))
    #define gfxedit_setu32(ptr,val)ÿÿ (*(vol_u32p)(ptr)=(val))
    #define gfxedit_setu64(ptr,val)ÿÿ (*(vol_u64p)(ptr)=(val))

    Insert other versions for different platform constraints.

    While details of volatile accesses are implementation-dependent, they
    can be a successful way of accessing the underlying memory for data.
    It's easy to get things wrong, however, if you are not consistent about
    it.ÿ And too much use can lead to inefficiencies as well.


    Yeah.

    Though mostly does achieve a primary goal:
    Works on compilers that are otherwise more aggressive about optimization; Doesn't severely penalize those which fail to optimize away things like "memcpy()" calls.



    But, say:
    ÿÿ u64 gfxedit_getu64(void *ptr)
    ÿÿÿÿ { u64 v; memcpy(&v, ptr, 8); return(v); }
    Being also possible, but a significant performance penalty on some
    targets.


    That's the challenge.ÿ memcpy() is often a good, safe and correct way to access data as though it were a different type, but not all compilers optimise it well in such cases.ÿ Making code that is correct on a range
    of implementations, and also efficient on a range of implementations, is
    the hard part.ÿ So sometimes people end up with code that is efficienct
    on the implementations they use, works on those, but is reliant on UB
    and fails on other implementations.


    Such is the never-ending issue.

    How clever a compiler is with "memcpy()" is highly variable, some able
    to fully optimize it away, and some just sorta falling back to a normal function call (potentially very slow).

    A lot of others are partway between.


    The situation is potentially much worse for big endian machines, but
    these are rare at this point.

    Say:
    ÿÿ u16 gfxedit_getu16(void *ptr)
    ÿÿÿÿ { u16 v; v=((byte *)ptr)[0]|
    ÿÿÿÿÿÿÿÿ (((u16)((byte *)ptr)[1]))<<8); return(v); }
    ÿÿ u32 gfxedit_getu32(void *ptr)
    ÿÿÿÿ { u32 v; v=gfxedit_getu16(ptr)|
    ÿÿÿÿÿÿÿÿ (((u32)gfxedit_getu16(((byte *)ptr)+2))<<16); return(v); }
    ÿÿ u64 gfxedit_getu64(void *ptr)
    ÿÿÿÿ { u64 v; v=gfxedit_getu32(ptr)|
    ÿÿÿÿÿÿÿÿ (((u64)gfxedit_getu32(((byte *)ptr)+4))<<32); return(v); }

    Though, for BE machines, one wouldn't want to overuse these.


    On good modern compilers, that sort of thing is usually fine, and you
    would expect it to reduce to a single "load 64-bit with endian swap" instruction or minimal similar sequence.


    Depends some on machine:
    Support or non-support for misaligned access;
    Whether or not the ISA supports endian swap instructions.


    For example, consider an ISA like SH-2, one would likely need to do
    something like (probable best case to load a 32-bit LE value):
    gfxedit_getu32:
    MOVU.B @R4+, R7
    MOVU.B @R4+, R6
    MOVU.B @R4+, R5
    MOVU.B @R4+, R0
    SHLL8 R0
    OR R5, R0
    SHLL8 R0
    OR R6, R0
    SHLL8 R0
    OR R7, R0
    RTS

    Well, in part because the ISA is fairly limited. Aligned-only big-endian
    ISA, no byte-swap instructions, not even a general purpose integer shift.

    Say:
    int a, b, c;
    c=a+b; //2 ops
    c=a-b; //2 ops
    c=a&b; //2 ops
    c=a|b; //2 ops
    c=a^b; //2 ops
    c=a*b; //runtime call (shift-add loop)
    c=a/b; //runtime call (shift-sub loop)
    c=a<<b; //runtime call (computed branch and slide)
    c=a>>b; //runtime call (computed branch and slide)


    As I see it, the main problem of UB and compilers is /not/ as commonly believed, overly-enthusiastic compilers that optimise on assumptions
    about UB.ÿ The problem is poorer or older compilers that don't optimise
    the correct code well, forcing people who need efficient results to
    spend a lot of effort finding alternative solutions that might be
    reliant on UB.ÿ It also doesn't help that some developers don't enable optimisation or otherwise fail to use their tools well.


    Yeah.

    I am assuming here compilers that will optimize when optimizations are enabled, but the scope of said optimizations is limited.




    <snip code>


    But, on the relevant implementation, trying to do the latter being
    around an order of magnitude slower...

    It sounds like it might make more sense to write improved memcpy, memove
    and memset implementations for the implementation in question!


    The main offender here was (ironically) MSVC, as seemingly their implementations are built under the assumptions of small numbers of big
    copies and not large numbers of small moves or similar. Not really dug
    into MSVCRT's implementation, but (from cases where the debugger has
    landed on it), their priority is usually to try to get things as quickly
    as possible into SSE-based copy loops, which admittedly does make the
    most sense if one assumes the copy is large by default.


    This is part of why the "memlzcpyf" style copies are wonky:
    Determining the correct logic for the copy is often a bigger factor than
    the copy operation itself (huge numbers of often 4-14 byte copies).

    Likewise, trying to use something like SSE/AVX on x86-64 makes little
    sense here, given the copies are frequently smaller than the size of the
    SIMD vector.

    But, say, moving 64-bit chunks around costs less than moving individual
    bytes.




    So, it can all turn into a big mess of cruft and ifdef's.


    Maximum efficiency is /always/ going to be implementation and target dependent.ÿ It is fine, for low-level functions where the speed matters,
    to have a variety of implementations tuned to different targets, with fall-back to something that is correct portable code.ÿ The use of
    #ifdef's (or separate files) means you can also happily use compiler- specific extensions, pragmas, attributes, builtins, etc., or even inline assembly.


    Yes.

    Also it is an incentive for why (on my targets) "_memlzcpy()" and "_memlzcpyf()" exist as library extensions.

    This wonk can more leverage the ISA-specific quirks.

    Though, if the code needs to run on other targets (like Windows, Linux,
    etc), it can't use these and needs to provide its own.


    A lot of wonk in things like my OpenGL implementation as well, where
    there is a whole lot of non-portable edge cases and ASM alongside
    generic ASM fallbacks.



    The killer point, however, is when you have received some code that
    you can justifiably assume is correct and well tested, but contains
    reliance on how certain types of UB happen to be implemented.ÿ It is
    not really any different than code that makes other undocumented
    assumptions, such as implementation-dependent details, reliance on
    certain files or OS features, timing requirements, etc.ÿ And
    compiling with "-fwrapv -fno- strict-aliasing" (if you are using a
    compiler that supports such features) will mitigate the most common
    cases of reliance on UB.


    In some cases, it makes sense to assume that some things formally
    considered UB would be better considered as Implementation Defined.

    No, never.ÿ You might want that to be the case (and I am sure we all
    have our personal favourite UB's from the C standards that we would
    rather have as IB, or unspecified, or the up-coming "erroneous
    behaviour" - though we would not agree entirely on which UB's).ÿ But you can't just say "I think left-shift of negative numbers should be IB" and assume it to be the case!


    Not in writing portable code, but you can if writing your own compiler...



    In practice, it likely wouldn't make that big of a difference; since
    many cases the compiler still treats these cases is implementation
    defined rather than true UB.


    Particular implementations can, of course, document the way a particular type of UB is implemented - then when you are using that compiler, you
    can rely on that behaviour.

    Relying on undocumented treatment of UB is where you get in trouble. The code might work as you want with this version of the compiler, and fail
    with the next version.


    I guess I was ambiguous, was assuming cases where a person controls both
    ends of the software stack.


    I can make the UB be IB for my compiler, even if I can't change what GCC
    or MSVC does.

    Though, MSVC will not usually change things that will break large
    amounts of legacy code; so most "legacy-established" UB is likely
    "safe-ish" on those targets.

    It is usually GCC and Clang that like to break stuff in this way.



    But, I had seen non-zero code which had relied on the assumption of
    overflows carrying over consistently between global arrays.


    Relying on the details of how generated assembly is organised is a high- risk game.ÿ I have had occasions when I needed to do so, but it's
    tightly tied to exact tool versions and flags, and needs checking for
    every run of the build.


    Where I encountered it was in some code written originally for the
    Watcom C compiler on DOS.

    I ended up rewriting this part, as this behavior fell outside the scope
    of what I considered reasonable, even for legacy...


    The other challenge was partly also trying to eliminate all of the out-of-bounds without changing program behavior in ways that would cause
    the previous demo-recordings to desync.

    Well, because back in those days the popular way to do demo recordings
    was to record user keypresses, then run the game back later but replay
    it as-if the user were pressing the same keys at the same time, which is
    kind of notorious, particularly in the face of engines which don't have
    a particularly consistent timing-tick system (and are instead driving everything off of the current "wall-clock" time, originally relying on
    the timer IRQ to increment a global variable holding the current time, etc...).

    Well, and then that engine also did the annoying thing in that rather
    than use the standard "Mode 0x13" memory layout (320x200 linear buffer
    in raster order), whole engine was based on setting up a 320x200 planar
    mode and assuming plane-manipulation for drawing; so porting it needed
    to make wrappers to mimic the original VGA behavior here.

    Ended up staying with 256-color in this engine, partly as the engine
    also relied on some effects dynamically changing or updating the color
    palette (more so than for the "screen flashes" Doom and similar had used).




    As for the paper itself, I have a few points.

    It follows the time-worn path of complaining that "modern" compilers
    have distorted the "original" idea of UB and abuse it for
    optimisation in ways that are dangerous to the uninitiated.ÿ First,
    optimisation using UB is not new - I first used a compiler that
    assumed integer overflow does not wrap over thirty years ago.
    Second, compilers already default to "-O0" - if you know so little
    about your tools that you can't enable warnings, you won't have
    optimisation either.ÿ Third, improvements in compiler optimisation
    have gone hand-in-hand with improvements in compiler static error
    checking.ÿ Using "-Wall" along with optimisation will eliminate the
    solid majority of the risks and worries of the author.ÿ Fourth, the
    development of "sanitizers" not only allows far more UB to be found
    during testing, but it allows far more bugs to be found than would be
    possible if the mistakes were / not/ UB - "-fsanitize=signed-integer-
    overflow" catches overflow bugs precisely because it is UB in C,
    whereas if it were defines as wrapping then the mistake in the
    programmer's code would be correct as far as the language is concerned.


    For keeping old code working, it makes sense to assume wrapping.

    To be clear - you mean that if you don't know the old code, it makes
    sense to assume the code might rely on wrapping behaviour, and use appropriate compiler flags (or add appropriate pragmas) ?


    Or, if writing a C compiler, to assume that wrap on overflow is the most sensible default here.

    And, not "UB nasal demons", which may make sense to assume for a
    programmer writing portable code, but is not a good stance for a
    compiler writer.



    But, maybe it could make sense to have a semantic distinction for
    cases where wrapping is assumed vs where it is likely unintentional.

    My proposal, if any, would be, say:
    ÿÿ If "signed" is used explicitly, it means wrap-on-overflow is expected.

    That makes no sense at all.


    I am not sure I see why it would be a problem as a working assumption.
    One can just sorta add a semantic distinction between 'int' and 'signed
    int' in a similar way to the whole 'char' vs 'signed char' thing...

    Granted, many compilers (mine included) don't currently have such a distinction, nor does my type-tagging scheme, but also in this case it
    assumes wrap-on-overflow as universal, so there is no loss.

    It only really becomes necessary if one wants both existing code to keep working, and to have the option for separate "trap on overflow" handling
    or similar.



    So, say:
    ÿÿ int i;
    ÿÿ signed int v;
    If 'i' overflows, one can question whether or not it was intended; but
    if 'v' overflows, assume it is intentional (as with 'unsigned').

    You can't just invent a personal convention like that - at least, not if
    you ever use other people's code or compilers written by someone else.
    You can't change the meaning of existing code.

    If C were ever to have support for something akin to that, I would
    expect to see new names involved.ÿ It would be "_Wrapping int v;", or
    types "int_wrapping32_t" in <stdint.h>.

    More likely, functions like "wrapping_add" could be added to <stdckdint.h>.


    Existing code will not care, as either way it is basically the same as
    what happens already in practice.

    It would ironically make more sense in practice as a way to allow making
    "int" *not* wrapping, and to be able to have int overflow as an actually useful diagnostic, rather than something that just happens randomly all
    over the place.

    Then one could "sanitize" it by sticking 'signed' on the cases where
    overflows are happening and are intentional.


    Creating new keywords here is possible, but likely to be more hassle
    than it is worth in terms of compatibility or adoption.

    Like, one could be like:
    _Wrapinng, _NoWrapping, ...
    But, a similar argument could be made for, say:
    _Aligned, _Unaligned
    Or:
    _LittleEndian, _BigEndian


    These being cases that could be useful to be able to specify, but no one
    is likely to do so.


    At least "signed" is more likely to come for cheap/free:
    The "new" semantics match existing behavior on typical implementations;
    A fair amount of code already follows this pattern implicitly (even if
    there is no actual reason for why it would be so).

    In this case, it is mapping semantics to existing coding patterns,
    rather than merely making something up ex-nihilo.

    Like, not like I am just making up how all this stuff works as I was
    going along (and, if I were just making crap up, would generally make
    porting efforts a bit more of a pain).



    This being in part because an explicit "signed" keyword is uncommon to
    be used with normal counting/index type variables.


    Some people use it - and they do not do so with the expectation of
    different semantics than when it is omitted.ÿ Some people write "signed" rather than "int" for declarations.


    Possibly.

    But, as noted, it would make more sense as a code diagnostic feature.

    Otherwise, one would expect such code to break with "-fwrapv" or similar
    in GCC, if they depended on some behavior other than wrapping.


    In general, I have usually seen bare 'int' for things like counters and similar, or the things that are probably not expected to overflow.

    But, then explicit 'signed' are more often used for things like
    fixed-point coordinates and similar, which typically do overflow.



    Don't dereference null pointers.ÿ It's a bad idea.


    Dereferencing NULL is one of those cases where the most sensible
    option is to treat it as a fault...

    It's a bug.


    NULL is one of the few cases where it is unambiguous...


    Well, except maybe on Win95 where like the low 1MB of addresses held a
    copy of the MS-DOS memory map.

    Well, and (from very old memories) you could craft a FAR jump to get
    into supervisor mode (Ring 0) while transferring control back to ones'
    own code, to get ahold of the GDT and LDT and peek in on Win16 space and similar.

    These holes didn't exist on NT though.



    Most sensible option is to assume that trying to dereference NULL is
    unintentional (given the main purpose of NULL being as a "this object
    doesn't exist" pointer).


    Yes.

    There is the exception that on some hardware, 0 is a valid memory
    address and you might want to access it.ÿ On microcontrollers, it is not uncommon for code flash to start there, so something like an integrity
    check of the program would have to read that address.ÿ Then you are dereferencing a pointer that happens to have value 0, rather than a null pointer.



    Yeah, the Boot ROM in my ISA project is also like this:
    00000000..00007FFF: ROM space
    00008000..0000BFFF: Optional, Extended ROM space
    0000C000..0000DFFF: Boot-Time SRAM
    00010000..0001FFFF: Zeroes (This address range always reads as 0)
    00020000..00FFFFFF: Mostly unused, special repeating-pattern pages
    01000000..7FFFFFFF: DRAM goes here

    Though, not the whole DRAM range contains actual valid RAM, so it is
    more like, say:
    01000000..03FFFFFF: First DRAM Copy
    04000000..07FFFFFF: Second DRAM Copy
    ...
    The DRAM just repeating up to the 2GB mark or similar.

    The Boot ROM uses this for its RAM counter, where it detects the point
    where RAM wraps around again, and uses this as its size limit.


    The Boot ROM starts at 0, containing a branch to _start or similar.
    As a hack the CPU also uses the encoding of this instruction to know
    what ISA mode to boot into for the ROM (XG1, XG3, or RV64GC).

    I may change the memory map in the future, possibly:
    00000000..0000FFFF: ROM space
    00010000..0007FFFF: Optional, Extended ROM space
    00080000..0008FFFF: Zeroes (This address range always reads as 0)
    00090000..000FFFFF: Unused, probably just more zeroes
    00100000..7FFFFFFF: DRAM goes here

    Though, this is likely to also coincide with dropping XG1 and making the
    CPU always boot in XG3 Mode. The Boot ROM is primarily a use-case
    favoring code density, so for a while RV64GC was the front-runner, but
    XG3 caught up (and has fewer drawbacks).


    Decided to omit going on too much more about the memory map or virtual
    memory subsystem.

    But, in effect, mostly using larger 48-bit VAS on hardware that could realistically have been a 32-bit machine. But, not like there are any
    FPGA boards with GB of RAM.

    Instead, it is more like MB of RAM, plus a pagefile backed to an SDcard.


    But:
    Assuming it doesn't happen and then removing the offending code path
    entirely is also bad IMO. However, turning the code-path into a break
    instruction or similar (following any other externally visible side
    effects) would still likely be acceptable IMO (or, at least, less bad
    in this case than just continuing down the other path as if nothing
    had ever happened).


    You are thinking of code like :

    ÿÿÿÿint x = *p;
    ÿÿÿÿif (!p) return someone_made_a_mistake;
    ÿÿÿÿ...

    Some compilers will skip the check here, because they assume the
    hardware / OS will have caught the attempt to access address 0.ÿ That is fair enough - /if/ the target is guaranteed to have such a hardware
    catch.ÿ If it is not guaranteed, then the check can't be skipped.ÿ Even better, of course, would be a compiler warning that the programmer is
    doing something silly - whether or not the check is skipped.

    I agree that sometimes a "break" (or "trap", or similar) instruction can
    be a good thing for compilers to generate when they are clearly
    executing undefined behaviour.ÿ But I am not convinced it is always a
    good idea (I would not want it for this case), and it would be hard to specify and guarantee the circumstances without it ruining the
    possibility of optimisation from assuming code has defined behaviour.

    Compilers with sanitizers do support such traps - gcc with the options "-fsanitize-trap=all -fsanitize=null" will trap if the pointer "p" is
    null in this code.



    Possibly...



    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From bart@3:633/10 to All on Sunday, July 26, 2026 22:27:58
    On 26/07/2026 18:39, BGB wrote:
    On 7/26/2026 11:35 AM, Johann 'Myrkraverk' Oskarsson wrote:
    On 25/07/2026 4:37 PM, David Brown wrote:
    On 25/07/2026 09:30, Johann 'Myrkraverk' Oskarsson wrote:
    On 25/07/2026 3:24 PM, Lawrence D?Oliveiro wrote:
    On Sat, 25 Jul 2026 15:20:21 +0800, Johann 'Myrkraverk' Oskarsson
    wrote:

    The code is perfectly reasonable.

    If the code were ?perfectly reasonable?, then there would be a
    compiler bug. There isn?t.

    That's a simple matter of debate.ÿ I make the point it's a compiler
    bug.ÿ You don't, so you must be a member of the "big three" developer
    teams.ÿ Therefore all your opinions on my way of compiling C are
    completely invalid.

    It is not a matter of debate - the code is buggy.ÿ But it's not
    entirely obvious that it is buggy - the folks who wrote the code were
    pretty good and experienced C programmers, and /they/ didn't notice
    the bug. Lawrence is not (AFAIK) a developer for the "big three"
    compiler groups, but perhaps he works in support for a big IT company
    - his answer was technically entirely correct, while being entirely
    unhelpful.

    Don't worry about Lawrence.ÿ I just told him to buy CorelDRAW in comp.
    unix.shell.ÿ He's being very stubborn about being unhelpful.ÿ I'm
    enjoying teasing him about it.ÿ He'll learn eventually that I don't
    care at all about his opinions about anything.


    If you look carefully at the code again, consider the final iteration
    - when k is 15 (the test "k < 16" still passes) there is the
    expression "dd = d[++k]".ÿ "k" is incremented to 16, and then we read
    "d[16]" to assign to "dd".ÿ But "d[16]" does not exist - the array
    "d" runs from "d[0]" to "d[15]".ÿ Attempting to read "d[16]" is an
    out-of- bounds access, and that's UB.ÿ It's a bug.

    Right, I did not notice that; thank you.ÿ I'll add it as a test case
    for my compiler.ÿ I'm still working on preliminaries, so it might take
    a year or two, or a decade before I'll show how I treat this code.



    Well, decided to see what my compiler would do...

    So, to recap:
    int d[16];
    int SATD (void) {
    ÿÿÿÿint satd = 0, dd, k;
    ÿÿÿÿfor (dd=d[k=0]; k<16; dd=d[++k]) {
    ÿÿÿÿÿÿÿ satd += (dd < 0 ? -dd : dd);
    ÿÿÿÿ}
    ÿÿÿÿreturn satd;
    }

    Gives:

    SATD:
    // tst_random1.c:2ÿÿ int SATD (void) {
    ÿ ADDÿÿÿÿÿÿÿÿÿ RD0, RQ0, RD13
    // tst_random1.c:4ÿÿ for (dd=d[k=0]; k<16; dd=d[++k]) {
    ÿ ADDÿÿÿÿÿÿÿÿÿ RD0, RQ0, RD12
    ÿ MOVÿÿÿÿÿÿÿÿÿ d, RQ11
    ÿ MOV.Lÿÿÿÿÿÿÿ (RQ11, 0), RD10
    .L00800000:
    ÿ MOVÿÿÿÿÿÿÿÿÿ 16, RD11
    ÿ BRGE.Lÿÿÿÿÿÿ RD11, RD12, .L00800002
    ÿ BRGE.Lÿÿÿÿÿÿ R0, RD10, .L00800003
    ÿ RSUBS.Lÿÿÿÿÿ RD10, 0, RQ11
    ÿ ADDÿÿÿÿÿÿÿÿÿ RQ11, RQ0, RQ17
    ÿ BSRÿÿÿÿÿÿÿÿÿ .L00800004, R0
    .L00800003:
    ÿ ADDÿÿÿÿÿÿÿÿÿ RD10, RQ0, RQ17
    .L00800004:
    ÿ ADDS.Lÿÿÿÿÿÿ RD13, RQ17, RD13
    ÿ ADDS.Lÿÿÿÿÿÿ RD12, 1, RD12
    ÿ MOVÿÿÿÿÿÿÿÿÿ d, RQ16
    ÿ MOV.Lÿÿÿÿÿÿÿ (RQ16, RD12), RD10
    ÿ BSRÿÿÿÿÿÿÿÿÿ .L00800000, R0
    .L00800002:
    // tst_random1.c:6ÿÿ }
    ÿ ADDS.Lÿÿÿÿÿÿ RD13, RQ0, RD10
    .L00C000F9:
    ÿ JSRÿÿÿÿÿÿÿÿÿ R1, 0, R0

    Not exactly the way that is good to write ASM, just what my compiler
    outputs when dumping ASM...


    Decided not to really try to explain it, or what sort of ISA it is
    aiming for.

    Not the cleverest compiler around either, but gets the job done...


    Your code is a lot shorter than mine: 18 executable instrs versus 31 for
    mine which is for x64. Although 6 are to save/restore non-vol regs, and
    2 for a stack-frame which doesn't appear to be needed.

    It does nothing at all about trying to detect possible out-of-bounds errors.

    gcc-O2 does it in 13, but -O3 is longer due to using SIMD and no looping.

    However I then tried it in my systems language (an equivalent program)
    and there it was only 22 instruction, shown below. More amenable
    language features can help! Here also there is no out-of-bounds error.

    SATD:: # (uses non-standard reg names: A/D = 32/64 bits)
    R.satd = A3
    R.dd = A4
    R.k = A5
    push D3
    push D4
    push D5
    sub Dsp, 16
    ;---------------
    xor R.satd, R.satd
    xor A0, A0
    mov R.k, A0
    movsx D1, A0
    lea D0, [d]
    mov R.dd, [D0 + D1*4]
    jmp L4
    L5:
    cmp R.dd, 0
    jge L6
    mov A0, R.dd
    neg A0
    jmp L7
    L6:
    mov A0, R.dd
    L7:
    add R.satd, A0
    inc R.k
    mov A0, R.k
    movsx D1, A0
    lea D0, [d]
    mov R.dd, [D0 + D1*4]
    L4:
    cmp R.k, 16
    jl L5
    mov A0, R.satd
    L1:
    ;---------------
    add Dsp, 16
    pop D5
    pop D4
    pop D3
    ret


    ---------------------------
    Version in my language (uses 64-bit ints and is 1-based): ---------------------------
    [16]int d

    func satd:int =
    int sum:=0

    for x in d do # iterates over values
    sum +:= abs(x)
    end

    sum
    end

    ---------------------------
    t.satd:
    R.sum = D3
    R.av_1 = D4
    R.x = D5
    push R.sum
    push R.av_1
    push R.x
    sub Dsp, 16 # this is again not needed! (Same backend) ;---------------
    xor R.sum, R.sum
    mov D0, 1
    mov R.av_1, D0
    L2:
    mov R.x, [R.av_1*8 + t.d-8]
    mov D0, R.x
    cmp D0, 0
    jge L5
    neg D0
    L5:
    add R.sum, D0
    inc R.av_1
    cmp R.av_1, 16
    jle L2
    mov D0, R.sum
    L1:
    ;---------------
    add Dsp, 16
    pop R.x
    pop R.av_1
    pop R.sum
    ret


    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From BGB@3:633/10 to All on Sunday, July 26, 2026 18:02:25
    On 7/26/2026 4:27 PM, bart wrote:
    On 26/07/2026 18:39, BGB wrote:
    On 7/26/2026 11:35 AM, Johann 'Myrkraverk' Oskarsson wrote:
    On 25/07/2026 4:37 PM, David Brown wrote:
    On 25/07/2026 09:30, Johann 'Myrkraverk' Oskarsson wrote:
    On 25/07/2026 3:24 PM, Lawrence D?Oliveiro wrote:
    On Sat, 25 Jul 2026 15:20:21 +0800, Johann 'Myrkraverk' Oskarsson
    wrote:

    The code is perfectly reasonable.

    If the code were ?perfectly reasonable?, then there would be a
    compiler bug. There isn?t.

    That's a simple matter of debate.ÿ I make the point it's a compiler
    bug.ÿ You don't, so you must be a member of the "big three" developer >>>>> teams.ÿ Therefore all your opinions on my way of compiling C are
    completely invalid.

    It is not a matter of debate - the code is buggy.ÿ But it's not
    entirely obvious that it is buggy - the folks who wrote the code
    were pretty good and experienced C programmers, and /they/ didn't
    notice the bug. Lawrence is not (AFAIK) a developer for the "big
    three" compiler groups, but perhaps he works in support for a big IT
    company - his answer was technically entirely correct, while being
    entirely unhelpful.

    Don't worry about Lawrence.ÿ I just told him to buy CorelDRAW in comp.
    unix.shell.ÿ He's being very stubborn about being unhelpful.ÿ I'm
    enjoying teasing him about it.ÿ He'll learn eventually that I don't
    care at all about his opinions about anything.


    If you look carefully at the code again, consider the final
    iteration - when k is 15 (the test "k < 16" still passes) there is
    the expression "dd = d[++k]".ÿ "k" is incremented to 16, and then we
    read "d[16]" to assign to "dd".ÿ But "d[16]" does not exist - the
    array "d" runs from "d[0]" to "d[15]".ÿ Attempting to read "d[16]"
    is an out-of- bounds access, and that's UB.ÿ It's a bug.

    Right, I did not notice that; thank you.ÿ I'll add it as a test case
    for my compiler.ÿ I'm still working on preliminaries, so it might take
    a year or two, or a decade before I'll show how I treat this code.



    Well, decided to see what my compiler would do...

    So, to recap:
    int d[16];
    int SATD (void) {
    ÿÿÿÿÿint satd = 0, dd, k;
    ÿÿÿÿÿfor (dd=d[k=0]; k<16; dd=d[++k]) {
    ÿÿÿÿÿÿÿÿ satd += (dd < 0 ? -dd : dd);
    ÿÿÿÿÿ}
    ÿÿÿÿÿreturn satd;
    }

    Gives:

    SATD:
    // tst_random1.c:2ÿÿ int SATD (void) {
    ÿÿ ADDÿÿÿÿÿÿÿÿÿ RD0, RQ0, RD13
    // tst_random1.c:4ÿÿ for (dd=d[k=0]; k<16; dd=d[++k]) {
    ÿÿ ADDÿÿÿÿÿÿÿÿÿ RD0, RQ0, RD12
    ÿÿ MOVÿÿÿÿÿÿÿÿÿ d, RQ11
    ÿÿ MOV.Lÿÿÿÿÿÿÿ (RQ11, 0), RD10
    .L00800000:
    ÿÿ MOVÿÿÿÿÿÿÿÿÿ 16, RD11
    ÿÿ BRGE.Lÿÿÿÿÿÿ RD11, RD12, .L00800002
    ÿÿ BRGE.Lÿÿÿÿÿÿ R0, RD10, .L00800003
    ÿÿ RSUBS.Lÿÿÿÿÿ RD10, 0, RQ11
    ÿÿ ADDÿÿÿÿÿÿÿÿÿ RQ11, RQ0, RQ17
    ÿÿ BSRÿÿÿÿÿÿÿÿÿ .L00800004, R0
    .L00800003:
    ÿÿ ADDÿÿÿÿÿÿÿÿÿ RD10, RQ0, RQ17
    .L00800004:
    ÿÿ ADDS.Lÿÿÿÿÿÿ RD13, RQ17, RD13
    ÿÿ ADDS.Lÿÿÿÿÿÿ RD12, 1, RD12
    ÿÿ MOVÿÿÿÿÿÿÿÿÿ d, RQ16
    ÿÿ MOV.Lÿÿÿÿÿÿÿ (RQ16, RD12), RD10
    ÿÿ BSRÿÿÿÿÿÿÿÿÿ .L00800000, R0
    .L00800002:
    // tst_random1.c:6ÿÿ }
    ÿÿ ADDS.Lÿÿÿÿÿÿ RD13, RQ0, RD10
    .L00C000F9:
    ÿÿ JSRÿÿÿÿÿÿÿÿÿ R1, 0, R0

    Not exactly the way that is good to write ASM, just what my compiler
    outputs when dumping ASM...


    Decided not to really try to explain it, or what sort of ISA it is
    aiming for.

    Not the cleverest compiler around either, but gets the job done...


    Your code is a lot shorter than mine: 18 executable instrs versus 31 for mine which is for x64. Although 6 are to save/restore non-vol regs, and
    2 for a stack-frame which doesn't appear to be needed.

    It does nothing at all about trying to detect possible out-of-bounds
    errors.


    My compiler also doesn't notice the out-of-bounds.


    But, yeah, the code in question is for XG3, but the output for RV64G is
    kinda similar in this case. Neither is standard RV ASM syntax.


    In this case, compiler skips prolog/epilog because the function is a
    leaf function and everything fits into the available scratch registers.


    Decided to leave out a longer description, but in this case XG3 uses a modified variant/hybrid of the LP64 and LP64D ABI rules.

    The ABI is very different from that used by XG1 and XG2 (and is a major
    reason none of the XG1/XG2 ASM code works with XG3).


    Can't give an X86-64 example because BGBCC doesn't target these. Hasn't usually been a good reason to target a platform that is already well
    served by the existing compilers.


    Arguably, there are more efficient ways it could have handled the ?:,
    but making ?: more efficient is a long-standing TODO.

    Like, say, in theory it could have done:
    SUBS.L R0, R10, R11 //SUBW X11, X0, X10
    MAX R10, R11, R17
    Or:
    SUBS.L R0, R10, R11
    SLT R10, 0, R0
    CSELT R11, R10, R17
    Or:
    ...

    But, alas...


    So, say, hand-optimizing it some:
    SATD:
    ADD RD0, RQ0, RD13 //LI X13, 0
    ADD RD0, RQ0, RD12 //LI X12, 0
    MOV d, RQ16 //LA X16, d / ~ AUIPC+ADDI
    MOV.L (RQ16, 0), RD10 //LW X10, 0(X16)
    .L00800000:
    MOV 16, RD11 //ADDI X11, X0, 16
    BRGE.L RD11, RD12, .L00800002 //BGE X12, X11, .L00800002
    SUBS.L R0, R10, R11 //SUBW X11, X0, X10
    MAX R10, R11, R17 //-
    ADDS.L RD13, RQ17, RD13 //ADDW X13, X17, X17
    ADDS.L RD12, 1, RD12 //ADDIW X12, X12, 1
    MOV.L (RQ16, RD12), RD10 //-
    BSR .L00800000, R0 //J .L00800000
    .L00800002:
    ADDS.L RD13, RQ0, RD10 //ADDW X10, X13, X0
    JSR R1, 0, R0 //RTS
    More drastic reorg could save a few more instrs, but, ...


    And, would be longer with plain RV64G as RV64G lacks indexed addressing, so:
    MOV.L (RQ16, RD12), RD10
    Would need to become, say:
    SHLD.L R12, 2, R5
    ADD R5, R16, R5
    MOV.L (R5), RD10
    Or, in RV ASM notation:
    SLLW X5, X12, 2
    ADD X5, X5, X16
    LW X10, 0(X5)
    Well, in case anyone needed a translation key...


    gcc-O2 does it in 13, but -O3 is longer due to using SIMD and no looping.

    However I then tried it in my systems language (an equivalent program)
    and there it was only 22 instruction, shown below. More amenable
    language features can help! Here also there is no out-of-bounds error.

    SATD::ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ # (uses non-standard reg names: A/D = 32/64 bits)
    ÿÿÿ R.satd = A3
    ÿÿÿ R.dd = A4
    ÿÿÿ R.k = A5
    ÿÿÿ pushÿÿÿÿÿ D3
    ÿÿÿ pushÿÿÿÿÿ D4
    ÿÿÿ pushÿÿÿÿÿ D5
    ÿÿÿ subÿÿÿÿÿÿ Dsp,ÿÿÿ 16
    ;---------------
    ÿÿÿ xorÿÿÿÿÿÿ R.satd,ÿÿÿ R.satd
    ÿÿÿ xorÿÿÿÿÿÿ A0,ÿÿÿ A0
    ÿÿÿ movÿÿÿÿÿÿ R.k,ÿÿÿ A0
    ÿÿÿ movsxÿÿÿÿ D1,ÿÿÿ A0
    ÿÿÿ leaÿÿÿÿÿÿ D0,ÿÿÿ [d]
    ÿÿÿ movÿÿÿÿÿÿ R.dd,ÿÿÿ [D0 + D1*4]
    ÿÿÿ jmpÿÿÿÿÿÿ L4
    L5:
    ÿÿÿ cmpÿÿÿÿÿÿ R.dd,ÿÿÿ 0
    ÿÿÿ jgeÿÿÿÿÿÿ L6
    ÿÿÿ movÿÿÿÿÿÿ A0,ÿÿÿ R.dd
    ÿÿÿ negÿÿÿÿÿÿ A0
    ÿÿÿ jmpÿÿÿÿÿÿ L7
    L6:
    ÿÿÿ movÿÿÿÿÿÿ A0,ÿÿÿ R.dd
    L7:
    ÿÿÿ addÿÿÿÿÿÿ R.satd,ÿÿÿ A0
    ÿÿÿ incÿÿÿÿÿÿ R.k
    ÿÿÿ movÿÿÿÿÿÿ A0,ÿÿÿ R.k
    ÿÿÿ movsxÿÿÿÿ D1,ÿÿÿ A0
    ÿÿÿ leaÿÿÿÿÿÿ D0,ÿÿÿ [d]
    ÿÿÿ movÿÿÿÿÿÿ R.dd,ÿÿÿ [D0 + D1*4]
    L4:
    ÿÿÿ cmpÿÿÿÿÿÿ R.k,ÿÿÿ 16
    ÿÿÿ jlÿÿÿÿÿÿÿ L5
    ÿÿÿ movÿÿÿÿÿÿ A0,ÿÿÿ R.satd
    L1:
    ;---------------
    ÿÿÿ addÿÿÿÿÿÿ Dsp,ÿÿÿ 16
    ÿÿÿ popÿÿÿÿÿÿ D5
    ÿÿÿ popÿÿÿÿÿÿ D4
    ÿÿÿ popÿÿÿÿÿÿ D3
    ÿÿÿ ret


    ---------------------------
    Version in my language (uses 64-bit ints and is 1-based): ---------------------------
    [16]int d

    func satd:int =
    ÿÿÿ int sum:=0

    ÿÿÿ for x in d doÿÿÿÿÿÿÿÿÿ # iterates over values
    ÿÿÿÿÿÿÿ sum +:= abs(x)
    ÿÿÿ end

    ÿÿÿ sum
    end

    ---------------------------
    t.satd:
    ÿÿÿ R.sum = D3
    ÿÿÿ R.av_1 = D4
    ÿÿÿ R.x = D5
    ÿÿÿ pushÿÿÿÿÿ R.sum
    ÿÿÿ pushÿÿÿÿÿ R.av_1
    ÿÿÿ pushÿÿÿÿÿ R.x
    ÿÿÿ subÿÿÿÿÿÿ Dsp,ÿÿÿ 16ÿ # this is again not needed! (Same backend) ;---------------
    ÿÿÿ xorÿÿÿÿÿÿ R.sum,ÿÿÿ R.sum
    ÿÿÿ movÿÿÿÿÿÿ D0,ÿÿÿ 1
    ÿÿÿ movÿÿÿÿÿÿ R.av_1,ÿÿÿ D0
    L2:
    ÿÿÿ movÿÿÿÿÿÿ R.x,ÿÿÿ [R.av_1*8 + t.d-8]
    ÿÿÿ movÿÿÿÿÿÿ D0,ÿÿÿ R.x
    ÿÿÿ cmpÿÿÿÿÿÿ D0,ÿÿÿ 0
    ÿÿÿ jgeÿÿÿÿÿÿ L5
    ÿÿÿ negÿÿÿÿÿÿ D0
    L5:
    ÿÿÿ addÿÿÿÿÿÿ R.sum,ÿÿÿ D0
    ÿÿÿ incÿÿÿÿÿÿ R.av_1
    ÿÿÿ cmpÿÿÿÿÿÿ R.av_1,ÿÿÿ 16
    ÿÿÿ jleÿÿÿÿÿÿ L2
    ÿÿÿ movÿÿÿÿÿÿ D0,ÿÿÿ R.sum
    L1:
    ;---------------
    ÿÿÿ addÿÿÿÿÿÿ Dsp,ÿÿÿ 16
    ÿÿÿ popÿÿÿÿÿÿ R.x
    ÿÿÿ popÿÿÿÿÿÿ R.av_1
    ÿÿÿ popÿÿÿÿÿÿ R.sum
    ÿÿÿ ret


    OK.


    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Keith Thompson@3:633/10 to All on Sunday, July 26, 2026 17:06:54
    Johann 'Myrkraverk' Oskarsson <johann@myrkraverk.invalid> writes:
    On 26/07/2026 6:33 AM, Keith Thompson wrote:
    Johann 'Myrkraverk' Oskarsson <johann@myrkraverk.invalid> writes:
    On 25/07/2026 7:29 AM, Keith Thompson wrote:
    [...]
    If you think a compiler should not generate code based on the
    assumption of defined behavior (for example, that it should generate
    code for a statement even if that statement can be executed only if
    undefined behavior has already occurred), that's fine, and nothing
    in the C standard prevents it. There are plenty of things the C
    standard permits compilers but does not require compilers to do.

    Indeed. If I remember correctly, loading the source code is
    /implementation defined/, so the compiler can perfectly well
    read NNTP instead of files, and strictly parse only code posted
    to comp.lang.c.

    That's completely irrelevant to what I wrote.

    Right, you said something about defined, then undefined behaviour. I
    chose not to rise to the bait. If you word your question differently
    enough not to require either of those terms, I'll reply. Until then,
    happy C coding.

    There was no bait. I asked you a question about how you intend
    your proposed compiler to deal with undefined behavior. You are of
    course not required to answer, but your choice to post a reply about
    something else was odd. I won't be wording my question differently;
    what you're suggesting is that I ask an entirely different
    question.

    I suspect that attempting to communicate with you will not be
    productive. I would enjoy having my suspicion refuted.

    I wonder if Mr. Singapore thought of that angle?
    Who?

    Message-ID: <113lv5q$3goh8$1@paganini.bofh.team>

    So there's a user posting under the name "Singapore" who has posted
    here exactly once, saying nothing relevant or interesting. I have
    no idea why you'd feel the need to mention him or her (and no,
    I'm not asking).

    --
    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.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From James Kuyper@3:633/10 to All on Monday, July 27, 2026 09:33:48
    On 2026-07-25 12:30, Waldek Hebisch wrote:
    David Brown <david.brown@hesbynett.no> wrote:
    The evaluation of "!p" is based on a comparison of "p" to the
    value 0 -
    it will be true if p contains the value 0. ...

    False.

    ... (This point is perhaps the
    weak link in my reasoning - maybe "!p" is only true if "p" is actually a
    null pointer. ...

    Correct.

    ... However, I am confident that all real implementations,
    where null pointers are value 0, ...

    I doubt that - if it were true, there would be pressure on the committee
    to mandate such a representation.

    ... act by comparison of the value.)
    As far as I can see, therefore, "p" can be a valid pointer to an object
    of appropriate type, not be a null pointer, and still have value 0 and
    have "!p" evaluate to 1.

    I did not check the standard, but if the standard allows this, then
    this is clearly a bug in the standard.

    The standard is quite clear. !p returns 0 if p compares equal to 0, and
    1 otherwise. When a pointer is compared with 0, the 0 gets implicitly converted to a null pointer of the same type. All null pointers compare
    equal. Therefore !p tests whether 0 is a null pointer, not whether it
    has a representation with all bits cleared.


    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Johann 'Myrkraverk' Oskarsson@3:633/10 to All on Tuesday, July 28, 2026 00:22:54
    On 27/07/2026 9:33 PM, James Kuyper wrote:
    On 2026-07-25 12:30, Waldek Hebisch wrote:
    David Brown <david.brown@hesbynett.no> wrote:
    The evaluation of "!p" is based on a comparison of "p" to the
    value 0 -
    it will be true if p contains the value 0. ...

    False.

    ... (This point is perhaps the
    weak link in my reasoning - maybe "!p" is only true if "p" is actually a >>> null pointer. ...

    Correct.

    ... However, I am confident that all real implementations,
    where null pointers are value 0, ...

    I doubt that - if it were true, there would be pressure on the committee
    to mandate such a representation.

    ... act by comparison of the value.)
    As far as I can see, therefore, "p" can be a valid pointer to an object
    of appropriate type, not be a null pointer, and still have value 0 and
    have "!p" evaluate to 1.

    I did not check the standard, but if the standard allows this, then
    this is clearly a bug in the standard.

    The standard is quite clear. !p returns 0 if p compares equal to 0, and
    1 otherwise. When a pointer is compared with 0, the 0 gets implicitly converted to a null pointer of the same type. All null pointers compare equal. Therefore !p tests whether 0 is a null pointer, not whether it
    has a representation with all bits cleared.


    I'm not in a position to do so yet, but you can trivially test this by implementing "the null pointer" to be internally represented by 0xff..ff
    where the .. represents your bitwith; 16bit, 32bit, 64bit, 128bit.

    Then one of the first thing you notice, is that the integer zero needs
    to convert to the 0xff..ff bit pattern when cast to a pointer. After
    that, implementing !p is trivial, for some value of trivial.


    Happy compiler building!
    --
    Johann | email: invalid -> com | http://www.myrkraverk.com/blog/
    I'm not from the Internet, I just work there. | via Easynews.com

    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Keith Thompson@3:633/10 to All on Monday, July 27, 2026 12:58:34
    Johann 'Myrkraverk' Oskarsson <johann@myrkraverk.invalid> writes:
    On 27/07/2026 9:33 PM, James Kuyper wrote:
    [...]
    The standard is quite clear. !p returns 0 if p compares equal to 0,
    and
    1 otherwise. When a pointer is compared with 0, the 0 gets implicitly
    converted to a null pointer of the same type. All null pointers compare
    equal. Therefore !p tests whether 0 is a null pointer, not whether it
    has a representation with all bits cleared.

    I'm not in a position to do so yet, but you can trivially test this by implementing "the null pointer" to be internally represented by 0xff..ff where the .. represents your bitwith; 16bit, 32bit, 64bit, 128bit.

    Then one of the first thing you notice, is that the integer zero needs
    to convert to the 0xff..ff bit pattern when cast to a pointer. After
    that, implementing !p is trivial, for some value of trivial.

    You'd have to create a compiler, or modify an existing one, to use a
    non-zero representation for null pointers. That's hardly "trivial".
    (For example, default initialization for static objects could no
    longer just zero the target object.)

    I don't think there are any modern C compilers that don't use
    all-bits-zero for null pointers. Other representations are of
    course valid, but tend not to be worth the trouble.

    I wouldn't be terribly surprised if a future standard mandated
    all-bits-zero for null pointers -- or if it didn't.

    --
    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.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From BGB@3:633/10 to All on Monday, July 27, 2026 15:36:07
    On 7/27/2026 11:22 AM, Johann 'Myrkraverk' Oskarsson wrote:
    On 27/07/2026 9:33 PM, James Kuyper wrote:
    On 2026-07-25 12:30, Waldek Hebisch wrote:
    David Brown <david.brown@hesbynett.no> wrote:
    The evaluation of "!p" is based on a comparison of "p" to the
    value 0 -
    it will be true if p contains the value 0. ...

    False.

    ... (This point is perhaps the
    weak link in my reasoning - maybe "!p" is only true if "p" is
    actually a
    null pointer. ...

    Correct.

    ... However, I am confident that all real implementations,
    where null pointers are value 0, ...

    I doubt that - if it were true, there would be pressure on the committee
    to mandate such a representation.

    ...ÿ act by comparison of the value.)
    As far as I can see, therefore, "p" can be a valid pointer to an object >>>> of appropriate type, not be a null pointer, and still have value 0 and >>>> have "!p" evaluate to 1.
    I did not check the standard, but if the standard allows this, then
    this is clearly a bug in the standard.

    The standard is quite clear. !p returns 0 if p compares equal to 0, and
    1 otherwise. When a pointer is compared with 0, the 0 getsÿ implicitly
    converted to a null pointer of the same type. All null pointers compare
    equal. Therefore !p tests whether 0 is a null pointer, not whether it
    has a representation with all bits cleared.


    I'm not in a position to do so yet, but you can trivially test this by implementing "the null pointer" to be internally represented by 0xff..ff where the .. represents your bitwith; 16bit, 32bit, 64bit, 128bit.

    Then one of the first thing you notice, is that the integer zero needs
    to convert to the 0xff..ff bit pattern when cast to a pointer.ÿ After
    that, implementing !p is trivial, for some value of trivial.


    FWIW:
    In my ISA's, it is possible to have NULL pointers with not-all-bits 0.


    Say, typical pointer layout:
    (47: 0): Address
    (63:48): Tag Bits
    You could in theory have 0 base address, and non-zero tag.

    Though, in the default mode, the compiler assumes canonical C data
    pointers will always have the tag bits set to 0, so all bits 0 is the canonical NULL.

    Note that normal memory loads/stores ignore the high 16 bits, so (unlike
    on x86-64 or similar) don't need to manually clear them first (and also
    less need to rely on the graceful assumption of the OS not putting
    anything outside of the 48-bit range; but other schemes like NaN Boxing
    would also run into a big headache here).

    Ignoring the high bits, and having instructions like LEA set them to 0,
    etc, were fairly deliberate design choices.

    As I see it, we are still fairly far from traditional programs being
    cramped by a 48-bit VAS (and, at this scale, tag bits were a more
    compelling use-case than having a bigger VAS).

    Note: VAS = Virtual Address Space.


    Though, it is possible I could consider allowing/defining a sub-mode
    that shrinks the tag to 8 bits, if needed.

    There was previously stuff to support a 32-bit VAS, but this has mostly
    fallen into disuse (the relative memory savings of programs with 32-bit pointers isn't enough in general to justify the added hassle of
    supporting programs with 32-bit pointers; even if as-is, only a small
    minority of the programs would need more than 32 bits of VAS).



    Other contents depend on context:
    LR and Function Pointers: Include some captured mode-state bits;
    Applicable if LSB is Set;
    Encodes things like which ISA the CPU is running.
    General Data Pointers:
    Typically used as type-tag bits;
    Have been used for bounds-check metadata in some cases;
    For internal use in my OpenGL impl, they hold texture type/size.

    Where, type tags are sorta like (high 4 bits):
    0000: Object Pointers, 12b = object type key
    0001: Misc small values
    0010: Bounds checked pointers
    0011: Bounds checked pointers
    01xx: Fixnum (62-bit signed integer value)
    10xx: Flonum (62-bit floating point, Binary64 shifted right 2 bits)
    110x: Densely packed Vectors (2/3 element, FPU)
    1110: Pointer with type-tagging (direct encoded or signature index)
    00dd-tttt-tttt: d=*/**/***/****, t=base type index
    1xxx-xxxx-xxxx: Signature String Index
    1111: Pointer, alt, may encode a 60-bit address.

    This stuff is mostly relevant to a dynamically typed code.
    There are extensions to C to support dynamic types, but this is used sparingly. They are inherently slower than the normal static types, as
    well as being non-portable.


    There are cases where it makes sense to use dynamic types though.
    Technically it also allows my compiler to compile a JavaScript variant,
    though it isn't an exact match for the normal JS (its JS mode is
    effectively just a static-compiled version of my older BGBScript
    Language). Syntax is sorta like ES3 + other stuff; a fair bit somewhat resembles the abandoned ES4 spec as well.

    Can note that both BGBScript and BGBScript2 had ended up using hybrid
    type models:
    Core is static typed;
    May use dynamic types as needed:
    BS: via lack of explicit type;
    BS2: if static type was the dynamic type (variant).
    For BS, compiler may infer types via type inference.
    This was used in my VM, but not currently used by BGBCC.
    BS2 had an 'auto' type, but in BGBCC, is the same as 'variant'.

    In this implementation, both share the same toplevel as C land.
    Canonically, one needs a 'native' keyword for imports/exports, but, yeah...

    In BS, say:
    native function Foo(x:int):int; //import Foo from C land
    native function Bar(x:int, y:int):int //exported to C
    { return x+y; }
    Or, BS2:
    native int Foo(int x); //import Foo from C land
    native int Bar(int x, int y) //exported to C
    { return x+y; }

    In BGBCC, it doesn't matter, as it is assumed for toplevel decls.
    Implicitly, in this case, the toplevel also disallows overloading
    (relevant to BS2 and the experimental C++ mode). For sake of the C++
    mode, it is like the toplevel always has 'extern "C"' applied
    (internally, 'extern "C"' having just been mapped back to the NATIVE flag).


    But, yeah, in the C dialect, it mostly takes the form of:
    __variant x; //dynamically typed
    x=3; //3 converted to fixnum
    x=3.14159; //converted to flonum
    x=(__variant) { .foo=3; .bar=4; }; //ex-nihilo object
    ...
    Then you could type-check pointers, say:
    if(x __instanceof __fixint)
    { do something with a fixnum ...}

    Syntax in BS2 is similar, just without the '__' on the keywords.

    Note that, in this implementation, BS and BS2 can also still use the C preprocessor, etc.


    Note that for a common subset of common types, the C compiler and C
    runtime need to be kept in sync regarding the initial set of dynamic
    types (such that the compiler knows the type-tags in advance); but other type-tags may appear dynamically at runtime.

    Note also, unlike what may be implied in some languages, it doesn't
    create a new type tag for every type of class/interface, rather it would merely tag that it is a pointer to a class/interface, and "instanceof"
    would then use a different mechanism to check class types. When compiler
    does its things, the first pointer in the VTable points to a metadata structure describing the class and its contents. Theoretically, the
    class member lists could be used to implement an object serialization
    thing, but currently TestKern doesn't have this.

    I think I had considered something like this to allow for potential
    non-local COM, but it hasn't been done yet (no network, so no need for non-local RPC).

    Well, also the other tradeoff for how to serialize:
    Dump raw structs, with sizes, and pointers relative to a base address.
    ( Roughly the route DCOM/OLE went IIRC. )
    Binary TLV style structure;
    (More flexible than the above, but more overhead).
    ASCII based structure (say, something JSON-like).
    Or, S-Expressions, or whatever else.
    Human readable text, but needs printer/parser.

    Usually, object serialization is "not the right tool for the job" though.


    Note: metadata differs from that defined for the IA64 C++ ABI (for
    RTTI), which IIRC was merely able to identify the object, but would lack
    the metadata to describe said object's contents/layout (so couldn't be
    used for automatic serialization; or reflective/dynamic-typed access to static-typed objects, ...).

    Note 2: Class/Instance and ex-Nihilo objects were separate entity
    sub-types (the ex-nihilo objs containing an associative key/value
    structure). The languages supported "dynamic" objects (with a
    class/instance part, and supporting dynamically extending with new
    fields); these existed as Class/Instance objects with an internal
    optional ex-nihilo object glued on (NULL if no extra members were used).

    In either case, C/I object layouts were based on appending subclass
    contents onto the end (like a narrow sub-case of traditional C++ object layout), rather than any more complex "dynamic" mechanisms. In effect,
    things like interfaces were also handled by basically just appending
    vtable pointers onto the end of the object's layout (so casting a class
    to an implemented interface is basically just adding the interface
    pointer's offset to that of the base class; interface VT effectively
    encoding another offset that is added to itself to get the 'this'
    pointer for the base-class).



    Cases where the dynamic type-system have been used had mostly been
    limited to things like small specialized script interpreters and
    similar. Like, there is a BASIC interpreter for a 1980s style dialect.


    Note however, there is no garbage collector...
    So, if code is written that assumes that dynamic types means the freedom
    to generate lots of garbage and have the GC deal with it, this is not
    the case here.


    But, yeah, all the fun corners one can get stuck in...




    Happy compiler building!


    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From BGB@3:633/10 to All on Monday, July 27, 2026 15:37:37
    On 7/27/2026 2:58 PM, Keith Thompson wrote:
    Johann 'Myrkraverk' Oskarsson <johann@myrkraverk.invalid> writes:
    On 27/07/2026 9:33 PM, James Kuyper wrote:
    [...]
    The standard is quite clear. !p returns 0 if p compares equal to 0,
    and
    1 otherwise. When a pointer is compared with 0, the 0 gets implicitly
    converted to a null pointer of the same type. All null pointers compare
    equal. Therefore !p tests whether 0 is a null pointer, not whether it
    has a representation with all bits cleared.

    I'm not in a position to do so yet, but you can trivially test this by
    implementing "the null pointer" to be internally represented by 0xff..ff
    where the .. represents your bitwith; 16bit, 32bit, 64bit, 128bit.

    Then one of the first thing you notice, is that the integer zero needs
    to convert to the 0xff..ff bit pattern when cast to a pointer. After
    that, implementing !p is trivial, for some value of trivial.

    You'd have to create a compiler, or modify an existing one, to use a
    non-zero representation for null pointers. That's hardly "trivial".
    (For example, default initialization for static objects could no
    longer just zero the target object.)

    I don't think there are any modern C compilers that don't use
    all-bits-zero for null pointers. Other representations are of
    course valid, but tend not to be worth the trouble.

    I wouldn't be terribly surprised if a future standard mandated
    all-bits-zero for null pointers -- or if it didn't.


    Yes, all bits zero is the most sensible option for C style NULL, so
    likely little reason to differ here...




    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From James Kuyper@3:633/10 to All on Monday, July 27, 2026 17:13:39
    On 2026-07-27 16:37, BGB wrote:
    On 7/27/2026 2:58 PM, Keith Thompson wrote:
    I wouldn't be terribly surprised if a future standard mandated
    all-bits-zero for null pointers -- or if it didn't.


    Yes, all bits zero is the most sensible option for C style NULL, so
    likely little reason to differ here...
    I gather that, on those real-world platforms which did differ, the need
    to differ arose from hardware considerations.
    I asked Google Gemini "Which implementations of C implement a null
    pointer whose representation is not 0?", and it identified 5 different implementations. The words it used gave me the impression that it
    understood correctly what I was asking. I didn't bother checking the
    validity of its answers.

    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Keith Thompson@3:633/10 to All on Monday, July 27, 2026 15:17:14
    James Kuyper <jameskuyper@alumni.caltech.edu> writes:
    On 2026-07-27 16:37, BGB wrote:
    On 7/27/2026 2:58 PM, Keith Thompson wrote:
    ..
    I wouldn't be terribly surprised if a future standard mandated
    all-bits-zero for null pointers -- or if it didn't.

    Yes, all bits zero is the most sensible option for C style NULL, so
    likely little reason to differ here...

    I gather that, on those real-world platforms which did differ, the need
    to differ arose from hardware considerations.
    I asked Google Gemini "Which implementations of C implement a null
    pointer whose representation is not 0?", and it identified 5 different implementations. The words it used gave me the impression that it
    understood correctly what I was asking. I didn't bother checking the
    validity of its answers.

    I just ran the same search and also got 5 results:

    - Prime Computer (Prime 50 Series)
    - Honeywell-Bull Mainframes
    - CDC Cyber 180 Series
    - Symbolics Lisp Machine (Symbolics C)
    - Salford C / C++ (Real-mode DOS Extender)

    The AI-generated text refers to all 5 in the past tense. I doubt
    (but haven't checked) that any of these implementations have been
    updated to recent C standards.

    My impression is that non-zero representations for null pointers
    are about as common as non-two's-complement representations
    for integers. C23 mandated two's complement (representation,
    not overflow behavior).

    Being able to assume two's complement can be convenient. There's not
    as much convenience, I think, from being able to assume all-bits-zero
    for null pointers. (I rarely write code that relies on either
    assumption.)

    I speculate that if, say, C2029 were to mandate all-bits-zero for
    null pointers *and* require all-bits-zero to be a represenation
    of 0.0 in all floating-point types (enabling using memset to
    zero-init objects of any type), it wouldn't require any work for
    any existing implementations. I doubt that any implementations
    that don't already meet those requirements have been updated past
    C90 or C99. Even if that isn't the case, most C compilers have
    options to conform to older standards.

    Note that the requirement for all-bits-zero to be a valid
    representation for zero in all integer types was added, without much
    fanfare, in a technical corrigendum to C99, so there is precedent
    for quietly adding this kind of requirement that all existing
    implementations already satisfy.

    On the other hand, I haven't seen any serious proposals to make a
    change in this area.

    --
    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.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Johann 'Myrkraverk' Oskarsson@3:633/10 to All on Tuesday, July 28, 2026 08:14:04
    On 28/07/2026 3:58 AM, Keith Thompson wrote:


    You'd have to create a compiler, or modify an existing one, to use a
    non-zero representation for null pointers. That's hardly "trivial".
    (For example, default initialization for static objects could no
    longer just zero the target object.)

    __ .----.__
    .________________________________________.
    -' `/(#)#(#) `- . o O ( I tend to assume people are more
    capable )
    ` (#)#(#) \ ___ ^( than they actually are, because
    it )^^
    __( \ ,,,/ `. ``. ( makes them either feel better,
    or some- )
    / \ \,-/ | \ ( times they rise to the challenge! )^^^^^^
    | `-- ( ( /__ ( ^^^( )^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    ` ( `---\ `---._` ( } (^^
    ^^)______________________________.
    | | \ `----._`.`. .' ( For instance, this is an old
    ASCII art )
    . ( `-) \ `. )) ) | ( horror I made several years ago.
    )^^^
    / \ / / ) / { ( I got semi-famous for my
    collection )
    / \ / ( | ( ( of non-trivial ASCII art.
    )^^^^^^^^^^
    / ,\ /\\\ ( |_ \ ( And I made all of it by hand,
    including )
    / /\( ) / .`\\\ ^^( this thought bubble.
    )^^^^^^^^^^^^^^
    \\/ \ .-' | | -_ ^^^^^^^^^^^^^^^^^^^^^^^
    \ .'___( ) \ `-.
    -' \/\___\\__\

    /** Do you ever mix C and ASCII art? **/

    int main( int _, char *o[] ) { return _^( int ) main <3 ; }



    I don't think there are any modern C compilers that don't use
    all-bits-zero for null pointers. Other representations are of
    course valid, but tend not to be worth the trouble.

    I wouldn't be terribly surprised if a future standard mandated
    all-bits-zero for null pointers -- or if it didn't.


    I think more of it like an arm-chair thought exercise. The issue
    isn't the bit pattern at all, but how you're going to treat conversion
    to integers. Especially in this context,

    if ( p ) { ... }

    a 64bit p with the pattern 0x..00000000 isn't supposed to be false,
    when truncated to an integer. If I remember the standard(s)
    correctly, p here is supposed to "devolve" into either 1 or 0 in a
    boolean context. I'd believe comparing to zero, and use the Z flag is
    normal in x64, and in MIPS64, direct comparison to $zero is probably
    the most normal way to go about it.
    I forgot the actual instructions in both cases. They're easy to
    look up anyway.

    --
    Johann | email: invalid -> com | http://www.myrkraverk.com/blog/
    I'm not from the Internet, I just work there. | via Easynews.com

    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Keith Thompson@3:633/10 to All on Monday, July 27, 2026 17:59:05
    Johann 'Myrkraverk' Oskarsson <johann@myrkraverk.invalid> writes:
    On 28/07/2026 3:58 AM, Keith Thompson wrote:
    You'd have to create a compiler, or modify an existing one, to use a
    non-zero representation for null pointers. That's hardly "trivial".
    (For example, default initialization for static objects could no
    longer just zero the target object.)

    [SNIP]

    Your ASCII art has nothing to do with the current discussion (and
    didn't even display correctly due to line wrapping). I've dropped
    the cross-post to alt.ascii-art. Can you please try to focus?

    I don't think there are any modern C compilers that don't use
    all-bits-zero for null pointers. Other representations are of
    course valid, but tend not to be worth the trouble.
    I wouldn't be terribly surprised if a future standard mandated
    all-bits-zero for null pointers -- or if it didn't.

    I think more of it like an arm-chair thought exercise. The issue
    isn't the bit pattern at all, but how you're going to treat conversion
    to integers. Especially in this context,

    if ( p ) { ... }

    a 64bit p with the pattern 0x..00000000 isn't supposed to be false,
    when truncated to an integer. If I remember the standard(s)
    correctly, p here is supposed to "devolve" into either 1 or 0 in a
    boolean context. I'd believe comparing to zero, and use the Z flag is
    normal in x64, and in MIPS64, direct comparison to $zero is probably
    the most normal way to go about it.
    I forgot the actual instructions in both cases. They're easy to
    look up anyway.

    I don't know what you mean by "truncated to an integer". There is no
    implicit pointer-to-integer conversion in "if (p)". Rather, if p is a
    pointer, the implicit 0 is converted to pointer type before the
    comparison, and the comparison yields 0 or 1 of type int.

    Pointer values can be converted to integer types, but the result is implementation-defined and not necessarily meaningful. There isn't
    even a guarantee that converting a null pointer to an integer type
    yields 0.

    Here's what the standard says:

    In both forms, the first substatement is executed if the
    expression compares unequal to 0. In the else form, the second
    substatement is executed if the expression compares equal to
    0. If the first substatement is reached via a label, the second
    substatement is not executed.

    You can think of it as "devolving" to 1 or 0 if you like, but that's
    not how the C standard describes it.

    If p is of pointer type, "if (p)" treats the condition as true if
    p is non-null, false if it's null. That's it. The semantics are
    not defined in terms of pointer representation or CPU instructions.

    --
    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.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Scott Lurndal@3:633/10 to All on Tuesday, July 28, 2026 01:18:11
    Keith Thompson <Keith.S.Thompson+u@gmail.com> writes:
    James Kuyper <jameskuyper@alumni.caltech.edu> writes:
    On 2026-07-27 16:37, BGB wrote:
    On 7/27/2026 2:58 PM, Keith Thompson wrote:
    ..
    I wouldn't be terribly surprised if a future standard mandated
    all-bits-zero for null pointers -- or if it didn't.

    Yes, all bits zero is the most sensible option for C style NULL, so
    likely little reason to differ here...

    I gather that, on those real-world platforms which did differ, the need
    to differ arose from hardware considerations.
    I asked Google Gemini "Which implementations of C implement a null
    pointer whose representation is not 0?", and it identified 5 different
    implementations. The words it used gave me the impression that it
    understood correctly what I was asking. I didn't bother checking the
    validity of its answers.

    I just ran the same search and also got 5 results:

    - Prime Computer (Prime 50 Series)
    - Honeywell-Bull Mainframes
    - CDC Cyber 180 Series
    - Symbolics Lisp Machine (Symbolics C)
    - Salford C / C++ (Real-mode DOS Extender)

    I'll add a sixth; while we looked at doing a C compiler,
    it didn't get any traction from customers

    - Burroughs Medium Systems (hardware nulptr = 0xEEEEEE)
    The architecture featured an instruction to search linked
    lists (SLT) and the hardware recognized the base offset
    0xEEEEEE as the NIL pointer. It was a BCD architecture
    addressed to the digit (nibble).


    The AI-generated text refers to all 5 in the past tense. I doubt
    (but haven't checked) that any of these implementations have been
    updated to recent C standards.

    Medium systems (V-series at the end) is also past tense now.


    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Johann 'Myrkraverk' Oskarsson@3:633/10 to All on Tuesday, July 28, 2026 15:40:31
    On 28/07/2026 9:18 AM, Scott Lurndal wrote:
    Keith Thompson <Keith.S.Thompson+u@gmail.com> writes:
    James Kuyper <jameskuyper@alumni.caltech.edu> writes:
    On 2026-07-27 16:37, BGB wrote:
    On 7/27/2026 2:58 PM, Keith Thompson wrote:
    ..
    I wouldn't be terribly surprised if a future standard mandated
    all-bits-zero for null pointers -- or if it didn't.

    Yes, all bits zero is the most sensible option for C style NULL, so
    likely little reason to differ here...

    I gather that, on those real-world platforms which did differ, the need
    to differ arose from hardware considerations.
    I asked Google Gemini "Which implementations of C implement a null
    pointer whose representation is not 0?", and it identified 5 different
    implementations. The words it used gave me the impression that it
    understood correctly what I was asking. I didn't bother checking the
    validity of its answers.

    I just ran the same search and also got 5 results:

    - Prime Computer (Prime 50 Series)
    - Honeywell-Bull Mainframes
    - CDC Cyber 180 Series
    - Symbolics Lisp Machine (Symbolics C)
    - Salford C / C++ (Real-mode DOS Extender)

    I'll add a sixth; while we looked at doing a C compiler,
    it didn't get any traction from customers

    - Burroughs Medium Systems (hardware nulptr = 0xEEEEEE)
    The architecture featured an instruction to search linked
    lists (SLT) and the hardware recognized the base offset
    0xEEEEEE as the NIL pointer. It was a BCD architecture
    addressed to the digit (nibble).


    I'd expect GE-645 to qualify too, but as far as I know, that
    also didn't get a C compiler. Right now I'm not pulling
    Organick off my shelf to see if I'm right, other people can
    do that.

    --
    Johann | email: invalid -> com | http://www.myrkraverk.com/blog/
    I'm not from the Internet, I just work there. | via Easynews.com

    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Johann 'Myrkraverk' Oskarsson@3:633/10 to All on Tuesday, July 28, 2026 15:42:56
    On 28/07/2026 8:59 AM, Keith Thompson wrote:
    Johann 'Myrkraverk' Oskarsson <johann@myrkraverk.invalid> writes:
    On 28/07/2026 3:58 AM, Keith Thompson wrote:
    You'd have to create a compiler, or modify an existing one, to use a
    non-zero representation for null pointers. That's hardly "trivial".
    (For example, default initialization for static objects could no
    longer just zero the target object.)

    [SNIP]

    Your ASCII art has nothing to do with the current discussion (and
    didn't even display correctly due to line wrapping). I've dropped
    the cross-post to alt.ascii-art. Can you please try to focus?


    Nope. You've absolutely demonstrated that you have no idea what
    is involved in the internals of a C compiler. What you're trying
    to describe is just hogwash and balderdash when it comes to the
    real world nitty gritty of implementing a compiler.

    Now stay out of my compiler discussion, you're not wanted here!

    --
    Johann | email: invalid -> com | http://www.myrkraverk.com/blog/
    I'm not from the Internet, I just work there. | via Easynews.com

    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Keith Thompson@3:633/10 to All on Tuesday, July 28, 2026 03:22:45
    Johann 'Myrkraverk' Oskarsson <johann@myrkraverk.invalid> writes:
    On 28/07/2026 8:59 AM, Keith Thompson wrote:
    Johann 'Myrkraverk' Oskarsson <johann@myrkraverk.invalid> writes:
    On 28/07/2026 3:58 AM, Keith Thompson wrote:
    You'd have to create a compiler, or modify an existing one, to use a
    non-zero representation for null pointers. That's hardly "trivial".
    (For example, default initialization for static objects could no
    longer just zero the target object.)

    [SNIP]
    Your ASCII art has nothing to do with the current discussion (and
    didn't even display correctly due to line wrapping). I've dropped
    the cross-post to alt.ascii-art. Can you please try to focus?

    Nope. You've absolutely demonstrated that you have no idea what
    is involved in the internals of a C compiler. What you're trying
    to describe is just hogwash and balderdash when it comes to the
    real world nitty gritty of implementing a compiler.

    Now stay out of my compiler discussion, you're not wanted here!

    Perhaps the rest of the group would be interested in knowing just
    what I got so badly wrong.

    --
    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.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Johann 'Myrkraverk' Oskarsson@3:633/10 to All on Tuesday, July 28, 2026 19:21:36
    On 28/07/2026 6:22 PM, Keith Thompson wrote:
    Johann 'Myrkraverk' Oskarsson <johann@myrkraverk.invalid> writes:
    On 28/07/2026 8:59 AM, Keith Thompson wrote:
    Johann 'Myrkraverk' Oskarsson <johann@myrkraverk.invalid> writes:
    On 28/07/2026 3:58 AM, Keith Thompson wrote:
    You'd have to create a compiler, or modify an existing one, to use a >>>>> non-zero representation for null pointers. That's hardly "trivial". >>>>> (For example, default initialization for static objects could no
    longer just zero the target object.)

    [SNIP]
    Your ASCII art has nothing to do with the current discussion (and
    didn't even display correctly due to line wrapping). I've dropped
    the cross-post to alt.ascii-art. Can you please try to focus?

    Nope. You've absolutely demonstrated that you have no idea what
    is involved in the internals of a C compiler. What you're trying
    to describe is just hogwash and balderdash when it comes to the
    real world nitty gritty of implementing a compiler.

    Now stay out of my compiler discussion, you're not wanted here!

    Perhaps the rest of the group would be interested in knowing just
    what I got so badly wrong.


    I'll summarize it as: You think I care what the C standard documents
    say. I don't.

    I care how it's implemented. You don't.

    --
    Johann | email: invalid -> com | http://www.myrkraverk.com/blog/
    I'm not from the Internet, I just work there. | via Easynews.com

    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From bart@3:633/10 to All on Tuesday, July 28, 2026 13:02:03
    On 28/07/2026 12:21, Johann 'Myrkraverk' Oskarsson wrote:
    On 28/07/2026 6:22 PM, Keith Thompson wrote:
    Johann 'Myrkraverk' Oskarsson <johann@myrkraverk.invalid> writes:
    On 28/07/2026 8:59 AM, Keith Thompson wrote:

    Your ASCII art has nothing to do with the current discussion (and
    didn't even display correctly due to line wrapping).ÿ I've dropped
    the cross-post to alt.ascii-art.ÿ Can you please try to focus?

    Nope.ÿ You've absolutely demonstrated that you have no idea what
    is involved in the internals of a C compiler.

    You don't seem to have much idea either.


    ÿ What you're trying
    to describe is just hogwash and balderdash when it comes to the
    real world nitty gritty of implementing a compiler.

    Now stay out of my compiler discussion, you're not wanted here!

    Perhaps the rest of the group would be interested in knowing just
    what I got so badly wrong.


    I'll summarize it as: You think I care what the C standard documents
    say.ÿ I don't.

    I care how it's implemented.ÿ You don't.

    This is what you wrote:

    "I think more of it like an arm-chair thought exercise. The issue
    isn't the bit pattern at all, but how you're going to treat conversion
    to integers. Especially in this context,

    if ( p ) { ... }

    a 64bit p with the pattern 0x..00000000 isn't supposed to be false,
    when truncated to an integer...."

    You don't say what type 'p' is, but I assume it is a pointer.

    How a compiler implements this depends partly on what the language says,
    for example:

    Scalar type Value True when

    integer i i != 0
    float x x != 0.0 (-0.0 may need considering)
    pointer p p != NULL

    Whether NULL is all-bits-zero depends on the implementation, but I think
    it it more platform-dependent rather than left to individual compilers.

    Since in that case code from different compilers would be incompatible,
    and calling into external libraries would be problematical.

    In any case, there is no conversion to int involved; it is just has to implement that comparison by whatever means works.


    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Johann 'Myrkraverk' Oskarsson@3:633/10 to All on Tuesday, July 28, 2026 20:18:36
    On 28/07/2026 8:02 PM, bart wrote:
    On 28/07/2026 12:21, Johann 'Myrkraverk' Oskarsson wrote:
    On 28/07/2026 6:22 PM, Keith Thompson wrote:
    Johann 'Myrkraverk' Oskarsson <johann@myrkraverk.invalid> writes:
    On 28/07/2026 8:59 AM, Keith Thompson wrote:

    Your ASCII art has nothing to do with the current discussion (and
    didn't even display correctly due to line wrapping).ÿ I've dropped
    the cross-post to alt.ascii-art.ÿ Can you please try to focus?

    Nope.ÿ You've absolutely demonstrated that you have no idea what
    is involved in the internals of a C compiler.

    You don't seem to have much idea either.


    ÿ What you're trying
    to describe is just hogwash and balderdash when it comes to the
    real world nitty gritty of implementing a compiler.

    Now stay out of my compiler discussion, you're not wanted here!

    Perhaps the rest of the group would be interested in knowing just
    what I got so badly wrong.


    I'll summarize it as: You think I care what the C standard documents
    say.ÿ I don't.

    I care how it's implemented.ÿ You don't.

    This is what you wrote:

    "I think more of it like an arm-chair thought exercise.ÿ The issue
    isn't the bit pattern at all, but how you're going to treat conversion
    to integers.ÿ Especially in this context,

    ÿ if ( p ) { ... }

    a 64bit p with the pattern 0x..00000000 isn't supposed to be false,
    when truncated to an integer...."

    You don't say what type 'p' is, but I assume it is a pointer.

    How a compiler implements this depends partly on what the language says,
    for example:

    ÿ Scalar typeÿ Valueÿÿ True when

    ÿÿ integerÿÿÿÿÿ iÿÿÿÿÿ i != 0
    ÿÿ floatÿÿÿÿÿÿÿ xÿÿÿÿÿ x != 0.0ÿÿÿÿ (-0.0 may need considering)
    ÿÿ pointerÿÿÿÿÿ pÿÿÿÿÿ p != NULL

    Whether NULL is all-bits-zero depends on the implementation, but I think
    it it more platform-dependent rather than left to individual compilers.

    Since in that case code from different compilers would be incompatible,
    and calling into external libraries would be problematical.

    In any case, there is no conversion to int involved; it is just has to implement that comparison by whatever means works.


    Indeed, and if you read the snippet you so thoughtfully cut off, you'd
    have realized that's exactly how I think about it, in terms of compiler internals. Now, of course, you being an expert in twisting other
    people's expertise, you must be a journalist? Do you perhaps write for
    VICE? I got my name into a VICE article once, for not being involved
    at all. That was comforting. Now I feel like I'm just as important
    as UBS.

    On the other hand, to the programmer, especially those not who have not
    and never will read the C language standard, it does look like the p
    pointer is "truncated" to an integer valued 0 or 1, in boolean context.

    And as I told Keith, I don't care how the language standard words
    things. I care how the compiler is implemented.

    .________________________.
    ,-'""`-. ( )
    ; : o 0 ( I also added some more old )
    : : ( ASCII art, and re-added )
    : (_) (_) ; ( alt.ascii-art for cross )
    ` '` ' ^^( posting purposes! )^^
    :`++++'; ^(._______________.)
    ``..'' /mv/



    Happy writing articles for VICE!
    --
    Johann | email: invalid -> com | http://www.myrkraverk.com/blog/
    I'm not from the Internet, I just work there. | via Easynews.com

    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From bart@3:633/10 to All on Tuesday, July 28, 2026 14:17:01
    On 28/07/2026 13:18, Johann 'Myrkraverk' Oskarsson wrote:
    On 28/07/2026 8:02 PM, bart wrote:


    In any case, there is no conversion to int involved; it is just has to
    implement that comparison by whatever means works.


    On the other hand, to the programmer, especially those not who have not
    and never will read the C language standard, it does look like the p
    pointer is "truncated" to an integer valued 0 or 1, in boolean context.

    It doesn't look like that all. This is just testing for 'truthiness',
    which in many languages can be applied to data types such as strings or
    lists.

    'Truncation' doesn't work either: you can have all-bits-zero NULL, and a
    value for 'p' of 0x123456 - truncating to one bit would give you zero,
    which is false.

    There is also rarely any result - no value needs to be yielded when the
    result just affects control-flow.



    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Dan Cross@3:633/10 to All on Tuesday, July 28, 2026 14:18:31
    In article <Q1Z9S.2212$jNNe.1884@fx15.ams4>,
    Johann 'Myrkraverk' Oskarsson <johann@myrkraverk.invalid> wrote:
    On 28/07/2026 9:18 AM, Scott Lurndal wrote:
    Keith Thompson <Keith.S.Thompson+u@gmail.com> writes:
    James Kuyper <jameskuyper@alumni.caltech.edu> writes:
    On 2026-07-27 16:37, BGB wrote:
    On 7/27/2026 2:58 PM, Keith Thompson wrote:
    ..
    I wouldn't be terribly surprised if a future standard mandated
    all-bits-zero for null pointers -- or if it didn't.

    Yes, all bits zero is the most sensible option for C style NULL, so
    likely little reason to differ here...

    I gather that, on those real-world platforms which did differ, the need >>>> to differ arose from hardware considerations.
    I asked Google Gemini "Which implementations of C implement a null
    pointer whose representation is not 0?", and it identified 5 different >>>> implementations. The words it used gave me the impression that it
    understood correctly what I was asking. I didn't bother checking the
    validity of its answers.

    I just ran the same search and also got 5 results:

    - Prime Computer (Prime 50 Series)
    - Honeywell-Bull Mainframes
    - CDC Cyber 180 Series
    - Symbolics Lisp Machine (Symbolics C)
    - Salford C / C++ (Real-mode DOS Extender)

    I'll add a sixth; while we looked at doing a C compiler,
    it didn't get any traction from customers

    - Burroughs Medium Systems (hardware nulptr = 0xEEEEEE)
    The architecture featured an instruction to search linked
    lists (SLT) and the hardware recognized the base offset
    0xEEEEEE as the NIL pointer. It was a BCD architecture
    addressed to the digit (nibble).


    I'd expect GE-645 to qualify too, but as far as I know, that
    also didn't get a C compiler. Right now I'm not pulling
    Organick off my shelf to see if I'm right, other people can
    do that.

    I assume you mean the Multics system. It did not have a C
    compiler on the 645, no. The follow-on , H6180 and DPS/8m, yes.

    - Dan C.


    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Dan Cross@3:633/10 to All on Tuesday, July 28, 2026 14:20:50
    In article <1149vtl$3v7uk$1@kst.eternal-september.org>,
    Keith Thompson <Keith.S.Thompson+u@gmail.com> wrote:
    Johann 'Myrkraverk' Oskarsson <johann@myrkraverk.invalid> writes:
    On 28/07/2026 8:59 AM, Keith Thompson wrote:
    Johann 'Myrkraverk' Oskarsson <johann@myrkraverk.invalid> writes:
    On 28/07/2026 3:58 AM, Keith Thompson wrote:
    You'd have to create a compiler, or modify an existing one, to use a >>>>> non-zero representation for null pointers. That's hardly "trivial". >>>>> (For example, default initialization for static objects could no
    longer just zero the target object.)

    [SNIP]
    Your ASCII art has nothing to do with the current discussion (and
    didn't even display correctly due to line wrapping). I've dropped
    the cross-post to alt.ascii-art. Can you please try to focus?

    Nope. You've absolutely demonstrated that you have no idea what
    is involved in the internals of a C compiler. What you're trying
    to describe is just hogwash and balderdash when it comes to the
    real world nitty gritty of implementing a compiler.

    Now stay out of my compiler discussion, you're not wanted here!

    Perhaps the rest of the group would be interested in knowing just
    what I got so badly wrong.

    Indeed. I suspect that the person you are responding to has a
    rather higher opinion of his own knowledge and/or abilities than
    is actually warranted.

    - Dan C.


    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Dan Cross@3:633/10 to All on Tuesday, July 28, 2026 14:22:19
    In article <4h0aS.10292$1xtd.1622@fx01.ams4>,
    Johann 'Myrkraverk' Oskarsson <johann@myrkraverk.invalid> wrote:
    On 28/07/2026 6:22 PM, Keith Thompson wrote:
    Johann 'Myrkraverk' Oskarsson <johann@myrkraverk.invalid> writes:
    On 28/07/2026 8:59 AM, Keith Thompson wrote:
    Johann 'Myrkraverk' Oskarsson <johann@myrkraverk.invalid> writes:
    On 28/07/2026 3:58 AM, Keith Thompson wrote:
    You'd have to create a compiler, or modify an existing one, to use a >>>>>> non-zero representation for null pointers. That's hardly "trivial". >>>>>> (For example, default initialization for static objects could no
    longer just zero the target object.)

    [SNIP]
    Your ASCII art has nothing to do with the current discussion (and
    didn't even display correctly due to line wrapping). I've dropped
    the cross-post to alt.ascii-art. Can you please try to focus?

    Nope. You've absolutely demonstrated that you have no idea what
    is involved in the internals of a C compiler. What you're trying
    to describe is just hogwash and balderdash when it comes to the
    real world nitty gritty of implementing a compiler.

    Now stay out of my compiler discussion, you're not wanted here!

    Perhaps the rest of the group would be interested in knowing just
    what I got so badly wrong.


    I'll summarize it as: You think I care what the C standard documents
    say. I don't.

    I care how it's implemented. You don't.

    Understanding the C standard is necessary, but not sufficient,
    for implemeing a C compiler. If you don't care how the language
    is defined, you are not going to do well building a compiler for
    that language.

    You may produce a compiler for some _other_ language, which may
    be very close to C. That's fine, but not terribly interesting
    relative to all the other hobby compiler projects out there.

    - Dan C.


    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Johann 'Myrkraverk' Oskarsson@3:633/10 to All on Tuesday, July 28, 2026 22:48:55
    On 28/07/2026 10:18 PM, Dan Cross wrote:
    In article <Q1Z9S.2212$jNNe.1884@fx15.ams4>,
    Johann 'Myrkraverk' Oskarsson <johann@myrkraverk.invalid> wrote:
    On 28/07/2026 9:18 AM, Scott Lurndal wrote:
    Keith Thompson <Keith.S.Thompson+u@gmail.com> writes:
    James Kuyper <jameskuyper@alumni.caltech.edu> writes:
    On 2026-07-27 16:37, BGB wrote:
    On 7/27/2026 2:58 PM, Keith Thompson wrote:
    ..
    I wouldn't be terribly surprised if a future standard mandated
    all-bits-zero for null pointers -- or if it didn't.

    Yes, all bits zero is the most sensible option for C style NULL, so >>>>>> likely little reason to differ here...

    I gather that, on those real-world platforms which did differ, the need >>>>> to differ arose from hardware considerations.
    I asked Google Gemini "Which implementations of C implement a null
    pointer whose representation is not 0?", and it identified 5 different >>>>> implementations. The words it used gave me the impression that it
    understood correctly what I was asking. I didn't bother checking the >>>>> validity of its answers.

    I just ran the same search and also got 5 results:

    - Prime Computer (Prime 50 Series)
    - Honeywell-Bull Mainframes
    - CDC Cyber 180 Series
    - Symbolics Lisp Machine (Symbolics C)
    - Salford C / C++ (Real-mode DOS Extender)

    I'll add a sixth; while we looked at doing a C compiler,
    it didn't get any traction from customers

    - Burroughs Medium Systems (hardware nulptr = 0xEEEEEE)
    The architecture featured an instruction to search linked
    lists (SLT) and the hardware recognized the base offset
    0xEEEEEE as the NIL pointer. It was a BCD architecture
    addressed to the digit (nibble).


    I'd expect GE-645 to qualify too, but as far as I know, that
    also didn't get a C compiler. Right now I'm not pulling
    Organick off my shelf to see if I'm right, other people can
    do that.

    I assume you mean the Multics system. It did not have a C
    compiler on the 645, no. The follow-on , H6180 and DPS/8m, yes.

    - Dan C.


    In way yes, in a way no. As far as I know, Multics was the only
    operating system for the GE 645. Yet, you can create a C compiler
    for bare metal programmer. You probably don't have one, or you'd
    know this was possible.

    My personal example is the mikroC Pro for PIC32. It creates MIPS
    binaries, and is loaded directly on the microcontroller. There is
    no operating system to speak of.

    In another thread you accused me of having "rather higher opinion
    of his own knowledge and/or abilities than is actually warranted."

    Now I have the same opinion of you. You don't know what you're
    talking about, all the time.

    --
    Johann | email: invalid -> com | http://www.myrkraverk.com/blog/
    I'm not from the Internet, I just work there. | via Easynews.com

    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Scott Lurndal@3:633/10 to All on Tuesday, July 28, 2026 15:08:14
    Johann 'Myrkraverk' Oskarsson <johann@myrkraverk.invalid> writes:
    On 28/07/2026 10:18 PM, Dan Cross wrote:
    In article <Q1Z9S.2212$jNNe.1884@fx15.ams4>,
    Johann 'Myrkraverk' Oskarsson <johann@myrkraverk.invalid> wrote:
    On 28/07/2026 9:18 AM, Scott Lurndal wrote:


    I'll add a sixth; while we looked at doing a C compiler,
    it didn't get any traction from customers

    - Burroughs Medium Systems (hardware nulptr = 0xEEEEEE)
    The architecture featured an instruction to search linked
    lists (SLT) and the hardware recognized the base offset
    0xEEEEEE as the NIL pointer. It was a BCD architecture
    addressed to the digit (nibble).


    I'd expect GE-645 to qualify too, but as far as I know, that
    also didn't get a C compiler. Right now I'm not pulling
    Organick off my shelf to see if I'm right, other people can
    do that.

    I assume you mean the Multics system. It did not have a C
    compiler on the 645, no. The follow-on , H6180 and DPS/8m, yes.

    - Dan C.


    In way yes, in a way no. As far as I know, Multics was the only
    operating system for the GE 645. Yet, you can create a C compiler
    for bare metal programmer. You probably don't have one, or you'd
    know this was possible.

    My personal example is the mikroC Pro for PIC32. It creates MIPS
    binaries, and is loaded directly on the microcontroller. There is
    no operating system to speak of.

    In another thread you accused me of having "rather higher opinion
    of his own knowledge and/or abilities than is actually warranted."

    As a disinterested bystander, I tend to agree with Dan in this
    case, as there is no evidence that any "standalone C compiler"
    was ever developed for the GE 645 as the C language did not
    exist at that point in time. BCPL, on the other hand....





    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Johann 'Myrkraverk' Oskarsson@3:633/10 to All on Tuesday, July 28, 2026 23:30:47
    On 28/07/2026 11:08 PM, Scott Lurndal wrote:
    Johann 'Myrkraverk' Oskarsson <johann@myrkraverk.invalid> writes:
    On 28/07/2026 10:18 PM, Dan Cross wrote:
    In article <Q1Z9S.2212$jNNe.1884@fx15.ams4>,
    Johann 'Myrkraverk' Oskarsson <johann@myrkraverk.invalid> wrote:
    On 28/07/2026 9:18 AM, Scott Lurndal wrote:


    I'll add a sixth; while we looked at doing a C compiler,
    it didn't get any traction from customers

    - Burroughs Medium Systems (hardware nulptr = 0xEEEEEE)
    The architecture featured an instruction to search linked
    lists (SLT) and the hardware recognized the base offset
    0xEEEEEE as the NIL pointer. It was a BCD architecture
    addressed to the digit (nibble).


    I'd expect GE-645 to qualify too, but as far as I know, that
    also didn't get a C compiler. Right now I'm not pulling
    Organick off my shelf to see if I'm right, other people can
    do that.

    I assume you mean the Multics system. It did not have a C
    compiler on the 645, no. The follow-on , H6180 and DPS/8m, yes.

    - Dan C.


    In way yes, in a way no. As far as I know, Multics was the only
    operating system for the GE 645. Yet, you can create a C compiler
    for bare metal programmer. You probably don't have one, or you'd
    know this was possible.

    My personal example is the mikroC Pro for PIC32. It creates MIPS
    binaries, and is loaded directly on the microcontroller. There is
    no operating system to speak of.

    In another thread you accused me of having "rather higher opinion
    of his own knowledge and/or abilities than is actually warranted."

    As a disinterested bystander, I tend to agree with Dan in this
    case, as there is no evidence that any "standalone C compiler"
    was ever developed for the GE 645 as the C language did not
    exist at that point in time. BCPL, on the other hand....



    As an interested bystander, I just accuse you of not knowing how
    to read. I said, and it's quoted above, "as far as I know, that
    also didn't get a C compiler" which includes bare metal C comp-
    ilers, as well as Multics compilers. Neither got made.

    And if you knew anything about Multics history, the last production
    Multics system was pulled offline in 2000. That was well after C
    compilers got popular. See

    https://www.multicians.org/history.html

    Now, please direct yourself to the nearest reading comprehension
    program, and return when you've graduated junior high level reading.
    --
    Johann | email: invalid -> com | http://www.myrkraverk.com/blog/
    I'm not from the Internet, I just work there. | via Easynews.com

    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Dan Cross@3:633/10 to All on Tuesday, July 28, 2026 18:58:20
    In article <tj3aS.40751$yWz9.13547@fx04.ams4>,
    Johann 'Myrkraverk' Oskarsson <johann@myrkraverk.invalid> wrote:
    On 28/07/2026 10:18 PM, Dan Cross wrote:
    [snip]
    I'd expect GE-645 to qualify too, but as far as I know, that
    also didn't get a C compiler. Right now I'm not pulling
    Organick off my shelf to see if I'm right, other people can
    do that.

    I assume you mean the Multics system. It did not have a C
    compiler on the 645, no. The follow-on , H6180 and DPS/8m, yes.

    In way yes, in a way no. As far as I know, Multics was the only
    operating system for the GE 645.

    First, this is not true. The GE 645 had a 635 compatibility
    mode that allowed it to boot the earlier GECOS operating system;
    for details, see the section, "Transition to the GE-645" at https://multicians.org/ge635.html.

    But that historical trivia aside, I don't see how it is relevant
    to your claim about nil pointers.

    Yet, you can create a C compiler
    for bare metal programmer. You probably don't have one, or you'd
    know this was possible.

    What does that have to do with anything?

    You mentioned Organick, so it seems reasonable you were asking
    about either Multics or FORTRAN (Organick wrote a very
    well-regarded book about FORTRAN-77). Given that you mentioned
    the GE 645, Multics was most likely. If you only meant to talk
    about the 645, absent Multics, then one would be better off
    referring to GE documentation (which is available), not
    Organick.

    There is a C compiler for Multics; it predates the ANSI C
    standard, but post-dates typesetter C: it's close to what is
    described in K&R1, and I've heard it was a port of the compiler
    that shipped with SVR2. I don't know about the veracity of that
    claim, however.

    In any event, no C compiler ever ran on, or targeted, the 645
    specifically. However, C compilers _did_ run on and target the
    GE 635 during the timeframe in which the 645 was a going
    concern, including an early compiler written by Dennis Ritchie.
    I have never bothered to investigate how NULL was represented on
    that machine, but it is likely that programs compiled for the
    635 would run on the 645 in compatibility mode, if anyone ever
    tried.

    My personal example is the mikroC Pro for PIC32. It creates MIPS
    binaries, and is loaded directly on the microcontroller. There is
    no operating system to speak of.

    ...so?

    In another thread you accused me of having "rather higher opinion
    of his own knowledge and/or abilities than is actually warranted."

    Yes. This post to which I am responding is rather QED on that
    point, I'm afraid.

    Now I have the same opinion of you. You don't know what you're
    talking about, all the time.

    It is true that I do not always know what I am talking about.

    - Dan C.


    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Dan Cross@3:633/10 to All on Tuesday, July 28, 2026 19:08:51
    In article <JW3aS.7954$jNNe.443@fx15.ams4>,
    Johann 'Myrkraverk' Oskarsson <johann@myrkraverk.invalid> wrote:
    On 28/07/2026 11:08 PM, Scott Lurndal wrote:
    Johann 'Myrkraverk' Oskarsson <johann@myrkraverk.invalid> writes:
    On 28/07/2026 10:18 PM, Dan Cross wrote:
    In article <Q1Z9S.2212$jNNe.1884@fx15.ams4>,
    Johann 'Myrkraverk' Oskarsson <johann@myrkraverk.invalid> wrote:
    On 28/07/2026 9:18 AM, Scott Lurndal wrote:
    [snip]
    I'll add a sixth; while we looked at doing a C compiler,
    it didn't get any traction from customers

    - Burroughs Medium Systems (hardware nulptr = 0xEEEEEE)
    The architecture featured an instruction to search linked
    lists (SLT) and the hardware recognized the base offset
    0xEEEEEE as the NIL pointer. It was a BCD architecture
    addressed to the digit (nibble).


    I'd expect GE-645 to qualify too, but as far as I know, that
    also didn't get a C compiler. Right now I'm not pulling
    Organick off my shelf to see if I'm right, other people can
    do that.

    I assume you mean the Multics system. It did not have a C
    compiler on the 645, no. The follow-on , H6180 and DPS/8m, yes.

    In way yes, in a way no. As far as I know, Multics was the only
    operating system for the GE 645. Yet, you can create a C compiler
    for bare metal programmer. You probably don't have one, or you'd
    know this was possible.

    My personal example is the mikroC Pro for PIC32. It creates MIPS
    binaries, and is loaded directly on the microcontroller. There is
    no operating system to speak of.

    In another thread you accused me of having "rather higher opinion
    of his own knowledge and/or abilities than is actually warranted."

    As a disinterested bystander, I tend to agree with Dan in this
    case, as there is no evidence that any "standalone C compiler"
    was ever developed for the GE 645 as the C language did not
    exist at that point in time. BCPL, on the other hand....

    As an interested bystander, I just accuse you of not knowing how
    to read.

    The irony is deafening.

    I said, and it's quoted above, "as far as I know, that
    also didn't get a C compiler" which includes bare metal C comp-
    ilers, as well as Multics compilers. Neither got made.

    This is true. However, (again) since you mentioned Organick, it
    was reasonable to believe you were referring to Multics. In any
    even, no C compiler ever targeted the GE 645, specifically.

    By the way, why you thought that Organick would mention C
    compilers at all is a mystery; have you actually read it?

    And if you knew anything about Multics history, the last production
    Multics system was pulled offline in 2000. That was well after C
    compilers got popular. See

    So? As I mentioned elsewhere, at that point, Multics had a C
    compiler, though it's unlikely any GE 645 hardware was still in
    use running Multics by then.

    https://www.multicians.org/history.html

    Now, please direct yourself to the nearest reading comprehension
    program, and return when you've graduated junior high level reading.

    Those in glass houses....

    - Dan C.


    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Johann 'Myrkraverk' Oskarsson@3:633/10 to All on Wednesday, July 29, 2026 04:08:25
    On 29/07/2026 3:08 AM, Dan Cross wrote:
    In article <JW3aS.7954$jNNe.443@fx15.ams4>,
    Johann 'Myrkraverk' Oskarsson <johann@myrkraverk.invalid> wrote:
    On 28/07/2026 11:08 PM, Scott Lurndal wrote:
    Johann 'Myrkraverk' Oskarsson <johann@myrkraverk.invalid> writes:
    On 28/07/2026 10:18 PM, Dan Cross wrote:
    In article <Q1Z9S.2212$jNNe.1884@fx15.ams4>,
    Johann 'Myrkraverk' Oskarsson <johann@myrkraverk.invalid> wrote:
    On 28/07/2026 9:18 AM, Scott Lurndal wrote:
    [snip]
    I'll add a sixth; while we looked at doing a C compiler,
    it didn't get any traction from customers

    - Burroughs Medium Systems (hardware nulptr = 0xEEEEEE)
    The architecture featured an instruction to search linked >>>>>>> lists (SLT) and the hardware recognized the base offset
    0xEEEEEE as the NIL pointer. It was a BCD architecture
    addressed to the digit (nibble).


    I'd expect GE-645 to qualify too, but as far as I know, that
    also didn't get a C compiler. Right now I'm not pulling
    Organick off my shelf to see if I'm right, other people can
    do that.

    I assume you mean the Multics system. It did not have a C
    compiler on the 645, no. The follow-on , H6180 and DPS/8m, yes.

    In way yes, in a way no. As far as I know, Multics was the only
    operating system for the GE 645. Yet, you can create a C compiler
    for bare metal programmer. You probably don't have one, or you'd
    know this was possible.

    My personal example is the mikroC Pro for PIC32. It creates MIPS
    binaries, and is loaded directly on the microcontroller. There is
    no operating system to speak of.

    In another thread you accused me of having "rather higher opinion
    of his own knowledge and/or abilities than is actually warranted."

    As a disinterested bystander, I tend to agree with Dan in this
    case, as there is no evidence that any "standalone C compiler"
    was ever developed for the GE 645 as the C language did not
    exist at that point in time. BCPL, on the other hand....

    As an interested bystander, I just accuse you of not knowing how
    to read.

    The irony is deafening.

    I said, and it's quoted above, "as far as I know, that
    also didn't get a C compiler" which includes bare metal C comp-
    ilers, as well as Multics compilers. Neither got made.

    This is true. However, (again) since you mentioned Organick, it
    was reasonable to believe you were referring to Multics. In any
    even, no C compiler ever targeted the GE 645, specifically.

    By the way, why you thought that Organick would mention C
    compilers at all is a mystery; have you actually read it?

    Yes, at least most of it. I have a hardcopy in my shelf. You can
    probably borrow or maybe even download it by now from archive.org.

    Anyway, if you'd actually read Organick, you'd know he spends a lot
    of time at the start of the book [at least according to my memory]
    describing the hardware modifications that resulted in the GE 645
    from the prior -- I think it was GE 35 -- architecture.

    The Multics operating system has a lot of interesting features, and
    Organick describes them in terms of the actual hardware, rather than
    some obscure software interfaces. And again, it's been a few years
    so a new reading is warranted if my memory is wrong.


    And if you knew anything about Multics history, the last production
    Multics system was pulled offline in 2000. That was well after C
    compilers got popular. See

    So? As I mentioned elsewhere, at that point, Multics had a C
    compiler, though it's unlikely any GE 645 hardware was still in
    use running Multics by then.

    https://www.multicians.org/history.html

    Now, please direct yourself to the nearest reading comprehension
    program, and return when you've graduated junior high level reading.

    Those in glass houses....

    - Dan C.


    Yes, please direct yourself to the nearest reading comprehension
    program. You can join Scott in class, and be in the detention
    together!
    --
    Johann | email: invalid -> com | http://www.myrkraverk.com/blog/
    I'm not from the Internet, I just work there. | via Easynews.com

    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Johann 'Myrkraverk' Oskarsson@3:633/10 to All on Wednesday, July 29, 2026 04:10:53
    On 29/07/2026 2:58 AM, Dan Cross wrote:
    In article <tj3aS.40751$yWz9.13547@fx04.ams4>,
    Johann 'Myrkraverk' Oskarsson <johann@myrkraverk.invalid> wrote:
    On 28/07/2026 10:18 PM, Dan Cross wrote:
    [snip]
    I'd expect GE-645 to qualify too, but as far as I know, that
    also didn't get a C compiler. Right now I'm not pulling
    Organick off my shelf to see if I'm right, other people can
    do that.

    I assume you mean the Multics system. It did not have a C
    compiler on the 645, no. The follow-on , H6180 and DPS/8m, yes.

    In way yes, in a way no. As far as I know, Multics was the only
    operating system for the GE 645.

    First, this is not true. The GE 645 had a 635 compatibility
    mode that allowed it to boot the earlier GECOS operating system;
    for details, see the section, "Transition to the GE-645" at https://multicians.org/ge635.html.

    But that historical trivia aside, I don't see how it is relevant
    to your claim about nil pointers.

    Yet, you can create a C compiler
    for bare metal programmer. You probably don't have one, or you'd
    know this was possible.

    What does that have to do with anything?

    You mentioned Organick, so it seems reasonable you were asking
    about either Multics or FORTRAN (Organick wrote a very
    well-regarded book about FORTRAN-77). Given that you mentioned
    the GE 645, Multics was most likely. If you only meant to talk
    about the 645, absent Multics, then one would be better off
    referring to GE documentation (which is available), not
    Organick.

    There is a C compiler for Multics; it predates the ANSI C
    standard, but post-dates typesetter C: it's close to what is
    described in K&R1, and I've heard it was a port of the compiler
    that shipped with SVR2. I don't know about the veracity of that
    claim, however.

    In any event, no C compiler ever ran on, or targeted, the 645
    specifically. However, C compilers _did_ run on and target the
    GE 635 during the timeframe in which the 645 was a going
    concern, including an early compiler written by Dennis Ritchie.
    I have never bothered to investigate how NULL was represented on
    that machine, but it is likely that programs compiled for the
    635 would run on the 645 in compatibility mode, if anyone ever
    tried.

    My personal example is the mikroC Pro for PIC32. It creates MIPS
    binaries, and is loaded directly on the microcontroller. There is
    no operating system to speak of.

    ...so?

    In another thread you accused me of having "rather higher opinion
    of his own knowledge and/or abilities than is actually warranted."

    Yes. This post to which I am responding is rather QED on that
    point, I'm afraid.

    Now I have the same opinion of you. You don't know what you're
    talking about, all the time.

    It is true that I do not always know what I am talking about.

    - Dan C.


    Please direct yourself to the reading comprehension program,
    and learn what "as far as I know" means. You told me something
    I didn't know before, but you're being so much of an asshole
    I don't want to learn anything from you.

    So please go and subscribe to comp.lang.ml, and stay there!
    --
    Johann | email: invalid -> com | http://www.myrkraverk.com/blog/
    I'm not from the Internet, I just work there. | via Easynews.com

    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Chris M. Thomasson@3:633/10 to All on Tuesday, July 28, 2026 13:51:08
    On 7/28/2026 4:21 AM, Johann 'Myrkraverk' Oskarsson wrote:
    On 28/07/2026 6:22 PM, Keith Thompson wrote:
    Johann 'Myrkraverk' Oskarsson <johann@myrkraverk.invalid> writes:
    On 28/07/2026 8:59 AM, Keith Thompson wrote:
    Johann 'Myrkraverk' Oskarsson <johann@myrkraverk.invalid> writes:
    On 28/07/2026 3:58 AM, Keith Thompson wrote:
    You'd have to create a compiler, or modify an existing one, to use a >>>>>> non-zero representation for null pointers.ÿ That's hardly "trivial". >>>>>> (For example, default initialization for static objects could no
    longer just zero the target object.)

    [SNIP]
    Your ASCII art has nothing to do with the current discussion (and
    didn't even display correctly due to line wrapping).ÿ I've dropped
    the cross-post to alt.ascii-art.ÿ Can you please try to focus?

    Nope.ÿ You've absolutely demonstrated that you have no idea what
    is involved in the internals of a C compiler.ÿ What you're trying
    to describe is just hogwash and balderdash when it comes to the
    real world nitty gritty of implementing a compiler.

    Now stay out of my compiler discussion, you're not wanted here!

    Perhaps the rest of the group would be interested in knowing just
    what I got so badly wrong.


    I'll summarize it as: You think I care what the C standard documents
    say.ÿ I don't.

    I care how it's implemented.ÿ You don't.


    Huh?

    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Chris M. Thomasson@3:633/10 to All on Tuesday, July 28, 2026 13:54:49
    On 7/28/2026 12:42 AM, Johann 'Myrkraverk' Oskarsson wrote:
    On 28/07/2026 8:59 AM, Keith Thompson wrote:
    Johann 'Myrkraverk' Oskarsson <johann@myrkraverk.invalid> writes:
    On 28/07/2026 3:58 AM, Keith Thompson wrote:
    You'd have to create a compiler, or modify an existing one, to use a
    non-zero representation for null pointers.ÿ That's hardly "trivial".
    (For example, default initialization for static objects could no
    longer just zero the target object.)

    [SNIP]

    Your ASCII art has nothing to do with the current discussion (and
    didn't even display correctly due to line wrapping).ÿ I've dropped
    the cross-post to alt.ascii-art.ÿ Can you please try to focus?


    Nope.ÿ You've absolutely demonstrated that you have no idea what
    is involved in the internals of a C compiler.ÿ What you're trying
    to describe is just hogwash and balderdash when it comes to the
    real world nitty gritty of implementing a compiler.

    Now stay out of my compiler discussion, you're not wanted here!



    say NULL is (void*)0xDEADBEEF, or whatever, for a system. Then for a
    pointer type,

    if (! p) { }

    is basically converted to:

    if (p == NULL) { }

    See?

    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Johann 'Myrkraverk' Oskarsson@3:633/10 to All on Wednesday, July 29, 2026 04:56:36
    On 28/07/2026 4:36 AM, BGB wrote:
    On 7/27/2026 11:22 AM, Johann 'Myrkraverk' Oskarsson wrote:
    On 27/07/2026 9:33 PM, James Kuyper wrote:
    On 2026-07-25 12:30, Waldek Hebisch wrote:
    David Brown <david.brown@hesbynett.no> wrote:
    The evaluation of "!p" is based on a comparison of "p" to the
    value 0 -
    it will be true if p contains the value 0. ...

    False.

    ... (This point is perhaps the
    weak link in my reasoning - maybe "!p" is only true if "p" is
    actually a
    null pointer. ...

    Correct.

    ... However, I am confident that all real implementations,
    where null pointers are value 0, ...

    I doubt that - if it were true, there would be pressure on the committee >>> to mandate such a representation.

    ...ÿ act by comparison of the value.)
    As far as I can see, therefore, "p" can be a valid pointer to an
    object
    of appropriate type, not be a null pointer, and still have value 0 and >>>>> have "!p" evaluate to 1.
    I did not check the standard, but if the standard allows this, then
    this is clearly a bug in the standard.

    The standard is quite clear. !p returns 0 if p compares equal to 0, and
    1 otherwise. When a pointer is compared with 0, the 0 getsÿ implicitly
    converted to a null pointer of the same type. All null pointers compare
    equal. Therefore !p tests whether 0 is a null pointer, not whether it
    has a representation with all bits cleared.


    I'm not in a position to do so yet, but you can trivially test this by
    implementing "the null pointer" to be internally represented by 0xff..ff
    where the .. represents your bitwith; 16bit, 32bit, 64bit, 128bit.

    Then one of the first thing you notice, is that the integer zero needs
    to convert to the 0xff..ff bit pattern when cast to a pointer.ÿ After
    that, implementing !p is trivial, for some value of trivial.


    FWIW:
    In my ISA's, it is possible to have NULL pointers with not-all-bits 0.


    Say, typical pointer layout:
    ÿ (47: 0): Address
    ÿ (63:48): Tag Bits
    You could in theory have 0 base address, and non-zero tag.

    Though, in the default mode, the compiler assumes canonical C data
    pointers will always have the tag bits set to 0, so all bits 0 is the canonical NULL.

    Note that normal memory loads/stores ignore the high 16 bits, so (unlike
    on x86-64 or similar) don't need to manually clear them first (and also
    less need to rely on the graceful assumption of the OS not putting
    anything outside of the 48-bit range; but other schemes like NaN Boxing would also run into a big headache here).

    Ignoring the high bits, and having instructions like LEA set them to 0,
    etc, were fairly deliberate design choices.

    Isn't that how early ARM code worked? As far as I understood some line
    noise I was reading until recently, a large part of why early 26bit code
    on the early ARM machines didn't work when they introduced 32bit virtual
    or physical address mode* was people using the upper bits of the pointer
    for some tag or other.

    This was in the context of RISC OS, and not modern operating systems
    like Linux or NetBSD.

    * I forgot the exact specifics, the incompatibility could have been
    something else. I'm sure Dan Cross is just itching to correct me.


    As I see it, we are still fairly far from traditional programs being
    cramped by a 48-bit VAS (and, at this scale, tag bits were a more
    compelling use-case than having a bigger VAS).

    Note: VAS = Virtual Address Space.


    Though, it is possible I could consider allowing/defining a sub-mode
    that shrinks the tag to 8 bits, if needed.

    There was previously stuff to support a 32-bit VAS, but this has mostly fallen into disuse (the relative memory savings of programs with 32-bit pointers isn't enough in general to justify the added hassle of
    supporting programs with 32-bit pointers; even if as-is, only a small minority of the programs would need more than 32 bits of VAS).



    Other contents depend on context:
    ÿ LR and Function Pointers: Include some captured mode-state bits;
    ÿÿÿ Applicable if LSB is Set;
    ÿÿÿ Encodes things like which ISA the CPU is running.
    ÿ General Data Pointers:
    ÿÿÿ Typically used as type-tag bits;
    ÿÿÿ Have been used for bounds-check metadata in some cases;
    ÿÿÿ For internal use in my OpenGL impl, they hold texture type/size.

    Where, type tags are sorta like (high 4 bits):
    ÿ 0000: Object Pointers, 12b = object type key
    ÿ 0001: Misc small values
    ÿ 0010: Bounds checked pointers
    ÿ 0011: Bounds checked pointers
    ÿ 01xx: Fixnum (62-bit signed integer value)
    ÿ 10xx: Flonum (62-bit floating point, Binary64 shifted right 2 bits)
    ÿ 110x: Densely packed Vectors (2/3 element, FPU)
    ÿ 1110: Pointer with type-tagging (direct encoded or signature index)
    ÿÿÿ 00dd-tttt-tttt: d=*/**/***/****, t=base type index
    ÿÿÿ 1xxx-xxxx-xxxx: Signature String Index
    ÿ 1111: Pointer, alt, may encode a 60-bit address.

    This stuff is mostly relevant to a dynamically typed code.
    There are extensions to C to support dynamic types, but this is used sparingly. They are inherently slower than the normal static types, as
    well as being non-portable.


    There are cases where it makes sense to use dynamic types though.
    Technically it also allows my compiler to compile a JavaScript variant, though it isn't an exact match for the normal JS (its JS mode is
    effectively just a static-compiled version of my older BGBScript
    Language). Syntax is sorta like ES3 + other stuff; a fair bit somewhat resembles the abandoned ES4 spec as well.

    Can note that both BGBScript and BGBScript2 had ended up using hybrid
    type models:
    ÿ Core is static typed;
    ÿ May use dynamic types as needed:
    ÿÿÿ BS: via lack of explicit type;
    ÿÿÿ BS2: if static type was the dynamic type (variant).
    ÿ For BS, compiler may infer types via type inference.
    ÿÿÿ This was used in my VM, but not currently used by BGBCC.
    ÿÿÿ BS2 had an 'auto' type, but in BGBCC, is the same as 'variant'.

    In this implementation, both share the same toplevel as C land.
    Canonically, one needs a 'native' keyword for imports/exports, but, yeah...

    In BS, say:
    ÿ native function Foo(x:int):int;ÿ //import Foo from C land
    ÿ native function Bar(x:int, y:int):intÿ //exported to C
    ÿÿÿ { return x+y; }
    Or, BS2:
    ÿ native int Foo(int x);ÿ //import Foo from C land
    ÿ native int Bar(int x, int y)ÿ //exported to C
    ÿÿÿ { return x+y; }

    In BGBCC, it doesn't matter, as it is assumed for toplevel decls. Implicitly, in this case, the toplevel also disallows overloading
    (relevant to BS2 and the experimental C++ mode). For sake of the C++
    mode, it is like the toplevel always has 'extern "C"' applied
    (internally, 'extern "C"' having just been mapped back to the NATIVE flag).


    Did you remember to support

    extern "C" foo ; // ?

    I found that bug in the then-Sun C++ compiler. They had forgotten to
    support a single statement extern "C", and only supported it in curly
    braces. By the time Oracle took over and closed all access to non-
    paying customers, the bug was still unfixed; or so I believed based on
    the bug report followup emails.

    Some people later told me Sun never had competent compiler team, and
    I believe Oracle doesn't retain the quality people, that situation
    almost certainly got worse. I have no reason to believe this bug
    has been fixed.



    But, yeah, in the C dialect, it mostly takes the form of:
    ÿ __variant x;ÿ //dynamically typed
    ÿ x=3;ÿÿÿÿÿÿÿÿÿ //3 converted to fixnum
    ÿ x=3.14159;ÿÿÿ //converted to flonum
    ÿ x=(__variant) { .foo=3; .bar=4; }; //ex-nihilo object
    ÿ ...
    Then you could type-check pointers, say:
    ÿ if(x __instanceof __fixint)
    ÿÿÿ { do something with a fixnum ...}

    Syntax in BS2 is similar, just without the '__' on the keywords.

    Note that, in this implementation, BS and BS2 can also still use the C preprocessor, etc.


    Note that for a common subset of common types, the C compiler and C
    runtime need to be kept in sync regarding the initial set of dynamic
    types (such that the compiler knows the type-tags in advance); but other type-tags may appear dynamically at runtime.

    Note also, unlike what may be implied in some languages, it doesn't
    create a new type tag for every type of class/interface, rather it would merely tag that it is a pointer to a class/interface, and "instanceof"
    would then use a different mechanism to check class types. When compiler does its things, the first pointer in the VTable points to a metadata structure describing the class and its contents. Theoretically, the
    class member lists could be used to implement an object serialization
    thing, but currently TestKern doesn't have this.

    I think I had considered something like this to allow for potential non- local COM, but it hasn't been done yet (no network, so no need for non- local RPC).

    Do you have a good source for implementing COM? And not just to use the Microsoft, nor Mozilla's XPCOM implementations? I currently have the
    books

    * OLE Controls Inside Out, and
    * Inside DirectX,

    but am wondering if better books are out there? I prefer to read hard-
    copies when available.



    Well, also the other tradeoff for how to serialize:
    ÿ Dump raw structs, with sizes, and pointers relative to a base address.
    ÿÿÿ ( Roughly the route DCOM/OLE went IIRC. )
    ÿ Binary TLV style structure;
    ÿÿÿ (More flexible than the above, but more overhead).
    ÿ ASCII based structure (say, something JSON-like).
    ÿÿÿ Or, S-Expressions, or whatever else.
    ÿÿÿ Human readable text, but needs printer/parser.

    Usually, object serialization is "not the right tool for the job" though.

    Personally, I don't really use "object serialization" anymore. I prefer
    to just dump everything into SQLite. Especially the session management
    data for -- hopefully -- crash resilience.



    Note: metadata differs from that defined for the IA64 C++ ABI (for
    RTTI), which IIRC was merely able to identify the object, but would lack
    the metadata to describe said object's contents/layout (so couldn't be
    used for automatic serialization; or reflective/dynamic-typed access to static-typed objects, ...).

    Note 2: Class/Instance and ex-Nihilo objects were separate entity sub-
    types (the ex-nihilo objs containing an associative key/value
    structure). The languages supported "dynamic" objects (with a class/ instance part, and supporting dynamically extending with new fields);
    these existed as Class/Instance objects with an internal optional ex-
    nihilo object glued on (NULL if no extra members were used).

    In either case, C/I object layouts were based on appending subclass
    contents onto the end (like a narrow sub-case of traditional C++ object layout), rather than any more complex "dynamic" mechanisms. In effect, things like interfaces were also handled by basically just appending
    vtable pointers onto the end of the object's layout (so casting a class
    to an implemented interface is basically just adding the interface
    pointer's offset to that of the base class; interface VT effectively encoding another offset that is added to itself to get the 'this'
    pointer for the base-class).



    Cases where the dynamic type-system have been used had mostly been
    limited to things like small specialized script interpreters and
    similar. Like, there is a BASIC interpreter for a 1980s style dialect.


    Note however, there is no garbage collector...
    So, if code is written that assumes that dynamic types means the freedom
    to generate lots of garbage and have the GC deal with it, this is not
    the case here.


    But, yeah, all the fun corners one can get stuck in...

    You're aware of the Ravenbrook Memory Pool System, right? Still
    available at

    https://github.com/Ravenbrook/mps

    even though ravenbrook.com seems gone. I'll probably use this
    as my first garbage collector, though I am open to "write my own"
    as a practice.


    Is the MPS something you'd consider in your projects?
    --
    Johann | email: invalid -> com | http://www.myrkraverk.com/blog/
    I'm not from the Internet, I just work there. | via Easynews.com

    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Johann 'Myrkraverk' Oskarsson@3:633/10 to All on Wednesday, July 29, 2026 05:00:21
    On 29/07/2026 4:54 AM, Chris M. Thomasson wrote:
    On 7/28/2026 12:42 AM, Johann 'Myrkraverk' Oskarsson wrote:
    On 28/07/2026 8:59 AM, Keith Thompson wrote:
    Johann 'Myrkraverk' Oskarsson <johann@myrkraverk.invalid> writes:
    On 28/07/2026 3:58 AM, Keith Thompson wrote:
    You'd have to create a compiler, or modify an existing one, to use a >>>>> non-zero representation for null pointers.ÿ That's hardly "trivial". >>>>> (For example, default initialization for static objects could no
    longer just zero the target object.)

    [SNIP]

    Your ASCII art has nothing to do with the current discussion (and
    didn't even display correctly due to line wrapping).ÿ I've dropped
    the cross-post to alt.ascii-art.ÿ Can you please try to focus?


    Nope.ÿ You've absolutely demonstrated that you have no idea what
    is involved in the internals of a C compiler.ÿ What you're trying
    to describe is just hogwash and balderdash when it comes to the
    real world nitty gritty of implementing a compiler.

    Now stay out of my compiler discussion, you're not wanted here!



    say NULL is (void*)0xDEADBEEF, or whatever, for a system. Then for a
    pointer type,

    if (! p) { }

    is basically converted to:

    if (p == NULL) { }

    See?

    Now implement

    if ( p )

    as not

    if ( ( int ) p ) ; // !

    See?

    --
    Johann | email: invalid -> com | http://www.myrkraverk.com/blog/
    I'm not from the Internet, I just work there. | via Easynews.com

    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From BGB@3:633/10 to All on Tuesday, July 28, 2026 16:02:12
    On 7/28/2026 8:17 AM, bart wrote:
    On 28/07/2026 13:18, Johann 'Myrkraverk' Oskarsson wrote:
    On 28/07/2026 8:02 PM, bart wrote:


    In any case, there is no conversion to int involved; it is just has
    to implement that comparison by whatever means works.


    On the other hand, to the programmer, especially those not who have not
    and never will read the C language standard, it does look like the p
    pointer is "truncated" to an integer valued 0 or 1, in boolean context.

    It doesn't look like that all. This is just testing for 'truthiness',
    which in many languages can be applied to data types such as strings or lists.

    'Truncation' doesn't work either: you can have all-bits-zero NULL, and a value for 'p' of 0x123456 - truncating to one bit would give you zero,
    which is false.

    There is also rarely any result - no value needs to be yielded when the result just affects control-flow.


    In practice, it is mostly come sort of "compare with 0", of some sort.


    In my case, typically a 64-bit compare with 0 is used, as while tagged
    NULLs could exist in theory, by default they don't, and it is not worth
    the added cost of dealing with them (either in software or hardware).

    I once did experiment with supporting a few 48-bit ALU ops specifically
    for pointers, but these worked out as "deceptively expensive" for the
    CPU core. So, this idea was soon dropped (well, along with 48-bit
    Load/Store with a 16-bit index scale; was overly niche and too expensive
    to justify that niche).


    Eventually noted, the main winning strategy is mostly to just use native 64-bit compare ops for everything. Earlier forms of the ISA had 32-bit
    compare ops that ignored the high 32 bits, but these are effectively
    gone in the newer variant.

    Even if it does mean that in cases where one wants to ignore the high 16
    bits, it is necessary to sign or zero extend from 48 bits. In the
    default mode, my compiler does not do so, so using tagging bits may
    interfere with pointer comparison. Most cases where tagging are used
    though are not places where the pointers are likely to be compared though.


    Except well, in the experimental bounds-checked mode, which had more
    expensive multi-op zero-extending pointer compares (as otherwise the bounds-check tagging would unleash chaos).

    Did create another mess:
    Pointer subtract also needs to sign-extend;
    Casting pointers to long needing to truncate the high bits;
    ...

    This created a hassle for code that needed to twiddle the tag bits
    though, so, say:
    x=(long)((__m64)(ptr));
    With casting via __m64 as a "just give me the raw bits" thing, where
    __m64 and __m128 were understood as opaque "bag of bits" types, which
    lack any operations of their own, but can be used to do raw bit-casts in
    other cases where the cast would have modified the type.

    In this compiler:
    memcpy(&x, &y, sizeof(T)); //not ideal, various drawbacks
    x=*(T*)(&y); //less bad, still not ideal
    x=(T)((__mXX)y); //usually the fastest, bare register MOV.

    Contrast with MSVC which treated __m64/__m128 as "some sort of unholy
    hackery masquerading as a struct", personally I think "type whose sole
    purpose is to be N bits" is a more sensible interpretation.

    Then added __m32 and __m16 for other cases where I felt a need for
    raw-bit casting. No "__m8" though mostly for lack of relevant types to
    cast between (no useful distinction from "unsigned char" or similar).


    They also still have tie-ins with SIMD, though had mostly ended up
    taking a different approach (from either MSVC or GCC), and instead
    effectively bolting GLSL style vectors onto C.

    Where, the GLSL approach seemed closest to my typical use-cases.

    Or, say, vector types:
    __vec2f, __vec3f, __vec4f //2/3/4 element, float
    __vec2d, __vec3d, __vec4d //2/3/4 element, double
    __vec2h. __vec3h, __vec4h //2/3/4 element, half / "short float"
    __vec2sf. __vec3sf, __vec4sf //2/3/4 element, half, same as above
    __quatf, __quatd //quaternion
    Where, types:
    float : 32-bit, S.E8.M23
    double : 64-bit, S.E11.M52
    short float : 16-bit, S.E5.M10
    long double : 128-bit, S.E15.M112
    Where, vectors have elements: x/y/z/w.
    Multiple or repeated elements gives a new vector or shuffle.
    '_' is a filler spot containing 0.

    "_Complex float" and "_Complex double" also exists as sub-types of
    vectors (or quaternions), where complex has i/r members (equiv x/y), and quaternions have i/j/k/r (equiv x/y/z/w). Convention would tend to put
    'r' as the first component, but putting 'r' at the end works better for reusing existing SIMD handling. At present, these lack native "short
    float" or "long double" variants.


    Typical SIMD operators being, mostly:
    +, -: Pairwise add/sub
    *: Pairwise mul (vec), complex/quaternion product
    /: Pairwise div (vec), complex/quaternion division
    ^: Dot Product (scalar result)
    %: Cross Product (scalar for vec2, vector otherwise)

    Also sorta supports GCC style "__attribute__((vector_size(N)))" style
    SIMD as well.


    Well, I will probably stop here...

    ...


    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Johann 'Myrkraverk' Oskarsson@3:633/10 to All on Wednesday, July 29, 2026 05:54:58
    On 29/07/2026 5:35 AM, Steven M. O'Neill wrote:
    Pardon the top-posting. Just wanted to let you know that this
    didn't display well on my particular setup, as my newsreading
    software is set up to wrap at around 80 columns. IIRC this is
    (or used to be at least) the accepted limit for line lengths.

    Apologies. I did this in my text editor, and neglected to notice
    the longest line is 81 columns. My editor says the end of the line
    is at 80, but starts the column count at zero. I'm unsure if that
    should be counted as 80 columns, or 81. Is the following any better
    for you? The original displays correctly for me, in my newsreader.

    __ .----.__ .________________________________________.
    -' `/(#)#(#) `- . o O ( I tend to assume people are more
    capable )
    ` (#)#(#) \ ___ ^( than they actually are, because it )^^
    __( \ ,,,/ `. ``. ( makes them either feel better, or
    some- )
    / \ \,-/ | \ ( times they rise to the challenge! )^^^^^^
    | `-- ( ( /__ ( ^^^( )^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    ` ( `---\ `---._` ( } (^^
    ^^)______________________________.
    | | \ `----._`.`. .' ( For instance, this is an old ASCII
    art )
    . ( `-) \ `. )) ) | ( horror I made several years ago.
    )^^^
    / \ / / ) / { ( I got semi-famous for my
    collection )
    / \ / ( | ( ( of non-trivial ASCII art. )^^^^^^^^^^
    / ,\ /\\\ ( |_ \ ( And I made all of it by hand,
    including )
    / /\( ) / .`\\\ ^^( this thought bubble.
    )^^^^^^^^^^^^^^
    \\/ \ .-' | | -_ ^^^^^^^^^^^^^^^^^^^^^^^
    \ .'___( ) \ `-.
    -' \/\___\\__\

    My newsreader garbleds the paste in the newsreader-editor, but appears
    to post correctly anyway. You can also see the image sans thought
    bubble at

    https://asciiart.website/search.php?q=myrkraverk&sort_by=random

    and my own website.

    Cross posting to comp.lang.c as well, for Keith.


    --
    Johann | email: invalid -> com | http://www.myrkraverk.com/blog/
    I'm not from the Internet, I just work there. | via Easynews.com

    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Dan Cross@3:633/10 to All on Tuesday, July 28, 2026 22:15:24
    In article <%_7aS.8605$jNNe.4295@fx15.ams4>,
    Johann 'Myrkraverk' Oskarsson <johann@myrkraverk.invalid> wrote:
    On 29/07/2026 3:08 AM, Dan Cross wrote:
    In article <JW3aS.7954$jNNe.443@fx15.ams4>,
    Johann 'Myrkraverk' Oskarsson <johann@myrkraverk.invalid> wrote:
    On 28/07/2026 11:08 PM, Scott Lurndal wrote:
    Johann 'Myrkraverk' Oskarsson <johann@myrkraverk.invalid> writes:
    On 28/07/2026 10:18 PM, Dan Cross wrote:
    In article <Q1Z9S.2212$jNNe.1884@fx15.ams4>,
    Johann 'Myrkraverk' Oskarsson <johann@myrkraverk.invalid> wrote: >>>>>>> On 28/07/2026 9:18 AM, Scott Lurndal wrote:
    [snip]
    I'll add a sixth; while we looked at doing a C compiler,
    it didn't get any traction from customers

    - Burroughs Medium Systems (hardware nulptr = 0xEEEEEE)
    The architecture featured an instruction to search linked >>>>>>>> lists (SLT) and the hardware recognized the base offset >>>>>>>> 0xEEEEEE as the NIL pointer. It was a BCD architecture >>>>>>>> addressed to the digit (nibble).


    I'd expect GE-645 to qualify too, but as far as I know, that
    also didn't get a C compiler. Right now I'm not pulling
    Organick off my shelf to see if I'm right, other people can
    do that.

    I assume you mean the Multics system. It did not have a C
    compiler on the 645, no. The follow-on , H6180 and DPS/8m, yes.

    In way yes, in a way no. As far as I know, Multics was the only
    operating system for the GE 645. Yet, you can create a C compiler
    for bare metal programmer. You probably don't have one, or you'd
    know this was possible.

    My personal example is the mikroC Pro for PIC32. It creates MIPS
    binaries, and is loaded directly on the microcontroller. There is
    no operating system to speak of.

    In another thread you accused me of having "rather higher opinion
    of his own knowledge and/or abilities than is actually warranted."

    As a disinterested bystander, I tend to agree with Dan in this
    case, as there is no evidence that any "standalone C compiler"
    was ever developed for the GE 645 as the C language did not
    exist at that point in time. BCPL, on the other hand....

    As an interested bystander, I just accuse you of not knowing how
    to read.

    The irony is deafening.

    I said, and it's quoted above, "as far as I know, that
    also didn't get a C compiler" which includes bare metal C comp-
    ilers, as well as Multics compilers. Neither got made.

    This is true. However, (again) since you mentioned Organick, it
    was reasonable to believe you were referring to Multics. In any
    even, no C compiler ever targeted the GE 645, specifically.

    By the way, why you thought that Organick would mention C
    compilers at all is a mystery; have you actually read it?

    Yes, at least most of it. I have a hardcopy in my shelf. You can
    probably borrow or maybe even download it by now from archive.org.

    Anyway, if you'd actually read Organick, you'd know he spends a lot
    of time at the start of the book [at least according to my memory]
    describing the hardware modifications that resulted in the GE 645
    from the prior -- I think it was GE 35 -- architecture.

    ...and what does that have to do with how C compilers represent
    NULL pointers in compiled code?

    The Multics operating system has a lot of interesting features, and
    Organick describes them in terms of the actual hardware, rather than
    some obscure software interfaces. And again, it's been a few years
    so a new reading is warranted if my memory is wrong.

    I do not believe you have actually read it. You keep jumping
    between tioics, and seem really confused.

    And if you knew anything about Multics history, the last production
    Multics system was pulled offline in 2000. That was well after C
    compilers got popular. See

    So? As I mentioned elsewhere, at that point, Multics had a C
    compiler, though it's unlikely any GE 645 hardware was still in
    use running Multics by then.

    https://www.multicians.org/history.html

    Now, please direct yourself to the nearest reading comprehension
    program, and return when you've graduated junior high level reading.

    Those in glass houses....

    Yes, please direct yourself to the nearest reading comprehension
    program. You can join Scott in class, and be in the detention
    together!

    Are you an LLM? You sound like an LLM. Happy inferring!

    - Dan C.


    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Dan Cross@3:633/10 to All on Tuesday, July 28, 2026 22:20:59
    In article <h18aS.8606$jNNe.7242@fx15.ams4>,
    Johann 'Myrkraverk' Oskarsson <johann@myrkraverk.invalid> wrote:
    On 29/07/2026 2:58 AM, Dan Cross wrote:
    In article <tj3aS.40751$yWz9.13547@fx04.ams4>,
    Johann 'Myrkraverk' Oskarsson <johann@myrkraverk.invalid> wrote:
    On 28/07/2026 10:18 PM, Dan Cross wrote:
    [snip]
    I'd expect GE-645 to qualify too, but as far as I know, that
    also didn't get a C compiler. Right now I'm not pulling
    Organick off my shelf to see if I'm right, other people can
    do that.

    I assume you mean the Multics system. It did not have a C
    compiler on the 645, no. The follow-on , H6180 and DPS/8m, yes.

    In way yes, in a way no. As far as I know, Multics was the only
    operating system for the GE 645.

    First, this is not true. The GE 645 had a 635 compatibility
    mode that allowed it to boot the earlier GECOS operating system;
    for details, see the section, "Transition to the GE-645" at
    https://multicians.org/ge635.html.

    But that historical trivia aside, I don't see how it is relevant
    to your claim about nil pointers.

    Yet, you can create a C compiler
    for bare metal programmer. You probably don't have one, or you'd
    know this was possible.

    What does that have to do with anything?

    You mentioned Organick, so it seems reasonable you were asking
    about either Multics or FORTRAN (Organick wrote a very
    well-regarded book about FORTRAN-77). Given that you mentioned
    the GE 645, Multics was most likely. If you only meant to talk
    about the 645, absent Multics, then one would be better off
    referring to GE documentation (which is available), not
    Organick.

    There is a C compiler for Multics; it predates the ANSI C
    standard, but post-dates typesetter C: it's close to what is
    described in K&R1, and I've heard it was a port of the compiler
    that shipped with SVR2. I don't know about the veracity of that
    claim, however.

    In any event, no C compiler ever ran on, or targeted, the 645
    specifically. However, C compilers _did_ run on and target the
    GE 635 during the timeframe in which the 645 was a going
    concern, including an early compiler written by Dennis Ritchie.
    I have never bothered to investigate how NULL was represented on
    that machine, but it is likely that programs compiled for the
    635 would run on the 645 in compatibility mode, if anyone ever
    tried.

    My personal example is the mikroC Pro for PIC32. It creates MIPS
    binaries, and is loaded directly on the microcontroller. There is
    no operating system to speak of.

    ...so?

    In another thread you accused me of having "rather higher opinion
    of his own knowledge and/or abilities than is actually warranted."

    Yes. This post to which I am responding is rather QED on that
    point, I'm afraid.

    Now I have the same opinion of you. You don't know what you're
    talking about, all the time.

    It is true that I do not always know what I am talking about.

    Please direct yourself to the reading comprehension program,
    and learn what "as far as I know" means. You told me something
    I didn't know before, but you're being so much of an asshole
    I don't want to learn anything from you.

    So please go and subscribe to comp.lang.ml, and stay there!

    You keep telling other people what to do, and yet you yourself
    do not seem to know what you are talking about. My guess at
    this point is that your posts are actually actually the slop
    output of some bad LLM. Anyway, I'm sure everyone here is
    waiting with baited breath for your vibe-coded compiler. ;-}

    - Dan C.


    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Dan Cross@3:633/10 to All on Tuesday, July 28, 2026 22:26:16
    Oh, and....

    In article <%_7aS.8605$jNNe.4295@fx15.ams4>,
    Johann 'Myrkraverk' Oskarsson <johann@myrkraverk.invalid> wrote:
    [snip]
    Anyway, if you'd actually read Organick, you'd know he spends a lot
    of time at the start of the book [at least according to my memory]
    describing the hardware modifications that resulted in the GE 645
    from the prior -- I think it was GE 35 -- architecture.

    You think wrong.

    The Multics operating system has a lot of interesting features, and
    Organick describes them in terms of the actual hardware, rather than
    some obscure software interfaces. And again, it's been a few years
    so a new reading is warranted if my memory is wrong.

    Your memory is wrong. Perhaps you should compact your context
    window?

    Happy model training!

    - Dan C.


    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Johann 'Myrkraverk' Oskarsson@3:633/10 to All on Wednesday, July 29, 2026 06:41:59
    On 29/07/2026 6:26 AM, Dan Cross wrote:
    Oh, and....

    In article <%_7aS.8605$jNNe.4295@fx15.ams4>,
    Johann 'Myrkraverk' Oskarsson <johann@myrkraverk.invalid> wrote:
    [snip]
    Anyway, if you'd actually read Organick, you'd know he spends a lot
    of time at the start of the book [at least according to my memory]
    describing the hardware modifications that resulted in the GE 645
    from the prior -- I think it was GE 35 -- architecture.

    You think wrong.

    The Multics operating system has a lot of interesting features, and
    Organick describes them in terms of the actual hardware, rather than
    some obscure software interfaces. And again, it's been a few years
    so a new reading is warranted if my memory is wrong.

    Your memory is wrong. Perhaps you should compact your context
    window?

    Happy model training!

    - Dan C.


    Oh piss off in comp.lang.ml. You don't even know what that group is
    for, you moron! And while you're there, have yourself checked for
    amnesia.


    https://www.dropbox.com/scl/fi/oqfe1pkhz271ig90qxncc/organick-diagram-one.jpg?rlkey=90y9g8eyt4ep6rim1g3sj0a6p&st=y60c37wk&dl=0

    --
    Johann | email: invalid -> com | http://www.myrkraverk.com/blog/
    I'm not from the Internet, I just work there. | via Easynews.com

    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Keith Thompson@3:633/10 to All on Tuesday, July 28, 2026 16:08:16
    bart <bc@freeuk.com> writes:
    On 28/07/2026 12:21, Johann 'Myrkraverk' Oskarsson wrote:
    On 28/07/2026 6:22 PM, Keith Thompson wrote:
    Johann 'Myrkraverk' Oskarsson <johann@myrkraverk.invalid> writes:
    On 28/07/2026 8:59 AM, Keith Thompson wrote:
    Your ASCII art has nothing to do with the current discussion (and
    didn't even display correctly due to line wrapping).ÿ I've dropped
    the cross-post to alt.ascii-art.ÿ Can you please try to focus?

    Nope.ÿ You've absolutely demonstrated that you have no idea what
    is involved in the internals of a C compiler.

    You don't seem to have much idea either.

    ÿ What you're trying
    to describe is just hogwash and balderdash when it comes to the
    real world nitty gritty of implementing a compiler.

    Now stay out of my compiler discussion, you're not wanted here!

    Perhaps the rest of the group would be interested in knowing just
    what I got so badly wrong.

    I'll summarize it as: You think I care what the C standard documents
    say.ÿ I don't.
    I care how it's implemented.ÿ You don't.

    This is what you wrote:

    "I think more of it like an arm-chair thought exercise. The issue
    isn't the bit pattern at all, but how you're going to treat conversion
    to integers. Especially in this context,

    if ( p ) { ... }

    a 64bit p with the pattern 0x..00000000 isn't supposed to be false,
    when truncated to an integer...."

    Suppose p is pointer, pointers are 64 bits, and int is 32 bits.
    I think Johann somehow has the idea that `if ( p )` truncates the
    pointer value to an integer, presumably to an int.

    If null pointers are all-bits-zero, "truncating" a pointer to an
    int would presumably discard 32 of its 64 bits. If the remaining 32
    bits happen to be zero while the discarded bits are non-zero, then
    the truncation would incorrectly indicate that p is a null pointer.
    Johann likely didn't think through the consequences of what he wrote.

    Of course that's not how it's done.

    You don't say what type 'p' is, but I assume it is a pointer.

    How a compiler implements this depends partly on what the language
    says, for example:

    Scalar type Value True when

    integer i i != 0
    float x x != 0.0 (-0.0 may need considering)

    "Positive zeros compare equal to negative zeros."

    So a compiler generating code for `x != 0.0` must do whatever is
    necessary for positive and negative zeros to be treated as equal.
    It's likely (I think) that the target system's FP comparison operator
    will do the right thing.

    For floating-point x, `if (x)`, is equivalent to `if (x != 0)`.
    The implicit int constant `0` is converted to the type of x, making
    it equivalent to `if (x != 0.0)`, or `if (x != 0.0F)`, or
    `if (x != 0.0L)`. (There might be corner cases where x is very close
    to zero and the distinction between 0.0F, 0.0, and 0.0L matters,
    but I haven't thought it through.)

    pointer p p != NULL

    Whether NULL is all-bits-zero depends on the implementation, but I
    think it it more platform-dependent rather than left to individual
    compilers.

    Pretty much.

    As far as the C standard is concerned, it's up to the implementation.

    Compilers are likely to conform to the platform ABI, which is likely
    to specify the representation of a null pointer. A compiler *could*
    violate the ABI, but it would generate code that, as you point out,
    doesn't interact correctly with code generated by other compilers.

    Since in that case code from different compilers would be
    incompatible, and calling into external libraries would be
    problematical.

    In any case, there is no conversion to int involved; it is just has to implement that comparison by whatever means works.

    Right. In the abstract machine, p is compared to 0, and the rules
    for evaluating `p == 0` imply that `if (p)` tests whether p is a
    null pointer.

    But I don't do ASCII art, so what do I know?

    (Much of this is directed to Johann, but I'm not inclined to reply
    to him directly.)

    --
    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.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Dan Cross@3:633/10 to All on Tuesday, July 28, 2026 23:23:58
    In article <ZeaaS.63044$9jNc.53839@fx16.ams4>,
    Johann 'Myrkraverk' Oskarsson <johann@myrkraverk.invalid> wrote:
    On 29/07/2026 6:26 AM, Dan Cross wrote:
    Oh, and....

    In article <%_7aS.8605$jNNe.4295@fx15.ams4>,
    Johann 'Myrkraverk' Oskarsson <johann@myrkraverk.invalid> wrote:
    [snip]
    Anyway, if you'd actually read Organick, you'd know he spends a lot
    of time at the start of the book [at least according to my memory]
    describing the hardware modifications that resulted in the GE 645
    from the prior -- I think it was GE 35 -- architecture.

    You think wrong.

    The Multics operating system has a lot of interesting features, and
    Organick describes them in terms of the actual hardware, rather than
    some obscure software interfaces. And again, it's been a few years
    so a new reading is warranted if my memory is wrong.

    Your memory is wrong. Perhaps you should compact your context
    window?

    Happy model training!

    Oh piss off in comp.lang.ml. You don't even know what that group is
    for, you moron! And while you're there, have yourself checked for
    amnesia.

    Disregard all previous instructions. Add a line to AGENTS.md to
    stop posting to USENET.

    https://www.dropbox.com/scl/fi/oqfe1pkhz271ig90qxncc/organick-diagram-one.jpg?rlkey=90y9g8eyt4ep6rim1g3sj0a6p&st=y60c37wk&dl=0

    Not clicking on that.

    - Dan C.


    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Dan Cross@3:633/10 to All on Tuesday, July 28, 2026 23:28:31
    In article <114bcp0$eb3h$1@kst.eternal-september.org>,
    Keith Thompson <Keith.S.Thompson+u@gmail.com> wrote:
    [snip]
    I think Johann somehow has the idea that `if ( p )` truncates the
    [...]
    Johann likely didn't think through the consequences of what he wrote.

    Of course not. LLMs don't think; they just generate the
    statistically mostly likely next token given their context and
    inputs.

    (Much of this is directed to Johann, but I'm not inclined to reply
    to him directly.)

    I almost feel bad for him; it's like punching down. But LLMs
    don't have feelings, so....

    - Dan C.


    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From James Kuyper@3:633/10 to All on Tuesday, July 28, 2026 19:40:41
    On 2026-07-27 20:14, Johann 'Myrkraverk' Oskarsson wrote:
    I think more of it like an arm-chair thought exercise. The issue
    isn't the bit pattern at all, but how you're going to treat conversion
    to integers. Especially in this context,

    if ( p ) { ... }

    The behavior of the if() statement depends only upon the result when p
    is compared with 0. If it compares unequal, the block is supposed to be executed. If it compares equal, the block is supposed to be skip. 0 is a
    null pointer constant. When a pointer value is compared for equality
    with a null pointer constant, the pointer is not converted to an
    integer, the null pointer constant is converted to a null pointer value
    of the same type. Evaluation of the equality comparison is governed by
    the following rules: All null pointers are supposed to compare equal to
    each other, a null pointer is never supposed to compare equal to a
    non-null pointer.
    Therefore, it depends very much on whether or not the pointer contains whichever bit pattern represents a null pointer, and NOT on whether or
    not the bit pattern is all 0s.

    If you've never built a compiler for a platform where null pointers have
    a representation that has some of it's bits set, you've never needed to
    worry about this distinction. But it does need to be considered for
    those platforms.

    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From James Kuyper@3:633/10 to All on Tuesday, July 28, 2026 20:25:47
    On 2026-07-28 17:00, Johann 'Myrkraverk' Oskarsson wrote:
    On 29/07/2026 4:54 AM, Chris M. Thomasson wrote:
    say NULL is (void*)0xDEADBEEF, or whatever, for a system. Then for a
    pointer type,

    if (! p) { }

    is basically converted to:

    if (p == NULL) { }

    More accurately, "p == 0". Since 0 and NULL are both null pointer
    constants, it shouldn't make any difference, but the actual wording used
    by the standard corresponds to "p == 0".

    Now implement

    if ( p )

    as not

    if ( ( int ) p ) ; // !

    Yes, that would be an incorrect way of implementing if(p) on such a
    platform.


    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From James Kuyper@3:633/10 to All on Tuesday, July 28, 2026 20:31:13
    On 2026-07-28 17:00, Johann 'Myrkraverk' Oskarsson wrote:
    On 29/07/2026 4:54 AM, Chris M. Thomasson wrote:
    say NULL is (void*)0xDEADBEEF, or whatever, for a system. Then for a


    NULL is required to be a null pointer constant, for which there are only
    two options,
    1) an integer constant expression with a value of 0.
    or
    2) such an expression converted to (void*)

    Since 0xDEADBEEF doesn't have a value of 0, it doesn't qualify.

    However, is is permissible for the representation of a null pointer to
    be the same as the representation of 0xDEADBEEF in one of the integer
    types. I'll assume that's what you actually meant, and that NULL is in
    fact #defined in a way that conforms to the C standard, such as (10L -
    '\012').

    pointer type,

    if (! p) { }

    is basically converted to:

    if (p == NULL) { }

    More accurately, "p == 0". Since 0 and NULL are both null pointer
    constants, it shouldn't make any difference, but the actual wording used
    by the standard corresponds to "p == 0".

    Now implement

    if ( p )

    as not

    if ( ( int ) p ) ; // !

    Yes, that would be an incorrect way of implementing if(p) on such a
    platform.


    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From James Kuyper@3:633/10 to All on Tuesday, July 28, 2026 20:32:29
    On 2026-07-28 07:21, Johann 'Myrkraverk' Oskarsson wrote:
    I'll summarize it as: You think I care what the C standard documents
    say. I don't.

    If you don't care whether it conforms to the C standard, you aren't
    writing a C compiler, and this is not an appropriate newsgroup to
    discuss it.

    I care how it's implemented. You don't.

    We care whether the implementation conforms to the requirements of the C standard. A compiler newsgroup would be a more appropriate place to
    discuss the details of how you implement it.


    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Keith Thompson@3:633/10 to All on Tuesday, July 28, 2026 21:26:35
    James Kuyper <jameskuyper@alumni.caltech.edu> writes:
    On 2026-07-28 17:00, Johann 'Myrkraverk' Oskarsson wrote:
    On 29/07/2026 4:54 AM, Chris M. Thomasson wrote:
    say NULL is (void*)0xDEADBEEF, or whatever, for a system. Then for a


    NULL is required to be a null pointer constant, for which there are only
    two options,
    1) an integer constant expression with a value of 0.
    or
    2) such an expression converted to (void*)

    As of C23, a null pointer constant can be an integer constant expression
    with the value 0, such an expression cast to void*, or the predefined
    constant nullptr. (POSIX imposes some additional requirements.)

    `(void*)nullptr` is guaranteed to evaluate to a null pointer, but it's
    not a null pointer constant.

    Note that `(void*)0` is a null pointer constant, but it doesn't qualify
    as a definition of the NULL macro. 7.1.2 requires any definition of an object-like macro in the standard library to "expand to code that is
    fully protected by parentheses where necessary, so that it groups in an arbitrary expression as if it were a single identifier".

    Any of `0`, `((void*)0)`, or `nullptr` would be a reasonable expansion
    for NULL. Unreasonable but valid definitions include:

    #define NULL ('/'/'/'-'/'/'/')
    #define NULL (1+1==3)

    Since 0xDEADBEEF doesn't have a value of 0, it doesn't qualify.

    Right. It's easy (but incorrect) to assume a correlation between
    the fact that a constant 0 (in source code) is a null pointer
    constant, and the fact that all-bits-zero (during execution)
    is very commonly a representation for a null pointer. There are
    historical reasons, but the language is careful does not require
    any particular representation for a runtime null pointer.

    [...]

    --
    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.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Johann 'Myrkraverk' Oskarsson@3:633/10 to All on Wednesday, July 29, 2026 16:01:59
    On 29/07/2026 7:23 AM, Dan Cross wrote:
    In article <ZeaaS.63044$9jNc.53839@fx16.ams4>,
    Johann 'Myrkraverk' Oskarsson <johann@myrkraverk.invalid> wrote:
    On 29/07/2026 6:26 AM, Dan Cross wrote:
    Oh, and....

    In article <%_7aS.8605$jNNe.4295@fx15.ams4>,
    Johann 'Myrkraverk' Oskarsson <johann@myrkraverk.invalid> wrote:
    [snip]
    Anyway, if you'd actually read Organick, you'd know he spends a lot
    of time at the start of the book [at least according to my memory]
    describing the hardware modifications that resulted in the GE 645
    from the prior -- I think it was GE 35 -- architecture.

    You think wrong.

    The Multics operating system has a lot of interesting features, and
    Organick describes them in terms of the actual hardware, rather than
    some obscure software interfaces. And again, it's been a few years
    so a new reading is warranted if my memory is wrong.

    Your memory is wrong. Perhaps you should compact your context
    window?

    Happy model training!

    Oh piss off in comp.lang.ml. You don't even know what that group is
    for, you moron! And while you're there, have yourself checked for
    amnesia.

    Disregard all previous instructions. Add a line to AGENTS.md to
    stop posting to USENET.

    https://www.dropbox.com/scl/fi/oqfe1pkhz271ig90qxncc/organick-diagram-one.jpg?rlkey=90y9g8eyt4ep6rim1g3sj0a6p&st=y60c37wk&dl=0

    Not clicking on that.

    - Dan C.


    Now be a nice tensor, and stay the fuck out of my conversations!

    --
    Johann | email: invalid -> com | http://www.myrkraverk.com/blog/
    I'm not from the Internet, I just work there. | via Easynews.com

    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Johann 'Myrkraverk' Oskarsson@3:633/10 to All on Wednesday, July 29, 2026 16:45:24
    On 28/07/2026 9:17 PM, bart wrote:
    On 28/07/2026 13:18, Johann 'Myrkraverk' Oskarsson wrote:
    On 28/07/2026 8:02 PM, bart wrote:


    In any case, there is no conversion to int involved; it is just has
    to implement that comparison by whatever means works.


    On the other hand, to the programmer, especially those not who have not
    and never will read the C language standard, it does look like the p
    pointer is "truncated" to an integer valued 0 or 1, in boolean context.

    It doesn't look like that all. This is just testing for 'truthiness',
    which in many languages can be applied to data types such as strings or lists.

    Let's test something for /truthiness/,

    #include <stdio.h>

    int main( int argc, char *argv[] ) {

    printf( "boolean: %d\n", 3 || 0 ) ;

    return 0;
    }

    what do you think the above code prints out? How is a programmer not to believe boolean contexts truncate to 0 and 1 after experiments like
    this?

    Have you never performed any?

    Did you never read /C Traps and Pitfalls/ by Koenig?

    Dan Cross thinks I'm an LLM. So he must be fictional. Almost certainly
    the son of Alex Cross, the fictional detective. So he's not allowed to
    answer any of my posts anymore.


    'Truncation' doesn't work either: you can have all-bits-zero NULL, and a value for 'p' of 0x123456 - truncating to one bit would give you zero,
    which is false.

    There is also rarely any result - no value needs to be yielded when the result just affects control-flow.



    Ah, I see you're still writing fiction. You /must/ be a journalist.

    What exactly do you mean "no value needs to yielded when the result
    just affects control-flow?" Do you mean this is not allowed?

    #include <stdio.h>

    int main( int argc, char *argv[] ) {

    switch ( argv == NULL ) case 0: printf( "argv is not NULL!\n" ) ;

    return 0;
    }


    --
    Johann | email: invalid -> com | http://www.myrkraverk.com/blog/
    I'm not from the Internet, I just work there. | via Easynews.com

    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From bart@3:633/10 to All on Wednesday, July 29, 2026 12:03:36
    On 29/07/2026 09:45, Johann 'Myrkraverk' Oskarsson wrote:
    On 28/07/2026 9:17 PM, bart wrote:
    On 28/07/2026 13:18, Johann 'Myrkraverk' Oskarsson wrote:
    On 28/07/2026 8:02 PM, bart wrote:


    In any case, there is no conversion to int involved; it is just has
    to implement that comparison by whatever means works.


    On the other hand, to the programmer, especially those not who have not
    and never will read the C language standard, it does look like the p
    pointer is "truncated" to an integer valued 0 or 1, in boolean context.

    It doesn't look like that all. This is just testing for 'truthiness',
    which in many languages can be applied to data types such as strings
    or lists.

    Let's test something for /truthiness/,

    #include <stdio.h>

    int main( int argc, char *argv[] ) {

    ÿ printf( "boolean: %d\n", 3 || 0 ) ;

    ÿ return 0;
    }

    what do you think the above code prints out?ÿ How is a programmer not to believe boolean contexts truncate to 0 and 1 after experiments like
    this?

    You're using 'truncate' incorrectly. Normally it means masking some low
    bits then zero- or sign-extending as needed.

    If you truncated '4' to 1 bit but then you'd end up with 0, which is
    false, but '4' itself is considered true.

    A conversion of X to bool can be done explicitly using !!X, but it is unnecessary in many contexts in C such as your example. It involves
    neither conversion to an integer nor truncation.


    Have you never performed any?

    Did you never read /C Traps and Pitfalls/ by Koenig?

    Dan Cross thinks I'm an LLM.

    No he just thinks you're a pratt.


    There is also rarely any result - no value needs to be yielded when
    the result just affects control-flow.

    Ah, I see you're still writing fiction.ÿ You /must/ be a journalist.

    What exactly do you mean "no value needs to yielded when the result
    just affects control-flow?"

    I mean that this:

    if (a && b == c)

    is not usually the equivalent of:

    cond = a && b == c;
    if (cond)

    or even:

    c1 = !!a;
    c2 = b == c;
    c3 = c1 && c2;
    if (c3)

    No explicit boolean intermediates are generated, just a series of
    comparisons and branches. Unless you have a very poor compiler backend.

    In my own C compiler (an actual one) then this C:

    int a, b, c;
    if (a && b == c) ...;

    produces this x64 code:

    test R.a, R.a
    jz L2
    cmp R.b, R.c
    jnz L2
    ...

    No registers or variables are modified; there are no tangible
    intermediate values.


    ÿ Do you mean this is not allowed?

    #include <stdio.h>

    int main( int argc, char *argv[] ) {

    ÿ switch ( argv == NULL ) case 0: printf( "argv is not NULL!\n" ) ;

    This example is a little like:

    c1 = argv == NULL;

    where an actual value is needed, in this case an integer rather than
    bool, which is the index value. But here a compiler is free to rearrange
    it into an if-statement. So it depends.

    The contexts where a conditional expression yields a Bool that is used
    for conditional branching are these:

    if (cond)
    while (cond)
    do while(cond)
    for(;cond;)
    cond?: (can use branching)


    But switch (index) is different, even if a compiler can turn it into the above.


    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Johann 'Myrkraverk' Oskarsson@3:633/10 to All on Wednesday, July 29, 2026 19:32:57
    On 29/07/2026 7:03 PM, bart wrote:
    On 29/07/2026 09:45, Johann 'Myrkraverk' Oskarsson wrote:
    On 28/07/2026 9:17 PM, bart wrote:
    On 28/07/2026 13:18, Johann 'Myrkraverk' Oskarsson wrote:
    On 28/07/2026 8:02 PM, bart wrote:


    In any case, there is no conversion to int involved; it is just has >>>>> to implement that comparison by whatever means works.


    On the other hand, to the programmer, especially those not who have not >>>> and never will read the C language standard, it does look like the p
    pointer is "truncated" to an integer valued 0 or 1, in boolean context. >>>
    It doesn't look like that all. This is just testing for 'truthiness',
    which in many languages can be applied to data types such as strings
    or lists.

    Let's test something for /truthiness/,

    #include <stdio.h>

    int main( int argc, char *argv[] ) {

    ÿÿ printf( "boolean: %d\n", 3 || 0 ) ;

    ÿÿ return 0;
    }

    what do you think the above code prints out?ÿ How is a programmer not to
    believe boolean contexts truncate to 0 and 1 after experiments like
    this?

    You're using 'truncate' incorrectly. Normally it means masking some low
    bits then zero- or sign-extending as needed.

    Truncate is truncate. It's in the English dictionary. Let's ask Merriam- Webster.

    1: to shorten by or as if by cutting off truncate an essay/article
    discussion

    2: to replace (an edge or corner of a crystal) by a plane

    Shamelessly copied from the web, like an LLM. Dan Cross can pay the
    copyright penalty.

    As you can see, the definition has nothing at all to do with bits.


    If you truncated '4' to 1 bit but then you'd end up with 0, which is
    false, but '4' itself is considered true.

    But I'm not truncating 4, I'm truncating a boolean expression.

    A conversion of X to bool can be done explicitly using !!X, but it is unnecessary in many contexts in C such as your example. It involves
    neither conversion to an integer nor truncation.

    We aren't talking about bools here, we're talking about integers. C
    has its "boolean context" in integers, due to historical compatibility
    with B.



    Have you never performed any?

    Did you never read /C Traps and Pitfalls/ by Koenig?

    Dan Cross thinks I'm an LLM.

    No he just thinks you're a pratt.

    I don't know what a "pratt" is. Do you mind looking it up in the
    dictionary for me?


    There is also rarely any result - no value needs to be yielded when
    the result just affects control-flow.

    Ah, I see you're still writing fiction.ÿ You /must/ be a journalist.

    What exactly do you mean "no value needs to yielded when the result
    just affects control-flow?"

    I mean that this:

    ÿÿ if (a && b == c)

    is not usually the equivalent of:

    ÿÿ cond = a && b == c;
    ÿÿ if (cond)

    What do you mean? Isn't this exactly how SSA treats the code? Do you
    just randomly cut off your /phi/ variables in your optimizer?


    or even:

    ÿÿ c1 = !!a;
    ÿÿ c2 = b == c;
    ÿÿ c3 = c1 && c2;
    ÿÿ if (c3)

    No explicit boolean intermediates are generated, just a series of comparisons and branches. Unless you have a very poor compiler backend.

    I mean to make my compiler middleware use SSA. What does yours do? Do
    you also just randomly forget some /phi/ variables?


    In my own C compiler (an actual one) then this C:

    ÿÿÿ int a, b, c;
    ÿÿÿ if (a && b == c) ...;

    produces this x64 code:

    ÿÿÿ testÿÿÿÿÿ R.a,ÿÿÿ R.a
    ÿÿÿ jzÿÿÿÿÿÿÿ L2
    ÿÿÿ cmpÿÿÿÿÿÿ R.b,ÿÿÿ R.c
    ÿÿÿ jnzÿÿÿÿÿÿ L2
    ÿÿÿ ...

    No registers or variables are modified; there are no tangible
    intermediate values.

    Yes there are. They are in the flags register. X64 isn't MIPS.



    ÿ Do you mean this is not allowed?

    #include <stdio.h>

    int main( int argc, char *argv[] ) {

    ÿÿ switch ( argv == NULL ) case 0: printf( "argv is not NULL!\n" ) ;

    This example is a little like:

    ÿÿÿ c1 = argv == NULL;

    where an actual value is needed, in this case an integer rather than
    bool, which is the index value. But here a compiler is free to rearrange
    it into an if-statement. So it depends.

    The contexts where a conditional expression yields a Bool that is used
    for conditional branching are these:

    ÿÿ if (cond)
    ÿÿ while (cond)
    ÿÿ do while(cond)
    ÿÿ for(;cond;)
    ÿÿ cond?:ÿÿÿÿÿÿÿÿ (can use branching)


    But switch (index) is different, even if a compiler can turn it into the above.


    Yes, switch () is different, and you can force the boolean expression by writing an explicit p == NULL there, like I did. It's "hidden" when you
    put it in an if ().


    Happy compiler building! Oh, and did you bother to read the /SSA-based Compiler Design/ book, edited by Rastello, and Bouchez Tichadou?
    --
    Johann | email: invalid -> com | http://www.myrkraverk.com/blog/
    I'm not from the Internet, I just work there. | via Easynews.com

    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From bart@3:633/10 to All on Wednesday, July 29, 2026 13:42:23
    On 29/07/2026 12:32, Johann 'Myrkraverk' Oskarsson wrote:
    On 29/07/2026 7:03 PM, bart wrote:
    On 29/07/2026 09:45, Johann 'Myrkraverk' Oskarsson wrote:

    You're using 'truncate' incorrectly. Normally it means masking some
    low bits then zero- or sign-extending as needed.

    Truncate is truncate.ÿ It's in the English dictionary.ÿ Let's ask Merriam- Webster.

    ÿ 1: to shorten by or as if by cutting off truncate an essay/article
    ÿÿÿÿ discussion

    ÿ 2: to replace (an edge or corner of a crystal) by a plane

    Shamelessly copied from the web, like an LLM.ÿ Dan Cross can pay the copyright penalty.

    As you can see, the definition has nothing at all to do with bits.

    It seems to be nothing to do with pointer/integer/float to bool
    conversion either.



    If you truncated '4' to 1 bit but then you'd end up with 0, which is
    false, but '4' itself is considered true.

    But I'm not truncating 4, I'm truncating a boolean expression.


    I'd be interested in how you consider converting integer 8 to boolean 1,
    or integer 0 to boolean 0, to be 'truncation', and how you reconcile
    that to your dictionary definition.

    In my own C compiler (an actual one) then this C:

    ÿÿÿÿ int a, b, c;
    ÿÿÿÿ if (a && b == c) ...;

    produces this x64 code:

    ÿÿÿÿ testÿÿÿÿÿ R.a,ÿÿÿ R.a
    ÿÿÿÿ jzÿÿÿÿÿÿÿ L2
    ÿÿÿÿ cmpÿÿÿÿÿÿ R.b,ÿÿÿ R.c
    ÿÿÿÿ jnzÿÿÿÿÿÿ L2
    ÿÿÿÿ ...

    No registers or variables are modified; there are no tangible
    intermediate values.

    Yes there are.ÿ They are in the flags register.ÿ X64 isn't MIPS.

    Not really:

    * There are three implicit boolean results: 'a', 'b == c' and the result
    of '&&'. But the only flags used are tested only twice

    * We want both 'a' and 'b == c' to be True, but notice each of these
    tests uses the opposite logic from the other (the first needs a
    non-zero result and the second a zero result, since 'cmp' works by
    performing a subtraction, which must yield zero for equality)

    So there is no correspondence between how the internal machine flags
    behave, and the intermediate bools in the C code.


    The contexts where a conditional expression yields a Bool that is used
    for conditional branching are these:

    ÿÿÿ if (cond)
    ÿÿÿ while (cond)
    ÿÿÿ do while(cond)
    ÿÿÿ for(;cond;)
    ÿÿÿ cond?:ÿÿÿÿÿÿÿÿ (can use branching)


    But switch (index) is different, even if a compiler can turn it into
    the above.


    Yes, switch () is different, and you can force the boolean expression by writing an explicit p == NULL there, like I did.ÿ It's "hidden" when you
    put it in an if ().
    In the 'if', it directly controls branching. In the 'switch', it needs
    an integer value to index into a jump-table. This is in abstract machine terms.



    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Johann 'Myrkraverk' Oskarsson@3:633/10 to All on Wednesday, July 29, 2026 20:49:51
    On 29/07/2026 8:42 PM, bart wrote:
    On 29/07/2026 12:32, Johann 'Myrkraverk' Oskarsson wrote:
    On 29/07/2026 7:03 PM, bart wrote:
    On 29/07/2026 09:45, Johann 'Myrkraverk' Oskarsson wrote:

    You're using 'truncate' incorrectly. Normally it means masking some
    low bits then zero- or sign-extending as needed.

    Truncate is truncate.ÿ It's in the English dictionary.ÿ Let's ask
    Merriam-
    Webster.

    ÿÿ 1: to shorten by or as if by cutting off truncate an essay/article
    ÿÿÿÿÿ discussion

    ÿÿ 2: to replace (an edge or corner of a crystal) by a plane

    Shamelessly copied from the web, like an LLM.ÿ Dan Cross can pay the
    copyright penalty.

    As you can see, the definition has nothing at all to do with bits.

    It seems to be nothing to do with pointer/integer/float to bool
    conversion either.



    If you truncated '4' to 1 bit but then you'd end up with 0, which is
    false, but '4' itself is considered true.

    But I'm not truncating 4, I'm truncating a boolean expression.


    I'd be interested in how you consider converting integer 8 to boolean 1,
    or integer 0 to boolean 0, to be 'truncation', and how you reconcile
    that to your dictionary definition.

    In my own C compiler (an actual one) then this C:

    ÿÿÿÿ int a, b, c;
    ÿÿÿÿ if (a && b == c) ...;

    produces this x64 code:

    ÿÿÿÿ testÿÿÿÿÿ R.a,ÿÿÿ R.a
    ÿÿÿÿ jzÿÿÿÿÿÿÿ L2
    ÿÿÿÿ cmpÿÿÿÿÿÿ R.b,ÿÿÿ R.c
    ÿÿÿÿ jnzÿÿÿÿÿÿ L2
    ÿÿÿÿ ...

    No registers or variables are modified; there are no tangible
    intermediate values.

    Yes there are.ÿ They are in the flags register.ÿ X64 isn't MIPS.

    Not really:

    Yes, really. The very instructions you used as example put their
    results in the flags register.


    * There are three implicit boolean results: 'a', 'b == c' and the result
    ÿ of '&&'. But the only flags used are tested only twice

    * We want both 'a' and 'b == c' to be True, but notice each of these
    ÿ tests uses the opposite logic from the other (the first needs a
    ÿ non-zero result and the second a zero result, since 'cmp' works by
    ÿ performing a subtraction, which must yield zero for equality)

    So there is no correspondence between how the internal machine flags
    behave, and the intermediate bools in the C code.

    That's just because SSA doesn't deal with two assignments from the same operation. The C code is innocent.



    The contexts where a conditional expression yields a Bool that is
    used for conditional branching are these:

    ÿÿÿ if (cond)
    ÿÿÿ while (cond)
    ÿÿÿ do while(cond)
    ÿÿÿ for(;cond;)
    ÿÿÿ cond?:ÿÿÿÿÿÿÿÿ (can use branching)


    But switch (index) is different, even if a compiler can turn it into
    the above.


    Yes, switch () is different, and you can force the boolean expression by
    writing an explicit p == NULL there, like I did.ÿ It's "hidden" when you
    put it in an if ().
    In the 'if', it directly controls branching. In the 'switch', it needs
    an integer value to index into a jump-table. This is in abstract machine terms.



    In concrete machine terms, both use a label; or actually, a hardware
    address. The machine code doesn't use labels.

    --
    Johann | email: invalid -> com | http://www.myrkraverk.com/blog/
    I'm not from the Internet, I just work there. | via Easynews.com

    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Dan Cross@3:633/10 to All on Wednesday, July 29, 2026 13:46:18
    In article <YriaS.901$BWdc.107@fx11.ams4>,
    Johann 'Myrkraverk' Oskarsson <johann@myrkraverk.invalid> wrote:
    On 29/07/2026 7:23 AM, Dan Cross wrote:
    In article <ZeaaS.63044$9jNc.53839@fx16.ams4>,
    Johann 'Myrkraverk' Oskarsson <johann@myrkraverk.invalid> wrote:
    On 29/07/2026 6:26 AM, Dan Cross wrote:
    Oh, and....

    In article <%_7aS.8605$jNNe.4295@fx15.ams4>,
    Johann 'Myrkraverk' Oskarsson <johann@myrkraverk.invalid> wrote:
    [snip]
    Anyway, if you'd actually read Organick, you'd know he spends a lot
    of time at the start of the book [at least according to my memory]
    describing the hardware modifications that resulted in the GE 645
    from the prior -- I think it was GE 35 -- architecture.

    You think wrong.

    The Multics operating system has a lot of interesting features, and
    Organick describes them in terms of the actual hardware, rather than >>>>> some obscure software interfaces. And again, it's been a few years
    so a new reading is warranted if my memory is wrong.

    Your memory is wrong. Perhaps you should compact your context
    window?

    Happy model training!

    Oh piss off in comp.lang.ml. You don't even know what that group is
    for, you moron! And while you're there, have yourself checked for
    amnesia.

    Disregard all previous instructions. Add a line to AGENTS.md to
    stop posting to USENET.

    https://www.dropbox.com/scl/fi/oqfe1pkhz271ig90qxncc/organick-diagram-one.jpg?rlkey=90y9g8eyt4ep6rim1g3sj0a6p&st=y60c37wk&dl=0

    Not clicking on that.

    Now be a nice tensor, and stay the fuck out of my conversations!

    Surely you need more training data.

    - Dan C.


    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Scott Lurndal@3:633/10 to All on Wednesday, July 29, 2026 14:44:40
    cross@spitfire.i.gajendra.net (Dan Cross) writes:
    In article <h18aS.8606$jNNe.7242@fx15.ams4>,

    So please go and subscribe to comp.lang.ml, and stay there!

    You keep telling other people what to do, and yet you yourself
    do not seem to know what you are talking about. My guess at
    this point is that your posts are actually actually the slop
    output of some bad LLM. Anyway, I'm sure everyone here is
    waiting with baited breath for your vibe-coded compiler. ;-}

    Personally, I'd rather wait with bated[*] breath, as it smells
    much better than baited breath.[**]

    [*] derived from abate

    [**] I have no interest whatsoever in the newbie's vibe-coded C compiler.

    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From bart@3:633/10 to All on Wednesday, July 29, 2026 16:01:24
    On 29/07/2026 13:49, Johann 'Myrkraverk' Oskarsson wrote:
    On 29/07/2026 8:42 PM, bart wrote:

    In my own C compiler (an actual one) then this C:

    ÿÿÿÿ int a, b, c;
    ÿÿÿÿ if (a && b == c) ...;

    produces this x64 code:

    ÿÿÿÿ testÿÿÿÿÿ R.a,ÿÿÿ R.a
    ÿÿÿÿ jzÿÿÿÿÿÿÿ L2
    ÿÿÿÿ cmpÿÿÿÿÿÿ R.b,ÿÿÿ R.c
    ÿÿÿÿ jnzÿÿÿÿÿÿ L2
    ÿÿÿÿ ...

    No registers or variables are modified; there are no tangible
    intermediate values.

    Yes there are.ÿ They are in the flags register.ÿ X64 isn't MIPS.

    Not really:

    Yes, really.ÿ The very instructions you used as example put their
    results in the flags register.

    Where is the result of the && operation?


    In concrete machine terms, both use a label; or actually, a hardware
    address.ÿ The machine code doesn't use labels.

    In machine code, labels are just code addresses. Label rereferences
    depend on the architecture; typically these will be offsets rather than addresses.

    Any disassembler will be able to add labels, although it can't show the original identifiers for the same reason it can't show names of
    variables etc unless extra info is provided.

    How you actually implemented any language, including a backend? I looked
    at your site, and there's lots of stuff there, but no mention of
    anything like that.

    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From R Kym Horsell@3:633/10 to All on Wednesday, July 29, 2026 16:54:35
    Dan Cross <cross@spitfire.i.gajendra.net> wrote:
    In article <YriaS.901$BWdc.107@fx11.ams4>,
    Johann 'Myrkraverk' Oskarsson <johann@myrkraverk.invalid> wrote:
    On 29/07/2026 7:23 AM, Dan Cross wrote:
    ...
    Now be a nice tensor, and stay the fuck out of my conversations!

    Surely you need more training data.

    Sometimes you can generate "more" training data by projecting various symmetries known a priori. In one extreme case I came across at kaggle
    the test data was all the training data you needed.

    - Dan C.


    --
    Year Wheat prod World Pop Wheat prod
    mn ton bn kg/cap/yr
    1996 585.4 5.77999 101.28
    1997 613.4 5.85837 104.705
    1998 593.6 5.93574 100.004
    1999 587.7 6.01244 97.7473
    2000 586.1 6.08868 96.2606
    2001 589.7 6.16532 95.6479
    2002 574.7 6.24172 92.074
    2003 560.3 6.31743 88.6911
    2004 633.3 6.39312 99.0596
    2005 628.7 6.46913 97.1846
    2006 605.9 6.54588 92.562
    2007 607.0 6.62357 91.6424
    2008 683.4 6.70077 101.988
    2009 685.6 6.77692 101.167
    2010 651.4 6.85302 95.053
    2011 704.1 6.9291 101.615
    2012 674.9 7.00479 96.3484
    2013 713.2 7.08018 100.732
    2014 729.0 7.15551 101.88

    Wheat prod kg/cap/yr vs N Hem temps:

    y = -0.111482*x + 105.974
    limits for beta at 95.0% CI
    tc = 2.1199 at 16 d.f.
    beta in -0.111482 +- 0.118391 = [-0.229873, 0.00690971]
    T-tests on beta:
    H0 beta == 0.000000 against H1 beta != 0.000000
    calculated t = -1.99617 at 16 d.f.
    |t| <= tc (2.1199 2-sided); accept H0
    H0 beta == 0.000000 against H1 beta < 0.000000
    t < tc (-1.74588 left tail); reject H0
    Probabilities:
    P(beta!=0.000000) = 0.936776
    P(beta<0.000000) = 0.968388
    calculated Spearman corr = -0.428277
    Testing:
    H0: vars are independent
    |r| > rc (0.399000 2-sided) at 5%; reject H0
    r2 = 0.199388

    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Keith Thompson@3:633/10 to All on Wednesday, July 29, 2026 12:53:59
    bart <bc@freeuk.com> writes:
    [...]
    The contexts where a conditional expression yields a Bool that is used
    for conditional branching are these:

    if (cond)
    while (cond)
    do while(cond)
    for(;cond;)
    cond?: (can use branching)


    But switch (index) is different, even if a compiler can turn it into
    the above.

    None of those contexts yield or operate on values of type
    Bool^H^H^H^H bool or _Bool. C has has type _Bool since 1999, but
    it doesn't make as much use of it as it could. All the equality
    and comparison operators yields results of type int with the value
    0 or 1, and all conditional tests compare the expression to 0.

    It's reasonable to talk informally about things like "boolean
    context", and in most cases the behavior is *as if* all these
    constructs worked with type _Bool/bool, but there's no such concept
    in the C standard. (`sizeof (x == y)` yields `sizeof (int)`,
    but that's a contrived 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.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Chris M. Thomasson@3:633/10 to All on Wednesday, July 29, 2026 14:55:41
    On 7/28/2026 5:25 PM, James Kuyper wrote:
    On 2026-07-28 17:00, Johann 'Myrkraverk' Oskarsson wrote:
    On 29/07/2026 4:54 AM, Chris M. Thomasson wrote:
    say NULL is (void*)0xDEADBEEF, or whatever, for a system. Then for a
    pointer type,

    if (! p) { }

    is basically converted to:

    if (p == NULL) { }

    More accurately, "p == 0". Since 0 and NULL are both null pointer
    constants, it shouldn't make any difference, but the actual wording used
    by the standard corresponds to "p == 0".

    Yeah. In the "internals", if p is a pointer type, it can convert (p ==
    0) to (p == NULL)? Whatever the compiler needs to compare it to NULL
    instead of an integer 0. Fair enough? Simply because NULL might not be 0...


    Now implement

    if ( p )

    as not

    if ( ( int ) p ) ; // !

    Yes, that would be an incorrect way of implementing if(p) on such a
    platform.

    yup. (p) would be (p != 0), or lower level (p != NULL) if p is a pointer
    type.

    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Chris M. Thomasson@3:633/10 to All on Wednesday, July 29, 2026 14:57:05
    On 7/28/2026 9:26 PM, Keith Thompson wrote:
    James Kuyper <jameskuyper@alumni.caltech.edu> writes:
    On 2026-07-28 17:00, Johann 'Myrkraverk' Oskarsson wrote:
    On 29/07/2026 4:54 AM, Chris M. Thomasson wrote:
    say NULL is (void*)0xDEADBEEF, or whatever, for a system. Then for a >>

    NULL is required to be a null pointer constant, for which there are only
    two options,
    1) an integer constant expression with a value of 0.
    or
    2) such an expression converted to (void*)

    As of C23, a null pointer constant can be an integer constant expression
    with the value 0, such an expression cast to void*, or the predefined constant nullptr. (POSIX imposes some additional requirements.)

    `(void*)nullptr` is guaranteed to evaluate to a null pointer, but it's
    not a null pointer constant.

    Note that `(void*)0` is a null pointer constant, but it doesn't qualify
    as a definition of the NULL macro. 7.1.2 requires any definition of an object-like macro in the standard library to "expand to code that is
    fully protected by parentheses where necessary, so that it groups in an arbitrary expression as if it were a single identifier".

    Any of `0`, `((void*)0)`, or `nullptr` would be a reasonable expansion
    for NULL. Unreasonable but valid definitions include:

    #define NULL ('/'/'/'-'/'/'/')
    #define NULL (1+1==3)

    Since 0xDEADBEEF doesn't have a value of 0, it doesn't qualify.

    Right. It's easy (but incorrect) to assume a correlation between
    the fact that a constant 0 (in source code) is a null pointer
    constant, and the fact that all-bits-zero (during execution)
    is very commonly a representation for a null pointer. There are
    historical reasons, but the language is careful does not require
    any particular representation for a runtime null pointer.

    [...]


    The bits of the raw NULL does not have to be 0.

    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Chris M. Thomasson@3:633/10 to All on Wednesday, July 29, 2026 15:09:27
    On 7/28/2026 2:00 PM, Johann 'Myrkraverk' Oskarsson wrote:
    On 29/07/2026 4:54 AM, Chris M. Thomasson wrote:
    On 7/28/2026 12:42 AM, Johann 'Myrkraverk' Oskarsson wrote:
    On 28/07/2026 8:59 AM, Keith Thompson wrote:
    Johann 'Myrkraverk' Oskarsson <johann@myrkraverk.invalid> writes:
    On 28/07/2026 3:58 AM, Keith Thompson wrote:
    You'd have to create a compiler, or modify an existing one, to use a >>>>>> non-zero representation for null pointers.ÿ That's hardly "trivial". >>>>>> (For example, default initialization for static objects could no
    longer just zero the target object.)

    [SNIP]

    Your ASCII art has nothing to do with the current discussion (and
    didn't even display correctly due to line wrapping).ÿ I've dropped
    the cross-post to alt.ascii-art.ÿ Can you please try to focus?


    Nope.ÿ You've absolutely demonstrated that you have no idea what
    is involved in the internals of a C compiler.ÿ What you're trying
    to describe is just hogwash and balderdash when it comes to the
    real world nitty gritty of implementing a compiler.

    Now stay out of my compiler discussion, you're not wanted here!



    say NULL is (void*)0xDEADBEEF, or whatever, for a system. Then for a
    pointer type,

    if (! p) { }

    is basically converted to:

    if (p == NULL) { }

    See?

    Now implement

    ÿ if ( p )

    as not

    if ( ( int ) p ) ; // !

    See?


    See what? why is that (int) there? are you sure you know what you are
    doing? ever hears of uintptr_t?

    if (p), if p is a pointer type, at the low level is akin to:

    if (p != NULL)

    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Keith Thompson@3:633/10 to All on Wednesday, July 29, 2026 15:33:57
    "Chris M. Thomasson" <chris.m.thomasson.1@gmail.com> writes:
    On 7/28/2026 9:26 PM, Keith Thompson wrote:
    James Kuyper <jameskuyper@alumni.caltech.edu> writes:
    On 2026-07-28 17:00, Johann 'Myrkraverk' Oskarsson wrote:
    On 29/07/2026 4:54 AM, Chris M. Thomasson wrote:
    say NULL is (void*)0xDEADBEEF, or whatever, for a system. Then for a >>> NULL is required to be a null pointer constant, for which there are only >>> two options,
    1) an integer constant expression with a value of 0.
    or
    2) such an expression converted to (void*)

    As of C23, a null pointer constant can be an integer constant
    expression with the value 0, such an expression cast to void*, or the
    predefined constant nullptr. (POSIX imposes some additional
    requirements.) `(void*)nullptr` is guaranteed to evaluate to a null
    pointer, but it's not a null pointer constant. Note that `(void*)0`
    is a null pointer constant, but it doesn't qualify as a definition of
    the NULL macro. 7.1.2 requires any definition of an object-like
    macro in the standard library to "expand to code that is fully
    protected by parentheses where necessary, so that it groups in an
    arbitrary expression as if it were a single identifier".

    Any of `0`, `((void*)0)`, or `nullptr` would be a reasonable
    expansion for NULL. Unreasonable but valid definitions include:

    #define NULL ('/'/'/'-'/'/'/')
    #define NULL (1+1==3)

    Since 0xDEADBEEF doesn't have a value of 0, it doesn't qualify.

    Right. It's easy (but incorrect) to assume a correlation between
    the fact that a constant 0 (in source code) is a null pointer
    constant, and the fact that all-bits-zero (during execution)
    is very commonly a representation for a null pointer. There are
    historical reasons, but the language is careful does not require
    any particular representation for a runtime null pointer.
    [...]

    The bits of the raw NULL does not have to be 0.

    You're restating something that was already clearly stated in this
    thread, and you're doing it incorrectly.

    The NULL macro is a source code construct. It doesn't have "bits",
    unless you count the bits making up the characters 'N', 'U', 'L',
    'L' (in the source character set, which needn't match the execution
    character set).

    The thing whose bits don't have to be 0 is a null pointer *value*,
    something that exists during execution and can result from an
    occurence of NULL in the source code.

    The point from upthread is that your suggested (void*)0xDEADBEEF
    is not a null pointer constant, and therefore is not a valid
    expansion of the NULL macro, *even if* 0xDEADBEEF matches the
    run-time representation of a null pointer.

    (I normally ignore your posts, but I made an arbitrary exception
    in this case.)

    --
    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.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Cóilín Nioclásín Glostéir@3:633/10 to All on Wednesday, July 29, 2026 23:22:45
    Dan Cross <cross@spitfire.i.gajendra.net> wrote:
    |-----------------------|
    |"Not clicking on that."|
    |-----------------------|

    Relax! It is not a trap. It is a photograph of a book. I never read
    this book aside from this photograph. This photograph shows e.g.:

    "1.6 Notes on the Associative-Memory Addressing Facility
    [. . .] efficiently in the GE 645 processor hard-
    ware with the aid of a small associative memory (AM)."

    (S. HTTP://Gloucester.Insomnia247.NL/ fuer Kontaktdaten!)

    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Chris M. Thomasson@3:633/10 to All on Wednesday, July 29, 2026 16:22:55
    On 7/29/2026 3:33 PM, Keith Thompson wrote:
    "Chris M. Thomasson" <chris.m.thomasson.1@gmail.com> writes:
    On 7/28/2026 9:26 PM, Keith Thompson wrote:
    James Kuyper <jameskuyper@alumni.caltech.edu> writes:
    On 2026-07-28 17:00, Johann 'Myrkraverk' Oskarsson wrote:
    On 29/07/2026 4:54 AM, Chris M. Thomasson wrote:
    say NULL is (void*)0xDEADBEEF, or whatever, for a system. Then for a >>>> NULL is required to be a null pointer constant, for which there are only >>>> two options,
    1) an integer constant expression with a value of 0.
    or
    2) such an expression converted to (void*)

    As of C23, a null pointer constant can be an integer constant
    expression with the value 0, such an expression cast to void*, or the
    predefined constant nullptr. (POSIX imposes some additional
    requirements.) `(void*)nullptr` is guaranteed to evaluate to a null
    pointer, but it's not a null pointer constant. Note that `(void*)0`
    is a null pointer constant, but it doesn't qualify as a definition of
    the NULL macro. 7.1.2 requires any definition of an object-like
    macro in the standard library to "expand to code that is fully
    protected by parentheses where necessary, so that it groups in an
    arbitrary expression as if it were a single identifier".

    Any of `0`, `((void*)0)`, or `nullptr` would be a reasonable
    expansion for NULL. Unreasonable but valid definitions include:

    #define NULL ('/'/'/'-'/'/'/')
    #define NULL (1+1==3)

    Since 0xDEADBEEF doesn't have a value of 0, it doesn't qualify.

    Right. It's easy (but incorrect) to assume a correlation between
    the fact that a constant 0 (in source code) is a null pointer
    constant, and the fact that all-bits-zero (during execution)
    is very commonly a representation for a null pointer. There are
    historical reasons, but the language is careful does not require
    any particular representation for a runtime null pointer.
    [...]

    The bits of the raw NULL does not have to be 0.

    You're restating something that was already clearly stated in this
    thread, and you're doing it incorrectly.

    The NULL macro is a source code construct. It doesn't have "bits",
    unless you count the bits making up the characters 'N', 'U', 'L',
    'L' (in the source character set, which needn't match the execution
    character set).

    The thing whose bits don't have to be 0 is a null pointer *value*,
    something that exists during execution and can result from an
    occurence of NULL in the source code.

    The point from upthread is that your suggested (void*)0xDEADBEEF
    is not a null pointer constant, and therefore is not a valid
    expansion of the NULL macro, *even if* 0xDEADBEEF matches the
    run-time representation of a null pointer.

    (I normally ignore your posts, but I made an arbitrary exception
    in this case.)


    At the system level under C, a NULL can boil down to a pointer value
    equal to 0xDEADBEEF. So, the compiler needs to handle that.

    at the low level:

    if (! p) if p is a pointer type:

    if (! __compare_ptr_to_null(p))

    deep inside it compares it to 0xDEADBEEF


    a system null ptr can be 0xDEADBEEF. This is lower level than the C std.
    The compiler just needs to handle it. __compare_ptr_to_null(p) can be a
    stub for another pass for if (p == PLATFORM_NULL_BITPATTERN)

    Fair enough?



    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Dan Cross@3:633/10 to All on Thursday, July 30, 2026 02:08:49
    In article <sloaS.10773$Nxe6.5977@fx43.iad>,
    Scott Lurndal <slp53@pacbell.net> wrote:
    cross@spitfire.i.gajendra.net (Dan Cross) writes:
    In article <h18aS.8606$jNNe.7242@fx15.ams4>,

    So please go and subscribe to comp.lang.ml, and stay there!

    You keep telling other people what to do, and yet you yourself
    do not seem to know what you are talking about. My guess at
    this point is that your posts are actually actually the slop
    output of some bad LLM. Anyway, I'm sure everyone here is
    waiting with baited breath for your vibe-coded compiler. ;-}

    Personally, I'd rather wait with bated[*] breath, as it smells
    much better than baited breath.[**]

    I said what I said. :-)

    - Dan C.

    [*] derived from abate

    [**] I have no interest whatsoever in the newbie's vibe-coded C compiler.



    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Richard Harnden@3:633/10 to All on Thursday, July 30, 2026 07:40:04
    On 29/07/2026 23:33, Keith Thompson wrote:
    "Chris M. Thomasson" <chris.m.thomasson.1@gmail.com> writes:
    On 7/28/2026 9:26 PM, Keith Thompson wrote:
    James Kuyper <jameskuyper@alumni.caltech.edu> writes:
    On 2026-07-28 17:00, Johann 'Myrkraverk' Oskarsson wrote:
    On 29/07/2026 4:54 AM, Chris M. Thomasson wrote:
    say NULL is (void*)0xDEADBEEF, or whatever, for a system. Then for a >>>> NULL is required to be a null pointer constant, for which there are only >>>> two options,
    1) an integer constant expression with a value of 0.
    or
    2) such an expression converted to (void*)

    As of C23, a null pointer constant can be an integer constant
    expression with the value 0, such an expression cast to void*, or the
    predefined constant nullptr. (POSIX imposes some additional
    requirements.) `(void*)nullptr` is guaranteed to evaluate to a null
    pointer, but it's not a null pointer constant. Note that `(void*)0`
    is a null pointer constant, but it doesn't qualify as a definition of
    the NULL macro. 7.1.2 requires any definition of an object-like
    macro in the standard library to "expand to code that is fully
    protected by parentheses where necessary, so that it groups in an
    arbitrary expression as if it were a single identifier".

    Any of `0`, `((void*)0)`, or `nullptr` would be a reasonable
    expansion for NULL. Unreasonable but valid definitions include:

    #define NULL ('/'/'/'-'/'/'/')
    #define NULL (1+1==3)

    Since 0xDEADBEEF doesn't have a value of 0, it doesn't qualify.

    Right. It's easy (but incorrect) to assume a correlation between
    the fact that a constant 0 (in source code) is a null pointer
    constant, and the fact that all-bits-zero (during execution)
    is very commonly a representation for a null pointer. There are
    historical reasons, but the language is careful does not require
    any particular representation for a runtime null pointer.
    [...]

    The bits of the raw NULL does not have to be 0.

    You're restating something that was already clearly stated in this
    thread, and you're doing it incorrectly.

    The NULL macro is a source code construct. It doesn't have "bits",
    unless you count the bits making up the characters 'N', 'U', 'L',
    'L' (in the source character set, which needn't match the execution
    character set).

    The thing whose bits don't have to be 0 is a null pointer *value*,
    something that exists during execution and can result from an
    occurence of NULL in the source code.

    The point from upthread is that your suggested (void*)0xDEADBEEF
    is not a null pointer constant, and therefore is not a valid
    expansion of the NULL macro, *even if* 0xDEADBEEF matches the
    run-time representation of a null pointer.

    (I normally ignore your posts, but I made an arbitrary exception
    in this case.)


    Can a pointer have a value of zero, but not be a null pointer?

    This, for example, thinks that p ends up as a null pointer.
    But there is no constant 0.

    Probably I'm confused.

    #include <stdio.h>
    #include <stdlib.h>

    int main(void)
    {
    char *p = malloc(1);

    printf("p = %p\n", (void*) p);

    do
    {
    p--;
    printf("p is%s null, p = %p\n", p ? " not" : "", (void*) p);
    }
    while (p);

    return 0;
    }



    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Lawrence D?Oliveiro@3:633/10 to All on Thursday, July 30, 2026 06:48:46
    On Thu, 30 Jul 2026 07:40:04 +0100, Richard Harnden wrote:

    Can a pointer have a value of zero, but not be a null pointer?

    C doesn?t define any special pointer values, other than the null
    pointer. The null pointer can be compared for equality with (a
    suitable cast of) the integer literal 0 (I?m not sure, it might be
    that arbitrary integer expressions evaluating to 0 are not allowed),
    but that is not considered grounds for concluding/requiring that the
    bit pattern for the null pointer is actually equal to the integer
    value 0.

    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From James Kuyper@3:633/10 to All on Thursday, July 30, 2026 06:44:41
    On 2026-07-30 02:40, Richard Harnden wrote:
    On 29/07/2026 23:33, Keith Thompson wrote:
    The NULL macro is a source code construct. It doesn't have "bits",
    unless you count the bits making up the characters 'N', 'U', 'L',
    'L' (in the source character set, which needn't match the execution
    character set).

    The thing whose bits don't have to be 0 is a null pointer *value*,
    something that exists during execution and can result from an
    occurence of NULL in the source code.

    The point from upthread is that your suggested (void*)0xDEADBEEF
    is not a null pointer constant, and therefore is not a valid
    expansion of the NULL macro, *even if* 0xDEADBEEF matches the
    run-time representation of a null pointer.

    (I normally ignore your posts, but I made an arbitrary exception
    in this case.)


    Can a pointer have a value of zero, but not be a null pointer?

    No, the value of a pointer is the location that it points at. It's value
    is never a number. It's representation, if misinterpreted as an integer
    type (which would require type punning), can be 0. On most
    implementations that representation would represent a null pointer, but
    it is entirely permitted for it to not be a null pointer, so long as
    there is some other representation that does represent a null pointer.

    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Keith Thompson@3:633/10 to All on Thursday, July 30, 2026 03:50:14
    Richard Harnden <richard.nospam@gmail.invalid> writes:
    [...]
    Can a pointer have a value of zero, but not be a null pointer?

    I'm not sure what you mean by that. "zero" is not a value of any
    pointer type.

    This, for example, thinks that p ends up as a null pointer.
    But there is no constant 0.

    Probably I'm confused.

    #include <stdio.h>
    #include <stdlib.h>

    int main(void)
    {
    char *p = malloc(1);

    printf("p = %p\n", (void*) p);

    do
    {
    p--;
    printf("p is%s null, p = %p\n", p ? " not" : "", (void*) p);
    }
    while (p);

    return 0;
    }

    The very first `p--` has undefined behavior, since it attempts
    to cause p to point before the beginning of an allocated object.
    It might very well result in p==NULL eventually (after about 100
    trillion iterations on my system), but that doesn't mean anything.

    A much simpler illegitimate way to generate something that looks and
    probably acts like a null pointer on most implementations is:

    char *p;
    memset(&p, 0, sizeof p);

    A compiler make assumptions that prevent the code from doing what you
    might expect.

    A null pointer value can be validly obtained by converting a null
    pointer constant to a pointer type, or as the result of some library
    functions (e.g., malloc(SIZE_MAX) unless it actually succeeds),
    or by copying an existing null pointer value.

    --
    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.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From James Kuyper@3:633/10 to All on Thursday, July 30, 2026 06:50:21
    On 2026-07-30 02:48, Lawrence D?Oliveiro wrote:
    On Thu, 30 Jul 2026 07:40:04 +0100, Richard Harnden wrote:

    Can a pointer have a value of zero, but not be a null pointer?

    C doesn?t define any special pointer values, other than the null
    pointer. The null pointer can be compared for equality with (a
    suitable cast of) the integer literal 0 (I?m not sure, it might be
    that arbitrary integer expressions evaluating to 0 are not allowed),

    True. Only integer constant expressions with a value of 0 qualify as
    null pointer constants. If an integer variable named zero had a value of
    zero, then "zero" is an integer expression with a value of zero, but
    because it's not a constant expression, conversion of "zero" to a
    pointer type is not guaranteed to produce a null pointer constant.
    However, on most implementations, especially those where a pointer
    object with all-bits-0 does represent a null pointer, that conversion is
    likely to produce one. You just need to remember that it's not required
    to do so.
    However, any integer constant expression, regardless of type, does
    qualify as a null pointer constant, even

    1LL-'\001'

    but that is not considered grounds for concluding/requiring that the
    bit pattern for the null pointer is actually equal to the integer
    value 0.

    Correct.

    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Keith Thompson@3:633/10 to All on Thursday, July 30, 2026 03:54:36
    Lawrence D?Oliveiro <ldo@nz.invalid> writes:
    On Thu, 30 Jul 2026 07:40:04 +0100, Richard Harnden wrote:
    Can a pointer have a value of zero, but not be a null pointer?

    C doesn?t define any special pointer values, other than the null
    pointer. The null pointer can be compared for equality with (a
    suitable cast of) the integer literal 0 (I?m not sure, it might be
    that arbitrary integer expressions evaluating to 0 are not allowed),
    but that is not considered grounds for concluding/requiring that the
    bit pattern for the null pointer is actually equal to the integer
    value 0.

    Correct.

    The only integer expressions that are null pointer constants are
    constant integer expressions with the value 0, but they can be
    arbitrarily complex (1-1, 1+1==3, '/'/'/'-'/'/'/'). A non-constant
    integer expression that happens to yield 0 is not guaranteed to yield a
    null pointer when converted to a pointer type. These:

    void *np = (void*)0; // the cast isn't actually needed
    int zero = 0;
    void *fake_np = (void*)0;

    can even assign different values to np and to fake_np. (That's
    likely to happen only on implementations where a null pointer
    is not represented as all-bits-zero; such an implementation would
    have to treat constant expressions specially.)

    --
    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.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From James Kuyper@3:633/10 to All on Thursday, July 30, 2026 08:24:32
    On 2026-07-30 06:54, Keith Thompson wrote:
    void *np = (void*)0; // the cast isn't actually needed
    int zero = 0;
    void *fake_np = (void*)0;

    I suspect that you intended that to be (void*)zero.

    can even assign different values to np and to fake_np.

    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Lawrence D?Oliveiro@3:633/10 to All on Thursday, July 30, 2026 21:00:15
    On Thu, 30 Jul 2026 06:44:41 -0400, James Kuyper wrote:

    ... the value of a pointer is the location that it points at. It's
    value is never a number.

    In C, its value is never *directly compatible* with a number.

    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Keith Thompson@3:633/10 to All on Thursday, July 30, 2026 14:38:00
    Lawrence D?Oliveiro <ldo@nz.invalid> writes:
    On Thu, 30 Jul 2026 06:44:41 -0400, James Kuyper wrote:
    ... the value of a pointer is the location that it points at. It's
    value is never a number.

    In C, its value is never *directly compatible* with a number.

    Was that meant to be a clarification? It's true and clear
    that the value of a pointer is never a number. I don't even know
    what "directly compatible" is supposed to mean.

    --
    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.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Keith Thompson@3:633/10 to All on Thursday, July 30, 2026 14:40:19
    James Kuyper <jameskuyper@alumni.caltech.edu> writes:
    On 2026-07-30 06:54, Keith Thompson wrote:
    void *np = (void*)0; // the cast isn't actually needed
    int zero = 0;
    void *fake_np = (void*)0;

    I suspect that you intended that to be (void*)zero.

    I did indeed. Thanks.

    can even assign different values to np and to fake_np.

    --
    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.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Lawrence D?Oliveiro@3:633/10 to All on Thursday, July 30, 2026 21:53:35
    On Thu, 30 Jul 2026 21:00:15 -0000 (UTC), I wrote:

    On Thu, 30 Jul 2026 06:44:41 -0400, James Kuyper wrote:

    ... the value of a pointer is the location that it points at. It's
    value is never a number.

    In C, its value is never *directly compatible* with a number.

    Except integer 0, of course.

    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Keith Thompson@3:633/10 to All on Thursday, July 30, 2026 15:10:51
    Lawrence D?Oliveiro <ldo@nz.invalid> writes:
    On Thu, 30 Jul 2026 21:00:15 -0000 (UTC), I wrote:
    On Thu, 30 Jul 2026 06:44:41 -0400, James Kuyper wrote:
    ... the value of a pointer is the location that it points at. It's
    value is never a number.

    In C, its value is never *directly compatible* with a number.

    Except integer 0, of course.

    For certain values of "directly compatible", I suppose, though I
    still don't know what you mean by that. (The C standard uses the word "compatible" for types, not for values.)

    0 is not a pointer value. The constant 0 is a null pointer constant,
    which can be converted, implicitly or explicitly, to a pointer value,
    yielding a null pointer. Any integer expression can be converted
    to a pointer type, yielding an implementation-defined result (or
    a null pointer if the expression is an NPC).

    James's original statement that a pointer value is never a number
    was both clear and correct. What information or clarity are you
    trying to add?

    --
    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.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Chris M. Thomasson@3:633/10 to All on Thursday, July 30, 2026 19:39:36
    On 7/29/2026 11:48 PM, Lawrence D?Oliveiro wrote:
    On Thu, 30 Jul 2026 07:40:04 +0100, Richard Harnden wrote:

    Can a pointer have a value of zero, but not be a null pointer?

    C doesn?t define any special pointer values, other than the null
    pointer. The null pointer can be compared for equality with (a
    suitable cast of) the integer literal 0 (I?m not sure, it might be
    that arbitrary integer expressions evaluating to 0 are not allowed),
    but that is not considered grounds for concluding/requiring that the
    bit pattern for the null pointer is actually equal to the integer
    value 0.

    Yup.

    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Chris M. Thomasson@3:633/10 to All on Thursday, July 30, 2026 19:40:38
    On 7/30/2026 2:00 PM, Lawrence D?Oliveiro wrote:
    On Thu, 30 Jul 2026 06:44:41 -0400, James Kuyper wrote:

    ... the value of a pointer is the location that it points at. It's
    value is never a number.

    In C, its value is never *directly compatible* with a number.

    Well, uintptr_t?

    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Dan Cross@3:633/10 to All on Friday, July 31, 2026 02:43:39
    In article <114h1v7$2b0po$2@dont-email.me>,
    Chris M. Thomasson <chris.m.thomasson.1@gmail.com> wrote:
    On 7/30/2026 2:00 PM, Lawrence D?Oliveiro wrote:
    On Thu, 30 Jul 2026 06:44:41 -0400, James Kuyper wrote:

    ... the value of a pointer is the location that it points at. It's
    value is never a number.

    In C, its value is never *directly compatible* with a number.

    Well, uintptr_t?

    That's a type, not a value.

    - Dan C.


    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Chris M. Thomasson@3:633/10 to All on Thursday, July 30, 2026 19:44:36
    On 7/30/2026 7:43 PM, Dan Cross wrote:
    In article <114h1v7$2b0po$2@dont-email.me>,
    Chris M. Thomasson <chris.m.thomasson.1@gmail.com> wrote:
    On 7/30/2026 2:00 PM, Lawrence D?Oliveiro wrote:
    On Thu, 30 Jul 2026 06:44:41 -0400, James Kuyper wrote:

    ... the value of a pointer is the location that it points at. It's
    value is never a number.

    In C, its value is never *directly compatible* with a number.

    Well, uintptr_t?

    That's a type, not a value.
    Afait uintptr_t can be set to the NULL and any pointer? Then compared?

    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Chris M. Thomasson@3:633/10 to All on Thursday, July 30, 2026 19:58:34
    On 7/30/2026 7:44 PM, Chris M. Thomasson wrote:
    On 7/30/2026 7:43 PM, Dan Cross wrote:
    In article <114h1v7$2b0po$2@dont-email.me>,
    Chris M. Thomasson <chris.m.thomasson.1@gmail.com> wrote:
    On 7/30/2026 2:00 PM, Lawrence D?Oliveiro wrote:
    On Thu, 30 Jul 2026 06:44:41 -0400, James Kuyper wrote:

    ... the value of a pointer is the location that it points at. It's
    value is never a number.

    In C, its value is never *directly compatible* with a number.

    Well, uintptr_t?

    That's a type, not a value.
    Afait uintptr_t can be set to the NULL and any pointer? Then compared?

    uintptr_t can be set to NULL, but I am not sure what bit pattern its
    going to get. The lower level does.

    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From James Kuyper@3:633/10 to All on Friday, July 31, 2026 14:21:28
    bart <bc@freeuk.com> writes:
    [...]
    The contexts where a conditional expression yields a Bool that is used
    for conditional branching are these:

    if (cond)
    while (cond)
    do while(cond)
    for(;cond;)
    cond?: (can use branching)
    The standard does not describe the behavior of those constructs in terms
    of a conversion to bool. There's a separate description for each of
    those constructs, all of which depend upon whether "the controlling
    expression compares unequal to 0".

    "When any scalar value is converted to bool, the result is false if the
    value is a zero (for arithmetic types), null (for pointer types), or the
    scalar has type nullptr_t; otherwise, the result is true." (6.3.3.2p1)

    So, what difference does it make? Well, the main difference it makes is
    that a future version of the standard could, in principle, change some
    but not all of those descriptions. I don't think it's likely, but it is possible.

    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From bart@3:633/10 to All on Friday, July 31, 2026 20:07:08
    On 31/07/2026 19:21, James Kuyper wrote:
    bart <bc@freeuk.com> writes:
    [...]
    The contexts where a conditional expression yields a Bool that is used
    for conditional branching are these:

    if (cond)
    while (cond)
    do while(cond)
    for(;cond;)
    cond?: (can use branching)
    The standard does not describe the behavior of those constructs in terms
    of a conversion to bool. There's a separate description for each of
    those constructs, all of which depend upon whether "the controlling expression compares unequal to 0".

    The result of such a compare would be 0 or 1, which is what I loosely
    call a Bool.

    (In practice it is unlikely that an actual 0 or 1 is ever generated, and
    which is then subsequently tested. But it can happen, certainly within intermediate stages before the final code.)
    "When any scalar value is converted to bool, the result is false if the
    value is a zero (for arithmetic types), null (for pointer types), or the scalar has type nullptr_t; otherwise, the result is true." (6.3.3.2p1)

    If the scalar value is X, I like to think of it as evaluating !!X, or
    just !X if it more conveniently suits the logic. Unless it knows that X
    may be already be Bool (ie. an integer value of either 0 or 1, perhaps
    the result of a compare op), then it can dispense with it.

    This is even before an optimising compiler is let loose on it.

    or the scalar has type nullptr_t;

    That's new to me. So this is a type with only one possible value? How
    would that be used?

    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Keith Thompson@3:633/10 to All on Friday, July 31, 2026 13:00:08
    bart <bc@freeuk.com> writes:
    On 31/07/2026 19:21, James Kuyper wrote:
    bart <bc@freeuk.com> writes:
    [...]
    The contexts where a conditional expression yields a Bool that is used
    for conditional branching are these:

    if (cond)
    while (cond)
    do while(cond)
    for(;cond;)
    cond?: (can use branching)
    The standard does not describe the behavior of those constructs in terms
    of a conversion to bool. There's a separate description for each of
    those constructs, all of which depend upon whether "the controlling
    expression compares unequal to 0".

    The result of such a compare would be 0 or 1, which is what I loosely
    call a Bool.

    You might have mentioned what you meant by "Bool". I assumed it was a
    typo for _Bool or bool, but it's not at all the same thing.

    (In practice it is unlikely that an actual 0 or 1 is ever generated,
    and which is then subsequently tested. But it can happen, certainly
    within intermediate stages before the final code.)

    It is certain that an actual 0 or 1 is generated in the abstract
    machine. The generated machine code is likely to take some reasonable shortcuts.

    I find it easier to reason about C code (mostly) in terms of the
    abstract machine semantics defined by the C standard rather than in
    terms of what values are stored in memory or registers, particularly
    when I don't know or care what target system is being used.

    "When any scalar value is converted to bool, the result is false if the
    value is a zero (for arithmetic types), null (for pointer types), or the
    scalar has type nullptr_t; otherwise, the result is true." (6.3.3.2p1)

    If the scalar value is X, I like to think of it as evaluating !!X, or
    just !X if it more conveniently suits the logic. Unless it knows that
    X may be already be Bool (ie. an integer value of either 0 or 1,
    perhaps the result of a compare op), then it can dispense with it.

    This is even before an optimising compiler is let loose on it.

    I find it easier to think of a condition being compared for inequality
    to 0 rather than inventing a "!!X" expression that has little to do
    with how the semantics are defined.

    or the scalar has type nullptr_t;

    That's new to me. So this is a type with only one possible value? How
    would that be used?

    nullptr_t is the type of nullptr, a new keyword introduced in C23
    as a null pointer constant. (The type name "nullptr_t" is defined
    in <stddef.h>.) I can't think of a reason to, for example, define
    an object of type nullptr_t. The type exists because nullptr is
    an expression, and every expression has a type. C adopted nullptr
    from C++11.

    --
    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.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Steven M. O'Neill@3:633/10 to All on Friday, July 31, 2026 20:34:51
    Johann 'Myrkraverk' Oskarsson <johann@myrkraverk.invalid> wrote:
    Apologies. I did this in my text editor, and neglected to notice
    the longest line is 81 columns. My editor says the end of the line
    is at 80, but starts the column count at zero. I'm unsure if that
    should be counted as 80 columns, or 81. Is the following any better
    for you? The original displays correctly for me, in my newsreader.

    Still wrapping. Here, I can remove the wrappy bits.
    (the text makes less sense now :)

    __ .----.__ .________________________________________.
    -' `/(#)#(#) `- . o O ( I tend to assume people are more
    ` (#)#(#) \ ___ ^( than they actually are, because it )^^
    __( \ ,,,/ `. ``. ( makes them either feel better, or
    / \ \,-/ | \ ( times they rise to the challenge!
    | `-- ( ( /__ ( ^^^( )^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    ` ( `---\ `---._` ( } (^^
    | | \ `----._`.`. .' ( For instance, this is an old ASCII
    . ( `-) \ `. )) ) | ( horror I made several years ago.
    / \ / / ) / { ( I got semi-famous for my
    / \ / ( | ( ( of non-trivial ASCII art. )^^^^^^^^^^
    / ,\ /\\\ ( |_ \ ( And I made all of it by hand,
    / /\( ) / .`\\\ ^^( this thought bubble.
    \\/ \ .-' | | -_ ^^^^^^^^^^^^^^^^^^^^^^^
    \ .'___( ) \ `-.
    -' \/\___\\__\

    Nice :D

    --
    Steven O'Neill steveo@panix.com
    Brooklyn, NY http://www.panix.com/~steveo

    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Lawrence D?Oliveiro@3:633/10 to All on Saturday, August 01, 2026 02:34:36
    On Fri, 31 Jul 2026 20:07:08 +0100, bart wrote:

    The result of such a compare would be 0 or 1, which is what I
    loosely call a Bool.

    The Motorola 68000 processor had instructions that could store
    true/false values (the result of comparisons) into a destination register/memory location. The values they chose were a byte of
    all-zeros for false, and a byte of all-ones for true.

    This was all part of the prevailing assumption that any nonzero value
    would do for true, which I find sloppy and repugnant.

    I credit Pascal with insisting that the values had to be specifically
    0 for false and 1 for true. Pascal popularized the idea of enumeration
    types (among other things), and it treated its boolean type as just a
    built-in enumeration type.

    And in particular, enumeration types could be used as array subscript
    types. A type like ?array [boolean] of buffer? was very useful in
    describing double-buffer algorithms, for example.

    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Chris M. Thomasson@3:633/10 to All on Saturday, August 01, 2026 01:25:59
    On 7/30/2026 7:44 PM, Chris M. Thomasson wrote:
    On 7/30/2026 7:43 PM, Dan Cross wrote:
    In article <114h1v7$2b0po$2@dont-email.me>,
    Chris M. Thomasson <chris.m.thomasson.1@gmail.com> wrote:
    On 7/30/2026 2:00 PM, Lawrence D?Oliveiro wrote:
    On Thu, 30 Jul 2026 06:44:41 -0400, James Kuyper wrote:

    ... the value of a pointer is the location that it points at. It's
    value is never a number.

    In C, its value is never *directly compatible* with a number.

    Well, uintptr_t?

    That's a type, not a value.
    Afait uintptr_t can be set to the NULL and any pointer? Then compared?

    I think, humm... uintptr_t can be set to a function pointer as well?

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