• Re: *rubeyes*: realloc(ptr, 0) is UB?

    From Lawrence D'Oliveiro@21:1/5 to Kaz Kylheku on Thu Jan 18 23:48:26 2024
    On Thu, 18 Jan 2024 22:45:59 -0000 (UTC), Kaz Kylheku wrote:

    On 2024-01-18, Lawrence D'Oliveiro <[email protected]d> wrote:
    On Thu, 18 Jan 2024 15:12:46 GMT, Scott Lurndal wrote:

    I've never found realloc useful, regardless of the value of the size
    parameter. YMMV.

    Looking back through my own code, I could only find one example, from
    quite a few years ago >><https://bitbucket.org/ldo17/dvd_menu_animator/src/master/>.

    And screw your code ...

    And screw your code too.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Kaz Kylheku@21:1/5 to Scott Lurndal on Fri Jan 19 00:03:33 2024
    On 2024-01-18, Scott Lurndal <[email protected]> wrote:
    Kaz Kylheku <[email protected]> writes:
    On 2024-01-18, Lawrence D'Oliveiro <[email protected]d> wrote:
    On Thu, 18 Jan 2024 15:12:46 GMT, Scott Lurndal wrote:

    I've never found realloc useful, regardless of the value
    of the size parameter. YMMV.

    Looking back through my own code, I could only find one example, from
    quite a few years ago >>><https://bitbucket.org/ldo17/dvd_menu_animator/src/master/>.

    And screw your code from years ago and its users, especially if it's
    just one place in the code. At least three places are required for it
    to be a issue (Microsoft Rule of Three).

    I would wager dollars to donuts that Lawrence's code didn't do
    a realloc(x, 0).

    Ok.

    realloc(x, 0);
    dollars_and_doughtnuts_t get_your_win_here;


    --
    TXR Programming Language: http://nongnu.org/txr
    Cygnal: Cygwin Native Application Library: http://kylheku.com/cygnal
    Mastodon: @[email protected]
    NOTE: If you use Google Groups, I don't see you, unless you're whitelisted.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Kaz Kylheku@21:1/5 to Keith Thompson on Fri Jan 19 03:06:59 2024
    On 2024-01-19, Keith Thompson <[email protected]> wrote:
    Kaz Kylheku <[email protected]> writes:
    On 2024-01-18, James Kuyper <[email protected]> wrote:
    It's OK to rely upon the requirements imposed by an implementation when
    the C standard doesn't impose any - but when you do so, you need to make >>> sure you actually know what those requirements are.

    Exactly, and in this specific case, it's not worth the effort compared
    to writing a realloc wrapper that avoids the undefined behavior, while
    itself providing a C99 conforming one.

    I'm not going to use realloc(ptr, 0) and check everyone's documentation.

    And then what if I don't find it defined? The what? Back to the
    wrapper I could have just written in the first place.
    [...]

    I think I would have *liked* to see C23 drop the special permission
    to return a null pointer for a requested size of zero.

    Yes. That would be the best thing.

    It also works well when the memory is subject to memcpy or memset,
    which have undefined behavior on null pointers.

    E.g. a dynamic array management routine can allocate a zero length array
    with malloc(0), get a non-null pointer and then initialize it with
    memset(ptr, 0, 0); no special casing in the code for zero length.

    C11 says (and
    this applies to malloc, realloc, and all other allocation functions):

    If the space cannot be allocated, a null pointer is returned. If
    the size of the space requested is zero, the behavior is
    implementation-defined: either a null pointer is returned, or
    the behavior is as if the size were some nonzero value, except
    that the returned pointer shall not be used to access an object.

    This could have been changed to:

    If the space cannot be allocated, a null pointer is returned. If
    the size of the space requested is zero, the behavior is as
    if the size were some nonzero value, except that the returned
    pointer shall not be used to access an object.

    I would add a footnote that implementors are encouraged to strive
    to minimize the nonzero value.

    Any existing implementations that always return a null pointer
    for malloc(0) would have to be updated. That shouldn't be a
    great burden.

    For that matter, compilers could fix this independently of libraries.

    Easy case: constant size: compiler adjusts malloc(0) call to malloc(1).

    Non-constant size calls: redirected through a compiler-generated stub:

    static void *__gcc_malloc_wrap(size_t __size)
    {
    return __size ? malloc(1) : malloc(__size);
    }

    Indirect calls: when address of malloc is taken, it takes this function.

    This would fix the issue retroactively for a good many C libraries,
    without them lifting a finger.

    Needless to say, calloc and realloc and others get that compiler
    treatment.

    Note that malloc(0) or realloc(ptr, 0) can still fail and return
    a null pointer if no space can be allocated, so all allocations
    should still be checked. But with this proposed change, code
    could rely on realloc(ptr, 0) returning a non-null pointer *unless*
    available memory is critically low -- pretty much the same as in C11,
    except that a null pointer would be an indication that something
    is seriously wrong.

    Yes; all ambiguity in regard to the null return value is gone.

    --
    TXR Programming Language: http://nongnu.org/txr
    Cygnal: Cygwin Native Application Library: http://kylheku.com/cygnal
    Mastodon: @[email protected]
    NOTE: If you use Google Groups, I don't see you, unless you're whitelisted.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Blue-Maned_Hawk@21:1/5 to Kaz Kylheku on Fri Jan 19 16:27:31 2024
    Kaz Kylheku wrote:

    On 2024-01-18, Blue-Maned_Hawk <[email protected]d> wrote:
    Kaz Kylheku wrote:

    On 2024-01-17, Blue-Maned_Hawk <[email protected]d> wrote:
    It looks to me like it was always undefined behavior, but it just
    wasn't explicitly stated as such in C99.

    That is incorrect. A behavior for the following can be readily
    inferred from C99:

    void *p = malloc(42); void *q = realloc(p, 0);

    Is that from an example?

    No.

    Ah, well then.

    All the examples in the document are purely informative and cannot
    define any behavior.

    Of course, that's not what "behavior can be readily inferred" means, no matter where I got the example.

    Inferences may be makeäble, but they have no power.



    --
    Blue-Maned_Hawk│shortens to Hawk│/ blu.mɛin.dʰak/ │he/him/his/himself/Mr.
    blue-maned_hawk.srht.site

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Kaz Kylheku@21:1/5 to [email protected] on Fri Jan 19 16:39:23 2024
    On 2024-01-19, Blue-Maned_Hawk <[email protected]d> wrote:
    Kaz Kylheku wrote:
    Of course, that's not what "behavior can be readily inferred" means, no
    matter where I got the example.

    Inferences may be makeäble, but they have no power.

    Yeah, tell that to your compiler when it surprisingly deletes code based
    on inference.

    --
    TXR Programming Language: http://nongnu.org/txr
    Cygnal: Cygwin Native Application Library: http://kylheku.com/cygnal
    Mastodon: @[email protected]
    NOTE: If you use Google Groups, I don't see you, unless you're whitelisted.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Lawrence D'Oliveiro@21:1/5 to Keith Thompson on Fri Jan 19 20:29:54 2024
    On Thu, 18 Jan 2024 16:13:48 -0800, Keith Thompson wrote:

    But with this proposed change, code could rely on realloc(ptr,
    0) returning a non-null pointer *unless* available memory is critically
    low -- pretty much the same as in C11,
    except that a null pointer would be an indication that something is
    seriously wrong.

    So having to allocate something, when it didn’t actually need to allocate anything, could lead to program failures in situations where things might otherwise work fine.

    Unless, of course, there was a special non-null preallocated address value
    that was returned for every zero-length allocation.

    Returning a null pointer for a zero-length allocation shouldn’t make any difference to the logic of your program.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Lawrence D'Oliveiro@21:1/5 to Scott Lurndal on Fri Jan 19 21:11:48 2024
    On Thu, 18 Jan 2024 23:47:55 GMT, Scott Lurndal wrote:

    <https://bitbucket.org/ldo17/dvd_menu_animator/src/master/>.

    I would wager dollars to donuts that Lawrence's code didn't do a
    realloc(x, 0).

    Is it cheating to look?

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Kaz Kylheku@21:1/5 to Lawrence D'Oliveiro on Fri Jan 19 21:35:11 2024
    On 2024-01-19, Lawrence D'Oliveiro <[email protected]d> wrote:
    On Thu, 18 Jan 2024 16:13:48 -0800, Keith Thompson wrote:

    But with this proposed change, code could rely on realloc(ptr,
    0) returning a non-null pointer *unless* available memory is critically
    low -- pretty much the same as in C11,
    except that a null pointer would be an indication that something is
    seriously wrong.

    So having to allocate something, when it didn’t actually need to allocate anything, could lead to program failures in situations where things might otherwise work fine.

    Unless, of course, there was a special non-null preallocated address value that was returned for every zero-length allocation.

    Returning a null pointer for a zero-length allocation shouldn’t make any difference to the logic of your program.

    Unfortunately, that requires checking for it in some cases.

    For instance:

    unsigned char *buf = malloc(s);
    memset(buf, 0, s);

    has undefined behavior if buf is null, even if s is zero.

    It is undefined behavior to pass a null pointer to a C library
    function, except where that is documented.

    the solution of using a special, pre-allocated address for every
    zero-length allocation would be fantastic, but a big change.

    At the very least, it should be one of the permitted choices of
    behavior.

    Keith posted the opinion that zero length allocations should have
    the behavior of returning a non-null allocated object which
    can be freed. (I.e. remove the freedom to return null.)

    If, additionally, implentations could have a single dedicated object for representing empty allocations (which can be passed to free any
    number of times), that would also be a nice requirement.

    All that the standrad would have to say is something like "pointers from separate zero-sized allocations need not be distinct".

    --
    TXR Programming Language: http://nongnu.org/txr
    Cygnal: Cygwin Native Application Library: http://kylheku.com/cygnal
    Mastodon: @[email protected]
    NOTE: If you use Google Groups, I don't see you, unless you're whitelisted.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Kaz Kylheku@21:1/5 to Keith Thompson on Fri Jan 19 21:50:39 2024
    On 2024-01-19, Keith Thompson <[email protected]> wrote:
    Lawrence D'Oliveiro <[email protected]d> writes:
    On Thu, 18 Jan 2024 16:13:48 -0800, Keith Thompson wrote:
    But with this proposed change, code could rely on realloc(ptr,
    0) returning a non-null pointer *unless* available memory is critically
    low -- pretty much the same as in C11,
    except that a null pointer would be an indication that something is
    seriously wrong.

    So having to allocate something, when it didn’t actually need to allocate >> anything, could lead to program failures in situations where things might
    otherwise work fine.

    Unless, of course, there was a special non-null preallocated address value >> that was returned for every zero-length allocation.

    That wouldn't meet the current requirements. If malloc(0) returns a
    non-null result, then two calls to malloc(0) must yield distinct results
    (if free() isn't called in between), just as two calls to malloc(1) must
    do.

    But if malloc(0) returns null, then two such calls don't yield distinct results.

    We already don't know today whether malloc(0) == malloc(0).

    to write robust code. If malloc(0) and realloc(ptr, 0) return a
    non-null pointer on success, then a null result *always* indicates an allocation failure.

    Not requiring the non-null return from malloc(0) to be distinct
    from previous malloc(0) return values (whether they were freed or not),
    could help to "sell" the idea of taking away the null return value.

    Some implementors might grumble that null return allowed malloc(0) to be efficient by not allocating anything. If they were allowed to return
    (void *) -1 or something, that would placate that concern.

    Say you have a large, sparse vector of dynamic vectors. Sparse in the
    sense that most of the dynamic vectors in the sparse vector are empty;
    only a few are nonempty. If those empty vectors come from malloc(0) in
    an efficient way (nothing is allocated on the heap), that's nice.

    --
    TXR Programming Language: http://nongnu.org/txr
    Cygnal: Cygwin Native Application Library: http://kylheku.com/cygnal
    Mastodon: @[email protected]
    NOTE: If you use Google Groups, I don't see you, unless you're whitelisted.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Scott Lurndal@21:1/5 to Lawrence D'Oliveiro on Fri Jan 19 21:31:14 2024
    Lawrence D'Oliveiro <[email protected]d> writes:
    On Thu, 18 Jan 2024 23:47:55 GMT, Scott Lurndal wrote:

    <https://bitbucket.org/ldo17/dvd_menu_animator/src/master/>.

    I would wager dollars to donuts that Lawrence's code didn't do a
    realloc(x, 0).

    Is it cheating to look?

    Wouldn't be much of a wager, then.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Tim Rentsch@21:1/5 to Scott Lurndal on Fri Jan 19 16:14:03 2024
    [email protected] (Scott Lurndal) writes:

    Tim Rentsch <[email protected]> writes:

    [email protected] (Scott Lurndal) writes:

    Kaz Kylheku <[email protected]> writes:

    I'm looking at the C99 and N3096 (April 2023) definitions of realloc
    side by side.

    N3096 says

    "Otherwise, if ptr does not match a pointer earlier returned by a memory >>>> management function, or if the space has been deallocated by a call to >>>> the free or realloc function, or if the size is zero, the behavior is
    undefined."

    Yikes! In C99 there is nothing about the size being zero:

    "Otherwise, if ptr does not match a pointer earlier returned by the
    calloc, malloc, or realloc function, or if the space has been
    deallocated by a call to the free or realloc function, the behavior is >>>> undefined."

    Nothing about "or if the size is zero".

    Yikes; when did this criminal stupidity get perpetrated?

    I'm not sure what stupidity you are referring to, but IIRC, there was
    some recent standardization activity relating to realloc
    when size == 0 because there were differences in the behavior
    between different implementations. Making the behavior
    undefined was the only rational choice.

    Is that last sentence your own assessment, or are you simply
    repeating someone else's assessment?

    My assessment. I've never found realloc useful, regardless of
    the value of the size parameter. YMMV.

    So you aren't really in a position to say whether this decision was
    the only rational choice. Generally I hope people who would make
    such a statement would first make an effort to learn and understand
    other people's thoughts on the matter.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Scott Lurndal@21:1/5 to Tim Rentsch on Sat Jan 20 00:26:20 2024
    Tim Rentsch <[email protected]> writes:
    [email protected] (Scott Lurndal) writes:

    Tim Rentsch <[email protected]> writes:

    [email protected] (Scott Lurndal) writes:

    Kaz Kylheku <[email protected]> writes:

    I'm looking at the C99 and N3096 (April 2023) definitions of realloc >>>>> side by side.

    N3096 says

    "Otherwise, if ptr does not match a pointer earlier returned by a memory >>>>> management function, or if the space has been deallocated by a call to >>>>> the free or realloc function, or if the size is zero, the behavior is >>>>> undefined."

    Yikes! In C99 there is nothing about the size being zero:

    "Otherwise, if ptr does not match a pointer earlier returned by the
    calloc, malloc, or realloc function, or if the space has been
    deallocated by a call to the free or realloc function, the behavior is >>>>> undefined."

    Nothing about "or if the size is zero".

    Yikes; when did this criminal stupidity get perpetrated?

    I'm not sure what stupidity you are referring to, but IIRC, there was
    some recent standardization activity relating to realloc
    when size == 0 because there were differences in the behavior
    between different implementations. Making the behavior
    undefined was the only rational choice.

    Is that last sentence your own assessment, or are you simply
    repeating someone else's assessment?

    My assessment. I've never found realloc useful, regardless of
    the value of the size parameter. YMMV.

    So you aren't really in a position to say whether this decision was
    the only rational choice. Generally I hope people who would make
    such a statement would first make an effort to learn and understand
    other people's thoughts on the matter.

    The fact that I didn't find it useful, doesn't imply in any way
    that I don't understand other's thoughts on the matter. I did
    spend several years on various standards committees, compromise
    was often the best way to make forward progress and 'undefined
    behavior' was a rational compromise in that context.

    I also believe that the fears over the impact of that decision
    are overblown.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Tim Rentsch@21:1/5 to Scott Lurndal on Sat Jan 20 15:41:48 2024
    [email protected] (Scott Lurndal) writes:

    Tim Rentsch <[email protected]> writes:

    [email protected] (Scott Lurndal) writes:

    Kaz Kylheku <[email protected]> writes:

    On 2024-01-17, Scott Lurndal <[email protected]> wrote:

    Kaz Kylheku <[email protected]> writes:

    Yikes; when did this criminal stupidity get perpetrated?

    I'm not sure what stupidity you are referring to, but IIRC, there was >>>>> some recent standardization activity relating to realloc
    when size == 0 because there were differences in the behavior
    between different implementations. Making the behavior
    undefined was the only rational choice.

    No, the rational choice is letting those implementations be
    nonconforming, until they fix their shit.

    You cannot claw back decades-old defined behaviors in a major

    Nobody is clawing anything back. [...]

    This statement seems directly contradicted by the proposed
    modification to the C standard, which changes previously
    defined behavior to undefined behavior.

    Clawing back implies that existing programs that rely
    on either behavior will stop working.

    That's not the case in the real world.

    There is no question that the proposed change (which is possibly
    ratified by now) would claw back some defined behavior in favor of
    undefined behavior. What you're saying is that there will be no
    consequences of that clawing back.

    First, there certainly will be _some_ consequences, because some
    people will modify their code even if they think there will be no
    changes in the compilers and libraries they use.

    Second, even if many compilers and many C libraries make no
    changes (at least in the near future), the chances are high that
    at least some will, especially in the presence of library software
    updates and new platforms coming on line (low-end consumer
    hardware such as switches and wifi routters), and consequently
    some applications will get bitten by this.

    Third, even if there are no changes in the near term, if we look
    out ten years or more it is highly likely that these things will
    change in many implementations, especially when taking into
    account cross-compiling. The idea that there will be no changes
    "in the real world" over a long time frame is incredibly naive.
    The Linux null-pointer-use bug illustrates the problem.

    From the application portability point of view, there is little
    difference between implementation defined and undefined behavior.

    This statement is nonsense. Essentially all C programs depend on implementation-defined behavior to some degree. If all of that ID
    behavior were changed to be undefined behavior, programming in C
    would be impossible for all practical purposes, whether or not
    portability is a consideration.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Lawrence D'Oliveiro@21:1/5 to Keith Thompson on Sun Jan 21 05:11:47 2024
    On Sat, 20 Jan 2024 19:50:31 -0800, Keith Thompson wrote:

    If realloc(ptr, 0) returns a null pointer there's no way to tell whether allocation failed (and ptr has not been freed) ...

    Actually, that doesn’t seem like a reasonable interpretation, because it leads to memory leaks.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Kaz Kylheku@21:1/5 to Lawrence D'Oliveiro on Sun Jan 21 05:20:02 2024
    On 2024-01-21, Lawrence D'Oliveiro <[email protected]d> wrote:
    On Sat, 20 Jan 2024 19:50:31 -0800, Keith Thompson wrote:

    If realloc(ptr, 0) returns a null pointer there's no way to tell whether
    allocation failed (and ptr has not been freed) ...

    Actually, that doesn’t seem like a reasonable interpretation, because it leads to memory leaks.

    That's not an "interpretation"; that's just a fact.

    A null return from realloc has two documented meanings; situations exist
    in which it cannot be distinguished which one, in any portable way.

    --
    TXR Programming Language: http://nongnu.org/txr
    Cygnal: Cygwin Native Application Library: http://kylheku.com/cygnal
    Mastodon: @[email protected]
    NOTE: If you use Google Groups, I don't see you, unless you're whitelisted.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Tim Rentsch@21:1/5 to Kaz Kylheku on Sun Jan 21 03:08:05 2024
    Kaz Kylheku <[email protected]> writes:

    [.. considering the behavior of malloc(0) ..]

    [If] implentations could have a single dedicated object for
    representing empty allocations (which can be passed to free any
    number of times), that would also be a nice requirement.

    That defeats the whole purpose of having malloc(0) return
    a non-null value. Don't you understand anything?

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Tim Rentsch@21:1/5 to Keith Thompson on Sun Jan 21 03:25:24 2024
    Keith Thompson <[email protected]> writes:

    What's being suggested is basically a second kind of null pointer, i.e.,
    a second unique pointer value that can't be dereferenced. And if two malloc(0) calls were allowed to return the same non-null value, that
    would require additional wording in the standard. I don't strongly
    object to the idea, but I don't think it's necessary.

    It's a counterproductive idea. The whole point of malloc(0) being
    able to return a non-null pointer is to get distinct "objects" for
    different zero-sized allocations. The "objects" can't be used in
    any way but comparing the pointers allows the "objects" from two
    different allocations to be distinguished.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Tim Rentsch@21:1/5 to Kaz Kylheku on Sun Jan 21 04:04:06 2024
    Kaz Kylheku <[email protected]> writes:

    [...]

    Not requiring the non-null return from malloc(0) to be distinct
    from previous malloc(0) return values (whether they were freed or not),
    could help to "sell" the idea of taking away the null return value.

    Some implementors might grumble that null return allowed malloc(0) to be efficient by not allocating anything. If they were allowed to return
    (void *) -1 or something, that would placate that concern. [...]

    You have the tail wagging the dog here. If the results of
    different malloc(0) calls don't need to be distinguishable,
    they might just as well all be null.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Tim Rentsch@21:1/5 to Kaz Kylheku on Sun Jan 21 03:47:32 2024
    Kaz Kylheku <[email protected]> writes:

    On 2024-01-19, Keith Thompson <[email protected]> wrote:

    Kaz Kylheku <[email protected]> writes:

    On 2024-01-18, James Kuyper <[email protected]> wrote:

    It's OK to rely upon the requirements imposed by an implementation when >>>> the C standard doesn't impose any - but when you do so, you need to make >>>> sure you actually know what those requirements are.

    Exactly, and in this specific case, it's not worth the effort compared
    to writing a realloc wrapper that avoids the undefined behavior, while
    itself providing a C99 conforming one.

    I'm not going to use realloc(ptr, 0) and check everyone's documentation. >>>
    And then what if I don't find it defined? The what? Back to the
    wrapper I could have just written in the first place.

    [...]

    I think I would have *liked* to see C23 drop the special permission
    to return a null pointer for a requested size of zero.

    Yes. That would be the best thing.

    It also works well when the memory is subject to memcpy or memset,
    which have undefined behavior on null pointers.

    That's a half-assed argument. There are other ways a pointer might
    have a null value than just being the result of a call to malloc().
    If code might call memset() et al with a zero size and a null
    pointer, it's better to address all possible cases at once rather
    than just some of them:

    static inline void *
    safer_memset( void *s, int c, size_t n ){
    return n ? memset( s, c, n ) : s;
    }

    static inline void *
    safer_memcpy( void *d, const void *s, size_t n ){
    return n ? memcpy( d, s, n ) : d;
    }

    /* ... etc ... */

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From bart@21:1/5 to Kaz Kylheku on Sun Jan 21 11:55:39 2024
    On 19/01/2024 21:50, Kaz Kylheku wrote:
    On 2024-01-19, Keith Thompson <[email protected]> wrote:
    Lawrence D'Oliveiro <[email protected]d> writes:
    On Thu, 18 Jan 2024 16:13:48 -0800, Keith Thompson wrote:
    But with this proposed change, code could rely on realloc(ptr,
    0) returning a non-null pointer *unless* available memory is critically >>>> low -- pretty much the same as in C11,
    except that a null pointer would be an indication that something is
    seriously wrong.

    So having to allocate something, when it didn’t actually need to allocate >>> anything, could lead to program failures in situations where things might >>> otherwise work fine.

    Unless, of course, there was a special non-null preallocated address value >>> that was returned for every zero-length allocation.

    That wouldn't meet the current requirements. If malloc(0) returns a
    non-null result, then two calls to malloc(0) must yield distinct results
    (if free() isn't called in between), just as two calls to malloc(1) must
    do.

    But if malloc(0) returns null, then two such calls don't yield distinct results.

    We already don't know today whether malloc(0) == malloc(0).

    to write robust code. If malloc(0) and realloc(ptr, 0) return a
    non-null pointer on success, then a null result *always* indicates an
    allocation failure.

    Not requiring the non-null return from malloc(0) to be distinct
    from previous malloc(0) return values (whether they were freed or not),
    could help to "sell" the idea of taking away the null return value.

    Some implementors might grumble that null return allowed malloc(0) to be efficient by not allocating anything. If they were allowed to return
    (void *) -1 or something, that would placate that concern.

    Say you have a large, sparse vector of dynamic vectors. Sparse in the
    sense that most of the dynamic vectors in the sparse vector are empty;
    only a few are nonempty. If those empty vectors come from malloc(0) in
    an efficient way (nothing is allocated on the heap), that's nice.

    malloc has sort of created a rod for its own back by needing to store
    the size of the allocation. That will take up some space even when
    malloc(0) is called, if NULL is not being returned.

    I've looked at my own allocator for small objects, which does not store
    the size. There, successive calls to my_alloc(0) return the same pointer
    value to a zero-sized memory block (but not if there are intervening
    calls to my_alloc(100).)

    A call to free a zero-sized block will be my_free(p,0) so it knows no
    action is needed.

    malloc(0) can be made efficient by it detecting the zero-size and
    returning a pointer to the same special memory block, even if there are intervening non-zero calls.

    A call to free(p) where p refers to that zero-sized block can also be
    detected.

    So it will save on memory if allocating millions of zero-sized blocks,
    but it means extra checks on each call.

    Other replies however suggested that such malloc(0) calls need to return
    unique values. But you can't have both have unique values and save
    memory (at best you will need 1 byte per malloc(0), and some hairy means
    of detecting whether the p in free(p) refers to one of those bytes, so a
    bigger runtime overhead).

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Tim Rentsch@21:1/5 to Keith Thompson on Sun Jan 21 05:10:40 2024
    Keith Thompson <[email protected]> writes:

    [...]

    Note that malloc(0) or realloc(ptr, 0) can still fail and return
    a null pointer if no space can be allocated, so all allocations
    should still be checked. [...]

    Note that it isn't hard to write code for realloc() that
    guarantees a realloc( p, 0 ) call will never fail (that
    is, if 'p' is non-null, there will never be a case where
    a null value indicating an allocation failure has to be
    returned). To do that, all the implementation needs to
    do is see if the malloc(0) allocation would fail, and
    if it would then simply return 'p', without freeing it.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Tim Rentsch@21:1/5 to Keith Thompson on Sun Jan 21 06:55:03 2024
    Keith Thompson <[email protected]> writes:

    [.. considering the definition of malloc(0) ..]

    I dislike the fact that the behavior is currently
    implementation-defined. I think requiring malloc(0) to return a
    null pointer would be an improvement over the current (C11)
    specification, though it would be an odd special case; a null
    pointer would mean either that the allocation failed (and the
    system is likely in a bad state) or that the requested size was 0.
    Note that an application might call malloc() with a variable
    argument whose value can just happen to be zero. [...]

    First I think it is worth going back and re-reading the Rationale
    where it talks about malloc() and friends.

    I'm sympathetic to the reaction about malloc() and friends having
    different behavior in different implemenations. On the other
    hand let's look at it from the point of view of implementations:

    (A) For malloc(0) returning non-null: that can be a convenience
    for some applications, and can be supplied in a way that client
    code cannot provide.

    (B) For malloc(0) returning null: most programs don't allocate
    zero-sized objects, except perhaps inadvertently; in most uses
    code will work without needing to distinguish, and in those cases
    where the distinction is important it isn't hard to write code
    that works for both allowed behaviors; being able to return null
    for zero-size requests both simplifies the code and uses less
    resource than choice (A). In small systems the added resource
    demands may very well be the deciding factor between choosing (A)
    and choosing (B).

    For most aspects, client code can ignore the distinction between
    behavior (A) and behavior (B). The big exception to that is how
    to detect errors. If we think the C standard should continue to
    allow both possibilities (and personally I think it should), a
    way around the problem is to provide a testable macro symbol, as
    for example

    #if __MALLOC_0_GIVES_NULL
    ...
    #elif __MALLOC_0_GIVES_NONNULL
    ...
    #else
    ... deal with uncertainty in some way
    ... (not hard although it may involve a run-time cost)
    #endif

    If some such macros were provided it isn't hard to define a macro
    that does error checking in both kinds of environments, so there
    could be code like this:

    Foo *p = malloc( n * sizeof *p );
    if( MALLOC_FAILED( p, n ) ) ...

    where the MALLOC_FAILED() macro would either simply test '!p' or
    would test '!p && n > 0', depending on whether the code is
    running in an (A) regime or a (B) regime.

    Personally I would like to see the memory allocation functions
    augmented to be explicit about which behavior is wanted:

    void *malloc0( size_t ); /* returns NULL for size of 0 */
    void *malloc1( size_t ); /* returns NULL only on failure */

    along with analogous changes for calloc(), realloc(), etc.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Kaz Kylheku@21:1/5 to Tim Rentsch on Sun Jan 21 17:32:52 2024
    On 2024-01-21, Tim Rentsch <[email protected]> wrote:
    Kaz Kylheku <[email protected]> writes:

    [.. considering the behavior of malloc(0) ..]

    [If] implentations could have a single dedicated object for
    representing empty allocations (which can be passed to free any
    number of times), that would also be a nice requirement.

    That defeats the whole purpose of having malloc(0) return
    a non-null value. Don't you understand anything?

    I undertand very clearly from past disccussions that you're attached to
    a particular use case whereby malloc(0) returns unique objects.

    However, I don't see that as the purpose, let alone the "whole purpose".

    The standard currently does not endorse malloc(0) as a factory
    for unique pointers.

    Currently, it cannot be relied on due to the possibility of
    the null return. If you want that behavior portably, you currently have
    to use malloc(1) instead, or some other nonzero value.
    Even if your program detects that malloc(0) returns a non-null
    pointer one time, there is no requirement that all subsequent such
    allocations will return non-null.

    --
    TXR Programming Language: http://nongnu.org/txr
    Cygnal: Cygwin Native Application Library: http://kylheku.com/cygnal
    Mastodon: @[email protected]
    NOTE: If you use Google Groups, I don't see you, unless you're whitelisted.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Tim Rentsch@21:1/5 to bart on Sun Jan 21 09:41:41 2024
    bart <[email protected]> writes:

    malloc has sort of created a rod for its own back by needing to
    store the size of the allocation.

    malloc does not need to store the size of the space requested.

    It does need to save enough information to be able to compute
    how much memory needs to be reclaimed, based on the pointer
    to the memory area to be free()'d. That might not be as much
    memory as a whole size word, although typically the overhead
    is as many bytes as a pointer, or a size_t, per block (including
    both allocated blocks and free blocks).

    That will take up some space even when malloc(0) is called, if
    NULL is not being returned. [...]

    Other replies however
    suggested that such malloc(0) calls need to return unique values.
    But you can't have both have unique values and save memory (at
    best you will need 1 byte per malloc(0), and some hairy means of
    detecting whether the p in free(p) refers to one of those bytes,
    so a bigger runtime overhead).

    In a conventional architecture, eg x64, it isn't too difficult to
    devise a method for malloc() and free() of zero-sized objects that
    (a) has a fast check to see if an argument value refers to a
    zero-size object, and (b) takes 1.5 bits or less per zero-size
    object allocated.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From bart@21:1/5 to Chris M. Thomasson on Sun Jan 21 20:34:55 2024
    On 21/01/2024 20:22, Chris M. Thomasson wrote:
    On 1/21/2024 3:55 AM, bart wrote:

    malloc has sort of created a rod for its own back by needing to store
    the size of the allocation. That will take up some space even when
    malloc(0) is called, if NULL is not being returned.
    [...]

    Why would malloc need to store the size of its allocations?


    If you do this:

    p = malloc(N);
    ...
    free(p);

    'free' will need to know what N was used to allocate p in order to
    deallocate right size of memory.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Lew Pitcher@21:1/5 to Chris M. Thomasson on Sun Jan 21 20:51:52 2024
    On Sun, 21 Jan 2024 12:22:05 -0800, Chris M. Thomasson wrote:

    On 1/21/2024 3:55 AM, bart wrote:
    On 19/01/2024 21:50, Kaz Kylheku wrote:
    On 2024-01-19, Keith Thompson <[email protected]> wrote:
    Lawrence D'Oliveiro <[email protected]d> writes:
    On Thu, 18 Jan 2024 16:13:48 -0800, Keith Thompson wrote:
    But with this proposed change, code could rely on realloc(ptr,
    0) returning a non-null pointer *unless* available memory is
    critically
    low -- pretty much the same as in C11,
    except that a null pointer would be an indication that something is >>>>>> seriously wrong.

    So having to allocate something, when it didn’t actually need to
    allocate
    anything, could lead to program failures in situations where things
    might
    otherwise work fine.

    Unless, of course, there was a special non-null preallocated address >>>>> value
    that was returned for every zero-length allocation.

    That wouldn't meet the current requirements.  If malloc(0) returns a
    non-null result, then two calls to malloc(0) must yield distinct results >>>> (if free() isn't called in between), just as two calls to malloc(1) must >>>> do.

    But if malloc(0) returns null, then two such calls don't yield distinct
    results.

    We already don't know today whether malloc(0) == malloc(0).

    to write robust code.  If malloc(0) and realloc(ptr, 0) return a
    non-null pointer on success, then a null result *always* indicates an
    allocation failure.

    Not requiring the non-null return from malloc(0) to be distinct
    from previous malloc(0) return values (whether they were freed or not),
    could help to "sell" the idea of taking away the null return value.

    Some implementors might grumble that null return allowed malloc(0) to be >>> efficient by not allocating anything. If they were allowed to return
    (void *) -1 or something, that would placate that concern.

    Say you have a large, sparse vector of dynamic vectors. Sparse in the
    sense that most of the dynamic vectors in the sparse vector are empty;
    only a few are nonempty. If those empty vectors come from malloc(0) in
    an efficient way (nothing is allocated on the heap), that's nice.

    malloc has sort of created a rod for its own back by needing to store
    the size of the allocation. That will take up some space even when
    malloc(0) is called, if NULL is not being returned.
    [...]

    Why would malloc need to store the size of its allocations?

    So that, in a naive implementation, free() knows how much memory to
    return to the freelist. See K&R Chapter 8, secion 8.7 "Example - A
    Storage Allocator" for an example.

    --
    Lew Pitcher
    "In Skills We Trust"

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From David Brown@21:1/5 to bart on Sun Jan 21 21:54:41 2024
    On 21/01/2024 21:34, bart wrote:
    On 21/01/2024 20:22, Chris M. Thomasson wrote:
    On 1/21/2024 3:55 AM, bart wrote:

    malloc has sort of created a rod for its own back by needing to store
    the size of the allocation. That will take up some space even when
    malloc(0) is called, if NULL is not being returned.
    [...]

    Why would malloc need to store the size of its allocations?


    If you do this:

        p = malloc(N);
        ...
        free(p);

    'free' will need to know what N was used to allocate p in order to
    deallocate right size of memory.


    It does not need to store the size of the allocation. It merely has to
    store sufficient information to be able to figure out how to deallocate properly. And any storage it needs can be in a different place from the allocation.

    Some malloc/free systems track the information separately from the
    allocation data. One possibility is to use pools for different memory
    sizes. When the user asks for X bytes, this is rounded up to the
    nearest pool size - call it Y - and the allocation is made from the Y
    pool, which can be viewed as an array of Y-size lumps. Only need one
    single bit to track each allocation, to say which indexes in the array
    are used. There are many other ways to do it.

    Personally, I think it would be often more efficient in modern C if the allocation system didn't track sizes at all, and "free" passed the
    original size as a parameter. But that ship sailed long ago for
    standard C. (C++ supports a sized deallocation system, and of course
    there's nothing to stop you making your own allocator system for C.)

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Kaz Kylheku@21:1/5 to David Brown on Sun Jan 21 21:20:43 2024
    On 2024-01-21, David Brown <[email protected]> wrote:
    Personally, I think it would be often more efficient in modern C if the allocation system didn't track sizes at all, and "free" passed the
    original size as a parameter. But that ship sailed long ago for
    standard C.

    That ship newly sailed in 2023.

    The N3096 draft describes a function free_sized that takes a size. If
    the size is wrong, the behavior is undefined.

    So now C has two ways to free an object: an efficient one where
    the program helps by giving the size, and the old free, which
    may have to do more work.

    I think going forward, it may start to become wise to detect the implementation's support for free_sized (e.g. in a configure script)
    and use that as much as possible, using free only when it's
    inconvenient: for instance a function resembling POSIX strdup
    might not be able to to safely assume that it can do
    free_sized(str, strlen(str)+1), because the underlying buffer
    might be larger.

    --
    TXR Programming Language: http://nongnu.org/txr
    Cygnal: Cygwin Native Application Library: http://kylheku.com/cygnal
    Mastodon: @[email protected]
    NOTE: If you use Google Groups, I don't see you, unless you're whitelisted.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Kaz Kylheku@21:1/5 to bart on Sun Jan 21 21:16:20 2024
    On 2024-01-21, bart <[email protected]> wrote:
    On 21/01/2024 20:22, Chris M. Thomasson wrote:
    On 1/21/2024 3:55 AM, bart wrote:

    malloc has sort of created a rod for its own back by needing to store
    the size of the allocation. That will take up some space even when
    malloc(0) is called, if NULL is not being returned.
    [...]

    Why would malloc need to store the size of its allocations?


    If you do this:

    p = malloc(N);
    ...
    free(p);

    'free' will need to know what N was used to allocate p in order to
    deallocate right size of memory.

    While that is true in the abstract, it is not necesarily the case
    that it needs to pull N out of the p object.

    For instance, suppose N is a 64 byte allocation, and the allocator
    has special heaps for small allocations.

    It can figure out that p points into a heap that has 64 byte objects
    and then do some pointer arithmetic to figure out which one,
    and add that to a free list or bitmap or whatever.

    --
    TXR Programming Language: http://nongnu.org/txr
    Cygnal: Cygwin Native Application Library: http://kylheku.com/cygnal
    Mastodon: @[email protected]
    NOTE: If you use Google Groups, I don't see you, unless you're whitelisted.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Kaz Kylheku@21:1/5 to Keith Thompson on Sun Jan 21 21:49:17 2024
    On 2024-01-21, Keith Thompson <[email protected]> wrote:
    David Brown <[email protected]> writes:
    [...]
    Personally, I think it would be often more efficient in modern C if
    the allocation system didn't track sizes at all, and "free" passed the
    original size as a parameter. But that ship sailed long ago for
    standard C. (C++ supports a sized deallocation system, and of course
    there's nothing to stop you making your own allocator system for C.)

    I suspect that calling malloc() with one size and free() with a
    different one would have been a rich source of subtle bugs.

    Welcome to C23! free_sized(malloc(42), 73) -> UB.

    --
    TXR Programming Language: http://nongnu.org/txr
    Cygnal: Cygwin Native Application Library: http://kylheku.com/cygnal
    Mastodon: @[email protected]
    NOTE: If you use Google Groups, I don't see you, unless you're whitelisted.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From bart@21:1/5 to David Brown on Sun Jan 21 21:37:57 2024
    On 21/01/2024 20:54, David Brown wrote:
    On 21/01/2024 21:34, bart wrote:
    On 21/01/2024 20:22, Chris M. Thomasson wrote:
    On 1/21/2024 3:55 AM, bart wrote:

    malloc has sort of created a rod for its own back by needing to
    store the size of the allocation. That will take up some space even
    when malloc(0) is called, if NULL is not being returned.
    [...]

    Why would malloc need to store the size of its allocations?


    If you do this:

         p = malloc(N);
         ...
         free(p);

    'free' will need to know what N was used to allocate p in order to
    deallocate right size of memory.


    It does not need to store the size of the allocation.

    So it doesn't specifically need to store N for free to do its job ...

    It merely has to
    store sufficient information to be able to figure out how to deallocate properly.  And any storage it needs can be in a different place from the allocation.

    Some malloc/free systems track the information separately from the
    allocation data.  One possibility is to use pools for different memory sizes.  When the user asks for X bytes, this is rounded up to the
    nearest pool size - call it Y - and the allocation is made from the Y
    pool, which can be viewed as an array of Y-size lumps.  Only need one
    single bit to track each allocation, to say which indexes in the array
    are used.  There are many other ways to do it.

    Personally, I think it would be often more efficient in modern C if the allocation system didn't track sizes at all, and "free" passed the
    original size as a parameter.

    ... but here you're specifically passing N for free to do its job.
    Suggesting this value is a good way to determine the necessary info.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From bart@21:1/5 to Kaz Kylheku on Sun Jan 21 22:09:45 2024
    On 21/01/2024 21:49, Kaz Kylheku wrote:
    On 2024-01-21, Keith Thompson <[email protected]> wrote:
    David Brown <[email protected]> writes:
    [...]
    Personally, I think it would be often more efficient in modern C if
    the allocation system didn't track sizes at all, and "free" passed the
    original size as a parameter. But that ship sailed long ago for
    standard C. (C++ supports a sized deallocation system, and of course
    there's nothing to stop you making your own allocator system for C.)

    I suspect that calling malloc() with one size and free() with a
    different one would have been a rich source of subtle bugs.

    Welcome to C23! free_sized(malloc(42), 73) -> UB.


    I've used such a sized free function in my own libraries for a long time.

    Yes, you can pass the wrong size (although there are debugging options
    where it will keep track of the sizes and double-check, if you suspect
    such a bug).

    But the real problems are the same as they are now in C:

    free(p) when p has not been assigned a value from malloc
    free(p); free(p); call twice
    --- forget to call free
    free(q); free the wrong pointer
    free(p+1); pass an invalid pointer

    Getting a p=malloc() correctly matched up with just one free(p) in a
    different part of the program, at a different time, is the hard bit.

    The easy bit is this:

    p = malloc(sizeof(*p));
    ...
    free_sized(p, sizeof(*p));

    (Is it still malloc or is there a malloc_sized? Since part of the point
    is not wasting time and memory managing that extra metadata.)

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Tim Rentsch@21:1/5 to Kaz Kylheku on Sun Jan 21 14:38:04 2024
    Kaz Kylheku <[email protected]> writes:

    On 2024-01-21, Lawrence D'Oliveiro <[email protected]d> wrote:

    On Sat, 20 Jan 2024 19:50:31 -0800, Keith Thompson wrote:

    If realloc(ptr, 0) returns a null pointer there's no way to tell whether >>> allocation failed (and ptr has not been freed) ...

    Actually, that doesn?t seem like a reasonable interpretation, because it
    leads to memory leaks.

    That's not an "interpretation"; that's just a fact.

    A null return from realloc has two documented meanings; situations exist
    in which it cannot be distinguished which one, in any portable way.

    I don't agree that they can't be distinguished in _any_ portable
    way.

    I suppose a case could be made that they can't be distinguished in
    any _convenient_ portable way. But that's not the same as _any_
    portable way.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Tim Rentsch@21:1/5 to Keith Thompson on Sun Jan 21 14:33:09 2024
    Keith Thompson <[email protected]> writes:

    Keith Thompson <[email protected]> writes:

    Kaz Kylheku <[email protected]> writes:

    On 2024-01-18, James Kuyper <[email protected]> wrote:

    It's OK to rely upon the requirements imposed by an implementation when >>>> the C standard doesn't impose any - but when you do so, you need to make >>>> sure you actually know what those requirements are.

    Exactly, and in this specific case, it's not worth the effort compared
    to writing a realloc wrapper that avoids the undefined behavior, while
    itself providing a C99 conforming one.

    I'm not going to use realloc(ptr, 0) and check everyone's documentation. >>>
    And then what if I don't find it defined? The what? Back to the
    wrapper I could have just written in the first place.

    [...]

    I think I would have *liked* to see C23 drop the special permission
    to return a null pointer for a requested size of zero. C11 says (and
    this applies to malloc, realloc, and all other allocation functions):

    If the space cannot be allocated, a null pointer is returned. If
    the size of the space requested is zero, the behavior is
    implementation-defined: either a null pointer is returned, or
    the behavior is as if the size were some nonzero value, except
    that the returned pointer shall not be used to access an object.

    This could have been changed to:

    If the space cannot be allocated, a null pointer is returned. If
    the size of the space requested is zero, the behavior is as
    if the size were some nonzero value, except that the returned
    pointer shall not be used to access an object.

    Any existing implementations that always return a null pointer
    for malloc(0) would have to be updated. That shouldn't be a
    great burden.

    Note that malloc(0) or realloc(ptr, 0) can still fail and return
    a null pointer if no space can be allocated, so all allocations
    should still be checked. But with this proposed change, code
    could rely on realloc(ptr, 0) returning a non-null pointer *unless*
    available memory is critically low -- pretty much the same as in C11,
    except that a null pointer would be an indication that something
    is seriously wrong.

    (I remember seeing a discussion about making the behavior of
    realloc(ptr, 0) undefined. I'm making inquiries, and I'll follow
    up if I learn anything relevant.)

    I got a response from JeanHeyd Meneide.

    If realloc(ptr, 0) returns a null pointer there's no way to tell whether allocation failed (and ptr has not been freed), or the implementation
    returns a null pointer for zero-sized allocations (and ptr has been
    freed). Some implementations set errno, but C doesn't require it.

    It's trivial to fix that problem: simply require implementations
    to define a preprocessor symbol about how the implementation
    works. Problem solved.

    (There are other instances of implementation-defined behavior
    that would benefit from analogous changes along these lines.)

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Richard Kettlewell@21:1/5 to Keith Thompson on Mon Jan 22 08:12:47 2024
    Keith Thompson <[email protected]> writes:
    Tim Rentsch <[email protected]> writes:
    It's trivial to fix that problem: simply require implementations
    to define a preprocessor symbol about how the implementation
    works. Problem solved.

    (There are other instances of implementation-defined behavior
    that would benefit from analogous changes along these lines.)

    I tend to agree that such a preprocessor symbol would be an improvement.

    I still think, as I wrote above, that removing the permission to
    return a null pointer on a successful zero-sized allocation would be a greater improvement.

    Fully agreed. That permission has been grit in the gears for a very long
    time, for much of which I had the misfortune of having to deal with it
    in real life thanks to IBM’s bad decisions.

    Fixing it would have been, what, a 2-line change to the impacted implementations, but apparently it’s better for all the users to deal
    with the consequences instead.

    A preprocessor symbol makes it easier for programmers to work around the potential difference between implementations. The change I advocate
    would make it completely unnecessary.

    Except, of course, that most code would still have to allow for pre-C26 behavior, even if the change were adopted in C26. That's unavoidable in
    the absence of time machines. On the gripping hand, since it seems that
    most existing implementations (well, all of the few I've tried) return a non-null pointer for malloc(0), it might be reasonable to ignore the few pre-C26 implementations that return a null pointer.

    “Reasonable” isn’t really relevant (at least in my working environment): either my code has to run on such implementations or it doesn’t.

    --
    https://www.greenend.org.uk/rjk/

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From David Brown@21:1/5 to Keith Thompson on Mon Jan 22 09:24:26 2024
    On 21/01/2024 22:31, Keith Thompson wrote:
    David Brown <[email protected]> writes:
    [...]
    Personally, I think it would be often more efficient in modern C if
    the allocation system didn't track sizes at all, and "free" passed the
    original size as a parameter. But that ship sailed long ago for
    standard C. (C++ supports a sized deallocation system, and of course
    there's nothing to stop you making your own allocator system for C.)

    I suspect that calling malloc() with one size and free() with a
    different one would have been a rich source of subtle bugs.


    Sure. But that's part of the fun of C :-)

    Many calls to malloc are of the form :

    p = malloc(sizeof(*p));

    so freeing them with :

    free_sized(p, sizeof(*p));

    should not be a significant risk.

    There are circumstances where you'd have to track the size as well, and
    there is then plenty of scope for mistakes.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From David Brown@21:1/5 to Kaz Kylheku on Mon Jan 22 09:20:27 2024
    On 21/01/2024 22:20, Kaz Kylheku wrote:
    On 2024-01-21, David Brown <[email protected]> wrote:
    Personally, I think it would be often more efficient in modern C if the
    allocation system didn't track sizes at all, and "free" passed the
    original size as a parameter. But that ship sailed long ago for
    standard C.

    That ship newly sailed in 2023.

    The N3096 draft describes a function free_sized that takes a size. If
    the size is wrong, the behavior is undefined.


    I hadn't noticed that addition in C23 - I'll look it up.

    So now C has two ways to free an object: an efficient one where
    the program helps by giving the size, and the old free, which
    may have to do more work.

    It would be a lot more efficient if the sized free was matched with an appropriate malloc - if malloc() still has to track the size somewhere
    in case the user calls free() instead of free_sized(), the gains are
    much less.


    I think going forward, it may start to become wise to detect the implementation's support for free_sized (e.g. in a configure script)
    and use that as much as possible, using free only when it's
    inconvenient: for instance a function resembling POSIX strdup
    might not be able to to safely assume that it can do
    free_sized(str, strlen(str)+1), because the underlying buffer
    might be larger.


    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Richard Kettlewell@21:1/5 to Tim Rentsch on Mon Jan 22 10:21:00 2024
    Tim Rentsch <[email protected]> writes:
    That's a half-assed argument. There are other ways a pointer might
    have a null value than just being the result of a call to malloc().
    If code might call memset() et al with a zero size and a null
    pointer, it's better to address all possible cases at once rather
    than just some of them:

    static inline void *
    safer_memset( void *s, int c, size_t n ){
    return n ? memset( s, c, n ) : s;
    }

    static inline void *
    safer_memcpy( void *d, const void *s, size_t n ){
    return n ? memcpy( d, s, n ) : d;
    }

    /* ... etc ... */

    Of course that’s what the cautious programmer must do practice. But in
    terms of the total cost (over all users, implementers, etc) fixing the definitions of memcpy/memset/etc (as well as malloc) would have been the
    better answer.

    Standard C is trying to have its own cake and eat it here: 0-sized
    allocations can be represented by null pointers when it’s malloc, but
    not when it’s memcpy.

    --
    https://www.greenend.org.uk/rjk/

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Richard Kettlewell@21:1/5 to bart on Mon Jan 22 10:22:23 2024
    bart <[email protected]> writes:
    malloc has sort of created a rod for its own back by needing to store
    the size of the allocation. That will take up some space even when
    malloc(0) is called, if NULL is not being returned.

    The alternative is making a rod for the back of every single caller
    (i.e. all consumers must track allocation sizes). I think the design of
    malloc got this one right.

    --
    https://www.greenend.org.uk/rjk/

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From bart@21:1/5 to Chris M. Thomasson on Mon Jan 22 11:55:38 2024
    On 22/01/2024 01:04, Chris M. Thomasson wrote:
    On 1/21/2024 12:34 PM, bart wrote:
    On 21/01/2024 20:22, Chris M. Thomasson wrote:
    On 1/21/2024 3:55 AM, bart wrote:

    malloc has sort of created a rod for its own back by needing to
    store the size of the allocation. That will take up some space even
    when malloc(0) is called, if NULL is not being returned.
    [...]

    Why would malloc need to store the size of its allocations?


    If you do this:

         p = malloc(N);
         ...
         free(p);

    'free' will need to know what N was used to allocate p in order to
    deallocate right size of memory.


    Why?

    Try writing an allocator with zero memory overheads that doesn't spend
    all its time on trying to deduce the extend of the block being allocated.

    Apparently, 'malloc' on my machines doesn't know about such techniques,
    because if I run this program:

    enum {n=1024};
    char* lastp = malloc(n);
    char* p;

    for(int i=0; i<10; ++i) {
    p=malloc(n);
    printf("%p %d\n", p, p-lastp);
    lastp=p;
    }

    Then on Windows I might get:

    0000026d06d15b30 18288
    0000026d06d15f40 1040
    0000026d06d16350 1040
    0000026d06d16760 1040
    0000026d06d16b70 1040
    0000026d06d16f80 1040
    0000026d06d17390 1040
    0000026d06d177a0 1040
    0000026d06d17bb0 1040
    0000026d06d17fd0 1056

    Some odd results, but generally it uses 16 bytes more than 1024. If I
    run it under WSL, the same thing. Also on rextester.com.

    You're saying it should be only 1024 bytes that are occupied? OK, tell
    the authors of these various mallocs that.

    BTW if I run this program (in my language which uses an allocator that
    requires a size to free):

    const n=1024
    ref byte p:=pcm_alloc(n), lastp

    to 10 do
    p:=pcm_alloc(n)
    println p, p-lastp
    lastp:=p
    od

    I get these results:

    02E2D440 1024
    02E2D840 1024
    02E2DC40 1024
    02E2E040 1024
    02E2E440 1024
    02E2E840 1024
    02E2EC40 1024
    02E2F040 1024
    02E2F440 1024
    02E2F840 1024

    Unless I use a bigger N, since then it switches to using malloc, and I
    get similar results to above. But the overheads then are less significant.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From bart@21:1/5 to bart on Mon Jan 22 12:00:51 2024
    On 22/01/2024 11:55, bart wrote:


    BTW if I run this program (in my language which uses an allocator that requires a size to free):

        const n=1024
        ref byte p:=pcm_alloc(n), lastp

    (p and lastp are declared in the wrong order. The results shown are from
    the fixed version.)

    I get these results:

    02E2D440 1024
    02E2D840 1024
    ...

    (If I compile to an .obj file, which enables high-memory code, and link
    using gcc, then it will show the bigger addresses like the C, but still
    with 1024-byte allocations.)

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From bart@21:1/5 to Richard Kettlewell on Mon Jan 22 11:27:06 2024
    On 22/01/2024 10:22, Richard Kettlewell wrote:
    bart <[email protected]> writes:
    malloc has sort of created a rod for its own back by needing to store
    the size of the allocation. That will take up some space even when
    malloc(0) is called, if NULL is not being returned.

    The alternative is making a rod for the back of every single caller
    (i.e. all consumers must track allocation sizes).

    Not at all. Let's first emulate a pair of functions where the caller is expected to remember the size:

    void* malloc_s (size_t n) {return malloc(n);}
    void free_s (void* p, size_t n) {free(p);}

    Then a typical alloc/dealloc sequence might look like this:

    typedef struct {int d,m,y;}Date;
    Date* p;

    p=malloc_s(sizeof(*p));
    ...
    free_s(p, sizeof(*p));

    Is that particularly onerous? If you have a fixed-size object like a
    struct, then you will always know its size.

    For variable-sized objects, then yes you need to keep a record of the
    size, but the chances are you have to do that anyway. For example to be
    able to iterate over that dynamic array.

    But if you really wanted (for example when allocating variable length, zero-terminated strings), you can write a couple of wrappers around
    malloc_s and free_s to emulate what malloc and free provide in terms of
    not needing to remember the allocation size:

    typedef long long int i64;

    void* malloc2(i64 n) {
    void* p;

    p=malloc_s(n+sizeof(i64));
    if (p==NULL) return NULL;
    *((i64*)p) = n;
    return (char*)p+sizeof(i64);
    }

    void free2(void* p) {
    i64* q = (i64*)((char*)p-sizeof(i64));
    i64 size = *q;

    free_s(q, *q);
    }


    Untested code, it's to demonstrate what's involved: you ask for an
    allocation 8 bytes bigger, use the 8 bytes at the beginning to store the
    size, and return a pointer to just after those 8 bytes. (I'm assuming
    8-byte alignment will suffice.)

    Now you can do this:

    p=malloc2(sizeof(*p));
    ...
    free2(p);


    I think the design of malloc got this one right.

    I think it got it wrong. Now everyone is lumbered with an allocation
    scheme that will ALWAYS have to store the size of that struct, perhaps
    taking as much space as the struct itself.

    Imagine allocating 100M of those structs, and also storing 100M copies
    of sizeof(Date) or whatever metadata is needed.

    Getting around that, by writing your own small-object allocator on top
    of malloc, is a lot harder that adding your own size-tracking on top of
    a malloc that does not store any extra data. As I've shown.

    (This is also a scheme where, if a user needs to get the size of an
    allocation block, it can do so:

    i64 size_s(void* p) {
    i64* q = (i64*)((char*)p-sizeof(i64));
    return *q;
    }

    But this will be the requested size not the capacity of the allocated
    block. That would depend on how malloc_s/free_s are implemented.)

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Scott Lurndal@21:1/5 to Malcolm McLean on Mon Jan 22 16:34:55 2024
    Malcolm McLean <[email protected]> writes:
    On 21/01/2024 12:04, Tim Rentsch wrote:
    Kaz Kylheku <[email protected]> writes:

    [...]

    Not requiring the non-null return from malloc(0) to be distinct
    from previous malloc(0) return values (whether they were freed or not),
    could help to "sell" the idea of taking away the null return value.

    Some implementors might grumble that null return allowed malloc(0) to be >>> efficient by not allocating anything. If they were allowed to return
    (void *) -1 or something, that would placate that concern. [...]

    You have the tail wagging the dog here. If the results of
    different malloc(0) calls don't need to be distinguishable,
    they might just as well all be null.
    No, because it's natural to write

    employees = malloc(Nemployees * sizeof(EMPLOYEE));
    if (!employees)
    goto out_of_memory;

    You don't want to have to special case Nemployees == 0.

    I see no problem in special casing it.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Richard Kettlewell@21:1/5 to Keith Thompson on Mon Jan 22 18:17:27 2024
    Keith Thompson <[email protected]> writes:
    Richard Kettlewell <[email protected]d> writes:
    Fully agreed. That permission has been grit in the gears for a very long
    time, for much of which I had the misfortune of having to deal with it
    in real life thanks to IBM’s bad decisions.

    Can you expand on "IBM's bad decisions"?

    AIX is one of the platforms that returns NULL for malloc(0) even when
    there’s some memory available (in fact the only one I know).

    --
    https://www.greenend.org.uk/rjk/

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Kaz Kylheku@21:1/5 to Richard Kettlewell on Mon Jan 22 18:54:16 2024
    On 2024-01-22, Richard Kettlewell <[email protected]d> wrote:
    Standard C is trying to have its own cake and eat it here: 0-sized allocations can be represented by null pointers when it’s malloc, but
    not when it’s memcpy.

    Having your cake and eating that same cake it is undefined behavior,

    even with an intervening sequence point.
    --
    TXR Programming Language: http://nongnu.org/txr
    Cygnal: Cygwin Native Application Library: http://kylheku.com/cygnal
    Mastodon: @[email protected]
    NOTE: If you use Google Groups, I don't see you, unless you're whitelisted.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Scott Lurndal@21:1/5 to Richard Kettlewell on Mon Jan 22 18:43:06 2024
    Richard Kettlewell <[email protected]d> writes:
    Keith Thompson <[email protected]> writes:
    Richard Kettlewell <[email protected]d> writes:
    Fully agreed. That permission has been grit in the gears for a very long >>> time, for much of which I had the misfortune of having to deal with it
    in real life thanks to IBM’s bad decisions.

    Can you expand on "IBM's bad decisions"?

    AIX is one of the platforms that returns NULL for malloc(0) even when >there’s some memory available (in fact the only one I know).

    AIX had this odd 'overcommit' feature, which allowed the system to
    overcommit memory resources. It would allocate virtual address space
    (via malloc) even if there wasn't enough memory + backing store to
    support it, and would take a SIGSEGV when referenced if, at the time
    of reference, there was still insufficient memory and/or backing store
    to support allocating a physical page to that portion of the VA space.

    Linux can be configured to operate in the same way.

    I don't believe AIX ever returned NULL from malloc() while still
    allocating VA space for the allocation.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Richard Kettlewell@21:1/5 to Scott Lurndal on Mon Jan 22 19:11:33 2024
    [email protected] (Scott Lurndal) writes:
    Richard Kettlewell <[email protected]d> writes:

    AIX is one of the platforms that returns NULL for malloc(0) even when >>there’s some memory available (in fact the only one I know).

    AIX had this odd 'overcommit' feature, which allowed the system to
    overcommit memory resources. It would allocate virtual address space
    (via malloc) even if there wasn't enough memory + backing store to
    support it, and would take a SIGSEGV when referenced if, at the time
    of reference, there was still insufficient memory and/or backing store
    to support allocating a physical page to that portion of the VA space.

    Linux can be configured to operate in the same way.

    Indeed, Linux defaults to overcommit for most allocations.

    I don't believe AIX ever returned NULL from malloc() while still
    allocating VA space for the allocation.

    I don’t really see the relevance. malloc(0) is NULL on AIX[1] because that’s what they’ve chosen to do in the C library’s malloc implementation, nothing to do with the kernel’s overcommit policies.

    [1] I see from the man page that they’ve added a macro to enable more
    sensible behaviour. Too late for us, we stopped supporting it years
    ago.

    --
    https://www.greenend.org.uk/rjk/

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Kaz Kylheku@21:1/5 to bart on Mon Jan 22 19:36:03 2024
    On 2024-01-22, bart <[email protected]> wrote:
    On 22/01/2024 10:22, Richard Kettlewell wrote:
    I think the design of malloc got this one right.

    I think it got it wrong. Now everyone is lumbered with an allocation
    scheme that will ALWAYS have to store the size of that struct, perhaps
    taking as much space as the struct itself.

    Implementations of malloc have special strategies for small objects,
    to allocate them compactly, with minimal overhead and fragmentation.
    It is not necessary to store the size in every such object.

    Imagine allocating 100M of those structs, and also storing 100M copies
    of sizeof(Date) or whatever metadata is needed.

    For the size to take up as much object as the struct itself, it
    has to be a struct that whose size is sizeof(size_t), which is tiny:
    often 8 bytes nowadays, or possibly 4.

    An object that is as small as size_t should just be passed around
    by value. It likely fits into a machine register, and so dynamically
    allocating it is inefficient.

    Every such allocation results in a pointer. The program has to retain at
    least one copy of that pointer. The pointer is probably also 8 bits
    wide, so even if those objects are allocated compactly without storing a
    size field, it takes double the space just to retain pointers to them.

    Getting around that, by writing your own small-object allocator on top
    of malloc, is a lot harder that adding your own size-tracking on top of
    a malloc that does not store any extra data. As I've shown.

    Writing small-object allocators on malloc is mostly a fool's errand,
    in the absence of special justification.

    --
    TXR Programming Language: http://nongnu.org/txr
    Cygnal: Cygwin Native Application Library: http://kylheku.com/cygnal
    Mastodon: @[email protected]
    NOTE: If you use Google Groups, I don't see you, unless you're whitelisted.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Kalevi Kolttonen@21:1/5 to Kaz Kylheku on Mon Jan 22 20:40:59 2024
    Kaz Kylheku <[email protected]> wrote:
    [...] Every such allocation results in a pointer. The
    program has to retain at least one copy of that pointer.
    The pointer is probably also 8 bits wide, so [...]

    Surely you meant to write 8 *bytes* wide.

    br,
    KK

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Lawrence D'Oliveiro@21:1/5 to David Brown on Mon Jan 22 22:09:50 2024
    On Mon, 22 Jan 2024 09:24:26 +0100, David Brown wrote:

    On 21/01/2024 22:31, Keith Thompson wrote:

    I suspect that calling malloc() with one size and free() with a
    different one would have been a rich source of subtle bugs.


    Sure. But that's part of the fun of C :-)

    Not so much fun for the poor users who paid you the big bucks and expected
    to get working code in return.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Lawrence D'Oliveiro@21:1/5 to bart on Mon Jan 22 22:08:49 2024
    On Mon, 22 Jan 2024 11:55:38 +0000, bart wrote:

    Then on Windows I might get:

    ...

    Some odd results ...

    ... but you repeat yourself.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From bart@21:1/5 to Kaz Kylheku on Mon Jan 22 22:14:51 2024
    On 22/01/2024 19:36, Kaz Kylheku wrote:
    On 2024-01-22, bart <[email protected]> wrote:
    On 22/01/2024 10:22, Richard Kettlewell wrote:
    I think the design of malloc got this one right.

    I think it got it wrong. Now everyone is lumbered with an allocation
    scheme that will ALWAYS have to store the size of that struct, perhaps
    taking as much space as the struct itself.

    Implementations of malloc have special strategies for small objects,
    to allocate them compactly, with minimal overhead and fragmentation.
    It is not necessary to store the size in every such object.

    Imagine allocating 100M of those structs, and also storing 100M copies
    of sizeof(Date) or whatever metadata is needed.

    For the size to take up as much object as the struct itself, it
    has to be a struct that whose size is sizeof(size_t), which is tiny:
    often 8 bytes nowadays, or possibly 4.

    An object that is as small as size_t should just be passed around
    by value. It likely fits into a machine register, and so dynamically allocating it is inefficient.

    Every such allocation results in a pointer. The program has to retain at least one copy of that pointer. The pointer is probably also 8 bits
    wide, so even if those objects are allocated compactly without storing a
    size field, it takes double the space just to retain pointers to them.

    You keep talking about all the clever, space-efficient ways that malloc
    /could/ manage memory. Unfortunately the people who write the mallocs I
    use haven't got the memo.

    If I go back to the program I posted earlier and allocate 8 bytes at a
    time, then successive values from malloc are 32 bytes apart (when
    allocations are clearly consecutive).

    If my struct actually needs 32 bytes, then it will allocate 48 bytes: a
    50% overhead.

    Do you have an actual transcript from your machine where it does
    anything different?

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

    enum {n=32};

    int main(void) {
    char* lastp=malloc(n);
    char* p;
    for (int i=0; i<10; ++i) {
    p=malloc(n);
    printf("%p %zu\n", p, p-lastp);
    lastp=p;
    }
    }
    --------------------------


    Getting around that, by writing your own small-object allocator on top
    of malloc, is a lot harder that adding your own size-tracking on top of
    a malloc that does not store any extra data. As I've shown.

    Writing small-object allocators on malloc is mostly a fool's errand,
    in the absence of special justification.


    On the Binary Tree benchmark, using a small-object allocator on top of
    malloc makes it three times as fast as just using malloc.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Scott Lurndal@21:1/5 to Kaz Kylheku on Mon Jan 22 22:45:25 2024
    Kaz Kylheku <[email protected]> writes:
    On 2024-01-22, bart <[email protected]> wrote:
    On 22/01/2024 10:22, Richard Kettlewell wrote:
    I think the design of malloc got this one right.

    I think it got it wrong. Now everyone is lumbered with an allocation
    scheme that will ALWAYS have to store the size of that struct, perhaps
    taking as much space as the struct itself.

    Implementations of malloc have special strategies for small objects,
    to allocate them compactly, with minimal overhead and fragmentation.
    It is not necessary to store the size in every such object.

    Imagine allocating 100M of those structs, and also storing 100M copies
    of sizeof(Date) or whatever metadata is needed.

    For the size to take up as much object as the struct itself, it
    has to be a struct that whose size is sizeof(size_t), which is tiny:
    often 8 bytes nowadays, or possibly 4.

    An object that is as small as size_t should just be passed around
    by value. It likely fits into a machine register, and so dynamically >allocating it is inefficient.

    Given the alignment requirements associated with various
    application binary interfaces, malloc must generally
    return an address aligned to an ABI specified boundary
    (often 16-byte). Such an allocator will often round the allocation
    size up to the next 16-byte boundary.

    "The malloc() and calloc() functions return a pointer to the
    allocated memory that is suitably aligned for any kind of variable"

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Richard Kettlewell@21:1/5 to bart on Mon Jan 22 23:05:56 2024
    bart <[email protected]> writes:
    Imagine allocating 100M of those structs, and also storing 100M copies
    of sizeof(Date) or whatever metadata is needed.

    I’m comfortable with that. It’s a general-purpose interface, it doesn’t have to be the most efficient conceivable option in any particular
    specialized use case.

    --
    https://www.greenend.org.uk/rjk/

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Blue-Maned_Hawk@21:1/5 to Kaz Kylheku on Mon Jan 22 23:12:09 2024
    Kaz Kylheku wrote:

    On 2024-01-19, Blue-Maned_Hawk <[email protected]d> wrote:
    Kaz Kylheku wrote:
    Of course, that's not what "behavior can be readily inferred" means,
    no matter where I got the example.

    Inferences may be makeäble, but they have no power.

    Yeah, tell that to your compiler when it surprisingly deletes code based
    on inference.


    Apologies, let me clarify: In a technical standard, inferences from nonnormative text cannot be used to make normative statements.


    --
    Blue-Maned_Hawk│shortens to Hawk│/ blu.mɛin.dʰak/ │he/him/his/himself/Mr.
    blue-maned_hawk.srht.site

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Tim Rentsch@21:1/5 to Richard Kettlewell on Mon Jan 22 20:01:42 2024
    Richard Kettlewell <[email protected]d> writes:

    Tim Rentsch <[email protected]> writes:

    That's a half-assed argument. There are other ways a pointer might
    have a null value than just being the result of a call to malloc().
    If code might call memset() et al with a zero size and a null
    pointer, it's better to address all possible cases at once rather
    than just some of them:

    static inline void *
    safer_memset( void *s, int c, size_t n ){
    return n ? memset( s, c, n ) : s;
    }

    static inline void *
    safer_memcpy( void *d, const void *s, size_t n ){
    return n ? memcpy( d, s, n ) : d;
    }

    /* ... etc ... */

    Of course that's what the cautious programmer must do practice. But in
    terms of the total cost (over all users, implementers, etc) fixing the definitions of memcpy/memset/etc (as well as malloc) would have been the better answer.

    Better in some ways, worse in others. Better for me is not
    always the same as better for thee.

    Standard C is trying to have its own cake and eat it here: 0-sized allocations can be represented by null pointers when it's malloc, but
    not when it's memcpy.

    Actually the two decisions have essentially nothing to do with
    each other. You might want to read what the C Rationale
    document has to say about the decisions behind various
    memory allocation policies.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Tim Rentsch@21:1/5 to Richard Kettlewell on Mon Jan 22 20:08:34 2024
    Richard Kettlewell <[email protected]d> writes:

    Keith Thompson <[email protected]> writes:

    Tim Rentsch <[email protected]> writes:

    It's trivial to fix that problem: simply require implementations
    to define a preprocessor symbol about how the implementation
    works. Problem solved.

    (There are other instances of implementation-defined behavior
    that would benefit from analogous changes along these lines.)

    I tend to agree that such a preprocessor symbol would be an
    improvement.

    I still think, as I wrote above, that removing the permission to
    return a null pointer on a successful zero-sized allocation would
    be a greater improvement.

    Fully agreed. That permission has been grit in the gears for a
    very long time, for much of which I had the misfortune of having
    to deal with it in real life thanks to IBM's bad decisions.

    Fixing it would have been, what, a 2-line change to the impacted implementations, [...]

    Again you are assuming that one choice is good for everyone.
    The people who wrote the C standard considered the question
    at length and reached a different conclusion.

    Incidentally, the change needed is a lot more than two lines.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Tim Rentsch@21:1/5 to Malcolm McLean on Mon Jan 22 20:20:59 2024
    Malcolm McLean <[email protected]> writes:

    On 21/01/2024 12:04, Tim Rentsch wrote:

    Kaz Kylheku <[email protected]> writes:

    [...]

    Not requiring the non-null return from malloc(0) to be distinct
    from previous malloc(0) return values (whether they were freed or not),
    could help to "sell" the idea of taking away the null return value.

    Some implementors might grumble that null return allowed malloc(0) to be >>> efficient by not allocating anything. If they were allowed to return
    (void *) -1 or something, that would placate that concern. [...]

    You have the tail wagging the dog here. If the results of
    different malloc(0) calls don't need to be distinguishable,
    they might just as well all be null.

    No, because it's natural to write

    employees = malloc(Nemployees * sizeof(EMPLOYEE));
    if (!employees)
    goto out_of_memory;

    You don't want to have to special case Nemployees == 0.

    (A) You misunderstood the point I was making (as Keith's
    followups more or less explained).

    (B) Handling both implementation choices is trivial:
    employees = malloc(Nemployees * sizeof(EMPLOYEE));
    if (!employees && Nemployees)
    goto out_of_memory;

    (C) Alternatively, it is quite straightforward to write
    a wrapper to malloc() and free() that returns a non-null
    pointer for a size of zero (and that is a lower cost
    path in terms of cycles used and memory resources needed
    than the behavior implementations are required to provide
    if they return a non-null pointer).

    (D) The code you suggest is "natural" for people who are
    lazy programmers who don't care about their code being
    correct. There are easy ways around the deficiencies
    of your example and any competent developer will use them.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From James Kuyper@21:1/5 to Kaz Kylheku on Tue Jan 23 01:16:52 2024
    On 1/22/24 18:12, Blue-Maned_Hawk wrote:
    Kaz Kylheku wrote:

    On 2024-01-19, Blue-Maned_Hawk <[email protected]d> wrote:
    Kaz Kylheku wrote:
    Of course, that's not what "behavior can be readily inferred" means,
    no matter where I got the example.

    Inferences may be makeäble, but they have no power.

    Yeah, tell that to your compiler when it surprisingly deletes code based
    on inference.


    Apologies, let me clarify: In a technical standard, inferences from nonnormative text cannot be used to make normative statements.

    True, but the relevant inferences were from normative text. The context
    of the above comments has been snipped, so I'll repeat it here:

    On 1/17/24 19:10, Kaz Kylheku wrote:
    On 2024-01-17, Blue-Maned_Hawk <[email protected]d> wrote:
    It looks to me like it was always undefined behavior, but it just wasn't
    explicitly stated as such in C99.

    That is incorrect. A behavior for the following can be readily inferred
    from C99:

    void *p = malloc(42);

    Per C99:
    "The malloc function returns either a null pointer or a pointer to the allocated space." (7.22.3.4p2)

    Thus, there are two possibilities:
    A: p == NULL
    B: p contains the address of a block of memory at least 42 bytes long,
    suitably aligned to store an object of any type.

    The standard does not mandate documentation of which of the two applies,
    so this has unspecified behavior.

    void *q = realloc(p, 0);

    Per C99:
    "The realloc function deallocates the old object pointed to by ptr and
    returns a pointer to a new object that has the size specified by size.
    The contents of the new object shall be the same as that of the old
    object prior to deallocation, up to the lesser of the new and old sizes.
    Any bytes in the new object beyond the size of the old object have indeterminate values.

    If ptr is a null pointer, the realloc function behaves like the malloc
    function for the specified size. Otherwise, if ptr does not match a
    pointer earlier returned by a memory management function, or if the
    space has been deallocated by a call to the free or realloc function,
    the behavior is undefined. If memory for the new object cannot be
    allocated, the old object is not deallocated and its value is
    unchanged." (7.22.3.5p2)

    Thus, if situation A applies, then "p = realloc(p, 0)" is the equivalent
    of p = malloc(0).

    If situation B applies, then the memory pointed at by p is deallocated.
    Since the value of p was in fact earlier returned by a memory management function, the only statement that might otherwise give that statement
    undefined behavior doesn't apply.

    Either way, C99 says the following:
    "If the size of the space requested is zero, the behavior is implementation-defined: either a null pointer is returned, or the
    behavior is as if the size were some nonzero value, except that the
    returned pointer shall not be used to access an object." (7.22.3p1)

    The behavior when the size is 0 is therefore implementation-defined.

    Thus, situation A splits into two different possibilities:
    A.a: q == NULL directly
    A.b: q contains the same result as q = malloc(0), which splits into
    A.b.a: q == NULL due to the equivalent of a call to malloc().
    A.b.b: q points at a block of memory at least 1 byte of long, suitably
    aligned to store an object of any type, but that pointer cannot be used
    to access that memory (which renders the alignment effectively irrelevant).

    A.a and A.b.a are indistinguishable, so there's really only two possible outcomes for situation A.

    Situation B is split the exact same way, except that B.b.b splits one additional way: q might or might not end up containing a pointer to that
    same memory that was allocated by the first call to malloc().

    Thus, the behavior is a mixture of unspecified and
    implementation-defined, giving several different possibilities, but none
    of them has undefined behavior.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From David Brown@21:1/5 to Lawrence D'Oliveiro on Tue Jan 23 09:28:08 2024
    On 22/01/2024 23:09, Lawrence D'Oliveiro wrote:
    On Mon, 22 Jan 2024 09:24:26 +0100, David Brown wrote:

    On 21/01/2024 22:31, Keith Thompson wrote:

    I suspect that calling malloc() with one size and free() with a
    different one would have been a rich source of subtle bugs.


    Sure. But that's part of the fun of C :-)

    Not so much fun for the poor users who paid you the big bucks and expected
    to get working code in return.

    Hence the smiley :-)

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Richard Kettlewell@21:1/5 to Tim Rentsch on Tue Jan 23 09:35:39 2024
    Tim Rentsch <[email protected]> writes:
    Richard Kettlewell <[email protected]d> writes:
    Of course that's what the cautious programmer must do practice. But in
    terms of the total cost (over all users, implementers, etc) fixing the
    definitions of memcpy/memset/etc (as well as malloc) would have been the
    better answer.

    Better in some ways, worse in others. Better for me is not
    always the same as better for thee.

    The way I think it’s better is total cost over all users, implementers,
    etc. I think the time and effort spent coping with the bugs arising from
    the current loose rules is much greater than the time it’d take to add ‘if(size == 0) { size=1; }’ to a handful of allocators and update some
    unit tests.

    Standard C is trying to have its own cake and eat it here: 0-sized
    allocations can be represented by null pointers when it's malloc, but
    not when it's memcpy.

    Actually the two decisions have essentially nothing to do with
    each other. You might want to read what the C Rationale
    document has to say about the decisions behind various
    memory allocation policies.

    Do you mean
    https://www.open-std.org/jtc1/sc22/wg14/www/C99RationaleV5.10.pdf
    s7.20.3?

    --
    https://www.greenend.org.uk/rjk/

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Tim Rentsch@21:1/5 to Richard Kettlewell on Tue Jan 23 09:08:17 2024
    Richard Kettlewell <[email protected]d> writes:

    Tim Rentsch <[email protected]> writes:

    Richard Kettlewell <[email protected]d> writes:

    Of course that's what the cautious programmer must do practice. But in
    terms of the total cost (over all users, implementers, etc) fixing the
    definitions of memcpy/memset/etc (as well as malloc) would have been the >>> better answer.

    Better in some ways, worse in others. Better for me is not
    always the same as better for thee.

    The way I think it's better is total cost over all users, implementers,
    etc. [...]

    Yes, I got that. Not everyone shares that view. It's not
    even a given that everyone thinks that's the best metric
    to optimize.

    Standard C is trying to have its own cake and eat it here: 0-sized
    allocations can be represented by null pointers when it's malloc, but
    not when it's memcpy.

    Actually the two decisions have essentially nothing to do with
    each other. You might want to read what the C Rationale
    document has to say about the decisions behind various
    memory allocation policies.

    Do you mean
    https://www.open-std.org/jtc1/sc22/wg14/www/C99RationaleV5.10.pdf
    s7.20.3?

    I was looking at C99RationaleV5.10, although I believe the comments
    are from the earlier, original Rationale document. I expect you
    have the right section; I don't have the document open to check.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Tim Rentsch@21:1/5 to Scott Lurndal on Tue Jan 23 11:53:02 2024
    [email protected] (Scott Lurndal) writes:

    Tim Rentsch <[email protected]> writes:

    [email protected] (Scott Lurndal) writes:

    Tim Rentsch <[email protected]> writes:

    [email protected] (Scott Lurndal) writes:

    Kaz Kylheku <[email protected]> writes:

    I'm looking at the C99 and N3096 (April 2023) definitions of realloc >>>>>> side by side.

    N3096 says

    "Otherwise, if ptr does not match a pointer earlier returned by a memory >>>>>> management function, or if the space has been deallocated by a call to >>>>>> the free or realloc function, or if the size is zero, the behavior is >>>>>> undefined."

    Yikes! In C99 there is nothing about the size being zero:

    "Otherwise, if ptr does not match a pointer earlier returned by the >>>>>> calloc, malloc, or realloc function, or if the space has been
    deallocated by a call to the free or realloc function, the behavior is >>>>>> undefined."

    Nothing about "or if the size is zero".

    Yikes; when did this criminal stupidity get perpetrated?

    I'm not sure what stupidity you are referring to, but IIRC, there was >>>>> some recent standardization activity relating to realloc
    when size == 0 because there were differences in the behavior
    between different implementations. Making the behavior
    undefined was the only rational choice.

    Is that last sentence your own assessment, or are you simply
    repeating someone else's assessment?

    My assessment. I've never found realloc useful, regardless of
    the value of the size parameter. YMMV.

    So you aren't really in a position to say whether this decision was
    the only rational choice. Generally I hope people who would make
    such a statement would first make an effort to learn and understand
    other people's thoughts on the matter.

    The fact that I didn't find it useful, doesn't imply in any way
    that I don't understand other's thoughts on the matter. I did
    spend several years on various standards committees, compromise
    was often the best way to make forward progress and 'undefined
    behavior' was a rational compromise in that context.

    There's a big difference between saying something is a rational
    compromise and saying it's the only rational choice. And you
    still haven't provided any evidence that you are in a position to
    offer an informed judgment on either premise, for the particular
    question of how realloc(p,0) should be treated. If all you're
    expressing is a personal opinion, I have no problem with that.
    If you want to claim that you are offering more than just a
    personal opinion, I think it's reasonable to ask for some
    supporting evidence to back that up.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Kaz Kylheku@21:1/5 to Tim Rentsch on Tue Jan 23 21:56:18 2024
    On 2024-01-23, Tim Rentsch <[email protected]> wrote:
    Richard Kettlewell <[email protected]d> writes:

    Tim Rentsch <[email protected]> writes:

    Richard Kettlewell <[email protected]d> writes:

    Of course that's what the cautious programmer must do practice. But in >>>> terms of the total cost (over all users, implementers, etc) fixing the >>>> definitions of memcpy/memset/etc (as well as malloc) would have been the >>>> better answer.

    Better in some ways, worse in others. Better for me is not
    always the same as better for thee.

    The way I think it's better is total cost over all users, implementers,
    etc. [...]

    Yes, I got that. Not everyone shares that view. It's not
    even a given that everyone thinks that's the best metric
    to optimize.

    It is an inherently collectivist view; I would expect communists
    to agree with it unanimously. (But then bungle the execution by
    forming a five year plan in which the work is divided into groups
    driven by meaningless local metrics and irrelevant incentives.)

    --
    TXR Programming Language: http://nongnu.org/txr
    Cygnal: Cygwin Native Application Library: http://kylheku.com/cygnal
    Mastodon: @[email protected]
    NOTE: If you use Google Groups, I don't see you, unless you're whitelisted.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Lawrence D'Oliveiro@21:1/5 to Scott Lurndal on Wed Jan 24 02:46:08 2024
    On Mon, 22 Jan 2024 18:43:06 GMT, Scott Lurndal wrote:

    AIX had this odd 'overcommit' feature, which allowed the system to
    overcommit memory resources. It would allocate virtual address space
    (via malloc) even if there wasn't enough memory + backing store to
    support it, and would take a SIGSEGV when referenced if, at the time of reference, there was still insufficient memory and/or backing store to support allocating a physical page to that portion of the VA space.

    Linux can be configured to operate in the same way.

    Not quite the same. Linux overcommit means “never having to say no”. If
    the system runs short of memory, then this wakes up the Dreaded OOM
    Killer, which starts scoring processes on their hunger for memory, and
    killing the ones that are the biggest memory hogs.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Tim Rentsch@21:1/5 to Keith Thompson on Tue Jan 23 19:28:38 2024
    Keith Thompson <[email protected]> writes:

    Tim Rentsch <[email protected]> writes:

    Keith Thompson <[email protected]> writes:

    Keith Thompson <[email protected]> writes:

    [...]

    I think I would have *liked* to see C23 drop the special permission
    to return a null pointer for a requested size of zero. C11 says (and
    this applies to malloc, realloc, and all other allocation functions):
    [...]

    Note that malloc(0) or realloc(ptr, 0) can still fail and return
    a null pointer if no space can be allocated, so all allocations
    should still be checked. But with this proposed change, code
    could rely on realloc(ptr, 0) returning a non-null pointer *unless*
    available memory is critically low -- pretty much the same as in C11,
    except that a null pointer would be an indication that something
    is seriously wrong.

    (I remember seeing a discussion about making the behavior of
    realloc(ptr, 0) undefined. I'm making inquiries, and I'll follow
    up if I learn anything relevant.)

    I got a response from JeanHeyd Meneide.

    If realloc(ptr, 0) returns a null pointer there's no way to tell
    whether allocation failed (and ptr has not been freed), or the
    implementation returns a null pointer for zero-sized allocations
    (and ptr has been freed). Some implementations set errno, but C
    doesn't require it.

    (Let me mention in passing that there is a way to tell that
    should work in practice on any non-malicious implementation.)

    It's trivial to fix that problem: simply require implementations
    to define a preprocessor symbol about how the implementation
    works. Problem solved.

    (There are other instances of implementation-defined behavior
    that would benefit from analogous changes along these lines.)

    I tend to agree that such a preprocessor symbol would be an
    improvement.

    I still think, as I wrote above, that removing the permission to
    return a null pointer on a successful zero-sized allocation would
    be a greater improvement.

    A preprocessor symbol makes it easier for programmers to work
    around the potential difference between implementations. The
    change I advocate would make it completely unnecessary.

    Except, of course, that most code would still have to allow for
    pre-C26 behavior, even if the change were adopted in C26. That's
    unavoidable in the absence of time machines. On the gripping
    hand, since it seems that most existing implementations (well, all
    of the few I've tried) return a non-null pointer for malloc(0), it
    might be reasonable to ignore the few pre-C26 implementations that
    return a null pointer.

    I have an observation worth mentioning. Using glibc (tested in
    Ubuntu), these assignments

    void *x = malloc( 0 );
    void *y = malloc( 1 );

    both return a non-null pointer. Following that, however, these
    calls

    void *xx = realloc( x, 0 );
    void *yy = realloc( y, 0 );

    both return a null pointer, and subsequent code shows that both x
    and y have indeed been deallocated. (Both gcc and clang give the
    same result, which isn't surprising since both are using glibc.)

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Lawrence D'Oliveiro@21:1/5 to Scott Lurndal on Wed Jan 24 04:42:19 2024
    On Mon, 22 Jan 2024 16:34:55 GMT, Scott Lurndal wrote:

    I see no problem in special casing [zero].

    Special-casing zero is something I thought we had left behind in the bad
    old days of FORTRAN IV.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Tim Rentsch@21:1/5 to James Kuyper on Tue Jan 23 22:15:04 2024
    James Kuyper <[email protected]> writes:

    On 1/22/24 18:12, Blue-Maned_Hawk wrote:

    Kaz Kylheku wrote:

    On 2024-01-19, Blue-Maned_Hawk <[email protected]d> wrote:

    Kaz Kylheku wrote:

    Of course, that's not what "behavior can be readily inferred"
    means, no matter where I got the example.

    Inferences may be makeable, but they have no power.

    Yeah, tell that to your compiler when it surprisingly deletes
    code based on inference.

    Apologies, let me clarify: In a technical standard, inferences
    from nonnormative text cannot be used to make normative statements.

    True, but the relevant inferences were from normative text. The
    context of the above comments has been snipped, so I'll repeat it
    here:

    On 1/17/24 19:10, Kaz Kylheku wrote:

    On 2024-01-17, Blue-Maned_Hawk <[email protected]d> wrote:

    It looks to me like it was always undefined behavior, but it
    just wasn't explicitly stated as such in C99.

    That is incorrect. A behavior for the following can be readily
    inferred from C99:

    void *p = malloc(42);

    Per C99:
    "The malloc function returns either a null pointer or a pointer to
    the allocated space." (7.22.3.4p2)

    Thus, there are two possibilities:
    A: p == NULL
    B: p contains the address of a block of memory at least 42 bytes
    long, suitably aligned to store an object of any type.

    The standard does not mandate documentation of which of the two
    applies, so this has unspecified behavior.

    The return value from malloc() is not unspecified behavior. In
    n1256, see section 7.20.3 paragraph 1, and also the definition of
    unspecified behavior in 3.4.4.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Lawrence D'Oliveiro@21:1/5 to Tim Rentsch on Wed Jan 24 06:33:19 2024
    On Tue, 23 Jan 2024 19:28:38 -0800, Tim Rentsch wrote:

    ... however, these calls

    void *xx = realloc( x, 0 );
    void *yy = realloc( y, 0 );

    both return a null pointer, and subsequent code shows that both x and y
    have indeed been deallocated. (Both gcc and clang give the same result, which isn't surprising since both are using glibc.)

    You could have just read the docs <https://manpages.debian.org/3/realloc.en.html>.

    But remember, there is no requirement for gcc, at least, to use glibc. You
    can build programs with it that link to alternatives C runtimes.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Kaz Kylheku@21:1/5 to Lawrence D'Oliveiro on Wed Jan 24 07:46:01 2024
    On 2024-01-24, Lawrence D'Oliveiro <[email protected]d> wrote:
    You could have just read the docs
    <https://manpages.debian.org/3/realloc.en.html>.

    You're mistaken; those are not the docs.

    --
    TXR Programming Language: http://nongnu.org/txr
    Cygnal: Cygwin Native Application Library: http://kylheku.com/cygnal
    Mastodon: @[email protected]

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Kaz Kylheku@21:1/5 to Tim Rentsch on Wed Jan 24 07:44:15 2024
    On 2024-01-24, Tim Rentsch <[email protected]> wrote:
    I have an observation worth mentioning. Using glibc (tested in
    Ubuntu), these assignments

    void *x = malloc( 0 );
    void *y = malloc( 1 );

    both return a non-null pointer. Following that, however, these
    calls

    void *xx = realloc( x, 0 );
    void *yy = realloc( y, 0 );

    both return a null pointer, and subsequent code shows that both x
    and y have indeed been deallocated. (Both gcc and clang give the
    same result, which isn't surprising since both are using glibc.)

    The behavior is documented by Glibc.

    https://www.gnu.org/software/libc/manual/html_node/Changing-Block-Size.html

    "If you pass a null pointer for ptr, realloc behaves just like ‘malloc
    (newsize)’. Otherwise, if newsize is zero realloc frees the block and
    returns NULL."

    (It'a also documented sloppily by that hapless source of misinformation
    known as the Linux man pages project, which says that realloc(ptr, 0) is equivalent to free(ptr). neglecting to make any remarks on the return
    value.

    For a good laugh, check out the inane diatribes in "man 7 regex".)

    --
    TXR Programming Language: http://nongnu.org/txr
    Cygnal: Cygwin Native Application Library: http://kylheku.com/cygnal
    Mastodon: @[email protected]

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From David Brown@21:1/5 to Tim Rentsch on Wed Jan 24 10:41:38 2024
    On 24/01/2024 04:28, Tim Rentsch wrote:
    Keith Thompson <[email protected]> writes:

    Tim Rentsch <[email protected]> writes:

    Keith Thompson <[email protected]> writes:

    I got a response from JeanHeyd Meneide.

    If realloc(ptr, 0) returns a null pointer there's no way to tell
    whether allocation failed (and ptr has not been freed), or the
    implementation returns a null pointer for zero-sized allocations
    (and ptr has been freed). Some implementations set errno, but C
    doesn't require it.

    (Let me mention in passing that there is a way to tell that
    should work in practice on any non-malicious implementation.)


    No, let's not let you mention that in passing. It will lead to another
    endless thread where people ask for an explanation of your method, while
    you pop by for a few days a month to tell us what you mean by
    "non-malicious".

    I cannot be alone in being curious as to what this method is, and when
    it might or might not be applicable.

    Please either explain yourself, or stop making such claims.

    (Note to others - Tim either does not read my posts, or actively refuses
    to reply to them. If anyone else wants to know his method, they will
    need to reply themselves.)

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Scott Lurndal@21:1/5 to Lawrence D'Oliveiro on Wed Jan 24 14:54:22 2024
    Lawrence D'Oliveiro <[email protected]d> writes:
    On Mon, 22 Jan 2024 18:43:06 GMT, Scott Lurndal wrote:

    AIX had this odd 'overcommit' feature, which allowed the system to
    overcommit memory resources. It would allocate virtual address space
    (via malloc) even if there wasn't enough memory + backing store to
    support it, and would take a SIGSEGV when referenced if, at the time of
    reference, there was still insufficient memory and/or backing store to
    support allocating a physical page to that portion of the VA space.

    Linux can be configured to operate in the same way.

    Not quite the same. Linux overcommit means “never having to say no”. If >the system runs short of memory, then this wakes up the Dreaded OOM
    Killer, which starts scoring processes on their hunger for memory, and >killing the ones that are the biggest memory hogs.

    The behavior of overcommit is configurable in linux. That's just one possibility.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From James Kuyper@21:1/5 to David Brown on Wed Jan 24 12:23:53 2024
    On 1/24/24 04:41, David Brown wrote:
    On 24/01/2024 04:28, Tim Rentsch wrote:
    ...
    (Let me mention in passing that there is a way to tell that
    should work in practice on any non-malicious implementation.)
    ...
    Please either explain yourself, or stop making such claims.

    As we all know, Tim will do neither of those things.

    (Note to others - Tim either does not read my posts, or actively
    refuses to reply to them. If anyone else wants to know his method,
    they will need to reply themselves.)

    He still seems to be reading at least some of mine.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Richard Kettlewell@21:1/5 to Tim Rentsch on Wed Jan 24 23:37:17 2024
    Tim Rentsch <[email protected]> writes:
    Richard Kettlewell <[email protected]d> writes:
    Tim Rentsch <[email protected]> writes:
    Richard Kettlewell <[email protected]d> writes:

    Of course that's what the cautious programmer must do practice. But in >>>> terms of the total cost (over all users, implementers, etc) fixing the >>>> definitions of memcpy/memset/etc (as well as malloc) would have been the >>>> better answer.

    Better in some ways, worse in others. Better for me is not
    always the same as better for thee.

    The way I think it's better is total cost over all users, implementers,
    etc. [...]

    Yes, I got that. Not everyone shares that view.

    The cost is an objective statement, not a view.

    In principle I could be wrong that the cost of everyone dealing with the resulting bugs, adding workarounds, etc, is greater than the cost of
    fixing implementations, but frankly it doesn’t seem very plausible.

    It's not even a given that everyone thinks that's the best metric to optimize.

    C’s history since standardization does include a disregard for the costs
    born by its users, yes.

    Standard C is trying to have its own cake and eat it here: 0-sized
    allocations can be represented by null pointers when it's malloc, but
    not when it's memcpy.

    Actually the two decisions have essentially nothing to do with
    each other. You might want to read what the C Rationale
    document has to say about the decisions behind various
    memory allocation policies.

    Do you mean
    https://www.open-std.org/jtc1/sc22/wg14/www/C99RationaleV5.10.pdf
    s7.20.3?

    I was looking at C99RationaleV5.10, although I believe the comments
    are from the earlier, original Rationale document. I expect you
    have the right section; I don't have the document open to check.

    It is thoroughly unenlightening as an explanation.

    * It says the rules about zero-length allocations are intended to
    permit a particular paradigm, of which an example is given.

    * The example includes no attempt at error handling, which is where the
    pain point from the permission for malloc(0)=NULL arises. So it seems
    like the author of that section didn’t consider the impact of the
    rules adopted.

    * Nothing about the example depends either on malloc(0)=NULL or
    malloc(0)!=NULL, making it mysterious why the example is said to
    support the decisions made in C89.

    * The text talks about the distinction betwen “nothing” and “zero” but
    in the context of malloc the distinction the caller actually needs to
    make is between success and failure.

    * The principle that “zero-length objects” are invalid does not seem to
    be rationalized anywhere in the text. While there may be some
    advantage to it, nobody wrote them down.

    A couple of other notes:

    * The Unix V7 rules for malloc neatly bypass any concerns about
    zero-length objects (if anybody cared about that at the time); the
    definition is “returns a pointer to a block of at least size bytes”.
    malloc(0) returning a pointer to a 1-byte block would be a non-issue
    under this definition regardless of any theoretical objections.

    * If it happens that c=0 at any point in the loop, the new C23 rules
    make the example in 7.20.3 undefined behavior. I guess the example has
    fallen out of favour.

    Finally back to the relationship to malloc:

    Actually the two decisions have essentially nothing to do with
    each other.

    It does seem like nobody considered the question of whether the two
    decisions were compatible; or at least if they did consider it then
    their reasoning was never added to the rationale.

    --
    https://www.greenend.org.uk/rjk/

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Tim Rentsch@21:1/5 to Kaz Kylheku on Thu Jan 25 09:08:21 2024
    Kaz Kylheku <[email protected]> writes:

    On 2024-01-21, Tim Rentsch <[email protected]> wrote:

    Kaz Kylheku <[email protected]> writes:

    [.. considering the behavior of malloc(0) ..]

    [If] implentations could have a single dedicated object for
    representing empty allocations (which can be passed to free any
    number of times), that would also be a nice requirement.

    That defeats the whole purpose of having malloc(0) return
    a non-null value. Don't you understand anything?

    I undertand very clearly from past disccussions that you're attached
    to a particular use case whereby malloc(0) returns unique objects.

    What I like or don't like has nothing to do with it. The motivating
    principle comes from the group that originally advocated it (one of
    whom was Doug McIlroy, IIRC), long before there ever was a C
    standard.

    However, I don't see that as the purpose, let alone the "whole
    purpose".

    Then I suggest you read up more on the history of the early
    discussions.

    The standard currently does not endorse malloc(0) as a factory
    for unique pointers.

    The C standard requires that all non-null return values from the
    memory allocation functions be distinct: "Each such allocation
    shall yield a pointer to an object disjoint from any other object."
    See, eg, section 7.20.3, paragraph 1, in n1256.

    Currently, it cannot be relied on due to the possibility of the null
    return. If you want that behavior portably, you currently have to
    use malloc(1) instead, or some other nonzero value. Even if your
    program detects that malloc(0) returns a non-null pointer one time,
    there is no requirement that all subsequent such allocations will
    return non-null.

    Your understanding of the term "implementation-defined" is flawed.
    Each implementation must make a particular choice and stick to it.
    In cases where the choice is allowed to vary the C standard uses
    different language, as for example "in an implementation-defined
    manner". Note also the item in Annex J.3.12: "Whether the calloc,
    malloc, and realloc functions return a null pointer or a pointer to
    an allocated object when the size requested is zero". Not "when" or
    "under what circumstances" but "whether". Granted, text in Annex J
    is non-normative, but surely it reflects the meaning intended by the
    C standard's authors.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From James Kuyper@21:1/5 to All on Thu Jan 25 13:27:22 2024
    On 2024-01-24 01:15:19 Tim Rentsch wrote:
    James Kuyper <[email protected]> writes:

    On 1/22/24 18:12, Blue-Maned_Hawk wrote:
    ...
    void *p = malloc(42);

    Per C99:
    "The malloc function returns either a null pointer or a pointer to
    the allocated space." (7.22.3.4p2)

    Thus, there are two possibilities:
    A: p == NULL
    B: p contains the address of a block of memory at least 42 bytes
    long, suitably aligned to store an object of any type.

    The standard does not mandate documentation of which of the two
    applies, so this has unspecified behavior.

    The return value from malloc() is not unspecified behavior. In
    n1256, see section 7.20.3 paragraph 1, and also the definition of
    unspecified behavior in 3.4.4.

    3.4.4p1:
    "unspecified behavior
    use of an unspecified value, or other behavior where this International Standard provides two or more possibilities and imposes no further
    requirements on which is chosen in any instance"

    7.20.3p1:
    "The order and contiguity of storage allocated by successive calls to
    the calloc, malloc, and realloc functions is unspecified. The pointer
    returned if the allocation succeeds is suitably aligned so that it may
    be assigned to a pointer to any type of object and then used to access
    such an object or an array of such objects in the space allocated (until
    the space is explicitly deallocated). The lifetime of an allocated
    object extends from the allocation until the deallocation. Each such
    allocation shall yield a pointer to an object disjoint from any other
    object. The pointer returned points to the start (lowest byte address)
    of the allocated space. If the space cannot be allocated, a null pointer
    is returned. If the size of the space requested is zero, the behavior is implementation- defined: either a null pointer is returned, or the
    behavior is as if the size were some nonzero value, except that the
    returned pointer shall not be used to access an object."

    Agreed - the value returned from malloc() is not unspecified behavior. I
    didn't say that it was. It is specified precisely which value must be
    returned, depending upon whether or not malloc() could allocate the
    space. What is unspecified by the standard is whether or not the space
    can be allocated. The standard provides two possibilities, allocable or
    not allocable, and imposes no requirements on which is chosen in any
    instance. That matches the definition of unspecified behavior.

    If you think that this is an inappropriate application of that term,
    that is irrelevant to the point I was making. What is relevant is that
    there are two possible outcomes, and that neither of them involves
    undefined behavior, and that neither of them sets up a situation that
    gives the call to realloc() undefined behavior.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Kaz Kylheku@21:1/5 to Tim Rentsch on Thu Jan 25 18:49:37 2024
    On 2024-01-25, Tim Rentsch <[email protected]> wrote:
    Your understanding of the term "implementation-defined" is flawed.

    No it isn't.

    Obviously, an implementation cannot vaccilate among different choices of
    how malloc(0) should behave.

    Just like an implementation cannot say that, in even-numbered lines of
    code, the right shift of a negative integer will propagate the sign,
    wheras in odd lines it will propagate a zero.

    Rather, the issue is that null is always a valid return, regardless
    of the choice of behavior.

    If an implementation is observed as returning non-null from malloc(0),
    then we know that its choice for implementation-defined behavior is to
    do that.

    But then if it returns null from another call to malloc(0), we
    cannot conclude that its nonconforming, because that null could
    be an indication of allocation failure, which is always allowed.

    We /can/ conclude that the allocation failed. But so what?

    We cannot write a run-time test for the behavior that is guaranteed to terminate.

    bool malloc_0_non_null = false;

    for (;;) {
    void *ptr = malloc(0);

    if (ptr) {
    malloc_0_non_null = true;
    free(ptr);
    break;
    }
    }

    This will obviously not terminate on an implementation that always
    returns null; so we need to somehow cut the loop short. For instance by
    capping the number of iterations.

    Problem is, we don't know the minimum number of iterations any given implementation will require. I.e. how many initial allocation failures
    the implementation will report before changing its mind and handing out
    the non-null.

    With the malloc zero ambiguity, ISO C had successfully added a new
    formally undecidable problem to the field of computer science
    (cue slow clap).

    I apologize for not making all this reasoning clearer in my posting.

    --
    TXR Programming Language: http://nongnu.org/txr
    Cygnal: Cygwin Native Application Library: http://kylheku.com/cygnal
    Mastodon: @[email protected]

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From David Brown@21:1/5 to Keith Thompson on Fri Jan 26 09:27:18 2024
    On 25/01/2024 22:40, Keith Thompson wrote:
    "Chris M. Thomasson" <[email protected]> writes:
    On 1/25/2024 11:35 AM, Keith Thompson wrote:
    [...]
    Unfortunately, code does not have access to the implementation's
    documentation.

    Mind if I quote that from time to time? Nice one.

    Feel free.

    To expand on that, I think it was Tim Rentsch who recently suggested
    adding predefined macros that code can query to find out which choices
    an implementation has made. I'm concerned that that would introduce a
    lot of new symbols into the global namespace, so I'm thinking about a
    new preprocessor pseudo-function, something like:

    #if IMPL(MALLOC0, NON_NULL_RESULT)
    ...
    #elif IMPL(MALLOC0, NULL_RESULT)
    ...
    #endif

    This is just off the top of my head. On the other hand, a bunch of
    macros that can be queried with #ifdef would be simpler to implement.


    I believe you can have your cake and eat it here.

    IMPL could be implemented as :

    #define IMPL(__group, __detail) defined(_IMPLEMENTATION_DETAIL_ ## \
    __group ## _IS_ ## __detail)


    Then the implementation detail would just have to define macros such as:

    #define _IMPLEMENTATION_DETAIL_MALLOC0_IS_NON_NULL_RESULT


    That would be easy to implement and avoid a risk of identifier collisions.

    All you need to do now is persuade the main C compiler and library
    implementers to follow this new standard !

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Tim Rentsch@21:1/5 to Kaz Kylheku on Fri Jan 26 05:51:29 2024
    Kaz Kylheku <[email protected]> writes:

    On 2024-01-23, Tim Rentsch <[email protected]> wrote:

    Richard Kettlewell <[email protected]d> writes:

    Tim Rentsch <[email protected]> writes:

    Richard Kettlewell <[email protected]d> writes:

    Of course that's what the cautious programmer must do practice. But in >>>>> terms of the total cost (over all users, implementers, etc) fixing the >>>>> definitions of memcpy/memset/etc (as well as malloc) would have been the >>>>> better answer.

    Better in some ways, worse in others. Better for me is not
    always the same as better for thee.

    The way I think it's better is total cost over all users, implementers,
    etc. [...]

    Yes, I got that. Not everyone shares that view. It's not
    even a given that everyone thinks that's the best metric
    to optimize.

    It is an inherently collectivist view; I would expect communists
    to agree with it unanimously. (But then bungle the execution by
    forming a five year plan in which the work is divided into groups
    driven by meaningless local metrics and irrelevant incentives.)

    All science trembles at the searing logic of your fiery intellect.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Tim Rentsch@21:1/5 to Richard Kettlewell on Fri Jan 26 07:54:01 2024
    Richard Kettlewell <[email protected]d> writes:

    Tim Rentsch <[email protected]> writes:

    Richard Kettlewell <[email protected]d> writes:

    Tim Rentsch <[email protected]> writes:

    Richard Kettlewell <[email protected]d> writes:

    Of course that's what the cautious programmer must do practice.
    But in terms of the total cost (over all users, implementers,
    etc) fixing the definitions of memcpy/memset/etc (as well as
    malloc) would have been the better answer.

    Better in some ways, worse in others. Better for me is not
    always the same as better for thee.

    The way I think it's better is total cost over all users,
    implementers, etc. [...]

    Yes, I got that. Not everyone shares that view.

    The cost is an objective statement, not a view.

    What I mean is that not everyone agrees with the idea that
    changing the definitions of memcpy/memset/etc will achieve the
    goal of minimizing the total cost of ownership.

    In principle I could be wrong that the cost of everyone dealing
    with the resulting bugs, adding workarounds, etc, is greater than
    the cost of fixing implementations, but frankly it doesn't seem
    very plausible.

    My comment is only about what other people consider to be so,
    not to assess the question itself.

    It's not even a given that everyone thinks that's the best metric
    to optimize.

    C's history since standardization does include a disregard for the
    costs born by its users, yes.

    I don't think that's true. Probably it is true that the
    priorities of folks who have worked on the ISO C committee
    don't exactly match your priorities.

    Standard C is trying to have its own cake and eat it here:
    0-sized allocations can be represented by null pointers when
    it's malloc, but not when it's memcpy.

    Actually the two decisions have essentially nothing to do with
    each other. You might want to read what the C Rationale
    document has to say about the decisions behind various
    memory allocation policies.

    Do you mean
    https://www.open-std.org/jtc1/sc22/wg14/www/C99RationaleV5.10.pdf
    s7.20.3?

    I was looking at C99RationaleV5.10, although I believe the comments
    are from the earlier, original Rationale document. I expect you
    have the right section; I don't have the document open to check.

    It is thoroughly unenlightening as an explanation.

    Oh. I suppose that depends on what it is one thinks the writing
    is trying to explain.

    * It says the rules about zero-length allocations are intended to
    permit a particular paradigm, of which an example is given.

    Actually it doesn't say that. What it does say is that the
    treatment of such cases was guided in part by a desire to
    support the given pattern.

    * The example includes no attempt at error handling, which is
    where the pain point from the permission for malloc(0)=NULL
    arises. So it seems like the author of that section didn't
    consider the impact of the rules adopted.

    The code pattern shown is something that was observed to be in
    widespread use, not something that the Rationale's authors
    constructed to illustrate a point. It isn't surprising that
    there isn't any mention of error handling, because it isn't
    important to what the section is trying to explain.

    * Nothing about the example depends either on malloc(0)=NULL or
    malloc(0)!=NULL, making it mysterious why the example is said
    to support the decisions made in C89.

    Nothing in the Rationale document says that the given code
    pattern supports the decisions made in the C standard. Any
    implication actually goes the other direction, that decisions
    about how malloc() works were guided (in part) by a desire to
    support the given code pattern.

    * The text talks about the distinction betwen 'nothing' and 'zero'
    but in the context of malloc the distinction the caller actually
    needs to make is between success and failure.

    That may indeed be an important distinction, but it is not
    important to what this part of the Rationale document is trying
    to explain.

    * The principle that 'zero-length objects' are invalid does not
    seem to be rationalized anywhere in the text. While there may
    be some advantage to it, nobody wrote them down.

    What the text actually says is that the C89 Committee decided not
    to accept the idea of zero-length objects. Probably the phrasing
    could have been better; in any case what is meant is that they
    decided not to /require/ support of zero-length objects, but
    rather have it be optional (and implementation-defined).

    A couple of other notes:

    * The Unix V7 rules for malloc neatly bypass any concerns about
    zero-length objects (if anybody cared about that at the time);
    the definition is "returns a pointer to a block of at least size
    bytes". malloc(0) returning a pointer to a 1-byte block would be
    a non-issue under this definition regardless of any theoretical
    objections.

    I don't know anything about what Unix V7 says about malloc(), so
    I have nothing to say about that.

    * If it happens that c=0 at any point in the loop, the new C23
    rules make the example in 7.20.3 undefined behavior. I guess
    the example has fallen out of favour.

    Invalidating working code is a significant part of why people
    object to the decision to make zero-sized realloc() calls be
    undefined behavior.

    Finally back to the relationship to malloc:

    Actually the two decisions have essentially nothing to do with
    each other.

    It does seem like nobody considered the question of whether the
    two decisions were compatible; [...]

    They may or may not have. You may want to read the Introduction
    section of the Rationale document.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Tim Rentsch@21:1/5 to Kaz Kylheku on Fri Jan 26 12:20:31 2024
    Kaz Kylheku <[email protected]> writes:

    On 2024-01-25, Tim Rentsch <[email protected]> wrote:

    Your understanding of the term "implementation-defined" is flawed.

    No it isn't.

    Obviously, an implementation cannot vaccilate among different
    choices of how malloc(0) should behave. [...]

    I think you've lost track of what point it is you are trying
    to make. I know I have.


    I apologize for not making all this reasoning clearer in my
    posting.

    I recommend adopting the habit of re-reading (and revising) what
    you have written, before posting it. I try to do this at least
    once before posting, and often two or three times. A technique
    that can be very effective is speaking the text out loud as you
    read it.

    A piece of advice that appears in most of the essays I have read
    about writing is something like, The essence of good writing is
    re-writing. Another one that I like (this one from Fred Brooks)
    is, Easy writing makes hard reading, and vice versa. Perhaps
    these suggestions will help you avoid confusing writing in the
    future.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Richard Kettlewell@21:1/5 to Tim Rentsch on Fri Jan 26 21:04:02 2024
    Tim Rentsch <[email protected]> writes:
    Richard Kettlewell <[email protected]d> writes:
    C's history since standardization does include a disregard for the
    costs born by its users, yes.

    I don't think that's true. Probably it is true that the
    priorities of folks who have worked on the ISO C committee
    don't exactly match your priorities.

    The practical impacts and the complaints have been widespread for many
    years. It’s not just me.

    It is thoroughly unenlightening as an explanation.

    Oh. I suppose that depends on what it is one thinks the writing
    is trying to explain.

    You were the one who recommend it in the context of this thread...

    It claims to be trying to explain the treatment of null pointers and zero-length allocations. You may have to take it on trust, but for
    someone who happens not to like the treatment chosen, it singularly
    fails to explain that.

    * The example includes no attempt at error handling, which is
    where the pain point from the permission for malloc(0)=NULL
    arises. So it seems like the author of that section didn't
    consider the impact of the rules adopted.

    The code pattern shown is something that was observed to be in
    widespread use, not something that the Rationale's authors
    constructed to illustrate a point. It isn't surprising that
    there isn't any mention of error handling, because it isn't
    important to what the section is trying to explain.

    The implication is that error handling (and its relevance to reliability
    and robustness) was not of interest.

    * Nothing about the example depends either on malloc(0)=NULL or
    malloc(0)!=NULL, making it mysterious why the example is said
    to support the decisions made in C89.

    Nothing in the Rationale document says that the given code
    pattern supports the decisions made in the C standard. Any
    implication actually goes the other direction, that decisions
    about how malloc() works were guided (in part) by a desire to
    support the given code pattern.

    The point is alternative decisions (for example, the V7 behavior) would
    have supported it just as well.

    * The principle that 'zero-length objects' are invalid does not
    seem to be rationalized anywhere in the text. While there may
    be some advantage to it, nobody wrote them down.

    What the text actually says is that the C89 Committee decided not
    to accept the idea of zero-length objects. Probably the phrasing
    could have been better; in any case what is meant is that they
    decided not to /require/ support of zero-length objects, but
    rather have it be optional (and implementation-defined).

    The text describes requiring zero-length objects as a “compelling
    theoretical disadvantage”, without explaining why it’s so compelling or even why requiring them would be a disadvantage. Earlier implementations apparently didn’t find any difficulty with them, or at least
    insufficient difficulty to influence the rules for malloc.

    A couple of other notes:

    * The Unix V7 rules for malloc neatly bypass any concerns about
    zero-length objects (if anybody cared about that at the time);
    the definition is "returns a pointer to a block of at least size
    bytes". malloc(0) returning a pointer to a 1-byte block would be
    a non-issue under this definition regardless of any theoretical
    objections.

    I don't know anything about what Unix V7 says about malloc(), so
    I have nothing to say about that.

    You know what I told you above.

    * If it happens that c=0 at any point in the loop, the new C23
    rules make the example in 7.20.3 undefined behavior. I guess
    the example has fallen out of favour.

    Invalidating working code is a significant part of why people
    object to the decision to make zero-sized realloc() calls be
    undefined behavior.

    The rationale notes that the C89 rules change the behavior of existing
    code (despite “existing code is important, existing implementations are not” and “avoid quiet changes”).

    Finally back to the relationship to malloc:

    Actually the two decisions have essentially nothing to do with
    each other.

    It does seem like nobody considered the question of whether the
    two decisions were compatible; [...]

    They may or may not have. You may want to read the Introduction
    section of the Rationale document.

    Being more specific about what you’re getting at might cause me to want
    to read that in more detail. So far I’ve just identified a couple of “important principles” that were violated by the rules C89 introduced
    for malloc.

    --
    https://www.greenend.org.uk/rjk/

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Kaz Kylheku@21:1/5 to Tim Rentsch on Sun Jan 28 18:17:11 2024
    On 2024-01-26, Tim Rentsch <[email protected]> wrote:
    Kaz Kylheku <[email protected]> writes:

    On 2024-01-25, Tim Rentsch <[email protected]> wrote:

    Your understanding of the term "implementation-defined" is flawed.

    No it isn't.

    Obviously, an implementation cannot vaccilate among different
    choices of how malloc(0) should behave. [...]

    I think you've lost track of what point it is you are trying
    to make. I know I have.

    Here I'm simply defending myself against the accusation that I don't
    understand the definition of implementation-defined behavior. (Based on
    what I'm guessing might be the basis of the perceived misunderstanding.)

    This is a mostly self-contained topic, which has a connection to an
    earlier point, that point being a relatively minor.

    I walked into it like you wanted me to, executing your plan
    to disconnect.

    --
    TXR Programming Language: http://nongnu.org/txr
    Cygnal: Cygwin Native Application Library: http://kylheku.com/cygnal
    Mastodon: @[email protected]

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From James Kuyper@21:1/5 to Kaz Kylheku on Sun Jan 28 20:09:07 2024
    On 1/25/24 13:49, Kaz Kylheku wrote:
    On 2024-01-25, Tim Rentsch <[email protected]> wrote:
    Your understanding of the term "implementation-defined" is flawed.

    No it isn't.

    Obviously, an implementation cannot vaccilate among different choices of
    how malloc(0) should behave.

    That might seem reasonable, but the committee has ruled otherwise. The committee's official response to a rather early DR (unfortunately, I
    cannot remember which one, and I can't figure out how to search for it - therefore, you're free to disbelieve what I'm about to say) said that
    when something is unspecified, an implementation is free to make
    different choices in different programs; or in different translation
    units in the same program, or on different lines of the same translation
    unit, or in different sub-expressions of the same expression, or in
    different runs of the same program, or in different executions of the
    same expression.

    This obviously cannot be true in general. Most importantly, many aspects
    of the representations of types are unspecified, but it would make it impossible for different programs to communicate with each other through
    a file, or even for different parts of a program to communicate with
    each other through the values stored in variables, if the
    representations were allowed to change. Since I've been unable to locate
    the DR, I cannot verify what the committee said about that issue if
    anything. However, in any case where it's not too difficult to deal with
    the possibility of an unspecified choice changing, you should generally
    assume that it IS possible.

    I don't think it would be particularly difficult to cope with the
    possibility that malloc(0) was documented as choosing randomly whether
    to return a null pointer or the same result as malloc(1).

    The fact that some unspecified behavior is implementation-defined
    doesn't change this - it just means that it must be documented. There's
    no limit on how bizarre the way is that an implementation decides among
    the choices - so long as that way is correctly documented.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Tim Rentsch@21:1/5 to Kaz Kylheku on Wed Jan 31 01:02:18 2024
    Kaz Kylheku <[email protected]> writes:

    On 2024-01-26, Tim Rentsch <[email protected]> wrote:

    Kaz Kylheku <[email protected]> writes:

    On 2024-01-25, Tim Rentsch <[email protected]> wrote:

    Your understanding of the term "implementation-defined" is flawed.

    No it isn't.

    Obviously, an implementation cannot vaccilate among different
    choices of how malloc(0) should behave. [...]

    I think you've lost track of what point it is you are trying
    to make. I know I have.

    Here I'm simply defending myself against the accusation that I
    don't understand the definition of implementation-defined
    behavior. (Based on what I'm guessing might be the basis of the
    perceived misunderstanding.)

    I would say you have done so. There was a miscommunication and
    you have identified the particular miscommunication sufficiently
    so that it is no longer an issue.

    This is a mostly self-contained topic, which has a connection to
    an earlier point, that point being a relatively minor.

    My problem is that I have lost track of what your overall point
    is (if indeed I ever had a good fix on that). So I remain
    confused about what you were trying to get at.

    I walked into it like you wanted me to, executing your plan
    to disconnect.

    That is a baseless accusation. Throughout this discussion I have
    been trying to improve communication, not impede it.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Tim Rentsch@21:1/5 to Richard Kettlewell on Thu Feb 1 05:39:28 2024
    Richard Kettlewell <[email protected]d> writes:

    Tim Rentsch <[email protected]> writes:

    Richard Kettlewell <[email protected]d> writes:

    C's history since standardization does include a disregard for the
    costs born by its users, yes.

    I don't think that's true. Probably it is true that the
    priorities of folks who have worked on the ISO C committee
    don't exactly match your priorities.

    The practical impacts and the complaints have been widespread for many
    years. It?s not just me.

    It is thoroughly unenlightening as an explanation.

    Oh. I suppose that depends on what it is one thinks the writing
    is trying to explain.

    You were the one who recommend it in the context of this thread...

    It claims to be trying to explain the treatment of null pointers and zero-length allocations. You may have to take it on trust, but for
    someone who happens not to like the treatment chosen, it singularly
    fails to explain that.

    * The example includes no attempt at error handling, which is
    where the pain point from the permission for malloc(0)=NULL
    arises. So it seems like the author of that section didn't
    consider the impact of the rules adopted.

    The code pattern shown is something that was observed to be in
    widespread use, not something that the Rationale's authors
    constructed to illustrate a point. It isn't surprising that
    there isn't any mention of error handling, because it isn't
    important to what the section is trying to explain.

    The implication is that error handling (and its relevance to reliability
    and robustness) was not of interest.

    * Nothing about the example depends either on malloc(0)=NULL or
    malloc(0)!=NULL, making it mysterious why the example is said
    to support the decisions made in C89.

    Nothing in the Rationale document says that the given code
    pattern supports the decisions made in the C standard. Any
    implication actually goes the other direction, that decisions
    about how malloc() works were guided (in part) by a desire to
    support the given code pattern.

    The point is alternative decisions (for example, the V7 behavior) would
    have supported it just as well.

    * The principle that 'zero-length objects' are invalid does not
    seem to be rationalized anywhere in the text. While there may
    be some advantage to it, nobody wrote them down.

    What the text actually says is that the C89 Committee decided not
    to accept the idea of zero-length objects. Probably the phrasing
    could have been better; in any case what is meant is that they
    decided not to /require/ support of zero-length objects, but
    rather have it be optional (and implementation-defined).

    The text describes requiring zero-length objects as a ?compelling
    theoretical disadvantage?, without explaining why it?s so compelling or
    even why requiring them would be a disadvantage. Earlier implementations apparently didn?t find any difficulty with them, or at least
    insufficient difficulty to influence the rules for malloc.

    A couple of other notes:

    * The Unix V7 rules for malloc neatly bypass any concerns about
    zero-length objects (if anybody cared about that at the time);
    the definition is "returns a pointer to a block of at least size
    bytes". malloc(0) returning a pointer to a 1-byte block would be
    a non-issue under this definition regardless of any theoretical
    objections.

    I don't know anything about what Unix V7 says about malloc(), so
    I have nothing to say about that.

    You know what I told you above.

    * If it happens that c=0 at any point in the loop, the new C23
    rules make the example in 7.20.3 undefined behavior. I guess
    the example has fallen out of favour.

    Invalidating working code is a significant part of why people
    object to the decision to make zero-sized realloc() calls be
    undefined behavior.

    The rationale notes that the C89 rules change the behavior of existing
    code (despite ?existing code is important, existing implementations are
    not? and ?avoid quiet changes?).

    Finally back to the relationship to malloc:

    Actually the two decisions have essentially nothing to do with
    each other.

    It does seem like nobody considered the question of whether the
    two decisions were compatible; [...]

    They may or may not have. You may want to read the Introduction
    section of the Rationale document.

    Being more specific about what you?re getting at might cause me to want
    to read that in more detail. So far I?ve just identified a couple of ?important principles? that were violated by the rules C89 introduced
    for malloc.

    I have read through your comments, carefully, and read through
    them again once or twice more. I have nothing useful to say at
    this time.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Tim Rentsch@21:1/5 to Keith Thompson on Sat Feb 10 02:47:17 2024
    Keith Thompson <[email protected]> writes:

    David Brown <[email protected]> writes:

    On 24/01/2024 04:28, Tim Rentsch wrote:

    Keith Thompson <[email protected]> writes:

    Tim Rentsch <[email protected]> writes:

    Keith Thompson <[email protected]> writes:

    I got a response from JeanHeyd Meneide.

    If realloc(ptr, 0) returns a null pointer there's no way to tell
    whether allocation failed (and ptr has not been freed), or the
    implementation returns a null pointer for zero-sized allocations
    (and ptr has been freed). Some implementations set errno, but C
    doesn't require it.

    To avoid any confusion, I wrote the above paragraph; JeanHeyd Meneide
    (the editor for C23 standard) did not.

    Thank you, I appreciate the clarification.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)