Hi Luther,
At 2024-09-28T10:47:44-0700, Luther Johnson wrote:
I don't know that structure copying breaks any
complexity or bounds on
execution time rules. Many compilers may be different, but in the
generated code I've seen, when you pass in a structure to a function,
the receiving function copies it to the stack. In the portable C
compiler, when you return a structure as a result, it is first copied
to a static area, a pointer to that area is returned, then the caller
copies that out to wherever it's meant to go, either a variable that's
being assigned (which could be on the stack or elsewhere), or to a
place on the stack that was reserved for it because that result will
now be an argument to another function to be called. So there's some
copying, but that's proportional to the size of the structure, it's
linear, and there's no dynamic memory allocation going on.
I have no problem with this presentation, but recall the principle--the
tenet--that Doug was upholding:
> At 2024-09-28T09:34:14-0400, Douglas McIlroy
wrote:
> > This complaint overlooks one tenet of C: every operation in what
> > you call "language runtime" takes O(1) time. Dynamic memory
> > allocation is not such an operation.
Even without dynamic memory allocation, if you did something linear,
something O(n), it was a lose and a violation of the tenet.
I can easily see the appeal of a language whose every operation really
is O(1). Once upon a time, a university course, or equivalent
experience, in assembly language (on a CLEAN instruction set, not x86)
is what taught you the virtues and drawbacks of thinking about and
implementing things that way. But my view is that C hasn't been one of
those languages for a very long time, since before its initial ANSI
standardization at the latest.
At 2024-09-28T10:52:16-0700, Luther Johnson wrote:
In the compilers I'm talking about, you pass a
structure by passing a
pointer to it - but the receiving function knows the argument is a
structure, and not a pointer to a structure, so it knows it needs to
use the pointer to copy to its own local version.
It's my understanding that the ability to work with structs as
first-class citizens in function calls, as parameters _or_ return types,
was something fairly late to stabilize in C compilers. Second-hand, I
gather that pre-standard C as told by K&R explicitly did _not_
countenance this. So a lot of early C code, including that in
libraries, indirected nearly all struct access, even when read-only,
through pointers.
This is often a win, but not always. A few minutes ago I shot off my
mouth to this list about how much better the standard library design
could have been if the return of structs by value had been supported
much earlier.
Our industry has, it seemss, been slow to appreciate the distinction
between what C++ eventually came to explicitly call "copy" semantics and
"move" semantics. Rust's paradigmatic dedication to the concept of data
"ownership" at last seems to be popularizing the practice of thinking
about these things. (For my part, I will forever hurl calumnies at
computer architects who refer to copy operations as "MOV" or similar.
If the operation doesn't destroy the source, it's not a move--I don't
care how many thousands of pages of manuals Intel writes saying
otherwise. Even the RISC-V specs screw this up, I assume in a
deliberate but embarrassing attempt to win mindshare among x86
programmers who cling to this myth as they do so many others.)
For a moment I considered giving credit to a virtuous few '80s C
programmers who recognized that there was indeed no need to copy a
struct upon passing it to a function if you knew the callee wasn't going
to modify that struct...but we had a way of saying this, "const", and
library writers of that era were infamously indifferent to using "const"
in their APIs where it would have done good. So, no, no credit.
Here's a paragraph from a 1987 text I wish I'd read back then, or at any
time before being exposed to C.
"[Language] does not define how parameter passing is implemented. A
program is erroneous if it depends on a specific implementation method.
The two obvious implementations are by copy and by reference. With an
implementation that copies parameters, an `out` or `in out` actual
parameter will not be updated until (normal) return from the subprogram.
Therefore if the subprogram propagates an exception, the actual
parameter will be unchanged. This is clearly not the case when a
reference implementation is used. The difficulty with this vagueness in
the definition of [language] is that it is quite awkward to be sure that
a program is independent of the implementation method. (You might
wonder why the language does not define the implementation method. The
reason is that the copy mechanism is very inefficient with large
parameters, whereas the reference mechanism is prohibitively expensive
on distributed systems.)"[1]
I admire the frankness. It points the way forward to reasoned
discussion of engineering tradeoffs, as opposed to programming language
boosterism. (By contrast, the trashing of boosters and their rhetoric
is an obvious service to humanity. See? I'm charitable!)
I concealed the name of the programming language because people have a
tendency to unfairly disregard and denigrate it in spite of (or because
of?) its many excellent properties and suitability for robust and
reliable systems, in contrast to slovenly prototypes that minimize
launch costs and impose negative externalities on users (and on anyone
unlucky enough to be stuck supporting them). But then again cowboy
programmers and their managers likely don't read my drivel anyway.
They're busy chasing AI money before the bubble bursts.
Anyway--the language is Ada.
Regards,
Branden
[1] Watt, Wichmann, Findlay. _Ada Language and Methodology_.
Prentice-Hall, 1987, p. 395.