Function Wrapper: Opinions and Critique Desired

I

I.M. !Knuth

A while back, I was mucking around with a recursive function. For
brevity's sake, I'll define it like this:

int func_recurs(int arg1, int arg2, int prev_pos)
{
int crnt_pos = 0;
int result;

/* stuff happens to arg1, arg2, and crnt_pos */

func_recurs(arg1, arg2, (prev_pos + crnt_pos) );

return result;
}

The critical part here (as I'm sure you've figured out already) is that
prev_pos (the previous position) of the last recursion is combined with
crnt_pos (the current positon) of the present recursion and passed along
to become the prev_pos of the next recursion.

But written this way, I had to remember that any call to func_recurs()
always had to look like this:

func_recurs(arg1, arg2, 0);

Frankly, I thought that constant 0 dangling at the end was rather
inelegant. Then it struck me that maybe I could "hide" func_recurs
inside another function so that I wouldn't have to strain my brain
remembering about that pesky third argument; something like:

func_lazymemory(int arg1, int arg2)
{
func_recurs(arg1, arg2, 0);
}

Being new to C, I felt pretty clever for coming up with this all on my
own. But then I thought: "Bah! Smells of kludge. There must be a
better way."

So I changed it to:

int func_recurs(int arg1, int arg2)
{
--> static int prev_pos; /* conveniently init'd to 0 on 1st call */
int crnt_pos = 0;
int result;

/* stuff happens to arg1, arg2, and crnt_pos */

--> prev_pos += crnt_pos; /* adjust for next recursion */
--> func_recurs(arg1, arg2);
--> prev_pos -= crnt_pos; /* restore for current recursion */

return result;
}

Drawing your attention to the four "-->" lines, I then thought: "Boy is
that ugly", but I no longer felt I'd somehow cheated inside my code. I
packed away the function and forgot about it ... until tonight.

Tonight I was browsing through some months' old threads on clc and came
across one that discussed function wrappers. "Cool," I thought. A
response referenced the FAQ, so I pulled it up and had a read. So, my
clever idea is old hat. Figures! :-D

I started thinking again about how I dealt with that function and came
up with some questions and concerns:

1a) Is my use of a wrapper appropriate (or am I ignorant of some wrapper
rule or style convention)?

1b) Wouldn't the wrapper be improved (and the program conserve some
storage) if it were re-written to pass pointer values like this?

func_lazymemory(int *arg1, int *arg2)
{
func_recurs(arg1, arg2, 0);
}

2a) Is one of my solutions better/cleaner/preferable over the other?

2b) Or are they equivalent, and picking one is a matter of (my) personal
style?

3) Is there some other (maybe obvious) solution to ditching that third
argument that I've somehow overlooked?


Thanks for your attention.
 
M

Morris Dovey

I.M. !Knuth (in [email protected]) said:

| 3) Is there some other (maybe obvious) solution to ditching that
| third argument that I've somehow overlooked?

