However, as the struct size shrinks, the choice of passing struct by
value or by pointer becomes less clear to me. Let me use a simple
struct as an example.
struct greg_ymd
{
int16_t year;
int8_t month;
int8_t day;
};
I use this struct to represent a date in the gregorian calendar
(fields not offset to start from 0). Let's say that I want to have a
function that adds a certain number of days to this ymd struct. There
are a couple of options that come to mind.
1. struct greg_ymd add_days( const struct greg_ymd ymd, int days );
2. <return type> add_days( struct greg_ymd* pymd, int days );
First of all, what is the performance implications of using struct
pass-by-value for smallish structs?
Is there a rule of thumb of struct size for an interface API that you
convert all struct parameter passing to use pointers?
Have you used struct by value parameter passing or return value at all
in your API design experience?
The struct pass-by-value version of the interface avoids the pesky
NULL pointer argument issue, but still I can't get away from it
completely since I use 'int (*compare_function)( const void* p, const
void* q )' to define the sorting property in my generic containers.
Thanks for your time.
I'd say that "small" structs can sensibly be passed by value (unless of
course the function needs to modify them), and "large" structs
should be passed by pointer for performance reasons.
I have no particular guidance to offer about the dividing line between
"small" and "large". I'd say that anything no larger than a pointer is
certainly "small", but beyond that ...
I know that's not very helpful.
> I use this struct to represent a date in the gregorian calendar
> (fields not offset to start from 0). Let's say that I want to have a
> function that adds a certain number of days to this ymd struct. There
> are a couple of options that come to mind.
>
> 1. struct greg_ymd add_days( const struct greg_ymd ymd, int days );
The "const" doesn't really serve any purpose here, any more than
"const int days" would.
> 2. <return type> add_days( struct greg_ymd* pymd, int days );
Here a "const" would be helpful:
<return type> add_days( const struct greg_ymd *pymd, int days );
since it guarantees to the caller that the object pointed to by
the argument won't be modified.
> First of all, what is the performance implications of using struct
> pass-by-value for smallish structs?
It depends on the compiler.
[...]
--
Keith Thompson (The_Other_Keith) ks...@mib.org <http://www.ghoti.net/~kst>
Nokia
"We must do something. This is something. Therefore, we must do this."
-- Antony Jay and Jonathan Lynn, "Yes Minister"
Yeah, I didn't really know how to answer the question either. I could
try to perform some experiments, but I don't know how useful it would
be. The two structs I use pass-by-value in their interface is the
greg_ymd above, and a timeval like struct. I provide my own simply
because struct timeval doesn't seem to be standard as far as I can
tell.
struct c_timeval
{
long int sec;
long int usec;
};
> > I use this struct to represent a date in the gregorian calendar
> > (fields not offset to start from 0). Â Let's say that I want to have a
> > function that adds a certain number of days to this ymd struct. Â There
> > are a couple of options that come to mind.
>
> > 1. Â struct greg_ymd add_days( const struct greg_ymd ymd, int days );
>
> The "const" doesn't really serve any purpose here, any more than
> "const int days" would.
Good point.
> > 2. Â <return type> add_days( struct greg_ymd* pymd, int days );
>
> Here a "const" would be helpful:
> Â Â <return type> add_days( const struct greg_ymd *pymd, int days );
> since it guarantees to the caller that the object pointed to by
> the argument won't be modified.
Actually, in this case, the pymd value would be modified, and the
return value would be used to indicate some error status or void if I
ignore pymd == NULL error issues.
The difference in usage would look like the following.
\code example
int main(void)
{
struct c_greg_ymd ymd = { 2000, 1, 1 };
#ifdef PASS_STRUCT_BY_VALUE
ymd = add_days( ymd, 7 ); /* ymd is now { 2000, 1, 8 } */
#else /* PASS_STRUCT_BY_POINTER
add_days( &ymd, 7 ); /* ymd is now { 2000, 1, 8 } */
#endif
}
\endcode
> > First of all, what is the performance implications of using struct
> > pass-by-value for smallish structs?
>
> It depends on the compiler.
And maybe the compiler optimization flags too.
Best regards,
John D.
I have done some tests in the past with 32-bit gcc and Visual C. Both
would move structs to registers for call-by-value params, local
assignment (as in swapping struct values) and return values if the
size was 4 bytes or less.
In practice though the cost difference between allocating a struct in
the caller and passing a pointer to it vice accepting a return value
must be vanishingly small even if the struct does fit in a register.
When the struct doesn't fit in a register, the two return mechanisms
would be all but identical.
I've always used caller allocates and passes pointer for APIs. This
is for at least 5 reasons. (1) It's efficient enough in all cases
I've ever encountered. (2) A single convention means the caller must
remember only a single convention. (3) You get "in", "out", and "in
out" parameters all with the same mechanism. (4) You also get
"optional" parameters with the same mechanism by allowing NULL to mean
"not needed." This works for all 3 kinds. (5) It's clean in the
implementation to uniformly use -> for all (or nearly all) element
access rather than a mix of . and ->.
A convention I find very useful is this
typedef struct foo_s {
... big collection of fields ...
} FOO;
// Prepares the raw memory of the foo for initialization.
// Often just zeros or sets a flag so that later functions can
// see it hasn't been set up yet. May even do nothing. Declare anyway
// as a placeholder for the convention. Don't allocate anything here.
// Can't fail, so a void function.
void init_foo(FOO *foo);
// Make a foo fully ready for use. Allocate storage and other
resources.
// Return 0 on success else an error code.
int set_up_foo(FOO *foo, ...);
// Release all resources of a foo, returning it to the init state.
// Return 0 on success else an error code. It's okay to set_up
immediately
// after a clear.
int clear_foo(FOO *foo);
Then in code:
// A way to eliminate the clutter of &. YMMV.
FOO foo_instance[1];
// Always init after declaration even if the init does nothing.
init_foo(foo_instance);
... yada yada ..
for (...) {
err = set_up_foo(foo_instance, ...);
... check for error.
... use the foo instance then release its resources
err = clear_foo(foo_instance);
... check for error.
... more processing that doesn't need the foo instance.
}
I've used this so often that it's a comfortable old friend. Once I
used setjmp/longjmp as poor-man's exceptions rather than returning
error codes. It's okay if error handling doesn't need any
granularity.
What does `const' buy you? Or, why not `const int days'? Other
than that, this seems plausible.
> 2.<return type> add_days( struct greg_ymd* pymd, int days );
This seems plausible, too.
> First of all, what is the performance implications of using struct
> pass-by-value for smallish structs?
Mu.
> Is there a rule of thumb of struct size for an interface API that you
> convert all struct parameter passing to use pointers?
When writing software that others might blame you for, the basic
rule of thumb is "Leave no fingerprints." ;-)
> Have you used struct by value parameter passing or return value at all
> in your API design experience?
Yes. For parameters, I'd estimate that I use a struct pointer
more frequently than a struct value, maybe 90%-10% or even more
lopsided. For function values, leaving "lookup-ish" functions aside,
I'd guess my own ratio is closer to 70%-30%. YMMV.
> The struct pass-by-value version of the interface avoids the pesky
> NULL pointer argument issue, but still I can't get away from it
> completely since I use 'int (*compare_function)( const void* p, const
> void* q )' to define the sorting property in my generic containers.
Sorry; I can't make sense of this. If "the pesky ... issue" is
that a struct pointer might be NULL, well, that can often be a help
rather than a harm: You can provide a NULL for an "optional" struct
pointer argument, but you cannot do so with a struct value. As for
your comparison function, the relevance escapes me: You've already
chosen to pass pointers, so what are you asking about?
--
Eric Sosman
eso...@ieee-dot-org.invalid
OK, then, here is something I don't understand: if those compilers do
that, how am I supposed to write an assembly language prologue/epilogue
to interface such things if arguments are not passed on the stack?
>
> In practice though the cost difference between allocating a struct in
> the caller and passing a pointer to it vice accepting a return value
> must be vanishingly small even if the struct does fit in a register.
> When the struct doesn't fit in a register, the two return mechanisms
> would be all but identical.
I read something recently (can't remember where) that passing arguments
in registers these days is more than likely "premature optimization", as
passing on the stack would be just as efficient given modern CPU designs.
>
> I've always used caller allocates and passes pointer for APIs.
That doesn't grok? You mean for structs as the OP asked?
> This
> is for at least 5 reasons. (1) It's efficient enough in all cases
> I've ever encountered. (2) A single convention means the caller must
> remember only a single convention. (3) You get "in", "out", and "in
> out" parameters all with the same mechanism. (4) You also get
> "optional" parameters with the same mechanism by allowing NULL to mean
> "not needed." This works for all 3 kinds. (5) It's clean in the
> implementation to uniformly use -> for all (or nearly all) element
> access rather than a mix of . and ->.
Agreed. I'll add that an uncomplicated call standard at the
implementation level is "tits".
>
> A convention I find very useful is this
>
> typedef struct foo_s {
>
> ... big collection of fields ...
>
> } FOO;
>
> // Prepares the raw memory of the foo for initialization.
> // Often just zeros or sets a flag so that later functions can
> // see it hasn't been set up yet. May even do nothing. Declare anyway
> // as a placeholder for the convention. Don't allocate anything here.
> // Can't fail, so a void function.
> void init_foo(FOO *foo);
Like a C++ constructor (with it's steroids taken away).
>
> // Make a foo fully ready for use. Allocate storage and other
> resources.
> // Return 0 on success else an error code.
> int set_up_foo(FOO *foo, ...);
Like a C++ constructor's steroidal effects.
>
> // Release all resources of a foo, returning it to the init state.
> // Return 0 on success else an error code. It's okay to set_up
> immediately
> // after a clear.
> int clear_foo(FOO *foo);
Symmetry lacking: you used 2 construction constituents but only one
destruction constituent. I think your "poor mans' OO" design is too
complex and probably missing something at the same time.
>
> Then in code:
>
> // A way to eliminate the clutter of &. YMMV.
> FOO foo_instance[1];
That is bizarre syntax.
>
> // Always init after declaration even if the init does nothing.
> init_foo(foo_instance);
A testament to C's defficiency over C++.
>
> ... yada yada ..
>
> for (...) {
>
> err = set_up_foo(foo_instance, ...);
>
> ... check for error.
>
> ... use the foo instance then release its resources
>
> err = clear_foo(foo_instance);
>
> ... check for error.
>
> ... more processing that doesn't need the foo instance.
> };
Or pass an "out" error argument to the functions. Or have an optional set
of functions that take an "out" error handler argurment. Or, learn, use,
abuse C++, the latter of which you are doing with the C code you
presented.
I really can't see that being a performance bottleneck, but you are
surely asking about theory/practice (read, being ultra-tidy in your
programming).
>
> 1. struct greg_ymd add_days( const struct greg_ymd ymd, int days );
> 2. <return type> add_days( struct greg_ymd* pymd, int days );
>
> First of all, what is the performance implications of using struct
> pass-by-value for smallish structs?
Profile it and see.
> Is there a rule of thumb of struct size for an interface API that you
> convert all struct parameter passing to use pointers?
I pass all structs by reference (I use a C/C++ compiler but limit
severely the amount of C++ things I use) and primitives by value.
> Have you used struct by value parameter passing or return value at all
> in your API design experience?
>
> The struct pass-by-value version of the interface avoids the pesky
> NULL pointer argument issue,
References are nice in that regard, but I have a feeling it is not
guaranteed portable (sorry for the C++ chat).
> but still I can't get away from it
> completely since I use 'int (*compare_function)( const void* p, const
> void* q )' to define the sorting property in my generic containers.
Default arguments (C++ again) can help with that. You can have null to
mean do an object compare or non-null to use the passed-in function.
(Side-stepping the "default arguments are evil" debate).
>
> Thanks for your time.
Much more often than not, "the pesky issue" is the case at hand.
Asserting for null pointers is a PITA and an unnecessary one. Potential
language-level solutions: references that can't be null, a "not null" or
"can be null" keyword.
How about this "rule": if it's a primitive, pass by value, else don't.
There are not stack frame guarantees that allow further portable
("overall") rules of thumb, as far as I know.
> First of all, what is the performance implications of using struct
> pass-by-value for smallish structs?
It depends upon the platform. On some platforms, a struct return is
implemented by passing in a pointer; the compiler will generate a
temporary in the caller if necessary.
That answer is lame. C is defficient and it's defficiencies must not be
glossed over... where is wikipedia when you need her!
Oh yeah, confuse all future readers by introducing yet another concept. C
IS a stack-based "language".
> you can deal with this yourself or let the compiler do it.
Yes, C is ambiguous.
> Concentrate instead on what makes the most sense for your interface.
Oh, you teaching? Someone you don't even know from Joe. Stop that!
>
> Historically only scalars could be returned because the returned
> value had to fit in a register. That restriction has been removed,
> but old interfaces live forever and with it the reluctance for
> returning structs.
Write a f!@n paper! (Or you new-fangled guys call that "Dr. Dobbs
Online"). (It was just a book chapter anyway).
It seems that you and the O.P. somehow regard null pointer values
as Bad Things, poison pills in your program. That's an attitude I can't
understand, as it seems important to be able to respond to "Get another
Thing" with "No Thing there, boss." The null-valued pointer is a very
convenient device, a way to pass either "Here's a Thing" or "No Thing"
through one channel, without the burden of inventing a separate channel
for an independent "Yes/No" answer (with "No" also having the meaning
of "Just pay no attention to that Thing in the other channel; it's not
really there").
A pointer type that cannot be null seems to me crippled, about as
useful as a numeric type that cannot be zero. A pointer that is known
to be non-null when I'm about to use it (or a number known to be non-
zero when I'm about to divide by it) is useful, but that's not the same
thing, not the same thing at all.
--
Eric Sosman
eso...@ieee-dot-org.invalid
Oh, come on! Show me a language that *prescribes* the run-time
cost of its operations -- or even the relative cost -- and I'll show
you a language with far more defficiencies [sic] than C.
--
Eric Sosman
eso...@ieee-dot-org.invalid
The only thing I can think of is it may prevent some logical error in
a bad implementation of add_days. Provided the implementation of
add_days is sound, there isn't anything 'const' buys you that I can
think of.
I personally don't have any problem with using or passing NULL
pointers myself. Unfortunately, that is not how everyone views it.
Let's watch an episode of the NULL pointer blame game.
Me: I got this new little library that does something cool.
I want you guys to try it out.
Develop: Yeah, it works pretty well, except that it crashes when
I pass a NULL pointer to the interface.
Me: So, don't pass NULL pointers, NULL pointers are not
intended to be semantically viable, like strcpy and friends,
so why should I complicate my interface to handle something
that developers should know better anyways. It's in the
documentation.
Manager: But that's the standard library and we don't have control
over that. We do have control over your library API, so why
not make it a little more robust to developer mistakes.
Testing resources can't cover everything, so why should we
make more opportunities to crash the application if we don't
have to.
Me: Invest in more testing then. We deal with strcpy issues
because we have to ... oorrrrr maybe we can just add asserts
to help the developers.
Tester: I don't like the assert idea. I don't want to have to test
two versions of the software, with and without assert.
Me: Okay, so if I make NULL pointer not crash, how do you want
to communicate the error, by return value?
Develop: Sounds fine.
Me: <grumbles>
... sometime later ...
Tester: We found some funny side effects from that new library your
using. It looks like there could be a problem.
Develop: Interesting, nothing came up in our tests. We'll ask the
library guy.
Me: Hey Developers, what's up?
Develop: The tester found some strange behavior from using your
library.
Me: Ok. What's the problem?
Develop: We're not sure, but since you're the expert on the way your
library works, we'd like your help to troubleshoot it.
Me: Fine. ... <looks at developer code and notices that there
are no error checks from using library function> ... I
noticed that you're not checking for errors from the API.
Could a NULL pointer be causing an issue?
Develop: Could be.
Me: hmmm ... thinking ... <If it's a NULL pointer again, I
should just let the NULL pointer crash their code, then at
least they know it was their fault, but management says
that they don't have resources for complete and perfect
testing, so make your library robust. I wonder how
passing struct by value would change things.>
Best regards,
John D.
What exactly do you mean by "primitive"?
If you mean that scalar types (i.e., numeric and pointer types)
should be passed by value and other types (arrays, structs, unions)
should be passed by pointer, well, that's a consistent rule, but it
fails to take advantage of C's ability to pass and return structs
and unions by value.
If you have a small struct type, I see no reason not to pass it
by value if that satisfies the semantics you need.
References are perfectly portable in C++. They don't exist in C
(which you'll notice is the language we discuss here).
The same way you would in any circumstances: by understanding the
calling convention used by the compiler, which is probably based
on the ABI for the platform.
But how often do you need to write an assembly language
prologue/epilogue anyway?
[...]
That answer is correct, and I see nothing lame about it.
Are you suggesting that the performance implications should be
defined by the language? How exactly would that work?
The purpose of a C program is not to generate CPU instructions.
CPU instructions are a tool used to create the run-time behavior
that the C program specifies; that behavior is what the program is
all about. (There are cases where you care which instructions are
generated, but such cases are, and should be, rare.)
(Why did you put the word "language" in quotation marks?)
You seem to be new here, so you may not be aware that we've
discussed this at great and tedious length before.
No, C is not a stack-based language. The word "stack" doesn't even
appear in the C language standard. The semantics of C function calls
do imply some kind of stack-like structure, but only in the sense
that storage for function calls is allocated and deallocated in a
last-in first-out manner. There is no implication of a stack laid
out in contiguous memory growing and shrinking in any consistent
direction.
Having said that, most C implementations *do* use a contiguous
stack in memory -- but not all do. There are implementations
where the storage for each function call is allocated on the heap.
The assumption of a contiguous stack is neither universally correct
nor particularly useful. And even in stack-based implementations,
it's very common to pass some arguments in registers.
http://redwing.hutman.net/~mreed/warriorshtm/android.htm
Really says it all about Kiki. I think we should all just post that URL
in response to any future posts of his...
--
"The anti-regulation business ethos is based on the charmingly naive notion
that people will not do unspeakable things for money." - Dana Carpender
Quoted by Paul Ciszek (pciszek at panix dot com). But what I want to know
is why is this diet/low-carb food author doing making pithy political/economic
statements?
Nevertheless, the above quote is dead-on, because, the thing is - business
in one breath tells us they don't need to be regulated (which is to say:
that they can morally self-regulate), then in the next breath tells us that
corporations are amoral entities which have no obligations to anyone except
their officers and shareholders, then in the next breath they tell us they
don't need to be regulated (that they can morally self-regulate) ...
http://redwing.hutman.net/~mreed/warriorshtm/troller.htm
Really says it all about Kenny. I think we should all just post that
URL
in response to any future posts of his...
If it weren't that such attention feeds his needs and he should be
just ignored. Doh!
At least for the "No Thing there, boss" case, this can also be done through
exceptions, which get you away from the channel entirely. That's actually
sort of nice in some cases, as it reduces the amount of testing you have
to do before other operations -- in some cases, anyway.
That said, you still really want a way to distinguish between "I have an
object" and "I don't have an object." Even the exception-oriented languages
I use (Ruby, Python) still have a way to set an object reference to nil/None.
(I don't know Python well enough yet to comment, but in Ruby, nil is actually
an object itself, the sole instance of NilClass.)
-s
--
Copyright 2010, all wrongs reversed. Peter Seebach / usenet...@seebs.net
http://www.seebs.net/log/ <-- lawsuits, religion, and funny pictures
http://en.wikipedia.org/wiki/Fair_Game_(Scientology) <-- get educated!
I am not speaking for my employer, although they do rent some of my opinions.
Just ignore him. We all know he's a troll; please don't feed him.
> At least for the "No Thing there, boss" case, this can also be done through
> exceptions, which get you away from the channel entirely. That's actually
> sort of nice in some cases, as it reduces the amount of testing you have
> to do before other operations -- in some cases, anyway.
>
> That said, you still really want a way to distinguish between "I have an
> object" and "I don't have an object." Even the exception-oriented languages
> I use (Ruby, Python) still have a way to set an object reference to nil/None.
FWIW, Haskell has the Maybe type constructor for this purpose:
data Maybe a = Nothing | Just a
E.g.:
Prelude> :type lookup
lookup :: (Eq a) => a -> [(a, b)] -> Maybe b
Prelude> lookup 1 [(1,"one"),(2,"two")]
Just "one"
Prelude> lookup 3 [(1,"one"),(2,"two")]
Nothing
Passing a "Maybe Foo" to a function expecting a "Foo" will result in a
type mismatch, forcing you to handle the Nothing case, e.g.:
case lookup var env of
Just x -> f x
Nothing -> ...
That's what I meant.
> but it
> fails to take advantage of C's ability to pass and return structs
> and unions by value.
I wouldn't pass a struct by value, simply to achieve greater style
consistency.
>
> If you have a small struct type, I see no reason not to pass it
> by value if that satisfies the semantics you need.
It's just one more step toward self-documenting code if using my rule.
Actually, I was making a case for pass-by-reference, I believe.
> That's an attitude I can't
> understand, as it seems important to be able to respond to "Get another
> Thing" with "No Thing there, boss." The null-valued pointer is a very
> convenient device, a way to pass either "Here's a Thing" or "No Thing"
> through one channel, without the burden of inventing a separate channel
> for an independent "Yes/No" answer (with "No" also having the meaning
> of "Just pay no attention to that Thing in the other channel; it's not
> really there").
I was saying that, given just pass-by-ptr, doesn't analyze the situation
and try to decide which is more often used (ptrs can be null or ptrs
can't be null) and therefor requires all those null ptr checks and that
is a PITA but doesn't have to be so.
>
> A pointer type that cannot be null seems to me crippled,
In lieue of references, I suggested a not_null keyword or the converse
keyword, but I'd opt for references (that can't be "null").
I don't remember. Probably because it lacks a lot of decidedly required
elements to be considered a modern language.
>
> You seem to be new here, so you may not be aware that we've
> discussed this at great and tedious length before.
Which part? The subject or ...?
>
> No, C is not a stack-based language.
Well then how can C be called from other languages if nothing about the
calling mechanism is defined? When I said "stack-based", I meant "it
behaves as if args are pushed right-to-left onto a stack..." etc.
> The word "stack" doesn't even
> appear in the C language standard. The semantics of C function calls
> do imply some kind of stack-like structure, but only in the sense
> that storage for function calls is allocated and deallocated in a
> last-in first-out manner.
Well that's understood, but it behaves that way.
> There is no implication of a stack laid
> out in contiguous memory growing and shrinking in any consistent
> direction.
I'll bet most every implementation does that though.
>
> Having said that, most C implementations *do* use a contiguous
> stack in memory -- but not all do.
See.
> There are implementations
> where the storage for each function call is allocated on the heap.
I don't need to be *that* portable. I focus on common practice, so my
assumptions seem well-founded.
> The assumption of a contiguous stack is neither universally correct
> nor particularly useful.
It is if you are just learning Assembly! =)
> And even in stack-based implementations,
> it's very common to pass some arguments in registers.
Seems like those implementations would be closed to interoperability with
most other languages and tools for they obliterate the easy-to-understand
concept of arguments passed on "the stack".
So then, unportably since a simple change of the compiler would muck-up
the assumptions.
>
> But how often do you need to write an assembly language
> prologue/epilogue anyway?
I think ever-increasingly as I find things that I don't like about the
compiler implementations or the language specification. (I'm learning
Assembly Language, so if the lights go out over there, it's probably just
me tweaking my prologue/epilogue code) =).
>>> How about this "rule": if it's a primitive, pass by value, else don't.
>>> There are not stack frame guarantees that allow further portable
>>> ("overall") rules of thumb, as far as I know.
>>
>> What exactly do you mean by "primitive"?
>>
>> If you mean that scalar types (i.e., numeric and pointer types)
>> should be passed by value and other types (arrays, structs, unions)
>> should be passed by pointer, well, that's a consistent rule,
>
> That's what I meant.
>
>> but it
>> fails to take advantage of C's ability to pass and return structs
>> and unions by value.
>
> I wouldn't pass a struct by value, simply to achieve greater style
> consistency.
That's not the only reason to pass or return by value. Modern compilers
(especially those that share code generation with C++) are very good at
optimising away the copies.
>> If you have a small struct type, I see no reason not to pass it
>> by value if that satisfies the semantics you need.
>
> It's just one more step toward self-documenting code if using my rule.
How?
--
Ian Collins
Considering that seems like premature optimization and time spent
thinking about that is time not spent on domain-level thinking, which is
a bad thing.
>
>>> If you have a small struct type, I see no reason not to pass it
>>> by value if that satisfies the semantics you need.
>>
>> It's just one more step toward self-documenting code if using my rule.
>
> How?
>
It is known that things passed by value are going to be primitive types
and not structures. A minor thing, but every little bit helps.
Going to the effort of passing by pointer is more like a premature
optimization (why else would you do it?).
>>>> If you have a small struct type, I see no reason not to pass it
>>>> by value if that satisfies the semantics you need.
>>>
>>> It's just one more step toward self-documenting code if using my rule.
>>
>> How?
>
> It is known that things passed by value are going to be primitive types
> and not structures. A minor thing, but every little bit helps.
Why does that matter?
If you were performing complex arithmetic, would you pass the two
complex number by value, or pointer?
--
Ian Collins
Because if you analyze the "extreme" case, you would pass those kinds of
things by ptr (by reference if in C++) so there is a "correctness" to the
"madness".
>
>>>>> If you have a small struct type, I see no reason not to pass it
>>>>> by value if that satisfies the semantics you need.
>>>>
>>>> It's just one more step toward self-documenting code if using my
>>>> rule.
>>>
>>> How?
>>
>> It is known that things passed by value are going to be primitive
>> types
>> and not structures. A minor thing, but every little bit helps.
>
> Why does that matter?
>
> If you were performing complex arithmetic, would you pass the two
> complex number by value, or pointer?
>
Probably by value because it "feels" like a primitive type. The *general*
rule is to pass structs by reference, there are exceptions to the rule,
but performance would probably not be part of the analysis in considering
when to make an exception to the rule just because the struct was small.
> So then, unportably since a simple change of the compiler would muck-up
> the assumptions.
Er, duh.
You're talking about trying to write assembly to interface with another
language's calling conventions. This is an EXEMPLAR of "unportable".
That said, in most cases, the ABI stays pretty stable for a fairly
long time, meaning that you should be able to predict the argument
passing behaviors for any specific function for any specific target
quite well.
>> But how often do you need to write an assembly language
>> prologue/epilogue anyway?
> I think ever-increasingly as I find things that I don't like about the
> compiler implementations or the language specification.
Down this path lies madness. You're going to waste more time than you
save, by several orders of magnitude.
The same way any other languages can call each other -- by definitions
of calling conventions being provided *by the platform*. The ABI,
usually.
>> There is no implication of a stack laid
>> out in contiguous memory growing and shrinking in any consistent
>> direction.
> I'll bet most every implementation does that though.
The majority, yes, but... Not as consistently as you might think. For
instance, if you are assuming that the local variables for each function
are contiguous, you're just plain wrong. That's not true on several
ABIs that are in fairly widespread use.
>> The assumption of a contiguous stack is neither universally correct
>> nor particularly useful.
> It is if you are just learning Assembly! =)
Actually, then it's even worse. If you make up silly theories about
how things really work in C, it usually has no major effect. If you do
that in assembly, you're doomed.
> Seems like those implementations would be closed to interoperability with
> most other languages and tools for they obliterate the easy-to-understand
> concept of arguments passed on "the stack".
Not at all.
ABI for the win. As long as you define WHICH registers are to be used,
and how, and in what order, and when, it's perfectly suitable. It doesn't
matter what the rules are, so long as they're documented and people can
follow them.
You began with the *goal* of writing non-portable code, and
now you raise the objection that it's non-portable? "Doctor, it
hurts when I stick my thumb in my eye."
Also, you've missed the meaning of the last phrase in Keith's
post: "... which is probably based on the ABI for the platform."
Unless you're writing to the bare metal with no O/S in sight, a
platform will have established conventions for communication between
a programming language and the O/S services, and often for at least
some of the communication between different languages. A compiler
for the platform will perforce follow those conventions, or it will
find itself unable to get along with other languages and/or the O/S.
It is true that a platform ABI may omit some items of interest
to some of its supported languages. An O/S whose services deal only
in integers and addresses and bags of bytes may not prescribe how
floating-point values should be passed around. Few platforms say
how a Lisp-style List is passed, or even encoded. And sometimes it
may happen that language X has an X-specific type and that two X
compilers manage the type differently, so components built by the
gxx and MSX compilers don't play nicely together. Well, that's life:
If you want to join the games, you have to learn the rules.
>> But how often do you need to write an assembly language
>> prologue/epilogue anyway?
>
> I think ever-increasingly as I find things that I don't like about the
> compiler implementations or the language specification. (I'm learning
> Assembly Language, so if the lights go out over there, it's probably just
> me tweaking my prologue/epilogue code) =).
If C is not the right language for your purposes, use a different
language. That will be far more productive than whining -- and it will
also be more productive than trying to twist C to your liking, whether
with macro magic, assembly abracadabra, or library lunacy. Trust me:
I've been there, I bear the scars.
--
Eric Sosman
eso...@ieee-dot-org.invalid
I suggest that "pass small types by value, large types by pointer" is a
more sensible rule. All scalar (what you call "primitive") types are
small; some struct and union types small.
I agree with Ian that passing small structures by pointer just because
they're pointers is premature optimization.
(Of course anything, scalar or not, can sensibly be passed by pointer
if the called function needs to modify its value. Or you can pass
it by value and return the new value.)
If you write straight C code that doesn't make any assumptions beyond
what's guaranteed by the standard, your code can be 100% portable to
all conforming C implementations. You don't have to know *or care*
whether function activation records are allocated on a stack or a
heap, or whether arguments are passed on the stack, in registers,
or by carrier pigeon. You pass expressions as arguments, you refer
to the corresponding parameters by name inside the function, and
it all just works -- as long as you don't do something that goes
beyond the guarantees the language gives you.
>> The assumption of a contiguous stack is neither universally correct
>> nor particularly useful.
>
> It is if you are just learning Assembly! =)
comp.lang.c is not a good place to learn assembly language.
C is not a very high-level language, but it's substantially higher than
assembly language. Most of things you're worrying about (and that
I'm sure are very valid concerns if you're writing assembly language)
are irrelevant in C, because they're below the level of abstraction
defined by the C language.
If you want to program in assembly language and worry about registers
vs. stacks and all that, feel free. There can be perfectly valid
reasons for choosing assembly language over C. And if you want to
tinker with the machine language prologue and epilogue generated
by your C compiler, you can do that too -- but it has to do with
your compiler, not with the C language. This is just the wrong
place to get advice about it.
>> And even in stack-based implementations,
>> it's very common to pass some arguments in registers.
>
> Seems like those implementations would be closed to interoperability with
> most other languages and tools for they obliterate the easy-to-understand
> concept of arguments passed on "the stack".
Nope. (Seebs already explained this.)
But I am planning on working outside of C and in Assembly Language.
>
>>> The assumption of a contiguous stack is neither universally correct
>>> nor particularly useful.
>>
>> It is if you are just learning Assembly! =)
>
> comp.lang.c is not a good place to learn assembly language.
Note that I am interested in runtime environments and code generation at
this time because I plan on building a compiler.
>
> C is not a very high-level language, but it's substantially higher than
> assembly language. Most of things you're worrying about (and that
> I'm sure are very valid concerns if you're writing assembly language)
> are irrelevant in C, because they're below the level of abstraction
> defined by the C language.
Unless you're writing a C compiler. =)
>
> If you want to program in assembly language and worry about registers
> vs. stacks and all that, feel free.
It's a stepping-stone to generating the assembly from a compiler.
> There can be perfectly valid
> reasons for choosing assembly language over C. And if you want to
> tinker with the machine language prologue and epilogue generated
> by your C compiler, you can do that too -- but it has to do with
> your compiler, not with the C language. This is just the wrong
> place to get advice about it.
I wasn't doing that (I have the Dragon book and others), but
understanding of call stacks and the like make for better programmers
IMHO (and someone has to *implement* the language afterall) so it seems
on-topic to me to discuss stacks and such even if it's just one popular
implementation of the runtime environment of C. Or is this ng limited to
just *usage* of C?
>
>>> And even in stack-based implementations,
>>> it's very common to pass some arguments in registers.
>>
>> Seems like those implementations would be closed to interoperability
>> with
>> most other languages and tools for they obliterate the
>> easy-to-understand
>> concept of arguments passed on "the stack".
>
> Nope. (Seebs already explained this.)
>
Well I'll figure it "all" out sooner or later as I implement it.
I guess what I am missing is the information of how much interoperability
there actually is or isn't across hardware, OSes, compilers for the
"simple" task of calling a C function.
>
>>> There is no implication of a stack laid
>>> out in contiguous memory growing and shrinking in any consistent
>>> direction.
>
>> I'll bet most every implementation does that though.
>
> The majority, yes, but... Not as consistently as you might think. For
> instance, if you are assuming that the local variables for each
> function
> are contiguous, you're just plain wrong.
I would not assume that and I have been remotely thinking about that as I
develop a scope-management strategy.
> That's not true on several
> ABIs that are in fairly widespread use.
>
>>> The assumption of a contiguous stack is neither universally correct
>>> nor particularly useful.
>
>> It is if you are just learning Assembly! =)
>
> Actually, then it's even worse. If you make up silly theories about
> how things really work in C, it usually has no major effect. If you do
> that in assembly, you're doomed.
You are assuming too much given such miniscule information about what I'm
working on.
>
>> Seems like those implementations would be closed to interoperability
>> with
>> most other languages and tools for they obliterate the
>> easy-to-understand
>> concept of arguments passed on "the stack".
>
> Not at all.
>
> ABI for the win. As long as you define WHICH registers are to be used,
> and how, and in what order, and when, it's perfectly suitable.
And I *have* seen that documentation on the MS site while I was reading
about naked function calls, but even so, I guess I was hoping for more
consistency than that. I know now though. Thx for reiterating that. It
helps to just gab about things sometimes and get verification of what
things mean outside of a specific platform.
> It doesn't
> matter what the rules are, so long as they're documented and people can
> follow them.
Got the 10000 foot view now. Now I'll ponder how much interoperability
there actually is in practice (not much apparently) and surely that will
lead to object formats and linkers probably, but that's way out there as
I just need enought tools to play in my own little sandbox. =)
That's fine. Some order to the chaos is better than none.
> All scalar (what you call "primitive") types are
> small; some struct and union types small.
>
> I agree with Ian that passing small structures by pointer just because
> they're pointers is premature optimization.
Would you care to restate that?
>
> (Of course anything, scalar or not, can sensibly be passed by pointer
> if the called function needs to modify its value.
That's an addendum to the other rule, as the complex number thing may be
(it's all a matter of personal style of course, so there is no right or
wrong answer).
Well that's some relief then.
>
>>> But how often do you need to write an assembly language
>>> prologue/epilogue anyway?
>
>> I think ever-increasingly as I find things that I don't like about the
>> compiler implementations or the language specification.
>
> Down this path lies madness. You're going to waste more time than you
> save, by several orders of magnitude.
>
I won't know that it is not viable until I fail at it though. I don't
know of how many obstacles are on the road ahead of me. One that I'm sure
is there but I won't get to for awhile is concurrency and how it
complicates the compiler-writer's job.
Yes! That's exactly the task at hand! I want to be able to call C
functions from Assembly Language (which of course is documented in many
places and I'm not asking how to do that here).
> and often for at least
> some of the communication between different languages. A compiler
> for the platform will perforce follow those conventions, or it will
> find itself unable to get along with other languages and/or the O/S.
>
> It is true that a platform ABI may omit some items of interest
> to some of its supported languages. An O/S whose services deal only
> in integers and addresses and bags of bytes may not prescribe how
> floating-point values should be passed around.
There's one of those potential obstacles for me: implementing floating
point (IEEE standard should help though), but maybe I won't need to go
that far.
> Few platforms say
> how a Lisp-style List is passed, or even encoded. And sometimes it
> may happen that language X has an X-specific type and that two X
> compilers manage the type differently, so components built by the
> gxx and MSX compilers don't play nicely together. Well, that's life:
> If you want to join the games, you have to learn the rules.
Or create new ones. =)
>
>>> But how often do you need to write an assembly language
>>> prologue/epilogue anyway?
>>
>> I think ever-increasingly as I find things that I don't like about the
>> compiler implementations or the language specification. (I'm learning
>> Assembly Language, so if the lights go out over there, it's probably
>> just
>> me tweaking my prologue/epilogue code) =).
>
> If C is not the right language for your purposes, use a different
> language.
I'll still have to interface C though.
> That will be far more productive than whining
Who's whining? I'm taking the bull by the horns!
>-- and it will
> also be more productive than trying to twist C to your liking, whether
> with macro magic, assembly abracadabra, or library lunacy. Trust me:
> I've been there, I bear the scars.
Perhaps you didn't go far enough?
I meant "just because they're structures".
[...]
I don't do it for performance reasons really, I forward-think to the time
when I'll have to write the code to generate assembly to pass structs and
passing an address is a whole lot simpler (trivial in comparison to
pass-struct-by-value).
Well then, don't complain about the importabilities inherent
in your chosen course.
>> and often for at least
>> some of the communication between different languages. A compiler
>> for the platform will perforce follow those conventions, or it will
>> find itself unable to get along with other languages and/or the O/S.
>>
>> It is true that a platform ABI may omit some items of interest
>> to some of its supported languages. An O/S whose services deal only
>> in integers and addresses and bags of bytes may not prescribe how
>> floating-point values should be passed around.
>
> There's one of those potential obstacles for me: implementing floating
> point (IEEE standard should help though), but maybe I won't need to go
> that far.
You still don't understand. Appealing to a standard that talks
about the representations of F-P numbers and the operations on them
gives you *no* help in figuring out how to hand them from place to
place. Since you have not yet grasped this rather essential (and
rather basic) point, I urge you to stop in your tracks before you do
yourself and others harm. You are a three-year-old trying to drive
an eighteen-wheeler, able to reach the pedals only if you crouch so
low you can't see through the windshield.
Quite seriously:
YOU DON'T KNOW WHAT YOU'RE DOING.
But, okay, all right, you'll have to learn for yourself. Callow
youth has never in all the history of Man listened to the voice of
experience, and you appear to be no exception.
>> Few platforms say
>> how a Lisp-style List is passed, or even encoded. And sometimes it
>> may happen that language X has an X-specific type and that two X
>> compilers manage the type differently, so components built by the
>> gxx and MSX compilers don't play nicely together. Well, that's life:
>> If you want to join the games, you have to learn the rules.
>
> Or create new ones. =)
If you run the bases backwards, you are out. O-U-T, out, and no
amount of squawking about your highly original rules will convince the
umpire otherwise.
>>>> But how often do you need to write an assembly language
>>>> prologue/epilogue anyway?
>>>
>>> I think ever-increasingly as I find things that I don't like about the
>>> compiler implementations or the language specification. (I'm learning
>>> Assembly Language, so if the lights go out over there, it's probably
>>> just
>>> me tweaking my prologue/epilogue code) =).
>>
>> If C is not the right language for your purposes, use a different
>> language.
>
> I'll still have to interface C though.
I don't see why. "If C is not the right language..." means there's
no reason for C to enter your picture at all. If C *does* enter your
picture, it follows that C *is* the right language (for suitable and
variable definitions of "right"), and if so you need to learn about C.
>> That will be far more productive than whining
>
> Who's whining? I'm taking the bull by the horns!
You are whining. And slinging the bull, too.
>> -- and it will
>> also be more productive than trying to twist C to your liking, whether
>> with macro magic, assembly abracadabra, or library lunacy. Trust me:
>> I've been there, I bear the scars.
>
> Perhaps you didn't go far enough?
Perhaps. Beyond madness, failure, and defeat, say some mystics,
lies salvation. I count myself a skeptic.
--
Eric Sosman
eso...@ieee-dot-org.invalid
How often do you do that? That last time I had to do that was in the 80s...
--
Ian Collins
You mentioned that you're writing a compiler. For what language? If
it's a C compiler, you have to be able to pass structures by value.
For what language? Isn't it obvious (?): The Better C/C++! You not need
be so elementary with me as I'm probably in the top 20% as far as the
body of knowledge goes (that's the "short answer", if you want the "long"
answer, just ask, but it will bore the room to death and bring out every
warrior in the place for sure, just kidding).
Thanks. I've *never* done it! You *had* to do it? I'm doing it because I
*want* to do it.
Yes, back in the days before compilers learnt how to optimise. These
days with common compiler techniques like RVO, there's no point.
--
Ian Collins
[snipped "troll bait"]
The question becomes, is he or isn't he? Kennedy thought he was
successful in baiting me but I said no he was not so maybe you're just
trying to up the ante?
And I thought RVO was a C++ thing! There's no point to what?
Have you heard of Dunning-Kruger?
Have you heard of rape?
*PLONK*