• Initialization Of Static Pointer Variables

    From Lawrence D?Oliveiro@3:633/10 to All on Thu Aug 20 23:14:01 2026
    The C spec goes out of its way to make clear that a NULL pointer does
    not necessarily have the same bit pattern as the integer value zero.
    This in spite of the fact that it is allowed to test a pointer value
    for NULL by comparing it to the integer value zero.

    The spec also says that all static variables are initialized to zero
    at program start time. Are pointer variables allowed to be given an
    all-zero bit pattern at this time? Or are they supposed to be
    initialized to the NULL value (if that?s different) instead?

    --- 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 Thu Aug 20 16:28:39 2026
    On 8/20/2026 4:14 PM, Lawrence D?Oliveiro wrote:
    The C spec goes out of its way to make clear that a NULL pointer does
    not necessarily have the same bit pattern as the integer value zero.
    This in spite of the fact that it is allowed to test a pointer value
    for NULL by comparing it to the integer value zero.

    The spec also says that all static variables are initialized to zero
    at program start time. Are pointer variables allowed to be given an
    all-zero bit pattern at this time? Or are they supposed to be
    initialized to the NULL value (if that?s different) instead?


    static int g_a = 0;

    int main()
    {
    int a = g_a;

    // oh my... a can be anything wrt the std?

    return 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 Thu Aug 20 16:29:19 2026
    On 8/20/2026 4:28 PM, Chris M. Thomasson wrote:
    On 8/20/2026 4:14 PM, Lawrence D?Oliveiro wrote:
    The C spec goes out of its way to make clear that a NULL pointer does
    not necessarily have the same bit pattern as the integer value zero.
    This in spite of the fact that it is allowed to test a pointer value
    for NULL by comparing it to the integer value zero.

    The spec also says that all static variables are initialized to zero
    at program start time. Are pointer variables allowed to be given an
    all-zero bit pattern at this time? Or are they supposed to be
    initialized to the NULL value (if that?s different) instead?


    static int g_a = 0;

    int main()
    {
    ˙˙˙ int a = g_a;

    ˙˙˙ // oh my... a can be anything wrt the std?

    ˙˙˙ return 0;
    }


    Oh shit! I did not mean to init g_a to zero here. Sorry!

    ;^o ouch.

    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Richard Tobin@3:633/10 to All on Thu Aug 20 23:42:24 2026
    In article <11681np$3s8jk$4@dont-email.me>,
    Lawrence D;Oliveiro <ldo@nz.invalid> wrote:

    The spec also says that all static variables are initialized to zero
    at program start time. Are pointer variables allowed to be given an
    all-zero bit pattern at this time? Or are they supposed to be
    initialized to the NULL value (if that's different) instead?

    ISO C 1999, 6.7.8:

    If an object that has automatic storage duration is not initialized
    explicitly, its value is indeterminate. If an object that has static
    storage duration is not initialized explicitly, then:

    ? if it has pointer type, it is initialized to a null pointer;

    ? if it has arithmetic type, it is initialized to (positive or
    unsigned) zero;

    ? if it is an aggregate, every member is initialized (recursively)
    according to these rules;

    ? if it is a union, the first named member is initialized
    (recursively) according to these rules

    -- Richard

    --- 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 Thu Aug 20 16:52:39 2026
    Lawrence D?Oliveiro <ldo@nz.invalid> writes:
    The C spec goes out of its way to make clear that a NULL pointer does
    not necessarily have the same bit pattern as the integer value zero.
    This in spite of the fact that it is allowed to test a pointer value
    for NULL by comparing it to the integer value zero.

    I don't know that it "goes out of its way" to say this. I haven't done
    an exhaustive check, but as I recall it just doesn't say anything about
    the representation of a null pointer (a more precise term than "NULL
    pointer"), except to say that it's unspecified. Possibly some care has
    been taken to avoid implying anything more specific.

    The spec also says that all static variables are initialized to zero
    at program start time. Are pointer variables allowed to be given an
    all-zero bit pattern at this time? Or are they supposed to be
    initialized to the NULL value (if that?s different) instead?

    This is very clearly stated in the standard. N3220 6.7.11p11 :

    If an object that has automatic storage duration is not initialized
    explicitly, its representation is indeterminate. If an object that
    has static or thread storage duration is not initialized explicitly,
    or any object is initialized with an empty initializer, then it is
    subject to default initialization, which initializes an object as
    follows:

    -- if it has pointer type, it is initialized to a null pointer;

    All other types are either numeric (and are initialized to zero of
    the type), or are aggregates, for which initialization is defined
    recursively.

    An implementation that stored all-bits-zero in an ununinitialized
    static pointer object would be non-conforming if that weren't the representation of a null pointer. This:

    #include <stdio.h>
    int main(void) {
    static void *ptr;
    if (ptr != NULL) puts("OOPS");
    if (ptr != 0) puts("OOPS");
    }

    must not print "OOPS".

    --
    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 Thu Aug 20 20:12:10 2026
    On 2026-08-20 19:14, Lawrence D?Oliveiro wrote:
    The C spec goes out of its way to make clear that a NULL pointer does
    not necessarily have the same bit pattern as the integer value zero.
    This in spite of the fact that it is allowed to test a pointer value
    for NULL by comparing it to the integer value zero.

    Not exactly. an integer constant expression with a value of zero is
    recognized as a null pointer constant. When compared with a value of
    pointer type, a null pointer constant gets converted into a null pointer
    of that type BEFORE carrying out the comparison, so what is actually
    being compared are two different pointer values. Note that if an integer expression with a value of 0 doesn't qualify as a constant expression, conversion to a pointer type produces an implementation-defined
    behavior; it's not required to result in a null pointer value.

    The spec also says that all static variables are initialized to zero
    at program start time. Are pointer variables allowed to be given an
    all-zero bit pattern at this time? Or are they supposed to be
    initialized to the NULL value (if that?s different) instead?

    "If an object that has static or thread storage duration is not
    initialized explicitly, or any object is initialized with an empty
    initializer, then it is subject to _default initialization_, which
    initializes an object as follows:

    ? if it has pointer type, it is initialized to a null pointer;
    ? if it has type nullptr_t, it is initialized to nullptr;
    ? if it has decimal floating type, it is initialized to positive zero,
    and the quantum exponent is implementation-defined;171)
    ? if it has arithmetic type, and it does not have decimal floating type,
    it is initialized to (positive or unsigned) zero;
    ? if it is an aggregate, every member is initialized (recursively)
    according to these rules, and any padding is initialized to zero bits;
    ? if it is a union, the first member that is not an unnamed bit-field is initialized (recursively) according to these rules, and any padding is initialized to zero bits." (6.7.11p14)

    Note that this does not apply to all static variables, only those that
    are not explicitly initialized with non-empty initializers.

    The phrase "default initialization" is italicized, an ISO convention
    indicating that this sentence constitutes the official definition of
    that phrase.
    It specifies zero bits only for aggregate and union padding. For any
    object with a type, it specifies that it store a representation of 0,
    which might not have all bits 0.

    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From bart@3:633/10 to All on Fri Aug 21 01:16:29 2026
    On 21/08/2026 00:14, Lawrence D?Oliveiro wrote:
    The C spec goes out of its way to make clear that a NULL pointer does
    not necessarily have the same bit pattern as the integer value zero.
    This in spite of the fact that it is allowed to test a pointer value
    for NULL by comparing it to the integer value zero.

    The spec also says that all static variables are initialized to zero
    at program start time. Are pointer variables allowed to be given an
    all-zero bit pattern at this time? Or are they supposed to be
    initialized to the NULL value (if that?s different) instead?

    The language would need to ensure that whatever bit-pattern is needed
    for NULL, would be written. This means for example that:

    static void* A[100000000];

    can't occupy the special .bss segment in an executable that takes almost
    no space; it would need 0.4 to 0.8GB of init data.

    In general it would be a massive PITA, if you have for example arrays of nested structs which have mixed pointer/non-pointer members.

    Imagine allocating such an array like this:

    p = malloc(N*sizeof(T));

    and you wanted to set all elements to zeros and NULLs. You couldn't for example do:

    memset(p, 0, N*sizeof(T));

    as the pointers would not be 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 Thu Aug 20 17:53:50 2026
    bart <bc@freeuk.com> writes:
    On 21/08/2026 00:14, Lawrence D?Oliveiro wrote:
    The C spec goes out of its way to make clear that a NULL pointer does
    not necessarily have the same bit pattern as the integer value zero.
    This in spite of the fact that it is allowed to test a pointer value
    for NULL by comparing it to the integer value zero.
    The spec also says that all static variables are initialized to zero
    at program start time. Are pointer variables allowed to be given an
    all-zero bit pattern at this time? Or are they supposed to be
    initialized to the NULL value (if that?s different) instead?

    The language would need to ensure that whatever bit-pattern is needed
    for NULL, would be written.

    Correct -- except that I'd say "ensures" rather than "would need to
    ensure".

    This means for example that:

    static void* A[100000000];

    can't occupy the special .bss segment in an executable that takes
    almost no space; it would need 0.4 to 0.8GB of init data.

    True. Of course the ".bss segment" is an implementation detail, not
    part of the language.

    In general it would be a massive PITA, if you have for example arrays
    of nested structs which have mixed pointer/non-pointer members.

    Imagine allocating such an array like this:

    p = malloc(N*sizeof(T));

    and you wanted to set all elements to zeros and NULLs. You couldn't
    for example do:

    memset(p, 0, N*sizeof(T));

    as the pointers would not be NULL.

    Yes, all these are practical disadvantages of using a representation
    other than all-bits-zero for null pointers, and are undoubtedly
    some of the reasons why the vast majority of C implementations *do*
    use all-bits-zero for null pointers.

    But a conforming implementation could of course use a different
    representation, and it would then have to do whatever it needs to
    to make it work.

    Using memset() to set pointers to null isn't portable as far as the
    language is concerned. It may well be portable enough for your
    purposes, and you might never encounter a system where it fails.

    --
    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 Thu Aug 20 23:01:06 2026
    bart <bc@freeuk.com> writes:
    On 21/08/2026 00:14, Lawrence D?Oliveiro wrote:
    The C spec goes out of its way to make clear that a NULL pointer does
    not necessarily have the same bit pattern as the integer value zero....> This means for example that:

    static void* A[100000000];

    can't occupy the special .bss segment in an executable that takes
    almost no space; it would need 0.4 to 0.8GB of init data.
    In general it would be a massive PITA, if you have for example arrays
    of nested structs which have mixed pointer/non-pointer members.

    Imagine allocating such an array like this:

    p = malloc(N*sizeof(T));

    and you wanted to set all elements to zeros and NULLs. You couldn't
    for example do:

    memset(p, 0, N*sizeof(T));

    as the pointers would not be NULL.

    Keep in mind that implementors are not generally idiots. They don't just randomly choose that a null pointer be represented by some arbitrary bit pattern. For those rare implementations which choose such a bit pattern,
    that pattern is forced on them by the design of the hardware the code
    will be running on. The consequences of having all-bits-zero not be the representation of a null pointer are pretty negative, as you've shown
    above, so there must be something else that they are avoiding by making
    that choice, which is even worse.

    --- 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 Fri Aug 21 09:03:18 2026
    On 21/08/2026 01:14, Lawrence D?Oliveiro wrote:
    The C spec goes out of its way to make clear that a NULL pointer does
    not necessarily have the same bit pattern as the integer value zero.
    This in spite of the fact that it is allowed to test a pointer value
    for NULL by comparing it to the integer value zero.

    The spec also says that all static variables are initialized to zero
    at program start time. Are pointer variables allowed to be given an
    all-zero bit pattern at this time? Or are they supposed to be
    initialized to the NULL value (if that?s different) instead?

    The standard does not say that static variables are initialised to zero.
    It gives the rules quite clearly.

    The standard says :

    """
    If an object that has automatic storage duration is not initialized explicitly, its value is indeterminate. If an object that has static or
    thread storage duration is not initialized explicitly, then:
    ? if it has pointer type, it is initialized to a null pointer;
    ? if it has arithmetic type, it is initialized to (positive or unsigned)
    zero;
    ? if it is an aggregate, every member is initialized (recursively)
    according to these rules, and any padding is initialized to zero bits;
    ? if it is a union, the first named member is initialize
    """


    Initialisation is as though the objects had been assigned "0". If you
    have a target where the bit pattern for a null pointer is something
    other than zero, that's what you get when pointer is default
    initialised. (Similarly, if your target's floating point format uses something other than zero bits for 0 value, you get the 0 value for
    default initialised floating point objects.)


    Of course this initialisation is vastly more efficient when zero bits
    works for everything.


    --- 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 Fri Aug 21 09:12:48 2026
    On 21/08/2026 02:16, bart wrote:
    On 21/08/2026 00:14, Lawrence D?Oliveiro wrote:
    The C spec goes out of its way to make clear that a NULL pointer does
    not necessarily have the same bit pattern as the integer value zero.
    This in spite of the fact that it is allowed to test a pointer value
    for NULL by comparing it to the integer value zero.

    The spec also says that all static variables are initialized to zero
    at program start time. Are pointer variables allowed to be given an
    all-zero bit pattern at this time? Or are they supposed to be
    initialized to the NULL value (if that?s different) instead?

    The language would need to ensure that whatever bit-pattern is needed
    for NULL, would be written. This means for example that:

    static void* A[100000000];

    can't occupy the special .bss segment in an executable that takes almost
    no space; it would need 0.4 to 0.8GB of init data.

    The .bss segment is a convenience of many efficient implementations, not
    part of the C language. Not all C compiler toolchains use .bss, though
    it is very common. (There's more variation in the other segments used.)

    And the toolchain (including linker, pre-main startup code, OS
    link/loader, and anything else involved) is free to do the
    initialisation any way it wants. It does not need to be a direct copy
    of pre-determined data in the executable into read-write memory for the
    array A. It can quite happily be a loop that is run before main(),
    writing "0xdeadbeef" (or whatever the null pointer bit pattern looks
    like) to every element of A. Direct copies are simple and work well in
    many cases, but they are not the only choice. (Some real-world systems
    use compressed initialisation data.)


    In general it would be a massive PITA, if you have for example arrays of nested structs which have mixed pointer/non-pointer members.


    Yes. That's one of many reasons why all-zeros being a null pointer is
    very convenient.

    Imagine allocating such an array like this:

    ˙˙˙ p = malloc(N*sizeof(T));

    and you wanted to set all elements to zeros and NULLs. You couldn't for example do:

    ˙˙˙ memset(p, 0, N*sizeof(T));

    as the pointers would not be NULL.


    Correct.




    --- 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 Fri Aug 21 08:02:18 2026
    On Thu, 20 Aug 2026 23:01:06 -0400, James Kuyper wrote:

    For those rare implementations which choose such a bit pattern, that
    pattern is forced on them by the design of the hardware the code
    will be running on.

    There might be some leeway.

    For example, on the original VAX architecture, only the bottom 3GiB of
    the total 32-bit address space were defined by the architecture to be
    mappable through page tables; the top 1GiB was ?reserved? and
    unusable.

    As part of this, address 0 was also mappable by the hardware. But what
    they did with the VAX/VMS ABI, was declare that the bottom page
    (addresses 0..511) would always remain unmapped, so they could use
    address 0 as an invalid address. (Remember also, they were supporting
    a whole lot of different languages before C became popular.) This way,
    pointers that hadn?t been allocated (presumably initialized to 0)
    would immediately trigger an ?access violation? exception if the
    program tried to dereference them. The same would apply to pointers
    pointing to small positive integer addresses (i.e. < 512).

    What about that top 1GiB? Seems like nobody thought of using any of
    that for invalid addresses -- nobody at DEC, anyway. I think there
    would have been an instinctive aversion to that (for obvious
    reasons?).

    But then, who knows what some third-party languages might have done
    ...

    --- 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 Fri Aug 21 17:50:39 2026
    On 21/08/2026 7:14 AM, Lawrence D?Oliveiro wrote:
    The C spec goes out of its way to make clear that a NULL pointer does
    not necessarily have the same bit pattern as the integer value zero.
    This in spite of the fact that it is allowed to test a pointer value
    for NULL by comparing it to the integer value zero.

    The spec also says that all static variables are initialized to zero
    at program start time. Are pointer variables allowed to be given an
    all-zero bit pattern at this time? Or are they supposed to be
    initialized to the NULL value (if that?s different) instead?

    Dear Lawrence,

    As the other trolls of comp.lang.c have made this a long winded discuss-
    ion, I'll try to answer succinctly [1]. They're supposed to be initial-
    ized to the NULL value, whatever that is.

    Previous discussions have established that the octal integer 0 is indeed converted to whatever /NULL/ means to the compiler, before being assign-
    ed to the pointer in question. This also happens /automagically/ inside
    the compiler before the data for both global, and local, /static varia-
    bles/ is set up in the data segment, or whatever passes for the data
    segment in the compiler. Often it's just the linker who does the BSS
    segment -- and off hand I'm not sure at the moment what BSS is -- and
    the compiler doesn't actually know.

    And before this turns into a 19th century run-on paragraph, I bid you
    farewell, and good luck with your career as a Python influencer!


    [1] I have this vocabulary, you fucking assholes Dan Cross and Chris M. Thomasson.
    --
    Johann | email: invalid -> com | http://www.myrkraverk.com/blog/
    I'm not from the Internet, I just work there. | via Easynews.com https://bsky.app/profile/myrkraverk.bsky.social | for ( ;; ) _:;

    --- 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 Fri Aug 21 17:54:06 2026
    On 21/08/2026 3:03 PM, David Brown wrote:
    On 21/08/2026 01:14, Lawrence D?Oliveiro wrote:
    The C spec goes out of its way to make clear that a NULL pointer does
    not necessarily have the same bit pattern as the integer value zero.
    This in spite of the fact that it is allowed to test a pointer value
    for NULL by comparing it to the integer value zero.

    The spec also says that all static variables are initialized to zero
    at program start time. Are pointer variables allowed to be given an
    all-zero bit pattern at this time? Or are they supposed to be
    initialized to the NULL value (if that?s different) instead?

    The standard does not say that static variables are initialised to zero.
    ˙It gives the rules quite clearly.

    The standard says :

    """
    If an object that has automatic storage duration is not initialized explicitly, its value is indeterminate. If an object that has static or thread storage duration is not initialized explicitly, then:
    ? if it has pointer type, it is initialized to a null pointer;
    ? if it has arithmetic type, it is initialized to (positive or unsigned) zero;
    ? if it is an aggregate, every member is initialized (recursively)
    according to these rules, and any padding is initialized to zero bits;
    ? if it is a union, the first named member is initialize
    """


    Initialisation is as though the objects had been assigned "0".˙ If you
    have a target where the bit pattern for a null pointer is something
    other than zero, that's what you get when pointer is default
    initialised.˙ (Similarly, if your target's floating point format uses something other than zero bits for 0 value, you get the 0 value for
    default initialised floating point objects.)


    Of course this initialisation is vastly more efficient when zero bits
    works for everything.


    Dear David Brown,

    Please don't confuse /automatic storage/ with /static storage/.
    Lawrence, our very own Python influencer, explicitly stated he was ask-
    ing about /static storage/, and both global and local static storage is
    indeed guaranteed to be zero/NULL initialized, before the main() runs.


    Best wishes, and please leave these ChatGPT answers to the web, and not
    Usenet!
    --
    Johann | email: invalid -> com | http://www.myrkraverk.com/blog/
    I'm not from the Internet, I just work there. | via Easynews.com https://bsky.app/profile/myrkraverk.bsky.social | for ( ;; ) _:;

    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Tim Rentsch@3:633/10 to All on Fri Aug 21 06:02:34 2026
    Lawrence D?Oliveiro <ldo@nz.invalid> writes:

    The C spec goes out of its way to make clear that a NULL pointer does
    not necessarily have the same bit pattern as the integer value zero.
    This in spite of the fact that it is allowed to test a pointer value
    for NULL by comparing it to the integer value zero.

    The spec also says that all static variables are initialized to zero
    at program start time. Are pointer variables allowed to be given an
    all-zero bit pattern at this time? Or are they supposed to be
    initialized to the NULL value (if that?s different) instead?

    It's worth pointing out that there may be more than one kind of
    null pointer representation. Any two pointer types (with certain
    exceptions) may have different representations for null pointers.
    For example, an int * and a short * could have different ways of
    representing null pointers. Also there is no guarantee that
    pointer types all have the same size; an implementation could
    choose to make the sizes of different pointer types all different
    (again, with certain exceptions).

    --- 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 Fri Aug 21 14:53:58 2026
    Lawrence =?iso-8859-13?q?D=FFOliveiro?= <ldo@nz.invalid> writes:
    On Thu, 20 Aug 2026 23:01:06 -0400, James Kuyper wrote:

    For those rare implementations which choose such a bit pattern, that
    pattern is forced on them by the design of the hardware the code
    will be running on.

    There might be some leeway.

    For example, on the original VAX architecture, only the bottom 3GiB of
    the total 32-bit address space were defined by the architecture to be >mappable through page tables; the top 1GiB was ?reserved? and
    unusable.

    As part of this, address 0 was also mappable by the hardware. But what
    they did with the VAX/VMS ABI, was declare that the bottom page
    (addresses 0..511) would always remain unmapped, so they could use
    address 0 as an invalid address.

    The VAX Unix (BSD) implementation reversed that and mapped a read-only
    page of zeros at address 0. A bad choice that caused many problems
    years later when System V merged a bunch of BSD user-space utilities
    into SVR4.


    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Richard Tobin@3:633/10 to All on Fri Aug 21 15:28:32 2026
    In article <aEZhS.24877$Qsy6.23652@fx33.iad>,
    Scott Lurndal <slp53@pacbell.net> wrote:

    The VAX Unix (BSD) implementation reversed that and mapped a read-only
    page of zeros at address 0.

    One effect of this was to make (char *)0 work as an empty string, which
    would be handy if it was standard but in fact is just a core dump
    waiting to happen when you run it on a different system.

    -- Richard

    --- 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 Fri Aug 21 12:12:48 2026
    On 8/20/2026 4:42 PM, Richard Tobin wrote:
    In article <11681np$3s8jk$4@dont-email.me>,
    Lawrence D;Oliveiro <ldo@nz.invalid> wrote:

    The spec also says that all static variables are initialized to zero
    at program start time. Are pointer variables allowed to be given an
    all-zero bit pattern at this time? Or are they supposed to be
    initialized to the NULL value (if that's different) instead?

    ISO C 1999, 6.7.8:

    If an object that has automatic storage duration is not initialized explicitly, its value is indeterminate. If an object that has static
    storage duration is not initialized explicitly, then:

    ? if it has pointer type, it is initialized to a null pointer;

    Oh. I must have missed that one.

    so:

    static void* g_ptr;


    is guaranteed to be nullptr? Just to clarify...



    ? if it has arithmetic type, it is initialized to (positive or
    unsigned) zero;

    ? if it is an aggregate, every member is initialized (recursively)
    according to these rules;

    ? if it is a union, the first named member is initialized
    (recursively) according to these rules


    [...]


    --- 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 Sat Aug 22 13:12:45 2026
    On 21/08/2026 21:12, Chris M. Thomasson wrote:
    On 8/20/2026 4:42 PM, Richard Tobin wrote:
    In article <11681np$3s8jk$4@dont-email.me>,
    Lawrence D;Oliveiro˙ <ldo@nz.invalid> wrote:

    The spec also says that all static variables are initialized to zero
    at program start time. Are pointer variables allowed to be given an
    all-zero bit pattern at this time? Or are they supposed to be
    initialized to the NULL value (if that's different) instead?

    ISO C 1999, 6.7.8:

    If an object that has automatic storage duration is not initialized
    explicitly, its value is indeterminate. If an object that has static
    storage duration is not initialized explicitly, then:

    ? if it has pointer type, it is initialized to a null pointer;

    Oh. I must have missed that one.

    so:

    static void* g_ptr;


    is guaranteed to be nullptr? Just to clarify...

    Yes.


    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From steve g@3:633/10 to All on Sun Aug 23 16:01:18 2026
    David Brown <david.brown@hesbynett.no> writes:

    On 21/08/2026 21:12, Chris M. Thomasson wrote:
    On 8/20/2026 4:42 PM, Richard Tobin wrote:
    In article <11681np$3s8jk$4@dont-email.me>,
    Lawrence D;Oliveiro˙ <ldo@nz.invalid> wrote:

    The spec also says that all static variables are initialized to zero
    at program start time. Are pointer variables allowed to be given an
    all-zero bit pattern at this time? Or are they supposed to be
    initialized to the NULL value (if that's different) instead?

    ISO C 1999, 6.7.8:

    If an object that has automatic storage duration is not initialized
    explicitly, its value is indeterminate. If an object that has static
    storage duration is not initialized explicitly, then:

    ? if it has pointer type, it is initialized to a null pointer;
    Oh. I must have missed that one.
    so:
    static void* g_ptr;
    is guaranteed to be nullptr? Just to clarify...

    Yes.

    I would still recomend memset or bzero. You never know what is complient
    or not - better to be safe then sorry.

    --- 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 Sun Aug 23 22:16:40 2026
    On Sun, 23 Aug 2026 16:01:18 -0400, steve g wrote:

    I would still recomend memset or bzero. You never know what is
    complient or not - better to be safe then sorry.

    This kind of practice can have its own unintended consequences.

    It?s quite common for the BSS area to be allocated using ?demand-zero?
    pages on modern OSes. This means no actual pages are allocated to
    begin with; instead, the page table entry is set to indicate that the
    page allocation (and initialization) will happen on the first actual
    access to that page.

    So if you explicitly initialize pages that are supposed to be zero
    anyway, you could be causing a lot of unnecessary page faulting, and
    also increasing your program?s physical memory usage, without actually
    gaining anything.

    --- 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 Sun Aug 23 15:29:11 2026
    steve g <Sgonedes1977@gmail.com> writes:
    David Brown <david.brown@hesbynett.no> writes:
    On 21/08/2026 21:12, Chris M. Thomasson wrote:
    On 8/20/2026 4:42 PM, Richard Tobin wrote:
    [...]
    Oh. I must have missed that one.
    so:
    static void* g_ptr;
    is guaranteed to be nullptr? Just to clarify...

    Yes.

    I would still recomend memset or bzero. You never know what is complient
    or not - better to be safe then sorry.

    Bad idea.

    bzero is non-standard. It was never in ISO C, and has been removed
    from POSIX.

    Calling memset() to set a pointer object to the null pointer fails
    if a null pointer is not represented as all-bits-zero. An explicit initialization to 0, NULL, or nullptr (C23 or later) is clearer and
    more portable. Implementations that don't use all-bits-zero for null
    pointers are rare, but so are implementations that don't zero-init uninitialized static objects -- and the former can be conforming.

    There are apparently a handful of C implementations for very
    small embedded systems that do not zero static objects. If you're
    using such an implementation, you're very probably aware of it,
    and you certainly should be. Your "You never know" statement above
    is incorrect. I suspect that most code written for such systems
    doesn't need to be portable.

    --
    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 Richard Tobin@3:633/10 to All on Sun Aug 23 22:28:05 2026
    In article <87y0dwsj1t.fsf@gmail.com>, steve g <Sgonedes1977@gmail.com> wrote: >>> static void* g_ptr;
    is guaranteed to be nullptr? Just to clarify...

    Yes.

    I would still recomend memset or bzero. You never know what is complient
    or not - better to be safe then sorry.

    Absolutely not. memset(..., 0, ...) or bzero(...) will produce all
    zeroes, which is not necessarily a null pointer.

    These set p to a null pointer:

    static void *p;
    void *p = 0;

    This is not guaranteed to:

    void *p;
    memset(&p, 0, sizeof(p));

    -- Richard

    --- 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 Mon Aug 24 10:16:19 2026
    On 24/08/2026 00:29, Keith Thompson wrote:
    steve g <Sgonedes1977@gmail.com> writes:
    David Brown <david.brown@hesbynett.no> writes:
    On 21/08/2026 21:12, Chris M. Thomasson wrote:
    On 8/20/2026 4:42 PM, Richard Tobin wrote:
    [...]
    Oh. I must have missed that one.
    so:
    static void* g_ptr;
    is guaranteed to be nullptr? Just to clarify...

    Yes.

    I would still recomend memset or bzero. You never know what is complient
    or not - better to be safe then sorry.

    Bad idea.

    bzero is non-standard. It was never in ISO C, and has been removed
    from POSIX.

    Calling memset() to set a pointer object to the null pointer fails
    if a null pointer is not represented as all-bits-zero. An explicit initialization to 0, NULL, or nullptr (C23 or later) is clearer and
    more portable. Implementations that don't use all-bits-zero for null pointers are rare, but so are implementations that don't zero-init uninitialized static objects -- and the former can be conforming.

    There are apparently a handful of C implementations for very
    small embedded systems that do not zero static objects. If you're
    using such an implementation, you're very probably aware of it,
    and you certainly should be. Your "You never know" statement above
    is incorrect. I suspect that most code written for such systems
    doesn't need to be portable.


    You probably also know if you are coding for one of the few systems that
    do not use all zero bits for null pointers - AFAIK they are basically
    museum relics. (Of course there are still a few museum relics in
    current use, but you would know it if you were coding for them.)

    Still, using memset or bzero to write zeros to data that is initialised
    by pre-main startup is worse than useless. It does nothing useful,
    takes a bit of extra time, may spoil some optimisations (compilers can sometimes eliminate or simplify file-static variables, but probably not
    if they have been the target of memset or bzero), makes a maintenance a
    pain (you have to add new calls for each new variable), risks someone
    making smart-arse and unwarranted assumptions (such as trying to combine variables in a single memset/bzero call), and can easily confuse readers
    who can't see why the code is doing this.

    It is true that pretty much every C compiler has its non-conformances,
    with some having more than others. But pretty much all (baring the aforementioned embedded toolchains) are going to get something this
    basic entirely correct. If you can't be entirely confident of a
    toolchain getting this right, you should - if possible - switch toolchains.

    (Fortunately in the embedded world, most development is moving to gcc or occasionally clang, with some industries sticking to the "big"
    commercial toolchain vendors. The weird and highly non-conforming are
    much rarer, and becoming horror stories to scare the new kids.)


    --- 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 Mon Aug 24 09:45:46 2026
    On 24/08/2026 09:16, David Brown wrote:
    On 24/08/2026 00:29, Keith Thompson wrote:
    steve g <Sgonedes1977@gmail.com> writes:
    David Brown <david.brown@hesbynett.no> writes:
    On 21/08/2026 21:12, Chris M. Thomasson wrote:
    On 8/20/2026 4:42 PM, Richard Tobin wrote:
    [...]
    Oh. I must have missed that one.
    so:
    static void* g_ptr;
    is guaranteed to be nullptr? Just to clarify...

    Yes.

    I would still recomend memset or bzero. You never know what is complient >>> or not - better to be safe then sorry.

    Bad idea.

    bzero is non-standard.˙ It was never in ISO C, and has been removed
    from POSIX.

    Calling memset() to set a pointer object to the null pointer fails
    if a null pointer is not represented as all-bits-zero.˙ An explicit
    initialization to 0, NULL, or nullptr (C23 or later) is clearer and
    more portable.˙ Implementations that don't use all-bits-zero for null
    pointers are rare, but so are implementations that don't zero-init
    uninitialized static objects -- and the former can be conforming.

    There are apparently a handful of C implementations for very
    small embedded systems that do not zero static objects.˙ If you're
    using such an implementation, you're very probably aware of it,
    and you certainly should be.˙ Your "You never know" statement above
    is incorrect.˙ I suspect that most code written for such systems
    doesn't need to be portable.


    You probably also know if you are coding for one of the few systems that
    do not use all zero bits for null pointers - AFAIK they are basically
    museum relics.˙ (Of course there are still a few museum relics in
    current use, but you would know it if you were coding for them.)

    Still, using memset or bzero to write zeros to data that is initialised
    by pre-main startup is worse than useless.˙ It does nothing useful,
    takes a bit of extra time, may spoil some optimisations (compilers can sometimes eliminate or simplify file-static variables, but probably not
    if they have been the target of memset or bzero), makes a maintenance a
    pain (you have to add new calls for each new variable), risks someone
    making smart-arse and unwarranted assumptions (such as trying to combine variables in a single memset/bzero call), and can easily confuse readers
    who can't see why the code is doing this.

    It is true that pretty much every C compiler has its non-conformances,
    with some having more than others.˙ But pretty much all (baring the aforementioned embedded toolchains) are going to get something this
    basic entirely correct.˙ If you can't be entirely confident of a
    toolchain getting this right, you should - if possible - switch toolchains.

    (Fortunately in the embedded world, most development is moving to gcc or occasionally clang, with some industries sticking to the "big"
    commercial toolchain vendors.˙ The weird and highly non-conforming are
    much rarer, and becoming horror stories to scare the new kids.)


    Can you safely say something like this ... ?

    struct foo
    {
    char *s;
    int n;
    char c;
    void *data;
    };

    static const struct foo FOO_INIT;

    int main(void)
    {
    struct foo foo;

    foo = FOO_INIT;

    ...




    --- 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 Mon Aug 24 03:04:02 2026
    Richard Harnden <richard.nospam@gmail.invalid> writes:
    [...]
    Can you safely say something like this ... ?

    struct foo
    {
    char *s;
    int n;
    char c;
    void *data;
    };

    static const struct foo FOO_INIT;

    int main(void)
    {
    struct foo foo;

    foo = FOO_INIT;

    ...

    That's safe for conforming implementations. You can also
    use an initializer:

    struct foo foo = FOO_INIT;

    It solves the problem of zero-initializing a non-static object
    (such objects contain garbage if they're not initialized) -- but
    it's better done by writing

    struct foo foo = { 0 };

    (In C23 and later you can omit the 0.)

    If you need a zero-initialized struct foo value as an expression,
    e.g. for use in an assigment or as an argument, you can use a
    compound literal in C99 or later:

    foo = (struct foo){ 0 };

    Again, you can omit the 0 in C23 or later.

    Here you're relying on the implementation to zero unspecified
    members, which is required by the standard.

    If you're stuck using a non-conforming implementation, you need
    to know exactly *how* it's non-conforming, and write whatever
    ugly code you need to work around it. Different non-conforming
    implementations are likely to be non-conforming in surprising ways.
    (Ideally your ugly code will also work on conforming implementations,
    but that might not be a priority.)

    --
    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 David Brown@3:633/10 to All on Mon Aug 24 12:40:00 2026
    On 24/08/2026 12:04, Keith Thompson wrote:
    Richard Harnden <richard.nospam@gmail.invalid> writes:
    [...]
    Can you safely say something like this ... ?

    struct foo
    {
    char *s;
    int n;
    char c;
    void *data;
    };

    static const struct foo FOO_INIT;

    int main(void)
    {
    struct foo foo;

    foo = FOO_INIT;

    ...

    That's safe for conforming implementations. You can also
    use an initializer:

    struct foo foo = FOO_INIT;

    It solves the problem of zero-initializing a non-static object
    (such objects contain garbage if they're not initialized) -- but
    it's better done by writing

    struct foo foo = { 0 };

    (In C23 and later you can omit the 0.)

    In what sense do you consider that "better" ? I think it is arguably
    neater and less cognitive effort to understand, but did you have
    anything else in mind?

    As I understand it, there is a distinction between :

    struct foo foo;
    foo = FOO_INIT; // or foo = (struct foo) { 0 };

    and

    struct foo foo = FOO_INIT; // or foo = { 0 };

    in that the former just copies the value of FOO_INIT while the later guarantees zero bit initialisation for padding bits and bytes.

    It's unlikely that an optimising compiler would generate different code
    in practice.


    If you need a zero-initialized struct foo value as an expression,
    e.g. for use in an assigment or as an argument, you can use a
    compound literal in C99 or later:

    foo = (struct foo){ 0 };

    Again, you can omit the 0 in C23 or later.

    Here you're relying on the implementation to zero unspecified
    members, which is required by the standard.

    If you're stuck using a non-conforming implementation, you need
    to know exactly *how* it's non-conforming, and write whatever
    ugly code you need to work around it. Different non-conforming implementations are likely to be non-conforming in surprising ways.
    (Ideally your ugly code will also work on conforming implementations,
    but that might not be a priority.)



    --- 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 Mon Aug 24 19:26:26 2026
    Subject: Cryptographic zero initialization (was: Re: Initialization Of Static Pointer Variables)

    On 24/08/2026 4:01 AM, steve g wrote:
    David Brown <david.brown@hesbynett.no> writes:

    On 21/08/2026 21:12, Chris M. Thomasson wrote:
    On 8/20/2026 4:42 PM, Richard Tobin wrote:
    In article <11681np$3s8jk$4@dont-email.me>,
    Lawrence D;Oliveiro˙ <ldo@nz.invalid> wrote:

    The spec also says that all static variables are initialized to zero >>>>> at program start time. Are pointer variables allowed to be given an
    all-zero bit pattern at this time? Or are they supposed to be
    initialized to the NULL value (if that's different) instead?

    ISO C 1999, 6.7.8:

    If an object that has automatic storage duration is not initialized
    explicitly, its value is indeterminate. If an object that has static
    storage duration is not initialized explicitly, then:

    ? if it has pointer type, it is initialized to a null pointer;
    Oh. I must have missed that one.
    so:
    static void* g_ptr;
    is guaranteed to be nullptr? Just to clarify...

    Yes.

    I would still recomend memset or bzero. You never know what is complient
    or not - better to be safe then sorry.

    And if your need is cryptographic security, you should be using Micro-
    soft's SecureZeroMemory() or its equivalent,


    https://learn.microsoft.com/en-us/previous-versions/windows/desktop/legacy/aa366877(v=vs.85)

    and never mind the legacy warning, it's still there in the header files,
    and it's just a fancy way to write STOSB, so I'm including alt.lang.asm,
    and comp.lang.asm in this discussion; this is to annoy the trolls in comp.lang.c.

    Of course, many of the trolls in comp.lang.c are Linux supremacists, so
    they're going to have to figure out what GCC offers for the equivalent functionality.

    After all, /SecureZeroMemory()/ cannot be optimized out, or it wouldn't
    be secure.

    I'm actually curious about the equivalent function in GCC, since one of
    my many hobbies is cryptographic programming.


    Happy programming cryptography in assembly!
    --
    Johann | email: invalid -> com | http://www.myrkraverk.com/blog/
    I'm not from the Internet, I just work there. | via Easynews.com https://bsky.app/profile/myrkraverk.bsky.social | for ( ;; ) _:;

    --- 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 Mon Aug 24 13:16:30 2026
    David Brown <david.brown@hesbynett.no> writes:
    On 24/08/2026 12:04, Keith Thompson wrote:
    Richard Harnden <richard.nospam@gmail.invalid> writes:
    [...]
    Can you safely say something like this ... ?

    struct foo
    {
    char *s;
    int n;
    char c;
    void *data;
    };

    static const struct foo FOO_INIT;

    int main(void)
    {
    struct foo foo;

    foo = FOO_INIT;

    ...
    That's safe for conforming implementations. You can also
    use an initializer:
    struct foo foo = FOO_INIT;
    It solves the problem of zero-initializing a non-static object
    (such objects contain garbage if they're not initialized) -- but
    it's better done by writing
    struct foo foo = { 0 };
    (In C23 and later you can omit the 0.)

    In what sense do you consider that "better" ? I think it is arguably
    neater and less cognitive effort to understand, but did you have
    anything else in mind?

    Not really. With { 0 }, you can see at a glance what it means.
    With FOO_INIT, you have to know how FOO_INIT is defined, possibly in
    a different source file.

    Of course if the initial value is something other than all-zeros, you
    can change the way FOO_INIT is defined, and { 0 } wouldn't work.

    As I understand it, there is a distinction between :

    struct foo foo;
    foo = FOO_INIT; // or foo = (struct foo) { 0 };

    and

    struct foo foo = FOO_INIT; // or foo = { 0 };

    in that the former just copies the value of FOO_INIT while the later guarantees zero bit initialisation for padding bits and bytes.

    It's unlikely that an optimising compiler would generate different
    code in practice.

    I hadn't thought about that (and it's not likely to matter). I don't
    remember the rules for initializing padding off the top of my head.

    [...]

    --
    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 David Brown@3:633/10 to All on Tue Aug 25 08:51:28 2026
    On 24/08/2026 22:16, Keith Thompson wrote:
    David Brown <david.brown@hesbynett.no> writes:
    On 24/08/2026 12:04, Keith Thompson wrote:
    Richard Harnden <richard.nospam@gmail.invalid> writes:
    [...]
    Can you safely say something like this ... ?

    struct foo
    {
    char *s;
    int n;
    char c;
    void *data;
    };

    static const struct foo FOO_INIT;

    int main(void)
    {
    struct foo foo;

    foo = FOO_INIT;

    ...
    That's safe for conforming implementations. You can also
    use an initializer:
    struct foo foo = FOO_INIT;
    It solves the problem of zero-initializing a non-static object
    (such objects contain garbage if they're not initialized) -- but
    it's better done by writing
    struct foo foo = { 0 };
    (In C23 and later you can omit the 0.)

    In what sense do you consider that "better" ? I think it is arguably
    neater and less cognitive effort to understand, but did you have
    anything else in mind?

    Not really. With { 0 }, you can see at a glance what it means.
    With FOO_INIT, you have to know how FOO_INIT is defined, possibly in
    a different source file.

    Of course if the initial value is something other than all-zeros, you
    can change the way FOO_INIT is defined, and { 0 } wouldn't work.


    Fair enough - and I fully agree.

    As I understand it, there is a distinction between :

    struct foo foo;
    foo = FOO_INIT; // or foo = (struct foo) { 0 };

    and

    struct foo foo = FOO_INIT; // or foo = { 0 };

    in that the former just copies the value of FOO_INIT while the later
    guarantees zero bit initialisation for padding bits and bytes.

    It's unlikely that an optimising compiler would generate different
    code in practice.

    I hadn't thought about that (and it's not likely to matter). I don't remember the rules for initializing padding off the top of my head.


    I agree it is not likely to matter. I have never, to my knowledge, encountered a situation where the padding in a struct was relevant. In situations where it might be relevant, such as with structs that match communication or network telegrams with checksums or CRCs, I ensure
    there is no padding. That means wither using "packed" attributes, or
    being careful with the definition, adding any padding manually with
    extra fields, and checking with "-Werror=padded" and/or sizeof checks.

    But as far as I can see, assignment for structs copies the /value/ of
    the struct - thus it does not necessarily copy padding bytes.
    Initialisation, on the other hand, includes zeroing the padding
    explicitly in the semantics.


    --- 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 Tue Aug 25 12:20:17 2026
    On 24/08/2026 11:04, Keith Thompson wrote:
    Richard Harnden <richard.nospam@gmail.invalid> writes:
    [...]
    Can you safely say something like this ... ?

    struct foo
    {
    char *s;
    int n;
    char c;
    void *data;
    };

    static const struct foo FOO_INIT;

    int main(void)
    {
    struct foo foo;

    foo = FOO_INIT;

    ...

    That's safe for conforming implementations. You can also
    use an initializer:

    struct foo foo = FOO_INIT;

    It solves the problem of zero-initializing a non-static object
    (such objects contain garbage if they're not initialized) -- but
    it's better done by writing

    struct foo foo = { 0 };

    (In C23 and later you can omit the 0.)

    If you need a zero-initialized struct foo value as an expression,
    e.g. for use in an assigment or as an argument, you can use a
    compound literal in C99 or later:

    foo = (struct foo){ 0 };

    Again, you can omit the 0 in C23 or later.

    Here you're relying on the implementation to zero unspecified
    members, which is required by the standard.

    If you're stuck using a non-conforming implementation, you need
    to know exactly *how* it's non-conforming, and write whatever
    ugly code you need to work around it. Different non-conforming implementations are likely to be non-conforming in surprising ways.
    (Ideally your ugly code will also work on conforming implementations,
    but that might not be a priority.)


    Thanks.

    Would "memcpy(&foo, &FOO_INIT, sizeof FOO_INIT);" be better, ie: correct
    for pointers and floats that are not all-bits-zero?


    --- 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 Tue Aug 25 14:15:05 2026
    On 25/08/2026 13:20, Richard Harnden wrote:
    On 24/08/2026 11:04, Keith Thompson wrote:
    Richard Harnden <richard.nospam@gmail.invalid> writes:
    [...]
    Can you safely say something like this ... ?

    struct foo
    {
    ˙˙˙˙ char *s;
    ˙˙˙˙ int n;
    ˙˙˙˙ char c;
    ˙˙˙˙ void *data;
    };

    static const struct foo FOO_INIT;

    int main(void)
    {
    ˙˙˙˙ struct foo foo;

    ˙˙˙˙ foo = FOO_INIT;

    ˙˙˙˙ ...

    That's safe for conforming implementations.˙ You can also
    use an initializer:

    ˙˙˙˙ struct foo foo = FOO_INIT;

    It solves the problem of zero-initializing a non-static object
    (such objects contain garbage if they're not initialized) -- but
    it's better done by writing

    ˙˙˙˙ struct foo foo = { 0 };

    (In C23 and later you can omit the 0.)

    If you need a zero-initialized struct foo value as an expression,
    e.g. for use in an assigment or as an argument, you can use a
    compound literal in C99 or later:

    ˙˙˙˙ foo = (struct foo){ 0 };

    Again, you can omit the 0 in C23 or later.

    Here you're relying on the implementation to zero unspecified
    members, which is required by the standard.

    If you're stuck using a non-conforming implementation, you need
    to know exactly *how* it's non-conforming, and write whatever
    ugly code you need to work around it.˙ Different non-conforming
    implementations are likely to be non-conforming in surprising ways.
    (Ideally your ugly code will also work on conforming implementations,
    but that might not be a priority.)


    Thanks.

    Would "memcpy(&foo, &FOO_INIT, sizeof FOO_INIT);" be better, ie: correct
    for pointers and floats that are not all-bits-zero?


    It would be correct for pointers and floats that are not all-bits-zero
    for null pointers and 0.0 floats. But it would not be better. It is a
    lot more cumbersome, and easier to get wrong than initialising to { 0 }.
    And one some platforms and targets it could be significantly slower if
    the memcpy is called as an external function. Even for toolchains that optimise the memcpy well, it can hinder other optimisation - with normal initialisation, compilers know exactly what is in the variable and can
    use that for later optimisation, and can easily omit code for the initialisation when you later set the fields.

    My strong recommendation is that local variables should almost always be initialised - don't declare them until you have something to put in
    them. Then initialise them with the data you want in them. For
    structs, it can often be convenient to initialise them to zero and then
    set fields after that - do the initialisation with "struct foo foo = {}"
    (or "{ 0 }" pre-C23). If you have a common initial value for the
    struct, "struct foo foo = FOO_INIT;" is fine - but don't do it if you
    are always clearing it to zeros.

    Don't faff around with memset, memcpy, or other nonsense. That makes
    your code bulkier and slower, confuses readers, and invites mistakes by
    either yourself or future maintainers. The do not add any value to the
    code.

    Oh, and don't mix variable and struct names - no "struct foo foo". But
    I realise that was just an example, not real code.



    --- 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 Tue Aug 25 15:11:44 2026
    On 25/08/2026 13:15, David Brown wrote:

    Oh, and don't mix variable and struct names - no "struct foo foo".

    Any reason why? Just a style thing?


    --- 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 Tue Aug 25 16:35:01 2026
    On 25/08/2026 16:11, Richard Harnden wrote:
    On 25/08/2026 13:15, David Brown wrote:

    Oh, and don't mix variable and struct names - no "struct foo foo".


    (To be clear - that was a recommendation, not a command :-) )

    Any reason why?˙ Just a style thing?


    It is mainly the confusion and readability aspects. It is not a good
    idea to give two different things the same identifier. Just because
    this was common practice in C programming in the previous century, does
    not mean it was /good/ practice even then.

    Some people like to include the "struct" in all their struct types,
    other people like to use typedef's so that their different types can be
    used directly.

    The former style goes back to the days when everything was an "int"
    unless you have very good reason for having something different, and you almost never saw structs being passed into or out of functions. (Some
    old C compilers didn't even support doing that.) structs were special, compared to scaler types, and people liked to indicate that by always
    naming them that way.

    The later style is more popular with C++ programmers and users of more
    modern languages - types are types, and the details of the type are
    details, not something that needs to be expressed every time you use the
    type.

    Of course opinions and personal preferences are valid, and I am not
    trying to say that one way is "good" and the other "bad". If you make different style choices than I do, that's fine.

    But unless you live in a bubble where you are the only person who ever
    sees or uses your code, you need to consider how other people can see or
    use your code. Maybe /you/ never use C++, but maybe someone else will
    want to use your code with C++, or someone who is more used to C++ or a different style has to work with your code. I am not saying you should
    use a style of type naming just because someone else uses that style -
    just that it is helpful to avoid code styles that can cause unnecessary confusion or conflicts.

    It's just about future-proofing and making your code readable to others
    - so that when they see "foo", they can easily see if it is a variable
    or a type.

    So it is a "style thing" - but a reasoned style thing, not just an
    arbitrary preference. Of course you can may reasons for liking "struct
    foo foo;" that you see as stronger than my reasons for disliking them.
    And of course things like consistency with existing code can override
    any preferences people may have.



    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Richard Tobin@3:633/10 to All on Tue Aug 25 16:19:07 2026
    In article <116k96l$3oj0e$1@dont-email.me>,
    David Brown <david.brown@hesbynett.no> wrote:

    It is not a good idea to give two different things the same
    identifier.

    On the other hand it is a bad idea to not use the obvious name for a
    variable. If I'm dealing with a single variable of type "struct dog",
    I'd prefer not to have to come up with another name just to avoid
    calling it "dog".

    -- Richard

    --- 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 Tue Aug 25 20:07:35 2026
    On 25/08/2026 18:19, Richard Tobin wrote:
    In article <116k96l$3oj0e$1@dont-email.me>,
    David Brown <david.brown@hesbynett.no> wrote:

    It is not a good idea to give two different things the same
    identifier.

    On the other hand it is a bad idea to not use the obvious name for a variable. If I'm dealing with a single variable of type "struct dog",
    I'd prefer not to have to come up with another name just to avoid
    calling it "dog".


    There's no fixed rules here - there are lots of factors that influence readability and the scope for misunderstanding. The size of the scope
    for the variable can make a big difference, for example. And "dog"
    might be better than, say, "cat".


    --- 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 Tue Aug 25 21:47:29 2026
    On Tue, 25 Aug 2026 15:11:44 +0100, Richard Harnden wrote:

    On 25/08/2026 13:15, David Brown wrote:

    Oh, and don't mix variable and struct names - no "struct foo foo".

    Any reason why? Just a style thing?

    This is one area where C++ is (by design?) backward-incompatible with
    C.

    In C, struct names and enum names occupy namespaces separate from the
    names of variables and typedefs. In C++, they are all in the same
    namespace.

    --- 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 Tue Aug 25 21:49:49 2026
    On Tue, 25 Aug 2026 16:19:07 -0000 (UTC), Richard Tobin wrote:

    On the other hand it is a bad idea to not use the obvious name for a variable. If I'm dealing with a single variable of type "struct
    dog", I'd prefer not to have to come up with another name just to
    avoid calling it "dog".

    At one point, I was using the convention of plurals for type names,
    and singular forms for variable/constant names (i.e. holding instances
    of the type). This doesn?t look so facile in C:

    typedef ... dogs;
    dogs dog;

    but it looks a bit better when the type reference comes after the name
    being declared:

    type dogs is ... ;
    dog : dogs;

    --- 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 Tue Aug 25 16:50:23 2026
    richard@cogsci.ed.ac.uk (Richard Tobin) writes:
    In article <116k96l$3oj0e$1@dont-email.me>,
    David Brown <david.brown@hesbynett.no> wrote:

    It is not a good idea to give two different things the same
    identifier.

    On the other hand it is a bad idea to not use the obvious name for a variable. If I'm dealing with a single variable of type "struct dog",
    I'd prefer not to have to come up with another name just to avoid
    calling it "dog".

    That's much more likely to happen in sample code of the kind
    commonly posted here than in real-world programs. Realistically,
    if you have a type "struct dog", a variable of that type is likely
    to have some more specific meaning.

    --
    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 Richard Tobin@3:633/10 to All on Wed Aug 26 00:41:10 2026
    In article <116l9o0$79br$1@kst.eternal-september.org>,
    Keith Thompson <Keith.S.Thompson+u@gmail.com> wrote:

    On the other hand it is a bad idea to not use the obvious name for a
    variable. If I'm dealing with a single variable of type "struct dog",
    I'd prefer not to have to come up with another name just to avoid
    calling it "dog".

    That's much more likely to happen in sample code of the kind
    commonly posted here than in real-world programs. Realistically,
    if you have a type "struct dog", a variable of that type is likely
    to have some more specific meaning.

    Sometimes, sometimes not. The argument passed to a method-like
    function will probably be a specific dog, but the method itself will
    apply to an arbitrary dog.

    -- Richard


    --- 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 Tue Aug 25 18:01:09 2026
    Richard Harnden <richard.nospam@gmail.invalid> writes:
    [...]
    Would "memcpy(&foo, &FOO_INIT, sizeof FOO_INIT);" be better, ie:
    correct for pointers and floats that are not all-bits-zero?

    Given that FOO_INIT is defined correctly, that should work.

    Calling memcpy() would be a great workaround for a language
    that doesn't support struct assignment as a language feature.
    Fortunately, C has not been such a language for a very long time.

    There's nothing wrong with

    foo = FOO_INIT;

    There might be some subtle semantic differences involving padding
    bits and/or padding bytes. That's unlikely to matter, and I still
    haven't looked up the relevant rules in the standard.

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

    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From David Brown@3:633/10 to All on Wed Aug 26 09:28:32 2026
    On 25/08/2026 23:47, Lawrence D?Oliveiro wrote:
    On Tue, 25 Aug 2026 15:11:44 +0100, Richard Harnden wrote:

    On 25/08/2026 13:15, David Brown wrote:

    Oh, and don't mix variable and struct names - no "struct foo foo".

    Any reason why? Just a style thing?

    This is one area where C++ is (by design?) backward-incompatible with
    C.


    You make it sound like it might have been designed specifically to be backwards incompatible with C, which is not the case.

    The point of having C++

    struct X { ... };

    behave as though it were C

    typedef struct X { ... } X;

    is simply that struct (and therefore also class) types are heavily used
    in C++, and the language does not want to distinguish them in normal usage.

    In C, you can choose to keep the distinction (by using the full name
    "struct foo") or remove it (by using typedefs). Some prefer one style,
    some prefer the other.

    In C, struct names and enum names occupy namespaces separate from the
    names of variables and typedefs. In C++, they are all in the same
    namespace.

    To be pedantic, the term you are looking for is "name space" - two
    words. That's the term used in both the C and C++ standards, while "namespace" (one word) is a specific feature of the C++ language.
    Obviously there is no confusion in what you wrote here, but it's always possible that someone will want to look at what the C standard says
    about the different name spaces - and then they should search the pdf
    files with the correct spelling.


    --- 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 Wed Aug 26 09:31:07 2026
    On 26/08/2026 03:01, Keith Thompson wrote:
    Richard Harnden <richard.nospam@gmail.invalid> writes:
    [...]
    Would "memcpy(&foo, &FOO_INIT, sizeof FOO_INIT);" be better, ie:
    correct for pointers and floats that are not all-bits-zero?

    Given that FOO_INIT is defined correctly, that should work.

    Calling memcpy() would be a great workaround for a language
    that doesn't support struct assignment as a language feature.
    Fortunately, C has not been such a language for a very long time.

    There's nothing wrong with

    foo = FOO_INIT;

    There might be some subtle semantic differences involving padding
    bits and/or padding bytes. That's unlikely to matter, and I still
    haven't looked up the relevant rules in the standard.


    Digging in the toolchain horror stories again, I have used a couple of embedded C compilers that did not support struct assignment (or passing
    or returning structs by value in functions). Fortunately I have not
    seen such blatant non-conformity for a couple of decades.


    --- 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 Wed Aug 26 09:50:10 2026
    David Brown <david.brown@hesbynett.no> wrote:
    On 26/08/2026 03:01, Keith Thompson wrote:
    Richard Harnden <richard.nospam@gmail.invalid> writes:
    [...]
    Would "memcpy(&foo, &FOO_INIT, sizeof FOO_INIT);" be better, ie:
    correct for pointers and floats that are not all-bits-zero?

    Given that FOO_INIT is defined correctly, that should work.

    Calling memcpy() would be a great workaround for a language
    that doesn't support struct assignment as a language feature.
    Fortunately, C has not been such a language for a very long time.

    There's nothing wrong with

    foo = FOO_INIT;

    There might be some subtle semantic differences involving padding
    bits and/or padding bytes. That's unlikely to matter, and I still
    haven't looked up the relevant rules in the standard.


    Digging in the toolchain horror stories again, I have used a couple of embedded C compilers that did not support struct assignment (or passing
    or returning structs by value in functions). Fortunately I have not
    seen such blatant non-conformity for a couple of decades.

    Last time that I checked sdcc did not support passing or returning
    structs by value. At that time they claimed C2011 support.

    --
    Waldek Hebisch

    --- 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 Wed Aug 26 03:40:24 2026
    antispam@fricas.org (Waldek Hebisch) writes:
    David Brown <david.brown@hesbynett.no> wrote:
    [...]
    Digging in the toolchain horror stories again, I have used a couple of
    embedded C compilers that did not support struct assignment (or passing
    or returning structs by value in functions). Fortunately I have not
    seen such blatant non-conformity for a couple of decades.

    At least that shows up as the code being flagged, unlike, for example,
    static objects not being zero-initialized.

    Last time that I checked sdcc did not support passing or returning
    structs by value. At that time they claimed C2011 support.

    It appears to support passing and returning structs in version 4.5.0,
    but not in 4.2.0. Both version support struct assignment.

    --
    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 David Brown@3:633/10 to All on Wed Aug 26 13:47:14 2026
    On 26/08/2026 11:50, Waldek Hebisch wrote:
    David Brown <david.brown@hesbynett.no> wrote:
    On 26/08/2026 03:01, Keith Thompson wrote:
    Richard Harnden <richard.nospam@gmail.invalid> writes:
    [...]
    Would "memcpy(&foo, &FOO_INIT, sizeof FOO_INIT);" be better, ie:
    correct for pointers and floats that are not all-bits-zero?

    Given that FOO_INIT is defined correctly, that should work.

    Calling memcpy() would be a great workaround for a language
    that doesn't support struct assignment as a language feature.
    Fortunately, C has not been such a language for a very long time.

    There's nothing wrong with

    foo = FOO_INIT;

    There might be some subtle semantic differences involving padding
    bits and/or padding bytes. That's unlikely to matter, and I still
    haven't looked up the relevant rules in the standard.


    Digging in the toolchain horror stories again, I have used a couple of
    embedded C compilers that did not support struct assignment (or passing
    or returning structs by value in functions). Fortunately I have not
    seen such blatant non-conformity for a couple of decades.

    Last time that I checked sdcc did not support passing or returning
    structs by value. At that time they claimed C2011 support.


    SDCC does its best, but it's very difficult to generate decent object
    code for its targets (mainly brain-dead 8-bit CISC microcontrollers) for
    some aspects of C. I don't know the specifics of what it does and does
    not support, but there are many non-conformities or missing features
    that are sometimes seen for toolchains for such targets. These are all
    taken from real toolchains, some of which sold for significant prices :

    * Failing to zero program-lifetime data (that's a nasty one, because it
    does not give compile-time errors)

    * 32-bit "double", or maybe no floating point support at all

    * Inability to pass and/or return structs by value

    * Lack of support for 64-bit long long

    * Functions not being re-entrant or recursive unless specifically marked
    by an extension

    * const data and non-const being in separate address spaces, and their pointers being incompatible (that's another painful one)

    * Lack of support for multi-dimensional arrays

    * Not allowing arrays of structs, and/or structs with array fields

    * Small limits on the number of parameters in functions, or the even the number of local variables

    * Failing to promote small integer types to "int" in arithmetic
    expressions (another cause of subtle problems)


    Embedded development got a lot simpler - but perhaps less interesting -
    when ARM and gcc became dominant.



    --- 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 Wed Aug 26 12:50:29 2026
    David Brown <david.brown@hesbynett.no> wrote:
    On 26/08/2026 11:50, Waldek Hebisch wrote:
    David Brown <david.brown@hesbynett.no> wrote:
    On 26/08/2026 03:01, Keith Thompson wrote:
    Richard Harnden <richard.nospam@gmail.invalid> writes:
    [...]
    Would "memcpy(&foo, &FOO_INIT, sizeof FOO_INIT);" be better, ie:
    correct for pointers and floats that are not all-bits-zero?

    Given that FOO_INIT is defined correctly, that should work.

    Calling memcpy() would be a great workaround for a language
    that doesn't support struct assignment as a language feature.
    Fortunately, C has not been such a language for a very long time.

    There's nothing wrong with

    foo = FOO_INIT;

    There might be some subtle semantic differences involving padding
    bits and/or padding bytes. That's unlikely to matter, and I still
    haven't looked up the relevant rules in the standard.


    Digging in the toolchain horror stories again, I have used a couple of
    embedded C compilers that did not support struct assignment (or passing
    or returning structs by value in functions). Fortunately I have not
    seen such blatant non-conformity for a couple of decades.

    Last time that I checked sdcc did not support passing or returning
    structs by value. At that time they claimed C2011 support.


    SDCC does its best, but it's very difficult to generate decent object
    code for its targets (mainly brain-dead 8-bit CISC microcontrollers) for some aspects of C. I don't know the specifics of what it does and does
    not support, but there are many non-conformities or missing features
    that are sometimes seen for toolchains for such targets. These are all taken from real toolchains, some of which sold for significant prices :

    * Failing to zero program-lifetime data (that's a nasty one, because it
    does not give compile-time errors)

    * 32-bit "double", or maybe no floating point support at all

    That does not bother me too much. If there is no hardware floating
    point, then compiler support means library + syntactic sugar.
    I would use compiler provided library only if it gives me exactly
    what I need, which is unlikely to be the case.

    * Inability to pass and/or return structs by value

    That bothers me quite a lot, especially if that includes structures
    that fit into machine register. AFAICS structures and unions are
    the only way to get resonable supply of types which contain the same information as a base type, but are incompatible with it (typedef
    is considerd different name for the type so would not do).

    * Lack of support for 64-bit long long

    That bothers me more than lack of double, but like floating point
    this is library + syntactic sugar. I can provide my own library
    if I need it. But replacement type normally would be a struct
    so I would like ability to pass and return them from functions.

    * Functions not being re-entrant or recursive unless specifically marked
    by an extension

    Not nice, but practical impact likely is minimal: for small embedded
    target recursive functions are likely to be quite rare.

    * const data and non-const being in separate address spaces, and their pointers being incompatible (that's another painful one)

    AFAICS this is essentially forced by the hardware + desire to keep
    const data in flash. And offending hardware is well known, so one
    knows which hardware should be avoided.

    * Lack of support for multi-dimensional arrays

    * Not allowing arrays of structs, and/or structs with array fields

    * Small limits on the number of parameters in functions, or the even the number of local variables

    Smallest MCU that I have has 256 bytes RAM, that obviously limits
    number of local variables. And there are smaller MCU-s. OTOH
    on some machines, like Cortex M0, supporting decent number of
    local variables is painful. I can understand compiler writer
    which decides to support only what works in natural way on given
    hardware. AFAIK gcc always tried to give smooth support in face
    of hardware limitations, but in the past it was rich source of bugs.

    * Failing to promote small integer types to "int" in arithmetic
    expressions (another cause of subtle problems)


    Embedded development got a lot simpler - but perhaps less interesting -
    when ARM and gcc became dominant.

    --
    Waldek Hebisch

    --- 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 Wed Aug 26 15:57:50 2026
    On 26/08/2026 14:50, Waldek Hebisch wrote:
    David Brown <david.brown@hesbynett.no> wrote:
    On 26/08/2026 11:50, Waldek Hebisch wrote:
    David Brown <david.brown@hesbynett.no> wrote:
    On 26/08/2026 03:01, Keith Thompson wrote:
    Richard Harnden <richard.nospam@gmail.invalid> writes:
    [...]
    Would "memcpy(&foo, &FOO_INIT, sizeof FOO_INIT);" be better, ie:
    correct for pointers and floats that are not all-bits-zero?

    Given that FOO_INIT is defined correctly, that should work.

    Calling memcpy() would be a great workaround for a language
    that doesn't support struct assignment as a language feature.
    Fortunately, C has not been such a language for a very long time.

    There's nothing wrong with

    foo = FOO_INIT;

    There might be some subtle semantic differences involving padding
    bits and/or padding bytes. That's unlikely to matter, and I still
    haven't looked up the relevant rules in the standard.


    Digging in the toolchain horror stories again, I have used a couple of >>>> embedded C compilers that did not support struct assignment (or passing >>>> or returning structs by value in functions). Fortunately I have not
    seen such blatant non-conformity for a couple of decades.

    Last time that I checked sdcc did not support passing or returning
    structs by value. At that time they claimed C2011 support.


    SDCC does its best, but it's very difficult to generate decent object
    code for its targets (mainly brain-dead 8-bit CISC microcontrollers) for
    some aspects of C. I don't know the specifics of what it does and does
    not support, but there are many non-conformities or missing features
    that are sometimes seen for toolchains for such targets. These are all
    taken from real toolchains, some of which sold for significant prices :

    * Failing to zero program-lifetime data (that's a nasty one, because it
    does not give compile-time errors)

    * 32-bit "double", or maybe no floating point support at all

    That does not bother me too much. If there is no hardware floating
    point, then compiler support means library + syntactic sugar.
    I would use compiler provided library only if it gives me exactly
    what I need, which is unlikely to be the case.

    Agreed. Usually when there is no floating point hardware, you want to
    avoid using floating point in the code unless it is particularly
    helpful. 64-bit doubles are rarely useful on small embedded systems.
    But it is still a non-conformity.


    * Inability to pass and/or return structs by value

    That bothers me quite a lot, especially if that includes structures
    that fit into machine register. AFAICS structures and unions are
    the only way to get resonable supply of types which contain the same information as a base type, but are incompatible with it (typedef
    is considerd different name for the type so would not do).


    That is correct - structs (and unions) are the only way to make new
    types in C. One thing I find annoying about embedded 32-bit ARM is that
    the EABI is painfully inefficient for passing and returning structs.
    But it is still very useful!

    * Lack of support for 64-bit long long

    That bothers me more than lack of double, but like floating point
    this is library + syntactic sugar. I can provide my own library
    if I need it. But replacement type normally would be a struct
    so I would like ability to pass and return them from functions.

    Of course you can always make things yourself, but it's a lot easier
    when the compiler handles them efficiently!


    * Functions not being re-entrant or recursive unless specifically marked
    by an extension

    Not nice, but practical impact likely is minimal: for small embedded
    target recursive functions are likely to be quite rare.

    They are a lot more common when you have an RTOS - the same function can
    be called from more than one thread. Even without an RTOS, you might
    still call small functions from both mainloop code and interrupt code.
    But it is correct that they are relatively rare.


    * const data and non-const being in separate address spaces, and their
    pointers being incompatible (that's another painful one)

    AFAICS this is essentially forced by the hardware + desire to keep
    const data in flash. And offending hardware is well known, so one
    knows which hardware should be avoided.

    Yes, but it only applies to /some/ compilers for such hardware, so it is
    not consistent. And code like this will be a problem :

    extern void debug_port_string(const char * s);

    void show_number(int n) {
    char s[20];

    snprintf(s, sizeof(s), "Number %i\n", n);
    debug_port_string(s);
    }


    * Lack of support for multi-dimensional arrays

    * Not allowing arrays of structs, and/or structs with array fields

    * Small limits on the number of parameters in functions, or the even the
    number of local variables

    Smallest MCU that I have has 256 bytes RAM, that obviously limits
    number of local variables. And there are smaller MCU-s. OTOH
    on some machines, like Cortex M0, supporting decent number of
    local variables is painful. I can understand compiler writer
    which decides to support only what works in natural way on given
    hardware. AFAIK gcc always tried to give smooth support in face
    of hardware limitations, but in the past it was rich source of bugs.


    gcc has never had a limit to the number of local variables. I have used
    it with an MCU that had no ram at all (just 32 8-bit registers).
    Obviously small hardware puts limits on many things, and that's fair
    enough. It is very annoying when it is the toolchain that is the
    limiting factor.

    * Failing to promote small integer types to "int" in arithmetic
    expressions (another cause of subtle problems)


    Embedded development got a lot simpler - but perhaps less interesting -
    when ARM and gcc became dominant.



    --- 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 Wed Aug 26 15:37:46 2026
    Keith Thompson <Keith.S.Thompson+u@gmail.com> writes:
    richard@cogsci.ed.ac.uk (Richard Tobin) writes:
    In article <116k96l$3oj0e$1@dont-email.me>,
    David Brown <david.brown@hesbynett.no> wrote:

    It is not a good idea to give two different things the same
    identifier.

    On the other hand it is a bad idea to not use the obvious name for a
    variable. If I'm dealing with a single variable of type "struct dog",
    I'd prefer not to have to come up with another name just to avoid
    calling it "dog".

    That's much more likely to happen in sample code of the kind
    commonly posted here than in real-world programs. Realistically,
    if you have a type "struct dog", a variable of that type is likely
    to have some more specific meaning.

    Or one might use 'struct canine' and then call it a dog...



    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From bart@3:633/10 to All on Wed Aug 26 17:33:56 2026
    On 26/08/2026 12:47, David Brown wrote:
    On 26/08/2026 11:50, Waldek Hebisch wrote:
    David Brown <david.brown@hesbynett.no> wrote:
    On 26/08/2026 03:01, Keith Thompson wrote:
    Richard Harnden <richard.nospam@gmail.invalid> writes:
    [...]
    Would "memcpy(&foo, &FOO_INIT, sizeof FOO_INIT);" be better, ie:
    correct for pointers and floats that are not all-bits-zero?

    Given that FOO_INIT is defined correctly, that should work.

    Calling memcpy() would be a great workaround for a language
    that doesn't support struct assignment as a language feature.
    Fortunately, C has not been such a language for a very long time.

    There's nothing wrong with

    ˙˙˙˙˙ foo = FOO_INIT;

    There might be some subtle semantic differences involving padding
    bits and/or padding bytes.˙ That's unlikely to matter, and I still
    haven't looked up the relevant rules in the standard.


    Digging in the toolchain horror stories again, I have used a couple of
    embedded C compilers that did not support struct assignment (or passing
    or returning structs by value in functions).˙ Fortunately I have not
    seen such blatant non-conformity for a couple of decades.

    Last time that I checked sdcc did not support passing or returning
    structs by value.˙ At that time they claimed C2011 support.


    SDCC does its best, but it's very difficult to generate decent object
    code for its targets (mainly brain-dead 8-bit CISC microcontrollers) for some aspects of C.˙ I don't know the specifics of what it does and does
    not support, but there are many non-conformities or missing features
    that are sometimes seen for toolchains for such targets.˙ These are all taken from real toolchains, some of which sold for significant prices :

    * Failing to zero program-lifetime data (that's a nasty one, because it
    does not give compile-time errors)

    * 32-bit "double", or maybe no floating point support at all

    * Inability to pass and/or return structs by value

    * Lack of support for 64-bit long long

    I suppose you could have 64-bit floats and ints, but if the device has,
    for example, only 64KB code + data, there wouldn't be much memory left
    after those libraries are included.

    They wouldn't run that fast either if it is a 8-bit device with a clock
    speed in MHz.

    For passing structs by value, C seems to require that copies are made
    first, even if marked 'const', and even if the ABI says they are passed
    by reference. At least that's what compilers for 64-bit machines seem to do.

    * Functions not being re-entrant or recursive unless specifically marked
    by an extension

    That sounds like a feature actually. Although if there are lots of such functions that keep their locals in static memory (off the stack), they
    will occupy memory even when not called.

    * const data and non-const being in separate address spaces, and their pointers being incompatible (that's another painful one)

    * Lack of support for multi-dimensional arrays

    I don't think C technically has multi-dimension arrays, it just allows
    1D arrays whose elements can be other fixed-size arrays.

    Do you mean elements can only be non-aggregate types, or that pointer dereference levels are limited?

    That would be an unreasonable limitation, assuming a cross-compiler is
    being used. (If the compiler has to run /on/ the device, as mine did,
    then it's more understandable!)

    * Not allowing arrays of structs, and/or structs with array fields

    See above.

    * Small limits on the number of parameters in functions, or the even the number of local variables

    * Failing to promote small integer types to "int" in arithmetic
    expressions (another cause of subtle problems)

    That's another useful feature, even if it makes it non-conforming. But
    perhaps it should be an option.



    --- 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 Wed Aug 26 17:09:59 2026
    bart <bc@freeuk.com> writes:

    For passing structs by value, C seems to require that copies are made
    first, even if marked 'const', and even if the ABI says they are passed
    by reference. At least that's what compilers for 64-bit machines seem to do.

    Which ABI? This is from the SPARC ABI:

    Structure, Union, and Quad-Precision Arguments

    As described in the data representation section, structures and unions can have byte, halfword, word, or
    doubleword alignment, depending on the constituents. To ensure proper argument alignment and to
    facilitate addressing, structure and union objects are not passed directly in the argument list. Quad-
    precision values follow the same conventions as structures and unions.

    The X86_64 ABI (which allows pass-by-value for structures up to 16 bytes long)

    The classification of aggregate (structures and arrays) and union types works
    as follows:

    1. If the size of an object is larger than four eightbytes, or it contains unaligned
    fields, it has class MEMORY 10.

    2. If a C++ object has either a non-trivial copy constructor or a non-trivial
    destructor 11, it is passed by invisible reference (the object is replaced in the
    parameter list by a pointer that has class INTEGER) 12.

    3. If the size of the aggregate exceeds a single eightbyte, each is classified
    separately. Each eightbyte gets initialized to class NO_CLASS.

    4. Each field of an object is classified recursively so that always two fields are
    considered. The resulting class is calculated according to the classes of the
    fields in the eightbyte:

    (a) If both classes are equal, this is the resulting class.
    (b) If one of the classes is NO_CLASS, the resulting class is the other

    class.
    (c) If one of the classes is MEMORY, the result is the MEMORY class.
    (d) If one of the classes is INTEGER, the result is the INTEGER.
    (e) If one of the classes is X87, X87UP, COMPLEX_X87 class, MEM-

    ORY is used as class.
    (f) Otherwise class SSE is used.

    5. Then a post merger cleanup is done:

    (a) If one of the classes is MEMORY, the whole argument is passed in
    memory.

    (b) If X87UP is not preceded by X87, the whole argument is passed in
    memory.

    (c) If the size of the aggregate exceeds two eightbytes and the first eight-
    byte isn't SSE or any other eightbyte isn't SSEUP, the whole argument
    is passed in memory.

    Passing Once arguments are classified, the registers get assigned (in left-to-right
    order) for passing as follows:

    1. If the class is MEMORY, pass the argument on the stack.

    2. If the class is INTEGER, the next available register of the sequence %rdi,
    %rsi, %rdx, %rcx, %r8 and %r9 is used13.

    3. If the class is SSE, the next available vector register is used, the registers
    are taken in the order from %xmm0 to %xmm7.

    4. If the class is SSEUP, the eightbyte is passed in the next available eightbyte
    chunk of the last used vector register.

    5. If the class is X87, X87UP or COMPLEX_X87, it is passed in memory.



    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From bart@3:633/10 to All on Wed Aug 26 19:03:00 2026
    On 26/08/2026 18:09, Scott Lurndal wrote:
    bart <bc@freeuk.com> writes:

    For passing structs by value, C seems to require that copies are made
    first, even if marked 'const', and even if the ABI says they are passed
    by reference. At least that's what compilers for 64-bit machines seem to do.

    Which ABI?

    I've seen it on Win64 ABI. There, structs of size 1/2/4/8 bytes are
    passed in registers, so by-value anyway. Anything else is passed by
    reference. The ABI says nothing about by-value copying, but C compilers
    do that anyway.

    Try it with this program:

    typedef struct{char a[17];} S;

    void F(const S);

    void G() {
    static S x;
    F(x);
    }

    MSVC will copy the 17 bytes then pass a reference to x. So will TCC. I
    no longer have gcc or clang on my PC to try it (and the godbolt versions
    are for Linux).


    This is from the SPARC ABI:

    Then you are going to have trouble finding an example which is an
    exception from these labyrinthine rules, and that is passed by reference.

    But when you do, you will see if the struct data is copied to a temp
    location before passing a reference to that temp.

    But from what I make out on godbolt (this is clang -O2 for arch64):

    stp xzr, xzr, [sp, #8]
    strb wzr, [sp, #24]

    these two lines appear to write 17 bytes to memory.

    If this is a C-only thing, then it will not be part of the ABI which
    should not be language-specific.



    --- 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 Wed Aug 26 20:56:38 2026
    On 26/08/2026 18:33, bart wrote:
    On 26/08/2026 12:47, David Brown wrote:
    On 26/08/2026 11:50, Waldek Hebisch wrote:
    David Brown <david.brown@hesbynett.no> wrote:
    On 26/08/2026 03:01, Keith Thompson wrote:
    Richard Harnden <richard.nospam@gmail.invalid> writes:
    [...]
    Would "memcpy(&foo, &FOO_INIT, sizeof FOO_INIT);" be better, ie:
    correct for pointers and floats that are not all-bits-zero?

    Given that FOO_INIT is defined correctly, that should work.

    Calling memcpy() would be a great workaround for a language
    that doesn't support struct assignment as a language feature.
    Fortunately, C has not been such a language for a very long time.

    There's nothing wrong with

    ˙˙˙˙˙ foo = FOO_INIT;

    There might be some subtle semantic differences involving padding
    bits and/or padding bytes.˙ That's unlikely to matter, and I still
    haven't looked up the relevant rules in the standard.


    Digging in the toolchain horror stories again, I have used a couple of >>>> embedded C compilers that did not support struct assignment (or passing >>>> or returning structs by value in functions).˙ Fortunately I have not
    seen such blatant non-conformity for a couple of decades.

    Last time that I checked sdcc did not support passing or returning
    structs by value.˙ At that time they claimed C2011 support.


    SDCC does its best, but it's very difficult to generate decent object
    code for its targets (mainly brain-dead 8-bit CISC microcontrollers)
    for some aspects of C.˙ I don't know the specifics of what it does and
    does not support, but there are many non-conformities or missing
    features that are sometimes seen for toolchains for such targets.
    These are all taken from real toolchains, some of which sold for
    significant prices :

    * Failing to zero program-lifetime data (that's a nasty one, because
    it does not give compile-time errors)

    * 32-bit "double", or maybe no floating point support at all

    * Inability to pass and/or return structs by value

    * Lack of support for 64-bit long long

    I suppose you could have 64-bit floats and ints, but if the device has,
    for example, only 64KB code + data, there wouldn't be much memory left
    after those libraries are included.

    64-bit int support usually doesn't take much code space, but software
    floating point support can do. It is not uncommon, for example, to use
    a "printf" that does not support floating point if there is no hardware floating point.


    They wouldn't run that fast either if it is a 8-bit device with a clock speed in MHz.

    Of course.


    For passing structs by value, C seems to require that copies are made
    first, even if marked 'const', and even if the ABI says they are passed
    by reference. At least that's what compilers for 64-bit machines seem to
    do.

    Marking a parameter as "const" does not do anything as far as the caller
    is concerned, so it still needs to make a copy if the value of the
    struct needs to be preserved. If it is a big struct, passing by pointer
    is going to be more efficient.


    * Functions not being re-entrant or recursive unless specifically
    marked by an extension

    That sounds like a feature actually. Although if there are lots of such functions that keep their locals in static memory (off the stack), they
    will occupy memory even when not called.

    It is a feature for the small microcontroller cores, if they don't have
    good "stack pointer + offset" addressing - local variables get fixed
    addresses in memory. On some small cores, this can be a lot more
    efficient than using a stack. Some compilers do a fair bit of lifetime analysis and whole-program analysis to see where these local memory
    slots can be re-used and shared.

    But again, it is a non-conformity, and can cause surprises.


    * const data and non-const being in separate address spaces, and their
    pointers being incompatible (that's another painful one)

    * Lack of support for multi-dimensional arrays

    I don't think C technically has multi-dimension arrays, it just allows
    1D arrays whose elements can be other fixed-size arrays.

    Do you mean elements can only be non-aggregate types, or that pointer dereference levels are limited?

    Without trying to be pedantic about terminology, I mean the later here.
    Thus such compilers can't handle "int xss[10][20];".


    That would be an unreasonable limitation, assuming a cross-compiler is
    being used. (If the compiler has to run /on/ the device, as mine did,
    then it's more understandable!)

    All these compilers I am thinking of are cross-compilers. I agree that
    it is not unreasonable for compilers written to run on very small
    systems to have very limited features.


    * Not allowing arrays of structs, and/or structs with array fields

    See above.

    Such compilers can't handle "struct foo fs[10];", or "struct X { int
    data[4]; }". (Or "struct X { struct Y y; };")


    * Small limits on the number of parameters in functions, or the even
    the number of local variables

    * Failing to promote small integer types to "int" in arithmetic
    expressions (another cause of subtle problems)

    That's another useful feature, even if it makes it non-conforming. But perhaps it should be an option.


    The idea of it is to get more efficient results for arithmetic using
    8-bit types, on an 8-bit microcontroller. I would be quite happy if the concept of "integer promotion" did not exist in C. But since it exists,
    this "feature" means code can mean something different on different
    systems, unexpectedly. It is much clearer if the compiler sticks to the
    C rules - consistency and predictability are more important than
    personal thoughts about how C should have or could have been defined.
    Peephole optimisation can usually remove most of the redundant
    operations that might be generated when 8 bit operations are promoted to 16-bit, negating most possible efficiency benefits from this "feature".
    (Or if the vendors supported C23, we could use _BitInt(8) to skip
    promotion.)


    --- 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 Wed Aug 26 12:37:47 2026
    On 8/26/2026 12:28 AM, David Brown wrote:
    On 25/08/2026 23:47, Lawrence D?Oliveiro wrote:
    On Tue, 25 Aug 2026 15:11:44 +0100, Richard Harnden wrote:

    On 25/08/2026 13:15, David Brown wrote:

    Oh, and don't mix variable and struct names - no "struct foo foo".

    Any reason why?˙ Just a style thing?

    This is one area where C++ is (by design?) backward-incompatible with
    C.


    You make it sound like it might have been designed specifically to be backwards incompatible with C, which is not the case.

    The point of having C++

    ˙˙˙˙struct X { ... };

    behave as though it were C

    ˙˙˙˙typedef struct X { ... } X;

    is simply that struct (and therefore also class) types are heavily used
    in C++, and the language does not want to distinguish them in normal usage.

    In C, you can choose to keep the distinction (by using the full name
    "struct foo") or remove it (by using typedefs).˙ Some prefer one style,
    some prefer the other.

    In C, struct names and enum names occupy namespaces separate from the
    names of variables and typedefs. In C++, they are all in the same
    namespace.

    To be pedantic, the term you are looking for is "name space" - two
    words.˙ That's the term used in both the C and C++ standards, while "namespace" (one word) is a specific feature of the C++ language.
    Obviously there is no confusion in what you wrote here, but it's always possible that someone will want to look at what the C standard says
    about the different name spaces - and then they should search the pdf
    files with the correct spelling.


    name space in C, not that bad.

    ct_*

    There. A name space.

    --- 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 Wed Aug 26 23:29:57 2026
    bart <bc@freeuk.com> wrote:
    On 26/08/2026 12:47, David Brown wrote:
    On 26/08/2026 11:50, Waldek Hebisch wrote:
    David Brown <david.brown@hesbynett.no> wrote:
    On 26/08/2026 03:01, Keith Thompson wrote:
    Richard Harnden <richard.nospam@gmail.invalid> writes:
    [...]
    Would "memcpy(&foo, &FOO_INIT, sizeof FOO_INIT);" be better, ie:
    correct for pointers and floats that are not all-bits-zero?

    Given that FOO_INIT is defined correctly, that should work.

    Calling memcpy() would be a great workaround for a language
    that doesn't support struct assignment as a language feature.
    Fortunately, C has not been such a language for a very long time.

    There's nothing wrong with

    ˙˙˙˙˙ foo = FOO_INIT;

    There might be some subtle semantic differences involving padding
    bits and/or padding bytes.˙ That's unlikely to matter, and I still
    haven't looked up the relevant rules in the standard.


    Digging in the toolchain horror stories again, I have used a couple of >>>> embedded C compilers that did not support struct assignment (or passing >>>> or returning structs by value in functions).˙ Fortunately I have not
    seen such blatant non-conformity for a couple of decades.

    Last time that I checked sdcc did not support passing or returning
    structs by value.˙ At that time they claimed C2011 support.


    SDCC does its best, but it's very difficult to generate decent object
    code for its targets (mainly brain-dead 8-bit CISC microcontrollers) for
    some aspects of C.˙ I don't know the specifics of what it does and does
    not support, but there are many non-conformities or missing features
    that are sometimes seen for toolchains for such targets.˙ These are all
    taken from real toolchains, some of which sold for significant prices :

    * Failing to zero program-lifetime data (that's a nasty one, because it
    does not give compile-time errors)

    * 32-bit "double", or maybe no floating point support at all

    * Inability to pass and/or return structs by value

    * Lack of support for 64-bit long long

    I suppose you could have 64-bit floats and ints, but if the device has,
    for example, only 64KB code + data, there wouldn't be much memory left
    after those libraries are included.

    They wouldn't run that fast either if it is a 8-bit device with a clock speed in MHz.

    I am not sure if you are familiar with typical modern MCU-s. First,
    in a sense you get a complete computer in a single chip. Just connect
    power, one or two capacitors and possibly connect appropriate voltage
    to configuration pins. Inside chip there is CPU, RAM and flash
    memory and a bunch of peripherials, like pin drivers, serial ports,
    ADC, etc. Flash is writable, but slow to write and number of writes
    may be limited. In several cases manufacturer promises that 100
    writes will work, usually more writes work than promised, but if you
    try to use flash as work storage, then it is easy to wear out flash
    and get non-working chip. So normally flash is used to store program
    and possibly some long term data (in particular configuration data).
    Flash used in MCU-s tend to have limits on clock frequency, about
    40 MHz seem to be practical limit. Classic MCU-s access flash via
    parallel bus, which allows pretty large bandwidth and prefetching.
    So you may have 200 MHz CPU fetching instructions from 40 MHz flash,
    which may work reasonably as a single flash read may deliver several instructions. There are a few chips that work differently: they have
    flash in separate die serially connected with the main die containing
    the rest (in particular CPU and RAM). Serial access tend to be slow
    so those chip have caches, if instructions are in cache access is
    fast, otherwise you need to wait say 100 cycles for data from the
    flash. MCU of this sort include some "STM32F103 compatible" chinese
    MCU-s, ESP8266, W801, RP2040 (the last one is used in Raspberry Pi
    Pico). Advantage of this design is that one can get relatively large
    RAM and flash at relatively low cost. Drawback is that cache misses
    introduce delays, so it is hard to give real time warranty (if needed).

    RAM in MCU-s can be tiny, say 32 _bytes_. Flash is typically larger
    but can be as small as 2KB. CPU-s in modern MCU-s typially can perform
    single instruction per clock. Some instructions may take more clocks
    and CPU may be forced to wait for intruction (or for bus). Nominally
    8-bit MCU frequently is capable on operating on 2 bytes in single
    instruction. Below some MCU-s the I have:
    bits RAM FLASH CLOCK pins Architcture
    MSP430G2452 16 256 8k 16MHz 20
    STM8S103F3 8 1k 8k 16MHz 20
    ATMega328P 8 2k 32k 20MHz 28
    CH32V003 32 2k 16k 48MHz 20 Risc-V EC
    STM32G030F4 32 4k 16k 48MHz 20 Cortex M0
    STM32F103C8 32 20k 64k 72MHz 48 Cortex M3
    STM32F401RC 32 64k 256k 105MHz 48 Cortex M4
    RP2040 32 264k 16M 2x133MHz 48 Cortex M0

    In case of RP2040 flash is provided in external chip. RP2040
    has dual core processor, that is why I wrote '2x' before clock
    speed.

    Even the slowest one, that is STM8S103F3 is _much_ faster than
    typical 8-bitters from 1980. STM32F103C8 on integer operations
    almost surely is faster than Sun Classic in our departamental
    server in 1994. MSP430G2452 and CH32V003 do not have hardware
    multiplier, so multiplication (and of course division) must be
    done in software. All other have hardware muliplier, but
    8-bitters can only multiply 8-bit quantities. STM8S103F3
    can divide 16-bit number by 16-bit number. RP2040 can divide
    32-bit number by 32-bit number, this is done accessing a special
    internal device, the other MCU-s do not have hardware division.

    64-bit addition and subtraction is rather small routine even
    on 8-bitters. On CH32V003 there is complication as the CPU
    has no carry flag, but IIUC 5 instruction sequence is enough
    to add two 64-bit numbers. 64-bit multiplication will execute
    several instructions on 8-bitters but code can be small.
    On MSP430G2452 I would expect hundreds of clocks per 64-bit
    multiplication. Similarly for CH32V003. STM8S103F3 and
    ATMega328P probably will need more than 100 clocks but
    probably below 200. The other should be able to do 64-bit
    multiplication in low tens of clocks or better. Division
    is more complicated, but naive method should run in
    hundreds of cycles and code should be reasonably sized.

    Testing shows that gcc multiplication routines for CH32V003
    doing signed 64-bit and 32-bit multiplication take 110 bytes (
    64-bit routine calls 32-bit routine). 64-bit division routine seem
    to increase size by slightly more than 1500 bytes (almost 10% of
    available program size). Five 64-bit integer operations (+ - * / %)
    increase size by about 2800 bytes.

    Four double precision floating point operations (+ - * /) for CH32V003
    increase size of executable by about 6700 bytes. Addition alone
    increases size by about 1900 bytes. Single precision (32-bit) floating
    point operations increase size of executable by about 3500 bytes.

    So on CH32V003 64-bit integer math is much cheaper in program size than
    double precision floating point math, especially if you can avoid
    integer division.

    BTW: Linker includes only routines that are actually used.

    For passing structs by value, C seems to require that copies are made
    first, even if marked 'const', and even if the ABI says they are passed
    by reference. At least that's what compilers for 64-bit machines seem to do.

    C says that arguments are passed by value, which needs a copy. C 'const'
    seem to give too weak warranty to prevent it.

    * Functions not being re-entrant or recursive unless specifically marked
    by an extension

    That sounds like a feature actually. Although if there are lots of such functions that keep their locals in static memory (off the stack), they
    will occupy memory even when not called.

    * const data and non-const being in separate address spaces, and their
    pointers being incompatible (that's another painful one)

    * Lack of support for multi-dimensional arrays

    I don't think C technically has multi-dimension arrays, it just allows
    1D arrays whose elements can be other fixed-size arrays.

    Do you mean elements can only be non-aggregate types, or that pointer dereference levels are limited?

    That would be an unreasonable limitation, assuming a cross-compiler is
    being used. (If the compiler has to run /on/ the device, as mine did,
    then it's more understandable!)

    Of devices I mentioned above probably the smallest device on which
    one can run a compiler is STM32G030F4, IIUC one could run Forth
    compiler on it, but the compiler uses almost all available space
    so it makes little sense. On bigger ones one could in principle
    have a compiler. But such things normally work as "devices"
    and do not need a compiler for normal operation. Compiler could
    be of some use if service personel need to update program
    running on the device. But normally service people either do not
    touch the program or just update it with newer version or bring
    with them a PC with a cross compiler.

    * Not allowing arrays of structs, and/or structs with array fields

    See above.

    * Small limits on the number of parameters in functions, or the even the
    number of local variables

    * Failing to promote small integer types to "int" in arithmetic
    expressions (another cause of subtle problems)

    That's another useful feature, even if it makes it non-conforming. But perhaps it should be an option.



    --
    Waldek Hebisch

    --- 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 Wed Aug 26 16:40:15 2026
    scott@slp53.sl.home (Scott Lurndal) writes:
    Keith Thompson <Keith.S.Thompson+u@gmail.com> writes:
    richard@cogsci.ed.ac.uk (Richard Tobin) writes:
    In article <116k96l$3oj0e$1@dont-email.me>,
    David Brown <david.brown@hesbynett.no> wrote:

    It is not a good idea to give two different things the same
    identifier.

    On the other hand it is a bad idea to not use the obvious name for a
    variable. If I'm dealing with a single variable of type "struct dog",
    I'd prefer not to have to come up with another name just to avoid
    calling it "dog".

    That's much more likely to happen in sample code of the kind
    commonly posted here than in real-world programs. Realistically,
    if you have a type "struct dog", a variable of that type is likely
    to have some more specific meaning.

    Or one might use 'struct canine' and then call it a dog...

    Yes, but "canine" and "dog" mean pretty much the same thing (unless
    "canine" also covers coyotes, wolves, et al, but I don't think
    we're talking about inheritance).

    "struct dog" might not be the best example, since I imagine a
    program most likely wouldn't have code referring to a specific
    dog or a dog in a specific role. "struct point" might make the
    *ahem* point more clearly. You can have functions that can take
    arbitrary arguments of type "struct point" (e.g., computing the
    distance from the origin), but you can also have a "struct point"
    object that's specifically the location of the cursor, or a corner
    of the screen, etc. For a function that takes an arbitrary "struct
    point", a name like "p" could be clear enough.

    A variable should have a name that reflects the meaning of that
    particular variable when possible. It isn't always possible.

    --
    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 Wed Aug 26 16:49:54 2026
    David Brown <david.brown@hesbynett.no> writes:
    On 26/08/2026 18:33, bart wrote:
    On 26/08/2026 12:47, David Brown wrote:
    [...]
    * Lack of support for multi-dimensional arrays
    I don't think C technically has multi-dimension arrays, it just
    allows 1D arrays whose elements can be other fixed-size arrays.
    Do you mean elements can only be non-aggregate types, or that
    pointer dereference levels are limited?

    Without trying to be pedantic about terminology, I mean the later
    here. Thus such compilers can't handle "int xss[10][20];".

    Being pedantic about terminology, C does (technically) have
    multidimensional arrays, and they are exactly the same thing as
    arrays of arrays. The standard does discuss multidimensional arrays
    in normative text, but that text is, strictly speaking, redundant;
    it could be derived from the rules for 1-dimensional arrays and
    pointer arithmetic. (I do not object to the inclusion of that text;
    some redundancy is good.)

    [...]

    --
    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 Wed Aug 26 16:54:04 2026
    David Brown <david.brown@hesbynett.no> writes:
    [...]
    SDCC does its best, but it's very difficult to generate decent object
    code for its targets (mainly brain-dead 8-bit CISC microcontrollers)
    for some aspects of C. I don't know the specifics of what it does and
    does not support, but there are many non-conformities or missing
    features that are sometimes seen for toolchains for such targets.
    These are all taken from real toolchains, some of which sold for
    significant prices :

    * Failing to zero program-lifetime data (that's a nasty one, because
    it does not give compile-time errors)

    * 32-bit "double", or maybe no floating point support at all

    I would be very annoyed by a C-like compiler that defines a type
    "double" that doesn't meet the standard's requirements for that type.
    I'd prefer it to allow "float" and reject "double".

    [...]

    * Lack of support for 64-bit long long

    Lack of support for long long is fine, once you accept that the
    compiler is non-conforming. Defining a type "long long" that's
    narrower than 64 bits is tantamount to lying to the user.

    Disclaimer: I haven't used such compilers.

    --
    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 Wed Aug 26 17:21:56 2026
    antispam@fricas.org (Waldek Hebisch) writes:
    bart <bc@freeuk.com> wrote:
    [...]
    For passing structs by value, C seems to require that copies are made
    first, even if marked 'const', and even if the ABI says they are passed
    by reference. At least that's what compilers for 64-bit machines seem to do.

    C says that arguments are passed by value, which needs a copy. C 'const' seem to give too weak warranty to prevent it.

    Pretty much. Attempting to modify an object via a const-qualified
    lvalue (say, by casting away the "const") has undefined behavior, and a compiler can assume that such modifications never happen, but that's not
    enough to allow eliminating the copy in all cases.

    For example:

    struct big { blah; blah; };

    void func(const struct big param) {
    // ...
    }

    In the abstract machine, func() receives a copy of the argument.
    Depending on the ABI, the caller might create a copy and pass the
    address of the copy.

    You might think that the "const" would allow generating code
    that passes the address instead, but suppose the argument is a
    "global variable" and func() calls something that modifies it.
    That modification must not affect the value of the parameter.

    A compiler can certainly avoid the copy if it can prove that doing so
    doesn't change the behavior. This is easier if the call is inlined.
    The argument object and the parameter object must have distinct
    addresses, but that's not necessarily enforced if the address is
    never taken.

    --
    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 bart@3:633/10 to All on Thu Aug 27 02:10:53 2026
    On 27/08/2026 00:29, Waldek Hebisch wrote:
    bart <bc@freeuk.com> wrote:
    On 26/08/2026 12:47, David Brown wrote:
    On 26/08/2026 11:50, Waldek Hebisch wrote:
    David Brown <david.brown@hesbynett.no> wrote:
    On 26/08/2026 03:01, Keith Thompson wrote:
    Richard Harnden <richard.nospam@gmail.invalid> writes:
    [...]
    Would "memcpy(&foo, &FOO_INIT, sizeof FOO_INIT);" be better, ie: >>>>>>> correct for pointers and floats that are not all-bits-zero?

    Given that FOO_INIT is defined correctly, that should work.

    Calling memcpy() would be a great workaround for a language
    that doesn't support struct assignment as a language feature.
    Fortunately, C has not been such a language for a very long time.

    There's nothing wrong with

    ˙˙˙˙˙ foo = FOO_INIT;

    There might be some subtle semantic differences involving padding
    bits and/or padding bytes.˙ That's unlikely to matter, and I still >>>>>> haven't looked up the relevant rules in the standard.


    Digging in the toolchain horror stories again, I have used a couple of >>>>> embedded C compilers that did not support struct assignment (or passing >>>>> or returning structs by value in functions).˙ Fortunately I have not >>>>> seen such blatant non-conformity for a couple of decades.

    Last time that I checked sdcc did not support passing or returning
    structs by value.˙ At that time they claimed C2011 support.


    SDCC does its best, but it's very difficult to generate decent object
    code for its targets (mainly brain-dead 8-bit CISC microcontrollers) for >>> some aspects of C.˙ I don't know the specifics of what it does and does
    not support, but there are many non-conformities or missing features
    that are sometimes seen for toolchains for such targets.˙ These are all
    taken from real toolchains, some of which sold for significant prices :

    * Failing to zero program-lifetime data (that's a nasty one, because it
    does not give compile-time errors)

    * 32-bit "double", or maybe no floating point support at all

    * Inability to pass and/or return structs by value

    * Lack of support for 64-bit long long

    I suppose you could have 64-bit floats and ints, but if the device has,
    for example, only 64KB code + data, there wouldn't be much memory left
    after those libraries are included.

    They wouldn't run that fast either if it is a 8-bit device with a clock
    speed in MHz.

    I am not sure if you are familiar with typical modern MCU-s. First,
    in a sense you get a complete computer in a single chip. Just connect
    power, one or two capacitors and possibly connect appropriate voltage
    to configuration pins. Inside chip there is CPU, RAM and flash
    memory and a bunch of peripherials, like pin drivers, serial ports,
    ADC, etc. Flash is writable, but slow to write and number of writes
    may be limited.

    I used only a couple of devices with on-chip peripherals, although since
    it was over 40 years ago, my know-how will be dated.

    However my comment was about emulating 64-bit int and float ops on small devices.

    On modern 64-bit processors, people still like to keep to a 32-bit int
    because it is more efficient, yet people are also saying it is fine to
    do /software emulation/ of 64 bit ops on magnitudes-slower 8-bit devices.

    (My application area was low-end CAD which needed lots of 32-bit
    floating point. That needed emulation on Z80, and also later
    8086/286/386 when there was no FPU. I didn't attempt 64-bit floats until
    I knew they were supported by hardware.

    However I accept that some applications may only need to do FP ops at a
    slow enough rate that the emulation can keep up.)

    64-bit addition and subtraction is rather small routine even
    on 8-bitters. On CH32V003 there is complication as the CPU
    has no carry flag,

    To get back to this point, previous discussions about a 64-bit C 'int'
    have suggested there weren't enough use-cases to make worthwhile as a
    default. So what sorts of things need a 64-bit range?

    (BTW I recently tested big-number multiplication on my Z80 emulator. Calculating 1000!, which has a 2600-digit result, would have taken about
    45 mins on a 4MHz device. Just because it can do it (it just has to fit
    into memory) doesn't mean it is viable! However a decent compiler will
    have improved on that time.)



    --- 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 Thu Aug 27 08:28:18 2026
    On 26/08/2026 21:37, Chris M. Thomasson wrote:
    On 8/26/2026 12:28 AM, David Brown wrote:
    On 25/08/2026 23:47, Lawrence D?Oliveiro wrote:
    On Tue, 25 Aug 2026 15:11:44 +0100, Richard Harnden wrote:

    On 25/08/2026 13:15, David Brown wrote:

    Oh, and don't mix variable and struct names - no "struct foo foo".

    Any reason why?˙ Just a style thing?

    This is one area where C++ is (by design?) backward-incompatible with
    C.


    You make it sound like it might have been designed specifically to be
    backwards incompatible with C, which is not the case.

    The point of having C++

    ˙˙˙˙˙struct X { ... };

    behave as though it were C

    ˙˙˙˙˙typedef struct X { ... } X;

    is simply that struct (and therefore also class) types are heavily
    used in C++, and the language does not want to distinguish them in
    normal usage.

    In C, you can choose to keep the distinction (by using the full name
    "struct foo") or remove it (by using typedefs).˙ Some prefer one
    style, some prefer the other.

    In C, struct names and enum names occupy namespaces separate from the
    names of variables and typedefs. In C++, they are all in the same
    namespace.

    To be pedantic, the term you are looking for is "name space" - two
    words.˙ That's the term used in both the C and C++ standards, while
    "namespace" (one word) is a specific feature of the C++ language.
    Obviously there is no confusion in what you wrote here, but it's
    always possible that someone will want to look at what the C standard
    says about the different name spaces - and then they should search the
    pdf files with the correct spelling.


    name space in C, not that bad.

    ct_*

    There. A name space.

    The distinction I was making seems to have whizzed far above your head.

    For the convenience of people reading and using the standard, I was
    explaining the terms and their spelling, as used in the C (and C++)
    standards. No one, I think, is in any doubt about what Lawrence meant
    in his post - nor what you and others colloquially call "name spaces" or "namespaces" in C by using prefixes on externally visible identifiers.

    No, "ct_*" is /not/ a "name space" in C. It's an identifier prefix. If
    you want a new name space in C, declare a struct - within the struct declaration, you have a new name space so that the identifiers of the
    fields are independent of any other identifiers in the 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 Thu Aug 27 09:03:29 2026
    On 27/08/2026 02:21, Keith Thompson wrote:
    antispam@fricas.org (Waldek Hebisch) writes:
    bart <bc@freeuk.com> wrote:
    [...]
    For passing structs by value, C seems to require that copies are made
    first, even if marked 'const', and even if the ABI says they are passed
    by reference. At least that's what compilers for 64-bit machines seem to do.

    C says that arguments are passed by value, which needs a copy. C 'const'
    seem to give too weak warranty to prevent it.

    Pretty much. Attempting to modify an object via a const-qualified
    lvalue (say, by casting away the "const") has undefined behavior, and a compiler can assume that such modifications never happen, but that's not enough to allow eliminating the copy in all cases.

    To be clear here - attempting to modify an object that is defined const,
    such as by casts and pointers, is UB. But if you have a const-qualified lvalue identifying an object that was not defined const, then you can
    "cast away the const" and change it.

    Thus :

    void foo(const int * p) {
    int * q = (int *) p;
    *q = 1;
    }

    int okay() {
    int x = 2;
    foo(&x);
    return x; // Returns 1
    }

    int bad() {
    const int x = 2;
    foo(&x);
    return x; // Could return 1, 2, or a nasal daemon
    }


    This also means that using "const T *" pointers for passing structs (or anything else) does not guarantee that the callee does not change
    anything, and the compiler, when compiling the calling code, can't
    assume that the passed-by-const-pointer data stays constant unless the
    data was defined as "const" and it assumes no UB occurs. (The same
    applies to pass by const reference in C++.)


    For example:

    struct big { blah; blah; };

    void func(const struct big param) {
    // ...
    }

    In the abstract machine, func() receives a copy of the argument.
    Depending on the ABI, the caller might create a copy and pass the
    address of the copy.

    You might think that the "const" would allow generating code
    that passes the address instead, but suppose the argument is a
    "global variable" and func() calls something that modifies it.
    That modification must not affect the value of the parameter.

    A compiler can certainly avoid the copy if it can prove that doing so
    doesn't change the behavior. This is easier if the call is inlined.
    The argument object and the parameter object must have distinct
    addresses, but that's not necessarily enforced if the address is
    never taken.


    I'd have preferred "const" to give stronger guarantees, but it's a bit
    late for that now!



    --- 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 Thu Aug 27 09:53:44 2026
    On 27/08/2026 03:10, bart wrote:
    On 27/08/2026 00:29, Waldek Hebisch wrote:

    I am not sure if you are familiar with typical modern MCU-s.˙ First,
    in a sense you get a complete computer in a single chip.˙ Just connect
    power, one or two capacitors and possibly connect appropriate voltage
    to configuration pins.˙ Inside chip there is CPU, RAM and flash
    memory and a bunch of peripherials, like pin drivers, serial ports,
    ADC, etc.˙ Flash is writable, but slow to write and number of writes
    may be limited.

    I used only a couple of devices with on-chip peripherals, although since
    it was over 40 years ago, my know-how will be dated.

    However my comment was about emulating 64-bit int and float ops on small devices.

    On modern 64-bit processors, people still like to keep to a 32-bit int because it is more efficient,

    I would be careful about that - I think there are many reasons why "int"
    is usually 32-bit on 64-bit systems. They are more efficient in some
    ways, and less efficient in others. I would guess, but not assume, that
    the prime reason is to minimise issues with existing 32-bit code that
    assumes "int" is exactly 32-bit.

    yet people are also saying it is fine to
    do /software emulation/ of 64 bit ops on magnitudes-slower 8-bit devices.


    Can you quote anyone saying that, or are you inventing things again?

    People have pointed out that C conformance /requires/ support for 64-bit
    (or bigger, but let's agree to simplify a little) integer types.
    Sometimes, but not often, they can even be useful in code for small microcontrollers.

    I think everyone realises that the implementation of 64-bit types on any processor that does not support them directly in hardware, needs to
    build them from smaller elements. The same applies to any types (or operations) in any language on any hardware - there's nothing special
    here. I don't think it makes sense to call it "software emulation" as
    though there is something complicated going on.


    (My application area was low-end CAD which needed lots of 32-bit
    floating point. That needed emulation on Z80, and also later
    8086/286/386 when there was no FPU. I didn't attempt 64-bit floats until
    I knew they were supported by hardware.

    However I accept that some applications may only need to do FP ops at a
    slow enough rate that the emulation can keep up.)

    Some things take one instruction on one processor, and multiple
    instructions on another processor - clearly the first processor will be
    faster (at the same clock rate) for the task. There's nothing unusual
    going on.



    --- 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 Thu Aug 27 10:02:58 2026
    On 27/08/2026 01:54, Keith Thompson wrote:
    David Brown <david.brown@hesbynett.no> writes:
    [...]
    SDCC does its best, but it's very difficult to generate decent object
    code for its targets (mainly brain-dead 8-bit CISC microcontrollers)
    for some aspects of C. I don't know the specifics of what it does and
    does not support, but there are many non-conformities or missing
    features that are sometimes seen for toolchains for such targets.
    These are all taken from real toolchains, some of which sold for
    significant prices :

    * Failing to zero program-lifetime data (that's a nasty one, because
    it does not give compile-time errors)

    * 32-bit "double", or maybe no floating point support at all

    I would be very annoyed by a C-like compiler that defines a type
    "double" that doesn't meet the standard's requirements for that type.
    I'd prefer it to allow "float" and reject "double".

    That's a reasonable preference (and one I'd agree with). Others prefer
    that "double" is a 32-bit floating point type on that system, so that
    existing code can be used unchanged (albeit obviously with lower
    precision and range).

    Sometimes there are compiler flags that can help catch unexpected use of double, or you can do things like "-Ddouble=BIG_MISTAKE" to cause
    compile-time errors if the type "double" is used explicitly.


    [...]

    * Lack of support for 64-bit long long

    Lack of support for long long is fine, once you accept that the
    compiler is non-conforming. Defining a type "long long" that's
    narrower than 64 bits is tantamount to lying to the user.

    My experience is that toolchains without 64-bit integer types don't
    support "long long int".

    It is very common in embedded systems to use <stdint.h> types with
    explicit sizes, so there are no doubts. It would take a /really/ nasty toolchain vendor to lie about the sizes of those types.


    Disclaimer: I haven't used such compilers.



    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From bart@3:633/10 to All on Thu Aug 27 12:01:59 2026
    On 27/08/2026 08:53, David Brown wrote:
    On 27/08/2026 03:10, bart wrote:
    On 27/08/2026 00:29, Waldek Hebisch wrote:

    I am not sure if you are familiar with typical modern MCU-s.˙ First,
    in a sense you get a complete computer in a single chip.˙ Just connect
    power, one or two capacitors and possibly connect appropriate voltage
    to configuration pins.˙ Inside chip there is CPU, RAM and flash
    memory and a bunch of peripherials, like pin drivers, serial ports,
    ADC, etc.˙ Flash is writable, but slow to write and number of writes
    may be limited.

    I used only a couple of devices with on-chip peripherals, although
    since it was over 40 years ago, my know-how will be dated.

    However my comment was about emulating 64-bit int and float ops on
    small devices.

    On modern 64-bit processors, people still like to keep to a 32-bit int
    because it is more efficient,

    I would be careful about that - I think there are many reasons why "int"
    is usually 32-bit on 64-bit systems.˙ They are more efficient in some
    ways, and less efficient in others.˙ I would guess, but not assume, that
    the prime reason is to minimise issues with existing 32-bit code that assumes "int" is exactly 32-bit.


    I'm not going to trawl through old threads, but I distinctly remember
    people saying that because most integer values that come up will fit
    into i32, then there was no need for 'int' to be i64.

    (I think I came up with some figures to show that 50% of integer values
    would fit into a byte value!)

    yet people are also saying it is fine to do /software emulation/ of 64
    bit ops on magnitudes-slower 8-bit devices.


    Can you quote anyone saying that, or are you inventing things again?

    You were bemoaning the lack of support for 64-bit ints and floats, and sometimes no floats at all, on small devices. Which would imply that you expected them to be emulated no matter what.

    And WH gave extensive examples of such emulation being done.

    People have pointed out that C conformance /requires/ support for 64-bit
    (or bigger, but let's agree to simplify a little) integer types.
    Sometimes, but not often, they can even be useful in code for small microcontrollers.

    I guess then that _BitInt support will soon be required?

    I gave an example of my own where a 1000! calculation (1000
    multi-precision multiplies) would take many minutes on an 8-bit
    processor running at 1980s speeds.

    In the 1980s, these restrictions would not have been remarkable. Nobody
    then would be complaining that their C compiler didn't support 64-bit integers, or 128- or 256-bit for that matter.

    But because now, decades later, C has those features since modern
    machines are so much more capable, it seems unreasonable to expect /everything/ to be supported on those old systems.

    (My 1000! example was written in my language that was adapted to target
    on Z80. It was amazing how many of its modern features still worked
    fine: modules, embedding, tables, etc. But its 'int' type was downgraded
    from i64 to i16. And automatic promotion, now from i8/u8 to i16, was
    removed.)

    I think everyone realises that the implementation of 64-bit types on any processor that does not support them directly in hardware, needs to
    build them from smaller elements.

    You're saying it again! i64 support on a modern machine is more critical because things like file and disk sizes, memory sizes and so on may need
    it. Those quantities were much smaller on smaller systems.

    I'm saying it is OK for a compiler vendor to choose to omit some
    features that are a poor match for the intended targets.

    I think you mentioned that gcc did not limit things like the number of a
    local variables in a function, yet still worked for a target with no
    memory?! I suppose /some/ limitations have to be in place?



    --- 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 Thu Aug 27 04:23:54 2026
    David Brown <david.brown@hesbynett.no> writes:
    On 27/08/2026 02:21, Keith Thompson wrote:
    antispam@fricas.org (Waldek Hebisch) writes:
    bart <bc@freeuk.com> wrote:
    [...]
    For passing structs by value, C seems to require that copies are made
    first, even if marked 'const', and even if the ABI says they are passed >>>> by reference. At least that's what compilers for 64-bit machines seem to do.

    C says that arguments are passed by value, which needs a copy. C 'const' >>> seem to give too weak warranty to prevent it.
    Pretty much. Attempting to modify an object via a const-qualified
    lvalue (say, by casting away the "const") has undefined behavior, and a
    compiler can assume that such modifications never happen, but that's not
    enough to allow eliminating the copy in all cases.

    To be clear here - attempting to modify an object that is defined
    const, such as by casts and pointers, is UB. But if you have a const-qualified lvalue identifying an object that was not defined
    const, then you can "cast away the const" and change it.

    You're right.

    [...]

    --
    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 Thu Aug 27 04:37:57 2026
    bart <bc@freeuk.com> writes:
    On 27/08/2026 08:53, David Brown wrote:
    On 27/08/2026 03:10, bart wrote:
    On 27/08/2026 00:29, Waldek Hebisch wrote:
    I am not sure if you are familiar with typical modern MCU-s.˙ First,
    in a sense you get a complete computer in a single chip.˙ Just connect >>>> power, one or two capacitors and possibly connect appropriate voltage
    to configuration pins.˙ Inside chip there is CPU, RAM and flash
    memory and a bunch of peripherials, like pin drivers, serial ports,
    ADC, etc.˙ Flash is writable, but slow to write and number of writes
    may be limited.

    I used only a couple of devices with on-chip peripherals, although
    since it was over 40 years ago, my know-how will be dated.

    However my comment was about emulating 64-bit int and float ops on
    small devices.

    On modern 64-bit processors, people still like to keep to a 32-bit
    int because it is more efficient,
    I would be careful about that - I think there are many reasons why
    "int" is usually 32-bit on 64-bit systems.˙ They are more efficient
    in some ways, and less efficient in others.˙ I would guess, but not
    assume, that the prime reason is to minimise issues with existing
    32-bit code that assumes "int" is exactly 32-bit.

    I'm not going to trawl through old threads, but I distinctly remember
    people saying that because most integer values that come up will fit
    into i32, then there was no need for 'int' to be i64.

    Every conforming C implementation must support (at least) 64-bit
    integers, even if it's emulated. Whether the specific type "int"
    is 64 bits or not is a separate issue. You seem to be conflating
    "integer" (a set of types) and "int" (a specific type).

    And if supporting 64-bit integers is impractical for a small target
    system, I have no objection to a non-conforming C-like implementation.

    [...]

    You were bemoaning the lack of support for 64-bit ints and floats, and sometimes no floats at all, on small devices. Which would imply that
    you expected them to be emulated no matter what.

    I don't recall anyone bemoaning the fact that int is rarely 64 bits.
    Somebody mentioned that some compilers for small target systems don't
    support 64-bit long long.

    And WH gave extensive examples of such emulation being done.

    People have pointed out that C conformance /requires/ support for
    64-bit (or bigger, but let's agree to simplify a little) integer
    types. Sometimes, but not often, they can even be useful in code for
    small microcontrollers.

    I guess then that _BitInt support will soon be required?

    No need to guess. _BitInt support is required by the current C
    standard. BITINT_MAXWIDTH must be >= ULLONG_WIDTH, so it must be at
    least 64.

    [...]

    I'm saying it is OK for a compiler vendor to choose to omit some
    features that are a poor match for the intended targets.

    I agree. It's OK to have a compiler that doesn't conform to the C
    standard, as long as it doesn't claim full conformance and is still
    useful.

    I think you mentioned that gcc did not limit things like the number of
    a local variables in a function, yet still worked for a target with no memory?! I suppose /some/ limitations have to be in place?

    Certainly there are capacity limits; you can't have a googolplex of
    local variables. The gcc approach is that such limits are not fixed,
    but are implicitly imposed by storage limitations.

    --
    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 David Brown@3:633/10 to All on Thu Aug 27 14:53:54 2026
    On 27/08/2026 09:03, David Brown wrote:
    On 27/08/2026 02:21, Keith Thompson wrote:
    antispam@fricas.org (Waldek Hebisch) writes:
    bart <bc@freeuk.com> wrote:
    [...]
    For passing structs by value, C seems to require that copies are made
    first, even if marked 'const', and even if the ABI says they are passed >>>> by reference. At least that's what compilers for 64-bit machines
    seem to do.

    C says that arguments are passed by value, which needs a copy.˙ C
    'const'
    seem to give too weak warranty to prevent it.

    Pretty much.˙ Attempting to modify an object via a const-qualified
    lvalue (say, by casting away the "const") has undefined behavior, and a
    compiler can assume that such modifications never happen, but that's not
    enough to allow eliminating the copy in all cases.

    To be clear here - attempting to modify an object that is defined const, such as by casts and pointers, is UB.˙ But if you have a const-qualified lvalue identifying an object that was not defined const, then you can
    "cast away the const" and change it.

    Thus :

    ˙˙˙˙void foo(const int * p) {
    ˙˙˙˙˙˙˙ int * q = (int *) p;
    ˙˙˙˙˙˙˙ *q = 1;
    ˙˙˙˙}

    ˙˙˙˙int okay() {
    ˙˙˙˙˙˙˙ int x = 2;
    ˙˙˙˙˙˙˙ foo(&x);
    ˙˙˙˙˙˙˙ return x;˙˙˙ // Returns 1
    ˙˙˙˙}

    ˙˙˙˙int bad() {
    ˙˙˙˙˙˙˙ const int x = 2;
    ˙˙˙˙˙˙˙ foo(&x);
    ˙˙˙˙˙˙˙ return x;˙˙˙ // Could return 1, 2, or a nasal daemon
    ˙˙˙˙}


    This also means that using "const T *" pointers for passing structs (or anything else) does not guarantee that the callee does not change
    anything, and the compiler, when compiling the calling code, can't
    assume that the passed-by-const-pointer data stays constant unless the
    data was defined as "const" and it assumes no UB occurs.˙ (The same
    applies to pass by const reference in C++.)


    For example:

    ˙˙˙˙ struct big { blah; blah; };

    ˙˙˙˙ void func(const struct big param) {
    ˙˙˙˙˙˙˙˙ // ...
    ˙˙˙˙ }

    In the abstract machine, func() receives a copy of the argument.
    Depending on the ABI, the caller might create a copy and pass the
    address of the copy.

    You might think that the "const" would allow generating code
    that passes the address instead, but suppose the argument is a
    "global variable" and func() calls something that modifies it.
    That modification must not affect the value of the parameter.

    A compiler can certainly avoid the copy if it can prove that doing so
    doesn't change the behavior.˙ This is easier if the call is inlined.
    The argument object and the parameter object must have distinct
    addresses, but that's not necessarily enforced if the address is
    never taken.


    I'd have preferred "const" to give stronger guarantees, but it's a bit
    late for that now!



    Slightly disappointingly, even though gcc's "access" attribute appears
    to be "stronger" than const, gcc does not appear to optimise on the
    assumption that it is obeyed.




    --- 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 Thu Aug 27 15:29:01 2026
    On 27/08/2026 13:01, bart wrote:
    On 27/08/2026 08:53, David Brown wrote:
    On 27/08/2026 03:10, bart wrote:
    On 27/08/2026 00:29, Waldek Hebisch wrote:

    I am not sure if you are familiar with typical modern MCU-s.˙ First,
    in a sense you get a complete computer in a single chip.˙ Just connect >>>> power, one or two capacitors and possibly connect appropriate voltage
    to configuration pins.˙ Inside chip there is CPU, RAM and flash
    memory and a bunch of peripherials, like pin drivers, serial ports,
    ADC, etc.˙ Flash is writable, but slow to write and number of writes
    may be limited.

    I used only a couple of devices with on-chip peripherals, although
    since it was over 40 years ago, my know-how will be dated.

    However my comment was about emulating 64-bit int and float ops on
    small devices.

    On modern 64-bit processors, people still like to keep to a 32-bit
    int because it is more efficient,

    I would be careful about that - I think there are many reasons why
    "int" is usually 32-bit on 64-bit systems.˙ They are more efficient in
    some ways, and less efficient in others.˙ I would guess, but not
    assume, that the prime reason is to minimise issues with existing 32-
    bit code that assumes "int" is exactly 32-bit.


    I'm not going to trawl through old threads, but I distinctly remember
    people saying that because most integer values that come up will fit
    into i32, then there was no need for 'int' to be i64.

    I think it is fair to say that most integer values fit into 32 bits,
    yes. The additional range of 64-bits is not, in itself, a good reason
    to make "int" 64-bit. Equally, of course, the fact that most integer
    values fit in 32-bits is not a good reason for making "int" 32-bit. It
    just means that range is not likely to be a major issue in the decision process. (This is different from the move from 16-bit to 32-bit.)

    Of course there are some uses of large integers, but they are, I think,
    rare enough that you would use specific types - like size_t, uintptr_t,
    or for arithmetic, int64_t and uint64_t.


    (I think I came up with some figures to show that 50% of integer values would fit into a byte value!)

    Most numbers are small, yes. (There's even a law about it.)


    yet people are also saying it is fine to do /software emulation/ of
    64 bit ops on magnitudes-slower 8-bit devices.


    Can you quote anyone saying that, or are you inventing things again?

    You were bemoaning the lack of support for 64-bit ints and floats, and sometimes no floats at all, on small devices. Which would imply that you expected them to be emulated no matter what.

    I was stating that this was a non-conformity that was found in
    real-world embedded compilers. I /have/ used 64-bit integers on 8-bit
    systems (both with "uint64_t" types, and "manually" on a toolchain that
    did not support 64-bit types), but not often. I have never needed
    64-bit double on an 8-bit microcontroller - I was stating the lack of
    support as a fact, not as something that bothered me.

    But I do think it makes sense to support these in C compilers, even if
    doing so is slow and takes a non-negligible fraction of code space on
    small microcontrollers. Programmers should decide on the trade-offs -
    only they will know if the speed costs are too high and they must use
    other methods (or re-design around a faster microcontroller).

    Supporting 64-bit ints is not difficult, even on an 8-bit target. It is
    not principally different than supporting them on a 32-bit target.
    Supporting 64-bit floating point without appropriate hardware is a bit
    fiddly, but not any more so than supporting 32-bit floating point.
    Supporting either of them with near maximal efficiency is a different
    matter, and a lot more work. I think it is entirely reasonable for
    8-bit toolchains to provide basic support - something that works and
    gives correct answers. Whether or not the toolchain developers should
    spend effort making them efficient depends on their customers' and
    users' needs and requests, and their priorities on other development.


    And WH gave extensive examples of such emulation being done.

    People have pointed out that C conformance /requires/ support for 64-
    bit (or bigger, but let's agree to simplify a little) integer types.
    Sometimes, but not often, they can even be useful in code for small
    microcontrollers.

    I guess then that _BitInt support will soon be required?

    I believe they are mandatory (not an optional feature) in C23 - so they
    are required if a compiler is C23 compliant. Of course, compilers don't
    have to be fully C23 compliant.


    I gave an example of my own where a 1000! calculation (1000 multi-
    precision multiplies) would take many minutes on an 8-bit processor
    running at 1980s speeds.

    The Z80 is unlikely to be a good choice of platform for large numerical calculations. But it's nice that it is possible to do so.


    In the 1980s, these restrictions would not have been remarkable. Nobody
    then would be complaining that their C compiler didn't support 64-bit integers, or 128- or 256-bit for that matter.

    C99 requires 64-bit integer types. C90 did not, nor did C prior to standardisation. A C compiler that does not support 64-bit integer
    types is not a C99 (or C11, or C17, or C23) compiler. I mentioned a
    number of non-conformities that I have seen in embedded compilers, some
    of which were severe and could potentially lead to silently incorrect semantics or restrict writing normal, clear C code, some of which were
    just a bit annoying, and some of which were rarely relevant in practice.


    But because now, decades later, C has those features since modern
    machines are so much more capable, it seems unreasonable to expect / everything/ to be supported on those old systems.

    I have not been talking just about old systems - you can buy 8-bit microcontrollers today, and you can buy commercial C compilers today
    that have some of these non-conformities. They are, of course, much
    less popular than they used to be. I don't think anyone is expecting
    C90 (or earlier) C compilers to support C99 features. And some of the non-conformities are /entirely/ reasonable - but that does not stop them
    from being non-conforming.


    I think everyone realises that the implementation of 64-bit types on
    any processor that does not support them directly in hardware, needs
    to build them from smaller elements.

    You're saying it again!

    I am not convinced you understood what I said before, nor what I wrote
    above. Certainly I was not repeating myself, so you have misunderstood something.

    i64 support on a modern machine is more critical
    because things like file and disk sizes, memory sizes and so on may need
    it. Those quantities were much smaller on smaller systems.


    True.

    I'm saying it is OK for a compiler vendor to choose to omit some
    features that are a poor match for the intended targets.


    You can say that if you like. I have had use of 64-bit integers on
    8-bit microcontrollers. It was not for file or disk sizes.

    And I have used a toolchain that did not support 64-bit integers, and I understand why it did not support them. Nonetheless, lack of such
    support was a non-conformity, and a source of minor irritation since it
    meant more manual coding on my part.

    I think you mentioned that gcc did not limit things like the number of a local variables in a function, yet still worked for a target with no memory?! I suppose /some/ limitations have to be in place?


    Yes. gcc does not place arbitrary limits on the number of local
    variables, but the target obviously did. As long as the generated code
    kept variables in registers, there was no problem - once the gcc backend attempted to spill variables to the stack, things failed.

    This is different from a toolchain that has an arbitrary limit to the
    number of local variables it allowed, regardless of hardware. (That particular compiler was a truly weird jumble that generated code in a
    limited subset of x86 assembly then translated those instructions into
    the target processor's code. It had a lot of bizarre limitations and
    outright bugs.)


    --- 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 Thu Aug 27 13:39:24 2026
    bart <bc@freeuk.com> wrote:
    On 27/08/2026 00:29, Waldek Hebisch wrote:
    bart <bc@freeuk.com> wrote:
    On 26/08/2026 12:47, David Brown wrote:
    On 26/08/2026 11:50, Waldek Hebisch wrote:
    David Brown <david.brown@hesbynett.no> wrote:
    On 26/08/2026 03:01, Keith Thompson wrote:
    Richard Harnden <richard.nospam@gmail.invalid> writes:
    [...]
    Would "memcpy(&foo, &FOO_INIT, sizeof FOO_INIT);" be better, ie: >>>>>>>> correct for pointers and floats that are not all-bits-zero?

    Given that FOO_INIT is defined correctly, that should work.

    Calling memcpy() would be a great workaround for a language
    that doesn't support struct assignment as a language feature.
    Fortunately, C has not been such a language for a very long time. >>>>>>>
    There's nothing wrong with

    ˙˙˙˙˙ foo = FOO_INIT;

    There might be some subtle semantic differences involving padding >>>>>>> bits and/or padding bytes.˙ That's unlikely to matter, and I still >>>>>>> haven't looked up the relevant rules in the standard.


    Digging in the toolchain horror stories again, I have used a couple of >>>>>> embedded C compilers that did not support struct assignment (or passing >>>>>> or returning structs by value in functions).˙ Fortunately I have not >>>>>> seen such blatant non-conformity for a couple of decades.

    Last time that I checked sdcc did not support passing or returning
    structs by value.˙ At that time they claimed C2011 support.


    SDCC does its best, but it's very difficult to generate decent object
    code for its targets (mainly brain-dead 8-bit CISC microcontrollers) for >>>> some aspects of C.˙ I don't know the specifics of what it does and does >>>> not support, but there are many non-conformities or missing features
    that are sometimes seen for toolchains for such targets.˙ These are all >>>> taken from real toolchains, some of which sold for significant prices : >>>>
    * Failing to zero program-lifetime data (that's a nasty one, because it >>>> does not give compile-time errors)

    * 32-bit "double", or maybe no floating point support at all

    * Inability to pass and/or return structs by value

    * Lack of support for 64-bit long long

    I suppose you could have 64-bit floats and ints, but if the device has,
    for example, only 64KB code + data, there wouldn't be much memory left
    after those libraries are included.

    They wouldn't run that fast either if it is a 8-bit device with a clock
    speed in MHz.

    I am not sure if you are familiar with typical modern MCU-s. First,
    in a sense you get a complete computer in a single chip. Just connect
    power, one or two capacitors and possibly connect appropriate voltage
    to configuration pins. Inside chip there is CPU, RAM and flash
    memory and a bunch of peripherials, like pin drivers, serial ports,
    ADC, etc. Flash is writable, but slow to write and number of writes
    may be limited.

    I used only a couple of devices with on-chip peripherals, although since
    it was over 40 years ago, my know-how will be dated.

    However my comment was about emulating 64-bit int and float ops on small devices.

    On modern 64-bit processors, people still like to keep to a 32-bit int because it is more efficient, yet people are also saying it is fine to
    do /software emulation/ of 64 bit ops on magnitudes-slower 8-bit devices.

    Clearly when there is no hardware support, then you need emulation.
    If you use limited number of 64 bit operations, then emulation
    may be OK. Otherwise you need better hardware or hard work to
    avoid 64 bit operations.

    (My application area was low-end CAD which needed lots of 32-bit
    floating point. That needed emulation on Z80, and also later
    8086/286/386 when there was no FPU. I didn't attempt 64-bit floats until
    I knew they were supported by hardware.

    However I accept that some applications may only need to do FP ops at a
    slow enough rate that the emulation can keep up.)

    64-bit addition and subtraction is rather small routine even
    on 8-bitters. On CH32V003 there is complication as the CPU
    has no carry flag,

    To get back to this point, previous discussions about a 64-bit C 'int'
    have suggested there weren't enough use-cases to make worthwhile as a default. So what sorts of things need a 64-bit range?

    1) Time. On small MCU-s you may need microsecond accuracy. 32-bit
    microsecond counter will overflow in few hours. So, for longer
    time intervals you need 64-bits. You could try separate couters
    for short and long duration, but that probably has similar cost
    as 64-bit arithmetic.

    2) SD-cards. Small MCU may talk to an SD-card. SD-card may contain
    a lot of data so 32-bit arithmetic may be inadequate.

    3) Cryptography. 32-bit cryptographic keys are deemed inadequate
    for most purposes. So, minimal reasonably safe cryptography needs
    64-bits. And with spead of radio controlled devices (like garage
    doors) there is increasing need for cryptography.

    4) Compensating sensors. There are now nice atmosphric pressure
    sensors on the market. To get full accuracy from such a sensor
    one has to do appropriate computation, for which 32-bit accuracy
    is inadequate (results from sensor are 20 bit, but intermediate
    calculations needs way more bits for full accuracy).

    5) Determinig orientation of a triangle. Once you have more than
    10-bit coordinates 30-bit arithmetic is not precise enough to
    correctly determine orientation. 64-bit arithmetic is enough for
    20-bit coordinates, which should be enough for most practical
    graphic uses.

    Note that except for graphic case and use for timing use of 64-bit
    operations is likely to be moderate, so even emulated math may be
    enough. For timing it may be enough to do addition and subtraction
    and there are possibilties to use reduced range when appropriate,
    so emulation may be good enough. For graphic you probably want
    significant compute power, but 64-bit operations may be rare enough
    to use emulation (IIUC that is approach used by most GPU-s).

    Let me mention thing that you snipped: linker includes support
    rountine only when needed. So, from user point of view there is
    a cost only when they use 64-bit operations and presumably they
    will use them only when there is a need.

    Of course there is cost for compiler writers, but that can be
    amortised over large number of users. gcc makes this cheaper
    by having emulation routines written in C and compiled for
    specific machine. They probably provide optimized hand
    written assembler for some machines, but not for all.

    Anyway, since from users point of view support for 64-bit
    operations is effectively free they demand it.

    (BTW I recently tested big-number multiplication on my Z80 emulator. Calculating 1000!, which has a 2600-digit result, would have taken about
    45 mins on a 4MHz device. Just because it can do it (it just has to fit
    into memory) doesn't mean it is viable! However a decent compiler will
    have improved on that time.)

    If you need a lot of multiplications on 8-bit machine without hardware multiplier and have 512 bytes of memory free, then using table of
    squares can give nice speedup. Of course, you get more speedup by
    using more adequate device...

    --
    Waldek Hebisch

    --- 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 Thu Aug 27 14:20:22 2026
    David Brown <david.brown@hesbynett.no> writes:
    On 27/08/2026 03:10, bart wrote:
    On 27/08/2026 00:29, Waldek Hebisch wrote:

    I am not sure if you are familiar with typical modern MCU-s.˙ First,
    in a sense you get a complete computer in a single chip.˙ Just connect
    power, one or two capacitors and possibly connect appropriate voltage
    to configuration pins.˙ Inside chip there is CPU, RAM and flash
    memory and a bunch of peripherials, like pin drivers, serial ports,
    ADC, etc.˙ Flash is writable, but slow to write and number of writes
    may be limited.

    I used only a couple of devices with on-chip peripherals, although since
    it was over 40 years ago, my know-how will be dated.

    However my comment was about emulating 64-bit int and float ops on small
    devices.

    On modern 64-bit processors, people still like to keep to a 32-bit int
    because it is more efficient,

    I would be careful about that - I think there are many reasons why "int"
    is usually 32-bit on 64-bit systems. They are more efficient in some
    ways, and less efficient in others. I would guess, but not assume, that
    the prime reason is to minimise issues with existing 32-bit code that >assumes "int" is exactly 32-bit.

    I would also argue that "more efficient" is not accurate. It would
    certainly be more space efficient but not more time efficient.


    --- 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 Thu Aug 27 14:24:58 2026
    Keith Thompson <Keith.S.Thompson+u@gmail.com> writes:
    scott@slp53.sl.home (Scott Lurndal) writes:
    Keith Thompson <Keith.S.Thompson+u@gmail.com> writes: >>>richard@cogsci.ed.ac.uk (Richard Tobin) writes:
    In article <116k96l$3oj0e$1@dont-email.me>,
    David Brown <david.brown@hesbynett.no> wrote:

    It is not a good idea to give two different things the same >>>>>identifier.

    On the other hand it is a bad idea to not use the obvious name for a
    variable. If I'm dealing with a single variable of type "struct dog", >>>> I'd prefer not to have to come up with another name just to avoid
    calling it "dog".

    That's much more likely to happen in sample code of the kind
    commonly posted here than in real-world programs. Realistically,
    if you have a type "struct dog", a variable of that type is likely
    to have some more specific meaning.

    Or one might use 'struct canine' and then call it a dog...

    Yes, but "canine" and "dog" mean pretty much the same thing (unless
    "canine" also covers coyotes, wolves, et al, but I don't think
    we're talking about inheritance).

    Well, as I have been programming in C++ primarily (aside from linux
    work in C) since 1989, I suspect inheritance was a factor when I
    wrote that :-).



    --- 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 Thu Aug 27 16:44:22 2026
    On 27/08/2026 16:20, Scott Lurndal wrote:
    David Brown <david.brown@hesbynett.no> writes:
    On 27/08/2026 03:10, bart wrote:
    On 27/08/2026 00:29, Waldek Hebisch wrote:

    I am not sure if you are familiar with typical modern MCU-s.˙ First,
    in a sense you get a complete computer in a single chip.˙ Just connect >>>> power, one or two capacitors and possibly connect appropriate voltage
    to configuration pins.˙ Inside chip there is CPU, RAM and flash
    memory and a bunch of peripherials, like pin drivers, serial ports,
    ADC, etc.˙ Flash is writable, but slow to write and number of writes
    may be limited.

    I used only a couple of devices with on-chip peripherals, although since >>> it was over 40 years ago, my know-how will be dated.

    However my comment was about emulating 64-bit int and float ops on small >>> devices.

    On modern 64-bit processors, people still like to keep to a 32-bit int
    because it is more efficient,

    I would be careful about that - I think there are many reasons why "int"
    is usually 32-bit on 64-bit systems. They are more efficient in some
    ways, and less efficient in others. I would guess, but not assume, that
    the prime reason is to minimise issues with existing 32-bit code that
    assumes "int" is exactly 32-bit.

    I would also argue that "more efficient" is not accurate. It would certainly be more space efficient but not more time efficient.


    Space efficient can translate to time efficient, when you have arrays of
    a type affecting cache hit ratios. There are also plenty of situations
    where 32-bit operations can be faster than 64-bit operations on 64-bit processors. Simple register-to-register moves and basic instructions
    like bitwise logic, adding and subtracting will all be single-cycle
    either way. Multiplication might be faster for 32-bit types, especially
    on older processors, and division can be significantly different speeds.
    Most importantly, perhaps, is that SIMD can typically handle 32-bit operations twice as well as 64-bit operations (if it can handle 64-bit operations at all).

    On the other hand, 32-bit operations might involve less efficient
    opcodes, or additional sign-extend or zero-extend operations, and some
    array addressing can be less efficient if 32-bit unsigned int (or 32-bit signed int with wrapping semantics) are used for the offset.

    All in all, they are more efficient in some ways, less efficient in others.


    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Tim Rentsch@3:633/10 to All on Thu Aug 27 13:11:30 2026
    scott@slp53.sl.home (Scott Lurndal) writes:

    David Brown <david.brown@hesbynett.no> writes:
    [...]

    I would be careful about that - I think there are many reasons why
    "int" is usually 32-bit on 64-bit systems. They are more efficient
    in some ways, and less efficient in others. I would guess, but not
    assume, that the prime reason is to minimise issues with existing
    32-bit code that assumes "int" is exactly 32-bit.

    I would also argue that "more efficient" is not accurate. It would certainly be more space efficient but not more time efficient.

    He did say more efficient /in some ways/.

    --- 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 Thu Aug 27 14:07:22 2026
    On 8/26/2026 11:28 PM, David Brown wrote:
    On 26/08/2026 21:37, Chris M. Thomasson wrote:
    On 8/26/2026 12:28 AM, David Brown wrote:
    On 25/08/2026 23:47, Lawrence D?Oliveiro wrote:
    On Tue, 25 Aug 2026 15:11:44 +0100, Richard Harnden wrote:

    On 25/08/2026 13:15, David Brown wrote:

    Oh, and don't mix variable and struct names - no "struct foo foo".

    Any reason why?˙ Just a style thing?

    This is one area where C++ is (by design?) backward-incompatible with
    C.


    You make it sound like it might have been designed specifically to be
    backwards incompatible with C, which is not the case.

    The point of having C++

    ˙˙˙˙˙struct X { ... };

    behave as though it were C

    ˙˙˙˙˙typedef struct X { ... } X;

    is simply that struct (and therefore also class) types are heavily
    used in C++, and the language does not want to distinguish them in
    normal usage.

    In C, you can choose to keep the distinction (by using the full name
    "struct foo") or remove it (by using typedefs).˙ Some prefer one
    style, some prefer the other.

    In C, struct names and enum names occupy namespaces separate from the
    names of variables and typedefs. In C++, they are all in the same
    namespace.

    To be pedantic, the term you are looking for is "name space" - two
    words.˙ That's the term used in both the C and C++ standards, while
    "namespace" (one word) is a specific feature of the C++ language.
    Obviously there is no confusion in what you wrote here, but it's
    always possible that someone will want to look at what the C standard
    says about the different name spaces - and then they should search
    the pdf files with the correct spelling.


    name space in C, not that bad.

    ct_*

    There. A name space.

    The distinction I was making seems to have whizzed far above your head.

    For the convenience of people reading and using the standard, I was explaining the terms and their spelling, as used in the C (and C++) standards.˙ No one, I think, is in any doubt about what Lawrence meant
    in his post - nor what you and others colloquially call "name spaces" or "namespaces" in C by using prefixes on externally visible identifiers.

    stdio vs standard_input_output?


    No, "ct_*" is /not/ a "name space" in C.˙ It's an identifier prefix.˙ If
    you want a new name space in C, declare a struct - within the struct declaration, you have a new name space so that the identifiers of the
    fields are independent of any other identifiers in the code.

    Are you missing the forest for the trees in a sense?
    _________________________
    struct ct_foo
    {
    int m_blah;
    };


    void
    ct_foo_init(
    ct_foo* self
    ) {
    self->m_blah = 42;
    }
    _________________________

    We can have some nice "separation" in the name spaces, well, you know,
    in C. Its kind of a name space in a sense... ?

    --- 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 Thu Aug 27 14:12:59 2026
    On 8/27/2026 12:03 AM, David Brown wrote:
    On 27/08/2026 02:21, Keith Thompson wrote:
    antispam@fricas.org (Waldek Hebisch) writes:
    bart <bc@freeuk.com> wrote:
    [...]
    For passing structs by value, C seems to require that copies are made
    first, even if marked 'const', and even if the ABI says they are passed >>>> by reference. At least that's what compilers for 64-bit machines
    seem to do.

    C says that arguments are passed by value, which needs a copy.˙ C
    'const'
    seem to give too weak warranty to prevent it.

    Pretty much.˙ Attempting to modify an object via a const-qualified
    lvalue (say, by casting away the "const") has undefined behavior, and a
    compiler can assume that such modifications never happen, but that's not
    enough to allow eliminating the copy in all cases.

    To be clear here - attempting to modify an object that is defined const, such as by casts and pointers, is UB.˙ But if you have a const-qualified lvalue identifying an object that was not defined const, then you can
    "cast away the const" and change it.

    Thus :

    ˙˙˙˙void foo(const int * p) {
    ˙˙˙˙˙˙˙ int * q = (int *) p;
    ˙˙˙˙˙˙˙ *q = 1;
    ˙˙˙˙}
    [...]

    Mutable?>


    --- 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 Thu Aug 27 17:50:38 2026
    On 2026-08-26 05:50, Waldek Hebisch wrote:
    Last time that I checked sdcc did not support passing or returning
    structs by value. At that time they claimed C2011 support.

    C2011 support is not the same as C2011 conformance. I know nothing of
    sdcc, but that wording allows for it to support particular features of
    C2011, without necessarily fully conforming to any version of the C
    standard.


    --- 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 Fri Aug 28 08:40:49 2026
    On 27/08/2026 23:07, Chris M. Thomasson wrote:
    On 8/26/2026 11:28 PM, David Brown wrote:
    On 26/08/2026 21:37, Chris M. Thomasson wrote:
    On 8/26/2026 12:28 AM, David Brown wrote:
    On 25/08/2026 23:47, Lawrence D?Oliveiro wrote:
    On Tue, 25 Aug 2026 15:11:44 +0100, Richard Harnden wrote:

    On 25/08/2026 13:15, David Brown wrote:

    Oh, and don't mix variable and struct names - no "struct foo foo". >>>>>>
    Any reason why?˙ Just a style thing?

    This is one area where C++ is (by design?) backward-incompatible with >>>>> C.


    You make it sound like it might have been designed specifically to
    be backwards incompatible with C, which is not the case.

    The point of having C++

    ˙˙˙˙˙struct X { ... };

    behave as though it were C

    ˙˙˙˙˙typedef struct X { ... } X;

    is simply that struct (and therefore also class) types are heavily
    used in C++, and the language does not want to distinguish them in
    normal usage.

    In C, you can choose to keep the distinction (by using the full name
    "struct foo") or remove it (by using typedefs).˙ Some prefer one
    style, some prefer the other.

    In C, struct names and enum names occupy namespaces separate from the >>>>> names of variables and typedefs. In C++, they are all in the same
    namespace.

    To be pedantic, the term you are looking for is "name space" - two
    words.˙ That's the term used in both the C and C++ standards, while
    "namespace" (one word) is a specific feature of the C++ language.
    Obviously there is no confusion in what you wrote here, but it's
    always possible that someone will want to look at what the C
    standard says about the different name spaces - and then they should
    search the pdf files with the correct spelling.


    name space in C, not that bad.

    ct_*

    There. A name space.

    The distinction I was making seems to have whizzed far above your head.

    For the convenience of people reading and using the standard, I was
    explaining the terms and their spelling, as used in the C (and C++)
    standards.˙ No one, I think, is in any doubt about what Lawrence meant
    in his post - nor what you and others colloquially call "name spaces"
    or "namespaces" in C by using prefixes on externally visible identifiers.

    stdio vs standard_input_output?


    No, "ct_*" is /not/ a "name space" in C.˙ It's an identifier prefix.
    If you want a new name space in C, declare a struct - within the
    struct declaration, you have a new name space so that the identifiers
    of the fields are independent of any other identifiers in the code.

    Are you missing the forest for the trees in a sense?

    I will try once more - a bit more slowly this time. Please try to read
    my post, and think about what I am saying.

    /Everyone/ knows how to use prefixes with identifiers in C, and this is
    used to group functions and other objects together. It is especially
    popular for reusable libraries. Anyone who has worked with C beyond a
    "Hello, world" program is aware of this.

    I was pointing out, to Lawrence and anyone else, the exact terminology
    used by the C standards. That was not to be pedantic, or nit-picking -
    it is useful to know such distinctions if you are looking things up in
    the C standards. If someone wants to know if the identifiers for union declarations and struct declarations are in separate name spaces in C,
    or if they are combined (like in C++), then the fastest way to do is
    likely to search a pdf of the C standards for "name space". If that
    person searches for "namespace", they will find nothing. Exact terms,
    exact spelling, /matters/.

    I don't think I can explain more clearly than that. Either you
    understand why exact terms are important, or you do not.


    --- 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 Fri Aug 28 08:43:28 2026
    On 27/08/2026 23:12, Chris M. Thomasson wrote:
    On 8/27/2026 12:03 AM, David Brown wrote:
    On 27/08/2026 02:21, Keith Thompson wrote:
    antispam@fricas.org (Waldek Hebisch) writes:
    bart <bc@freeuk.com> wrote:
    [...]
    For passing structs by value, C seems to require that copies are made >>>>> first, even if marked 'const', and even if the ABI says they are
    passed
    by reference. At least that's what compilers for 64-bit machines
    seem to do.

    C says that arguments are passed by value, which needs a copy.˙ C
    'const'
    seem to give too weak warranty to prevent it.

    Pretty much.˙ Attempting to modify an object via a const-qualified
    lvalue (say, by casting away the "const") has undefined behavior, and a
    compiler can assume that such modifications never happen, but that's not >>> enough to allow eliminating the copy in all cases.

    To be clear here - attempting to modify an object that is defined
    const, such as by casts and pointers, is UB.˙ But if you have a const-
    qualified lvalue identifying an object that was not defined const,
    then you can "cast away the const" and change it.

    Thus :

    ˙˙˙˙˙void foo(const int * p) {
    ˙˙˙˙˙˙˙˙ int * q = (int *) p;
    ˙˙˙˙˙˙˙˙ *q = 1;
    ˙˙˙˙˙}
    [...]

    Mutable?>


    C++ has "mutable", but C does not.

    But the existence of "mutable" in C++ does illustrate the issue -
    "const" in C and C++, at least in cases like this, does not mean the pointed-to data and its representation cannot ever change. It is more
    of a pinky-promise by the programmer that they will not change the
    meaning of the data.


    --- 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 Fri Aug 28 12:19:23 2026
    On 8/27/2026 11:40 PM, David Brown wrote:
    On 27/08/2026 23:07, Chris M. Thomasson wrote:
    On 8/26/2026 11:28 PM, David Brown wrote:
    On 26/08/2026 21:37, Chris M. Thomasson wrote:
    On 8/26/2026 12:28 AM, David Brown wrote:
    On 25/08/2026 23:47, Lawrence D?Oliveiro wrote:
    On Tue, 25 Aug 2026 15:11:44 +0100, Richard Harnden wrote:

    On 25/08/2026 13:15, David Brown wrote:

    Oh, and don't mix variable and struct names - no "struct foo foo". >>>>>>>
    Any reason why?˙ Just a style thing?

    This is one area where C++ is (by design?) backward-incompatible with >>>>>> C.


    You make it sound like it might have been designed specifically to
    be backwards incompatible with C, which is not the case.

    The point of having C++

    ˙˙˙˙˙struct X { ... };

    behave as though it were C

    ˙˙˙˙˙typedef struct X { ... } X;

    is simply that struct (and therefore also class) types are heavily
    used in C++, and the language does not want to distinguish them in
    normal usage.

    In C, you can choose to keep the distinction (by using the full
    name "struct foo") or remove it (by using typedefs).˙ Some prefer
    one style, some prefer the other.

    In C, struct names and enum names occupy namespaces separate from the >>>>>> names of variables and typedefs. In C++, they are all in the same
    namespace.

    To be pedantic, the term you are looking for is "name space" - two
    words.˙ That's the term used in both the C and C++ standards, while >>>>> "namespace" (one word) is a specific feature of the C++ language.
    Obviously there is no confusion in what you wrote here, but it's
    always possible that someone will want to look at what the C
    standard says about the different name spaces - and then they
    should search the pdf files with the correct spelling.


    name space in C, not that bad.

    ct_*

    There. A name space.

    The distinction I was making seems to have whizzed far above your head.

    For the convenience of people reading and using the standard, I was
    explaining the terms and their spelling, as used in the C (and C++)
    standards.˙ No one, I think, is in any doubt about what Lawrence
    meant in his post - nor what you and others colloquially call "name
    spaces" or "namespaces" in C by using prefixes on externally visible
    identifiers.

    stdio vs standard_input_output?


    No, "ct_*" is /not/ a "name space" in C.˙ It's an identifier prefix.
    If you want a new name space in C, declare a struct - within the
    struct declaration, you have a new name space so that the identifiers
    of the fields are independent of any other identifiers in the code.

    Are you missing the forest for the trees in a sense?

    I will try once more - a bit more slowly this time.˙ Please try to read
    my post, and think about what I am saying.

    /Everyone/ knows how to use prefixes with identifiers in C, and this is
    used to group functions and other objects together.˙ It is especially popular for reusable libraries.˙ Anyone who has worked with C beyond a "Hello, world" program is aware of this.

    I was pointing out, to Lawrence and anyone else, the exact terminology
    used by the C standards.˙ That was not to be pedantic, or nit-picking -
    it is useful to know such distinctions if you are looking things up in
    the C standards.˙ If someone wants to know if the identifiers for union declarations and struct declarations are in separate name spaces in C,
    or if they are combined (like in C++), then the fastest way to do is
    likely to search a pdf of the C standards for "name space".˙ If that
    person searches for "namespace", they will find nothing.˙ Exact terms,
    exact spelling, /matters/.

    I don't think I can explain more clearly than that.˙ Either you
    understand why exact terms are important, or you do not.



    Thanks! Well, ct_understand_* api prefix... Sorry for the thoughts
    drifting over my head right now. ;^)

    Thanks for your patience and not killfiling me.

    --- 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 Fri Aug 28 12:21:46 2026
    On 8/27/2026 11:43 PM, David Brown wrote:
    [...]
    C++ has "mutable", but C does not.

    But the existence of "mutable" in C++ does illustrate the issue -
    "const" in C and C++, at least in cases like this, does not mean the pointed-to data and its representation cannot ever change.˙ It is more
    of a pinky-promise by the programmer that they will not change the
    meaning of the data.


    Agreed!

    --- 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 Sat Aug 29 04:26:47 2026
    On Thu, 27 Aug 2026 17:50:38 -0400, James Kuyper wrote:

    C2011 support is not the same as C2011 conformance.

    Which is which? Can you ?support? without ?conforming?? ?Conform?
    without ?supporting?? Can one be ?partial? but not the other? If so,
    which?

    And which one is equivalent to ?implementing??

    --- 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 Fri Aug 28 23:01:31 2026
    Lawrence D?Oliveiro <ldo@nz.invalid> writes:
    On Thu, 27 Aug 2026 17:50:38 -0400, James Kuyper wrote:
    C2011 support is not the same as C2011 conformance.
    Which is which? Can you ?support? without ?conforming?? ?Conform?
    without ?supporting?? Can one be ?partial? but not the other? If so,
    which?

    And which one is equivalent to ?implementing??

    I'm not sure what you found difficult to understand. It seemed clear
    enough to me (if you don't snip some of what James wrote):

    C2011 support is not the same as C2011 conformance. I know nothing of
    sdcc, but that wording allows for it to support particular features of
    C2011, without necessarily fully conforming to any version of the C
    standard.

    Waldek Hebisch has previously written that sdcc "claimed C2011
    support". It's plausible that that could have meant that it supports
    some C2011-specific features (i.e., features that are in C2011 but
    not in C1999), but does not fully conform to the C2011 standard.

    For example, an implementation might support _Generic, but not
    passing structs by value (which appears to be the case for sdcc
    4.2.0). Depending on how it's worded, the documentation that
    Waldek referred to could be accurate.

    "C2011 conformance" would of course mean full conformance to the
    C2011 standard.

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

    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From David Brown@3:633/10 to All on Sat Aug 29 13:39:59 2026
    On 29/08/2026 08:01, Keith Thompson wrote:
    Lawrence D?Oliveiro <ldo@nz.invalid> writes:
    On Thu, 27 Aug 2026 17:50:38 -0400, James Kuyper wrote:
    C2011 support is not the same as C2011 conformance.
    Which is which? Can you ?support? without ?conforming?? ?Conform?
    without ?supporting?? Can one be ?partial? but not the other? If so,
    which?

    And which one is equivalent to ?implementing??

    I'm not sure what you found difficult to understand. It seemed clear
    enough to me (if you don't snip some of what James wrote):

    C2011 support is not the same as C2011 conformance. I know nothing of
    sdcc, but that wording allows for it to support particular features of
    C2011, without necessarily fully conforming to any version of the C
    standard.

    Waldek Hebisch has previously written that sdcc "claimed C2011
    support". It's plausible that that could have meant that it supports
    some C2011-specific features (i.e., features that are in C2011 but
    not in C1999), but does not fully conform to the C2011 standard.

    For example, an implementation might support _Generic, but not
    passing structs by value (which appears to be the case for sdcc
    4.2.0). Depending on how it's worded, the documentation that
    Waldek referred to could be accurate.

    "C2011 conformance" would of course mean full conformance to the
    C2011 standard.


    It is not unreasonable to think that without additional context, "C11
    support" means supporting /all/ C11 features. But even that is not
    really the same as "C11 conformance" - I would say that a compiler that
    had the keywords "bool", "true", "false", "static_assert" and "nullptr"
    from C23 might well have full C11 support without being C11 compliant
    (due to possible conflicts with user identifiers).

    It probably makes more sense, to reduce confusion, to talk about either "/partial/ C11 support" if "C11 compliance" is not appropriate.


    --- 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 Sat Aug 29 18:47:34 2026
    On 2026-08-29 00:26, Lawrence D?Oliveiro wrote:
    On Thu, 27 Aug 2026 17:50:38 -0400, James Kuyper wrote:

    [snippage restored:]
    On 2026-08-26 05:50, Waldek Hebisch wrote:
    Last time that I checked sdcc did not support passing or returning
    structs by value. At that time they claimed C2011 support.


    C2011 support is not the same as C2011 conformance.

    Which is which? Can you ?support? without ?conforming?? ?Conform?
    without ?supporting?? Can one be ?partial? but not the other? If so,
    which?

    Well, per Waldek, sdcc did not support passing or returning structs by
    value, so it cannot fully conform to any version of the C standard.
    However, sdcc still claimed to support C2011. Therefore, at least as
    used by the people who made that claim, they can support C2011 without conforming to it.

    And which one is equivalent to ?implementing??

    Based upon the above, I would describe sdcc as "partially implementing
    C2011". It would not qualify as "fully implementing C2011".


    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Andrey Tarasevich@3:633/10 to All on Sun Aug 30 11:23:00 2026
    On Thu 8/20/2026 4:14 PM, Lawrence D?Oliveiro wrote:
    The C spec goes out of its way to make clear that a NULL pointer does
    not necessarily have the same bit pattern as the integer value zero.
    This in spite of the fact that it is allowed to test a pointer value
    for NULL by comparing it to the integer value zero.

    Firstly, no, it is NOT allowed "to test a pointer value for NULL by
    comparing it to the integer value zero". What's allowed is to test a
    pointer value for NULL by comparing it to _integer_ _constant_
    _expression_ (ICE) that evaluates to zero. That's quite a different thing.

    Secondly, the importance of ICE zero here is for the compiler to
    recognize that ICE zero at compile time and replace it with proper null-pointer bit pattern (in all pointer contexts). In other words, that "integer zero" you are talking about is pure syntactic sugar, which
    exists only in the source code of your program. It is no longer "integer
    zero" in the translated code.

    The spec also says that all static variables are initialized to zero
    at program start time. Are pointer variables allowed to be given an
    all-zero bit pattern at this time? Or are they supposed to be
    initialized to the NULL value (if that?s different) instead?

    The latter. They are required to be initialized to the proper
    null-pointer bit pattern, not to all-zero bit pattern.

    This is actually what one can easily observe in C++ with pointers of pointer-to-member type, which also support null values and ICE-zero comparisons, Yet, in a typical implementation their nulls are usually physically represented by all-1 bit pattern. Which means that static
    variables of such types are initialized to 0xFF...F at program startup
    (by whatever means).

    --
    Best regards,
    Andrey



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