• That depends... (Was: Regarding assignment to struct)

    From Kenny McCormack@21:1/5 to schwarzb@delq.com on Fri May 2 20:44:45 2025
    In article <51ba1k5h5lkj75qvfuj0ferlddpb6bi0n8@4ax.com>,
    Barry Schwarz <schwarzb@delq.com> wrote:
    ...
    Wouldn't it be quicker and easier to write a simple program to test
    this rather than wait for someone to compose a response? You already
    have 90% of the code written.

    That depends on what the actual personal goal is.

    Somehow, I don' think Lew's goal(s) (and reason for posting) are what you
    think they are.

    --
    Alice was something of a handful to her father, Theodore Roosevelt. He was once asked by a visiting dignitary about parenting his spitfire of a daughter and he replied, "I can be President of the United States, or I can control Alice. I cannot possibly do both."

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Lew Pitcher@21:1/5 to Kenny McCormack on Sat May 3 01:13:48 2025
    On Fri, 02 May 2025 20:44:45 +0000, Kenny McCormack wrote:

    In article <51ba1k5h5lkj75qvfuj0ferlddpb6bi0n8@4ax.com>,
    Barry Schwarz <schwarzb@delq.com> wrote:
    ...
    Wouldn't it be quicker and easier to write a simple program to test
    this rather than wait for someone to compose a response? You already
    have 90% of the code written.

    That depends on what the actual personal goal is.

    Somehow, I don' think Lew's goal(s) (and reason for posting) are what you think they are.

    My goal is to add a feature to one of my older programs. The feature will require passing a number of objects down and up the function call chain, and I'm looking at various alternatives to accomplish this.

    ATM, it looks like I either go with "global" objects, or I change the
    argument list of several functions to include about 5 more objects each. Because these additional objects all relate to one another, I'm thinking
    of grouping them in a struct, and passing a pointer to the struct back
    and forth.

    But... I remembered this (apparently) seldom-used feature of being able
    to pass a struct by value up and down the chain (down, as a function
    argument, up as a function return value). As I've not done this before,
    and I've not /seen/ it done in other peoples code, I wonder how viable
    a solution it might be.

    If it is legal, then why isn't it used more often? Is it a readability/ maintainability issue, or is it something else?

    --
    Lew Pitcher
    "In Skills We Trust"

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Lawrence D'Oliveiro@21:1/5 to Lew Pitcher on Sat May 3 02:28:56 2025
    On Sat, 3 May 2025 01:13:48 -0000 (UTC), Lew Pitcher wrote:

    But... I remembered this (apparently) seldom-used feature of being able
    to pass a struct by value up and down the chain (down, as a function argument, up as a function return value). As I've not done this before,
    and I've not /seen/ it done in other peoples code, I wonder how viable
    a solution it might be.

    As someone who has written lots of Python wrappers around C libraries, one thing I could never get to work right was passing structs as arguments by value. (The case of returning a struct as a function result never arose.) Presumably this was a limitation of libffi, which lies at the heart of the Python ctypes module.

    Luckily, in the one case where this happened, there were alternative API
    calls I was able to use.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Janis Papanagnou@21:1/5 to Lew Pitcher on Sat May 3 06:17:51 2025
    On 03.05.2025 03:13, Lew Pitcher wrote:
    [...]

    My goal is to add a feature to one of my older programs. The feature will require passing a number of objects down and up the function call chain, and I'm looking at various alternatives to accomplish this.

    ATM, it looks like I either go with "global" objects, or I change the argument list of several functions to include about 5 more objects each. Because these additional objects all relate to one another, I'm thinking
    of grouping them in a struct, and passing a pointer to the struct back
    and forth.

    But... I remembered this (apparently) seldom-used feature of being able
    to pass a struct by value up and down the chain (down, as a function argument, up as a function return value). As I've not done this before,
    and I've not /seen/ it done in other peoples code, I wonder how viable
    a solution it might be.

    When I read about that I immediately thought about 'complex' numbers
    (and functions that process structs of two floats). I'd expect that
    such code would exist (maybe not for a 'complex' type but some struct).
    It would IMO certainly provide a "better" interface to pass 'complex'
    items than to pass the pieces of complex numbers or use other kludges.

    Where I'd be more reluctant is to pass (as in your original example)
    also pointers to items as 'struct' attribute; not that this should be
    a problem, but to me that has a smell [in the general case] and I'd
    in such cases might just return a pointer to a linked structure. (But
    I think that's just a matter of taste of course.) In your case it may
    make perfectly sense.


    If it is legal, then why isn't it used more often?

    Reasons I can think of are; compatibility considerations (to really
    old K&R "C" versions), support of compilers (whose development was
    abandoned but still in use because they "just work"), maybe just not
    knowing that feature, or lacking the information, or bad paragons,
    or having got (bad or good?) advice.

    I seem to recall my student days where we learned (based on old K&R)
    that you cannot return or assign structs in "C" (as opposed to e.g.
    Pascal that has a powerful ':=' assignment that allowed to assign
    complex nested data structures). Such [outdated] information might
    have contributed to only rare uses of structs in those contexts.

    Is it a readability/
    maintainability issue, or is it something else?

    I don't see how that could be the case. Sure, you can write arbitrary
    badly readable or badly maintainable code. But, as I'd see it e.g. for
    structs like the above mentioned 'complex' type functions it would IMO
    add to its legibility.

    If that makes the organization and handling of your functions simpler
    or better organized (and don't have any legacy requirements) then I'd
    just use the feature.

    Janis

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Kaz Kylheku@21:1/5 to Kaz Kylheku on Sat May 3 05:11:25 2025
    On 2025-05-03, Kaz Kylheku <643-408-1753@kylheku.com> wrote:
    - some structures cannot be copied; duplicating objects can
    be nontrivial when they manage resources or are sensitive
    to their own address (like have pointers to parts of themselves).
    E.g. you can't just have a FILE parameter and pass (*stdout) to it.
    Under object-based/oriented programming in C, we usually pass
    around pointers to objects. Treating OOP objects by value requires
    techniques found in C++: you need handlers to be invoked when
    objects are copied ("copy constructors").

    Expanding on this point, C++ goes to town with pass-by-value interfaces involving objects. For instance the std::basic_string template allows
    C++ programs to treat dynamic strings as just values to be tossed
    around:

    std::string path_combine(std::string dir, std::string name)
    {
    return dir + "/" + name;
    }

    C++ experience informs us that you can get away with doing this
    with PODs (plain old datatypes---structs with no constructors
    or destructors) only to a point. But in C, that's all you have.

    You are limited in C by not being able to endow the type with
    the brains for what to do when it is copied: like for instance
    to share a pointer to some associated data with the new copy,
    bumping up a reference count.

    That tends to exert "downward pressure" on the technique.

    --
    TXR Programming Language: http://nongnu.org/txr
    Cygnal: Cygwin Native Application Library: http://kylheku.com/cygnal
    Mastodon: @Kazinator@mstdn.ca

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From James Kuyper@21:1/5 to Lew Pitcher on Sat May 3 00:47:06 2025
    On 5/2/25 21:13, Lew Pitcher wrote:
    ...
    But... I remembered this (apparently) seldom-used feature of being able
    to pass a struct by value up and down the chain (down, as a function argument, up as a function return value). As I've not done this before,
    and I've not /seen/ it done in other peoples code, I wonder how viable
    a solution it might be.

    If it is legal, then why isn't it used more often? Is it a readability/ maintainability issue, or is it something else?

    The most basic reason is that a pointer to a struct is smaller than most structs (particularly one that contains 5 items). In addition, passing
    it up and down the chain nominally requires making multiple copies of
    it, one in each function that takes it as an argument, and another copy
    when you store the return value - though compilers can optimize some of
    that away. Changes made to one copy will not be made to the other
    copies, which is very often not what you want.
    The cases where I've seen the approach used the most successfully were
    to implement things like complex numbers (before they were added to C99).

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Kaz Kylheku@21:1/5 to Lew Pitcher on Sat May 3 04:31:14 2025
    On 2025-05-03, Lew Pitcher <lew.pitcher@digitalfreehold.ca> wrote:
    If it is legal, then why isn't it used more often? Is it a readability/ maintainability issue, or is it something else?

    It's not used more often because

    - programmers are irrationallyafraid that there will be lots of
    overhead when the structure gets large.

    (In fact, passing large structs by value is implemented
    using a hidden pointer; a copy is not made unless the calee
    modifies the parameter. Returning likewise: caller passes
    a pointer to a location where to place the structure.
    It is not as bad as you might think.)

    Some of the fear is rational: you usually have a very good
    idea how pointer passing is implemented, but not necessarily how
    struct passing is.

    - some structures cannot be copied; duplicating objects can
    be nontrivial when they manage resources or are sensitive
    to their own address (like have pointers to parts of themselves).
    E.g. you can't just have a FILE parameter and pass (*stdout) to it.
    Under object-based/oriented programming in C, we usually pass
    around pointers to objects. Treating OOP objects by value requires
    techniques found in C++: you need handlers to be invoked when
    objects are copied ("copy constructors").

    - ABI concerns might prevent you from writing a public API that has
    functions that take and/or return structs by value for fear of
    causing complications to FFI users, or incompatibilities when
    differnet compiler are used.

    (We have such APIs in ISO C, though: ldiv and friends.)

    (In POSIX, I seem to recall, some platforms have made pthread_t a
    struct. Glibc on Linux might have had it that way? I think it's an
    unsigned int or long now. Anyway, in those implementations,
    pthread_equal takes two structs by value. And, aha, that's why there
    is pthread_equal; because pthread_t types might not be scalar values
    comparable with ==: i.e. pointers or integers.)

    --
    TXR Programming Language: http://nongnu.org/txr
    Cygnal: Cygwin Native Application Library: http://kylheku.com/cygnal
    Mastodon: @Kazinator@mstdn.ca

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Tim Rentsch@21:1/5 to Lew Pitcher on Sun May 4 10:59:56 2025
    Lew Pitcher <lew.pitcher@digitalfreehold.ca> writes:

    On Fri, 02 May 2025 20:44:45 +0000, Kenny McCormack wrote:

    In article <51ba1k5h5lkj75qvfuj0ferlddpb6bi0n8@4ax.com>,
    Barry Schwarz <schwarzb@delq.com> wrote:
    ...

    Wouldn't it be quicker and easier to write a simple program to test
    this rather than wait for someone to compose a response? You already
    have 90% of the code written.

    That depends on what the actual personal goal is.

    Somehow, I don' think Lew's goal(s) (and reason for posting) are what you
    think they are.

    My goal is to add a feature to one of my older programs. The feature will require passing a number of objects down and up the function call chain, and I'm looking at various alternatives to accomplish this.

    ATM, it looks like I either go with "global" objects, or I change the argument list of several functions to include about 5 more objects each. Because these additional objects all relate to one another, I'm thinking
    of grouping them in a struct, and passing a pointer to the struct back
    and forth.

    But... I remembered this (apparently) seldom-used feature of being able
    to pass a struct by value up and down the chain (down, as a function argument, up as a function return value). As I've not done this before,
    and I've not /seen/ it done in other peoples code, I wonder how viable
    a solution it might be.

    It works. As far as correctness goes, there is no reason not
    to use it.

    More to say about whether and how to use it, see below.

    If it is legal, then why isn't it used more often? Is it a readability/ maintainability issue, or is it something else?

    I expect there are at least three reasons.

    One: old habits die hard. People who learned C during the K&R
    era (and I am one) discovered that struct types are second-class
    citizens: they couldn't be initialized when local to a function,
    or assigned to at all, to say nothing of being function arguments
    or return values. It became an ingrained habit: Don't Do That.

    Two: old habits die hard part two. The rules for struct types
    improved in C89/C90, but they still weren't first class, because
    member initializers had to be constant expressions. And if
    initialization didn't work, who knows what else didn't work?
    What became ingrained was not fact but superstition: struct
    types don't work except in simple ways.

    Three: performance concerns. Having a parameter or a return
    value be of struct type could be expensive in either space or
    time or both. In some cases the performance hit is subtle;
    for example, a compiler optimization that works with scalar types
    sometimes fails with struct types, even when the struct is no
    bigger than the scalar type used where the optimization succeeds.

    All that said, let me give some personal reactions.

    I don't mind using struct types for parameters or return values,
    when the need arises, but I find it almost never does. I admit
    to having a small bias against it, for no particular reason, so
    there is an energy barrier against using struct types in such
    cases, but the barrier is not insurmountable.

    Assuming one is using C99 or later (and these days I basically
    never use C90), there is a technique worth knowing that gets most
    of the benefit of using struct type parameters without actually
    giving structs as arguments. The idea is to use pointers rather
    than actual structs as arguments, with the struct "arguments"
    being locals in the calling functions. What really makes this
    approach work is compound literals. For example:

    #include <stdlib.h>

    typedef struct { char *p; unsigned u; } PU;

    void use_PU( PU * );

    void
    call_use_PU( unsigned n ){
    PU it[1] = {{ 0, 0 }};
    use_PU( it );
    use_PU( (PU[1]){{ malloc(n), n }} );
    }

    In effect the function call_use_PU() is giving struct arguments,
    but the actual struct objects are held locally in the caller; in
    the first case the argument value is the address of a ordinary
    local object, and in the second case the argument value is the
    address of a compound literal object. As far as memory footprint
    goes this approach is the same as giving an actual struct for the
    argument, but without the complications of putting a struct
    object in the physical argument list.

    Having struct locals be contained in arrays of length 1 has another
    benefit, in that the -> operator can be used everywhere, without
    regard to whether the struct is a local or a pointer parameter. I
    find the uniformity helps streamline program development.

    For what it's worth, those are my thoughts on the question.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Tim Rentsch@21:1/5 to Kaz Kylheku on Sun May 4 11:05:21 2025
    Kaz Kylheku <643-408-1753@kylheku.com> writes:

    On 2025-05-03, Lew Pitcher <lew.pitcher@digitalfreehold.ca> wrote:

    If it is legal, then why isn't it used more often? Is it a readability/
    maintainability issue, or is it something else?

    It's not used more often because

    - programmers are irrationallyafraid that there will be lots of
    overhead when the structure gets large.

    (In fact, passing large structs by value is implemented
    using a hidden pointer; a copy is not made unless the calee
    modifies the parameter.

    It appears that this statement doesn't hold under gcc
    or clang.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Lew Pitcher@21:1/5 to Tim Rentsch on Sun May 4 18:16:16 2025
    On Sun, 04 May 2025 10:59:56 -0700, Tim Rentsch wrote:
    [snip]
    For what it's worth, those are my thoughts on the question.

    Thanks, Tim.

    Your reply was exactly what I was looking for.

    I, too, started my C journey with K&R, and even though that
    "Recent Changes to C" addendum (afaict, only published in
    the 1983 edition of "UNIX programmer's manual Volume 2", and
    (later) on Dennis Ritchie's Bell Labs webpage) talked about
    structs as closer-to-first-class objects, I never really
    tried them. Nor have I seen them used in the various open-source
    projects that I've read through.

    Now, I have a clearer idea of the benefits and pitfalls of
    using structs in the way "Recent Changes" and the C standard
    say that they can be used.


    Thanks, again, for the thoughtful reply

    --
    Lew Pitcher
    "In Skills We Trust"

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Janis Papanagnou@21:1/5 to Kaz Kylheku on Mon May 5 12:30:02 2025
    On 03.05.2025 07:11, Kaz Kylheku wrote:

    Expanding on this point, C++ goes to town with pass-by-value interfaces involving objects. For instance the std::basic_string template allows
    C++ programs to treat dynamic strings as just values to be tossed
    around:

    std::string path_combine(std::string dir, std::string name)
    {
    return dir + "/" + name;
    }

    Hmm.. - my C++ days were long ago but I seem to recall that the
    suggestion for passing non-trivial objects by-value was that they
    should instead be passed by 'const &' (as a "quasi-by-value") for
    performance reasons.

    C++ experience informs us that you can get away with doing this
    with PODs (plain old datatypes---structs with no constructors
    or destructors) only to a point. But in C, that's all you have.

    [...]

    Janis

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Kaz Kylheku@21:1/5 to Janis Papanagnou on Mon May 5 18:47:19 2025
    On 2025-05-05, Janis Papanagnou <janis_papanagnou+ng@hotmail.com> wrote:
    On 03.05.2025 07:11, Kaz Kylheku wrote:

    Expanding on this point, C++ goes to town with pass-by-value interfaces
    involving objects. For instance the std::basic_string template allows
    C++ programs to treat dynamic strings as just values to be tossed
    around:

    std::string path_combine(std::string dir, std::string name)
    {
    return dir + "/" + name;
    }

    Hmm.. - my C++ days were long ago but I seem to recall that the
    suggestion for passing non-trivial objects by-value was that they
    should instead be passed by 'const &' (as a "quasi-by-value") for
    performance reasons.

    Right; but we don't have to.

    I was going to add something else, and started exploring the
    generated code for simple functions that just do a + b on
    a pair of strings, and trivial functions which call them.

    The code generated by GNU C++ with -O3 is so abhorrently verbose,
    whether you use const references or go by-value, that I have no appetite
    to go into it.

    --
    TXR Programming Language: http://nongnu.org/txr
    Cygnal: Cygwin Native Application Library: http://kylheku.com/cygnal
    Mastodon: @Kazinator@mstdn.ca

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