There is usually some other method (no matter what you're doing) :)

I have two short pieces of recursive code that you might find
interesting:

http://www.iedu.com/mrd/c/getsm.c and
http://www.iedu.com/mrd/c/getsmx.c that deal with a similar situation.

The two are functionally equivalent; but the second module "cheats" in
that it uses a gcc extension to allow embedding one function
declaration inside another. It's non-standard but may have some value
as a thought-provoking device.
 
B

Barry Schwarz

A while back, I was mucking around with a recursive function. For
brevity's sake, I'll define it like this:

int func_recurs(int arg1, int arg2, int prev_pos)
{
int crnt_pos = 0;
int result;

/* stuff happens to arg1, arg2, and crnt_pos */

func_recurs(arg1, arg2, (prev_pos + crnt_pos) );

return result;
}

The critical part here (as I'm sure you've figured out already) is that
prev_pos (the previous position) of the last recursion is combined with
crnt_pos (the current positon) of the present recursion and passed along
to become the prev_pos of the next recursion.

But written this way, I had to remember that any call to func_recurs()
always had to look like this:

func_recurs(arg1, arg2, 0);

Frankly, I thought that constant 0 dangling at the end was rather
inelegant. Then it struck me that maybe I could "hide" func_recurs
inside another function so that I wouldn't have to strain my brain
remembering about that pesky third argument; something like:

func_lazymemory(int arg1, int arg2)
{
func_recurs(arg1, arg2, 0);
}

Being new to C, I felt pretty clever for coming up with this all on my
own. But then I thought: "Bah! Smells of kludge. There must be a
better way."

So I changed it to:

int func_recurs(int arg1, int arg2)
{
--> static int prev_pos; /* conveniently init'd to 0 on 1st call */

Just a nit. Static variables are initialized prior to your program
beginning execution and independent of your function.


Remove del for email
 
K

Keith Thompson

Barry Schwarz said:
On Wed, 19 Jul 2006 06:33:14 +0000 (UTC), "I.M. !Knuth"


Just a nit. Static variables are initialized prior to your program
beginning execution and independent of your function.

True -- but, by the as-if rule, an implementation conceivably *could*
delay the initialization until the first call. (Nothing outside the
function can see the variable before that and detect that it hasn't
been initialized. Local static variables can be seen via pointers
outside the function that defines them, but there's no way to
initialize such a pointer before calling the function.)

But I can't think of a good reason for an implementation to do such a
silly thing.
 
I

I.M. !Knuth

I have two short pieces of recursive code that you might find
interesting:

http://www.iedu.com/mrd/c/getsm.c and
http://www.iedu.com/mrd/c/getsmx.c that deal with a similar situation.

The two are functionally equivalent; but the second module "cheats" in
that it uses a gcc extension to allow embedding one function
declaration inside another. It's non-standard but may have some value
as a thought-provoking device.

My, what large comment blocks you have. :-D

I found a number of (even trivial) things thought-provoking. The
imbedded function (getsmx.c) is outright trippy; it would have taken me
a while just to find it without your comments marking its start and end,
and even more time to figure out was going on.

Where you comment like this:

#include <stdio.h> /* fgetc() */

I've been commenting in the reverse:

fgetc(foo); /* <stdio.h> */

I haven't yet noodled much with structures, nor braved malloc at all, so
I'm a bit lost on how you entirely eliminated...

typedef struct {...} getstr; /* getsm.c */

....from getsmx.c. Though I can see where the structure's contents moved
to. I'll take another look after I get some sleep...

Why have some local variables been explicitly declared with automatic
storage duration?

Having an #ifdef encapsulated "in-line" test main() is frickin' cool. I'll
have to borrow this idea. ;-)

I note you use **argv as a parameter to main() instead of the conventional
*argv[]. I like that. The latter confused and frightened me when I was
first learning pointers. Even now, the former seems clearer to me.

I note also that you don't make use at all of argc. This made me laugh. I
recently finished a program that made extensive use of **argv, but I
couldn't find a use for argc. Everytime I re-compiled it I got the
message:

Warning: 'argc' initialized but never used.

....or some such. I contemplated putting in some dummy code just to make
the warning go away. Does gcc not spit out a similar warning?


Thank you most kindly for letting me peek inside your code.
 
I

I.M. !Knuth

Barry Schwarz said:
Just a nit. Static variables are initialized prior to your program
beginning execution and independent of your function.

D'oh! I knew that. Really, I did.
 
I

I.M. !Knuth

Keith Thompson said:
True -- but, by the as-if rule, an implementation conceivably *could*
delay the initialization until the first call. (Nothing outside the
function can see the variable before that and detect that it hasn't
been initialized. Local static variables can be seen via pointers
outside the function that defines them, but there's no way to
initialize such a pointer before calling the function.)

But I can't think of a good reason for an implementation to do such a
silly thing.

So, hypothetically, I only look dumb under some implementation-specific
circumstances. :-D

BTW, what's the "as-if rule"?
 
K

Keith Thompson

I.M. !Knuth said:
So, hypothetically, I only look dumb under some implementation-specific
circumstances. :-D

Um, sure. :cool:}
BTW, what's the "as-if rule"?

The general idea is that the standard describes the behavior of an
abstract machine, but the actual generated code is allowed to behave
differently as long as the result is *as if* it followed the semantics
described in the standard. It allows for optimizations such as
eliminating calculations whose results are never used.

C99 5.1.2.2.3p3:

In the abstract machine, all expressions are evaluated as
specified by the semantics. An actual implementation need not
evaluate part of an expression if it can deduce that its value is
not used and that no needed side effects are produced (including
any caused by calling a function or accessing a volatile object).

C99 5.1.2.2.3p5:

The least requirements on a conforming implementation are:

-- At sequence points, volatile objects are stable in the sense
that previous accesses are complete and subsequent accesses
have not yet occurred.

-- At program termination, all data written into files shall be
identical to the result that execution of the program according
to the abstract semantics would have produced.

-- The input and output dynamics of interactive devices shall take
place as specified in 7.19.3. The intent of these requirements
is that unbuffered or line-buffered output appear as soon as
possible, to ensure that prompting messages actually appear
prior to a program waiting for input.
 
J

jaysome

So, hypothetically, I only look dumb under some implementation-specific
circumstances. :-D

BTW, what's the "as-if rule"?

The intention was to provide direction through example. The examples
were meant to be unambiguous. I think the committee achieved that for
the most part.

Best regards
 
K

Keith Thompson

jaysome said:
On Thu, 20 Jul 2006 06:29:49 +0000 (UTC), "I.M. !Knuth"


The intention was to provide direction through example. The examples
were meant to be unambiguous. I think the committee achieved that for
the most part.

Um, what does that have to with the "as-if" rule?
 
M

Michael Wojcik

A while back, I was mucking around with a recursive function. For
brevity's sake, I'll define it like this:

int func_recurs(int arg1, int arg2, int prev_pos)
...

But written this way, I had to remember that any call to func_recurs()
always had to look like this:

func_recurs(arg1, arg2, 0);

Frankly, I thought that constant 0 dangling at the end was rather
inelegant. Then it struck me that maybe I could "hide" func_recurs
inside another function so that I wouldn't have to strain my brain
remembering about that pesky third argument; something like:

func_lazymemory(int arg1, int arg2)
{
func_recurs(arg1, arg2, 0);
}

Being new to C, I felt pretty clever for coming up with this all on my
own. But then I thought: "Bah! Smells of kludge. There must be a
better way."

There are other ways, certainly. But this style - the recursive
function that includes parameters for current state, and a wrapper
that passes the initial state - is very common in functional
programming. You'll often find it in programs written in Lisp, SML,
and other functional languages.

So it's a common idiom. I wouldn't consider it a kluge at all.

One change I would make is to give func_recurs internal linkage (by
adding the "static" sc-specifier), since the public interface to this
operation is now func_lazymemory.
 
M

Morris Dovey

My ISP is dropping over half of the clc postings. I took a quick look
through Google's archive to see what I've missed and found IM!Knuth's
reply with questions and comments. Sorry I'm so late responding.

| My, what large comment blocks you have. :-D

Deteriorating eyesight. They're easier for me to find if I draw a box
around 'em. :)

| I haven't yet noodled much with structures, nor braved malloc at
all, so I'm
| a bit lost on how you entirely eliminated...
|
| typedef struct {...} getstr; /* getsm.c */
|
| ...from getsmx.c. Though I can see where the structure's contents
moved
| to. I'll take another look after I get some sleep...

The variables defined in the containing function are known in the
contained function.

| Why have some local variables been explicitly declared with
automatic
| storage duration?

Good question. In writing/reading recursive functions it's generally a
good idea to be aware of how much 'baggage' is being carried with each
recursion. It's really just there to call attention to the fact that
"here's some baggage".

| Having an #ifdef encapsulated "in-line" test main() is frickin'
cool. I'll
| have to borrow this idea. ;-)

Actually, it's laziness. It's a way for me to always have a unit test
module handy when I need one - without having to remember where I
saved it. It's also one more way for me to remind myself how I planned
to use/invoke the function when I wrote it. I have a really powerful
memory - but it's all short-term. :)

| I note you use **argv as a parameter to main() instead of the
conventional
| *argv[]. I like that. The latter confused and frightened me when I
was
| first learning pointers. Even now, the former seems clearer to me.

It's one of those "six of one, half dozen of the other" kinds of
things. Probably an indicator that my wheels are in a rut.

| I note also that you don't make use at all of argc. This made me
laugh. I
| recently finished a program that made extensive use of **argv, but I
| couldn't find a use for argc. Everytime I re-compiled it I got the
| message:
|
| Warning: 'argc' initialized but never used.
|
| ...or some such. I contemplated putting in some dummy code just to
make
| the warning go away. Does gcc not spit out a similar warning?

I think so (no gcc on this machine) but you can pacify it by adding a
statement (which is probably discarded during compilation) something
like:

(void) argc;

| Thank you most kindly for letting me peek inside your code.

You're entirely welcome. Most of that stuff came out of discussions
here on clc and everything except 'snuf' is benign. The snuf program
is a Linux executable used to kill the first instance of a process by
name (rather than by PID number). It's handy for dealing with
non-terminating loops and similar "oops!" situations.
 
I

I.M. !Knuth

Morris Dovey said:
My ISP is dropping over half of the clc postings. I took a quick look
through Google's archive to see what I've missed and found IM!Knuth's
reply with questions and comments. Sorry I'm so late responding.

No apology necessary. As it happens, your predicament presents me with
an opportunity to repay your kindness. My ISP has good completion but
only 2 months retention. This has been annoying me greatly since I
started following clc. I tried backtracking on Google, but (because my
eyesight isn't worth writing home about either) enlarging the font size
just causes those annoying ads to get in the way; so I searched around
for a good free text server and found:

news.cambrium.nl /* may I plug open NNTP servers here? */

As of this post, it's got over 44,000 messages in clc dating back to mid
October of last year with -- as far as I can tell -- rock solid
completion. I've been reading off it for about a week now. Hope this
helps you out. ;-)

And now back to our program....

| ...I'm a bit lost on how you entirely eliminated...
|
| typedef struct {...} getstr; /* getsm.c */
|
| ...from getsmx.c. Though I can see where the structure's contents
| moved to. I'll take another look after I get some sleep...

The variables defined in the containing function are known in the
contained function.

Oh hey, I never noticed that ... and that isn't even the part that was
messing with me. :-D
| Why have some local variables been explicitly declared with automatic
| storage duration?

Good question. In writing/reading recursive functions it's generally a
good idea to be aware of how much 'baggage' is being carried with each
recursion. It's really just there to call attention to the fact that
"here's some baggage".

Sound advice noted.

<snip>

Thanks again for broadening my horizons (even if ever so slightly).
 
I

I.M. !Knuth

...so I searched around for a good free text server and found:

news.cambrium.nl /* may I plug open NNTP servers here? */

Oops! I should have added that it doens't allow posting.
 
I

I.M. !Knuth

There are other ways, certainly. But this style - the recursive
function that includes parameters for current state, and a wrapper
that passes the initial state - is very common in functional
programming. You'll often find it in programs written in Lisp, SML,
and other functional languages.

So it's a common idiom. I wouldn't consider it a kluge at all.

Cool. And reassuring. It felt like a kludge because it just seemed
*too* easy. :)
One change I would make is to give func_recurs internal linkage (by
adding the "static" sc-specifier), since the public interface to this
operation is now func_lazymemory.

Now here you had me scratching my head (and searching the FAQ).

So if I read you correctly, your recommending that I define the
functions like this:

int func_lazymemory(int arg1, int arg2)
{ ... }

....and...

int func_recurs(static int arg1, static int arg2, int prev_pos)
{ ... }

....so that storage will only be allocated once for both arg1 and arg2.
Correct?

If so, this handily provides and alternate answer to question 1b from my
original post (which I just now noticed I bungled) where I was trying to
pass pointers to conserve storage:

/* Let the prototypes be: */

int func_lazymemory(int, int);
int func_recurs(int *, int *, int);


/* And the definition of func_lazymemory() be */

int func_lazy memory(int arg1, int arg2)
{
/* stuff happens, etc. */

func_recurs(&arg1, &arg1, 0);

return result;
}

Ultimately, this would have the same net effect, no?
 
I

I.M. !Knuth

Morris Dovey said:
| I note also that you don't make use at all of argc. This made me
| laugh. I recently finished a program that made extensive use of
| **argv, but I couldn't find a use for argc. Everytime I re-compiled
| it I got the message:
|
| Warning: 'argc' initialized but never used.
|
| ...or some such. I contemplated putting in some dummy code just to
| make the warning go away. Does gcc not spit out a similar warning?

I think so (no gcc on this machine) but you can pacify it by adding a
statement (which is probably discarded during compilation) something
like:

(void) argc;

Just tried it, and yep, it does the trick. Casting to void never would
have occurred to me.

Thanks again.
 
K

Keith Thompson

I.M. !Knuth said:
Now here you had me scratching my head (and searching the FAQ).

So if I read you correctly, your recommending that I define the
functions like this:

int func_lazymemory(int arg1, int arg2)
{ ... }

...and...

int func_recurs(static int arg1, static int arg2, int prev_pos)
{ ... }

...so that storage will only be allocated once for both arg1 and arg2.
Correct?

No, that's not even legal. He's suggesting that you make the
*functions* static, not their parameters.

int func_lazymemory(int arg1, int arg2)
{ ... }

static int func_recurs(int arg1, int arg2, int prev_pos)
{ ... }

The only effect of the "static" keyword here is that the function name
"func_recurs" is not visible outside the current translation unit.
(Making it more globally visible would probably be harmless, but it
clutters the namespace.) There's no effect on allocation.
 
I

I.M. !Knuth

No, that's not even legal. He's suggesting that you make the
*functions* static, not their parameters.

int func_lazymemory(int arg1, int arg2)
{ ... }

static int func_recurs(int arg1, int arg2, int prev_pos)
{ ... }

D'oh! <slaps forehead>

Thanks.
The only effect of the "static" keyword here is that the function name
"func_recurs" is not visible outside the current translation unit.
(Making it more globally visible would probably be harmless, but it
clutters the namespace.) There's no effect on allocation.

So this *does* come down to Q1.29 in the FAQ. I skimmed over the
namespace stuff to get to the "juicy bits" I thought pertained to me.
I'll reread it more slowly. I better flip open K&R too. I thought
"static" just kept a variable's storage alive within file scope. So by
"the current translation unit" you mean inside the function wrapper?

I'm so confused....
 
R

Richard Heathfield

[much apparently random snippage, in an attempt to clarify exactly what I'm
answering and why]
I.M. !Knuth said:
[...] He's suggesting that you make the *functions* static, not their
parameters.

int func_lazymemory(int arg1, int arg2)
{ ... }

static int func_recurs(int arg1, int arg2, int prev_pos)
{ ... }
[...]
The only effect of the "static" keyword here is that the function name
"func_recurs" is not visible outside the current translation unit. [...]
[...] I thought
"static" just kept a variable's storage alive within file scope. So by
"the current translation unit" you mean inside the function wrapper?

No.

Imagine a C source file. Got that? Just one. Now imagine that the C
preprocessor has done its thing, and #included every #include it's been
told about, recursively, until there is no more #including to be done. Once
that's out of the way, we have a single unit of the program that is waiting
to be translated. We call this a "translation unit". And so does the
Standard. In the following quote from the Standard, I have marked off one
sentence with *** *** marks because it is particularly relevant to you
right now.


2.1.1.1 Program structure

A C program need not all be translated at the same time. The text
of the program is kept in units called source files in this Standard.
A source file together with all the headers and source files included
via the preprocessing directive #include , less any source lines
skipped by any of the conditional inclusion preprocessing directives,
is called a translation unit. Previously translated translation units
may be preserved individually or in libraries. ***The separate
translation units of a program communicate by (for example) calls to
functions whose identifiers have external linkage***, by manipulation of
objects whose identifiers have external linkage, and by manipulation
of data files. Translation units may be separately translated and
then later linked to produce an executable program.


If a function is static, it doesn't have external linkage, and can't be
called-by-name from other translation units.
 
I

I.M. !Knuth

Imagine a C source file. Got that?....
<snip>

Ouch! You do carry a big stick, Sir.

Yeah, I got it. For one thing, I was messing up my own definitions of
global vs. static, and the FAQ's glossary filled me in on "translation
unit". Upon re-reading, Mr. Thompson clearly stated everything I needed
to know.

It's getting into the wee hours in my time zone, and I've had a long
day. I'd better just go sleep on it before I cause any more trouble.
:)

Thanks all, and g'night.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,755
Messages
2,569,539
Members
45,024
Latest member
ARDU_PROgrammER

Latest Threads

Top