The lack of default function parameter in C99 makes compatibility difficult.

M

Michael Tsang

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

Assume that you have written a library using plain standard C99 and you want
to extend the functionality of a function. However, the added functionality
requires some optional parameters. For example, in C++, you can change:

void foo(int a);

to

void foo(int a, const char *b = NULL);

and modify the definition. In this way, new functionality is added to foo
and the compatibility of old code (which calls foo with a single parameter)
is retained. However, in C99, you can't do this. You may consider writing a
new function in the library, however, if the new functionality only requires
very little changes in the original function, you may end up duplicating a
lot of code in the old function and the new function, and one more
identifier is now added into the namespace. Therefore, when more and more
functionality is added into the library, more and more identifier is added
and the code is finally unmaintainable (due to lots of duplicated code). A
new library version is therefore needed.
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.9 (GNU/Linux)

iEYEARECAAYFAkr5dTYACgkQG6NzcAXitM+n9wCgjlIMDxlzojl8vuEF44zcIFfX
+GsAn2JqI340NGAUyBADwjNko9noH2Cy
=GpjM
-----END PGP SIGNATURE-----
 
T

Tom St Denis

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

Assume that you have written a library using plain standard C99 and you want
to extend the functionality of a function. However, the added functionality
requires some optional parameters. For example, in C++, you can change:

void foo(int a);

to

void foo(int a, const char *b = NULL);

and modify the definition. In this way, new functionality is added to foo
and the compatibility of old code (which calls foo with a single parameter)
is retained. However, in C99,
<snip>

The normal way to achieve this in C is either through a macro, e.g.,

#define foo(a) foo_ex(a, NULL)

or via a function

void foo(int a) { foo_ex(a, NULL); }

Usually it's just important to think about what the possible inputs to
the function could be even if you aren't going to use them right
away. I've had to very rarely use the _ex trick because added
functionality was later desired [e.g. not known at design time].

Tom
 
E

Eric Sosman

Michael said:
Assume that you have written a library using plain standard C99 and you want
to extend the functionality of a function. However, the added functionality
requires some optional parameters. For example, in C++, you can change:

void foo(int a);

to

void foo(int a, const char *b = NULL);

and modify the definition. In this way, new functionality is added to foo
and the compatibility of old code (which calls foo with a single parameter)
is retained. However, in C99, you can't do this. You may consider writing a
new function in the library, however, if the new functionality only requires
very little changes in the original function, you may end up duplicating a
lot of code in the old function and the new function, and one more
identifier is now added into the namespace. Therefore, when more and more
functionality is added into the library, more and more identifier is added
and the code is finally unmaintainable (due to lots of duplicated code). A
new library version is therefore needed.

"Lots of duplication?"

void newfoo(int a, const char *b) {
// as much code as you want
}

void foo(int a) {
newfoo(a, NULL);
}
 
N

Nick Keighley

Assume that you have written a library using plain standard C99 and you want
to extend the functionality of a function. However, the added functionality
requires some optional parameters. For example, in C++, you can change:

void foo(int a);

to

void foo(int a, const char *b = NULL);

and modify the definition. In this way, new functionality is added to foo
and the compatibility of old code (which calls foo with a single parameter)
is retained. However, in C99, you can't do this.

It is useful but not as useful as you make out.
You may consider writing a
new function in the library, however, if the new functionality only requires
very little changes in the original function, you may end up duplicating a
lot of code in the old function and the new function,

Refactor Mercilessly

As your code has degenerated into a morass I sentence you to read the
entirity of "Structured Design" by Constantine and Yourdon.

"Whenever possible, we wish to maximize fan-in during the design
process. Fan-in is the raison d'être of modularity: Each instance of
multiple fan-in means that some duplicate code has been avoided."
and one more
identifier is now added into the namespace. Therefore, when more and more
functionality is added into the library, more and more identifier is added
and the code is finally unmaintainable (due to lots of duplicated code). A
new library version is therefore needed.

your development process is broken
 
B

BGB / cr88192

Michael Tsang said:
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

Assume that you have written a library using plain standard C99 and you
want
to extend the functionality of a function. However, the added
functionality
requires some optional parameters. For example, in C++, you can change:

void foo(int a);

to

void foo(int a, const char *b = NULL);

and modify the definition. In this way, new functionality is added to foo
and the compatibility of old code (which calls foo with a single
parameter)
is retained. However, in C99, you can't do this. You may consider writing
a
new function in the library, however, if the new functionality only
requires
very little changes in the original function, you may end up duplicating a
lot of code in the old function and the new function, and one more
identifier is now added into the namespace. Therefore, when more and more
functionality is added into the library, more and more identifier is added
and the code is finally unmaintainable (due to lots of duplicated code). A
new library version is therefore needed.

possible ways to address this:
when initially designing the API, try to provide for whatever parameters may
be needed to a function, even if not needed at the moment.

for example, everything else in the API may require a passed context, but
the particular function in question does not need it in order to perform its
work. one then includes the context anyways, since likely soon enough there
may be a special case / ... for which info in the context is needed.

another way is to think of API functions not as globs of logic code doing
"something or another", but as abstract operations, and one can ask:
does it make sense to export this code to the public world?
what are the 'fundamental' operations?
what is the 'model' of this API?
what may make sense to provide to these operations within this model?
....

another strategy, maybe less painful, is to go and find documentation for
other APIs which do similar things (even if only vaguely), and then
consider:
what sort of things are they doing (in terms of particular tasks)?
how are they doing them (in terms of a general design strategy)?
followed by:
what are the relative strengths of one design strategy vs another?
what are the relative weaknesses of one design strategy vs another?
in which way can a given design strategy benefit the project as a whole?
in which ways can a given design strategy be detrimental, or run into severe
or arbitrary limitations?
....


at this point, one can either write down there ideas and considerations
somewhere, maybe desribing the rationale for various choices, and then start
working on a spec for the API as they would want to see it according to
their prior considerations (may or may not take into account what resources
or facilities one has available to actually do so, as there are merits to
either approach), ...

(at this point, I often have a habit of going into writing streams of emails
and/or usenet posts detailing various thoughts and considerations, usually
hoping for feedback or new ideas/information, followed by no one really
giving a crap, so I guess this is kind of a downside...).

as an upside, this process generally provides a baseline set of
documentation for a project, and even for various "idea specs" which go
nowhere (in time, one may end up with a lot of these), there may be some
use, as one may later run accross a similar situation and be able to review
what they had previously seen and considered, as well as possibly new
information or circumstances, and ways to adjust the prior idea to the new
set of problems.

....


now, failing all this, there are a few possibilities:
using variable arguments ('stdarg.h'), to add a variable argument list to
the function, and then attempt to detect and use them. this has precedent in
POSIX, although, granted, this is IMO a very ugly and frustrating approach.

as another had suggested, one can use macros as wrappers. this works at the
source level, but may break in the face of binary compatibility issues.

another option then, is to create a new version (by renaming the old version
and adding the new arguments, as before), and replacing the old version with
a stub that does little more than call the new version with the new args.

MYAPI_API void myapiFooCommandEx(int a, char *b)
{
...
}

MYAPI_API void myapiFooCommand(int a)
{ myapiFooCommandEx(a, NULL); }


or such...
 
B

bartc

Eric Sosman said:
"Lots of duplication?"

void newfoo(int a, const char *b) {
// as much code as you want
}

void foo(int a) {
newfoo(a, NULL);
}

That's one extra parameter. As more are added, you need more wrapper
functions, more names, more docs, and multiple function signatures to keep
compatible.

And extra overhead if not inlined (can a function be inlined if the body is
not visible to the compiler?)

I can't see a downside with the OP's implication that default arguments
should exist in C.
 
E

Eric Sosman

bartc said:
That's one extra parameter. As more are added, you need more wrapper
functions, more names, more docs, and multiple function signatures to
keep compatible.

If you're encountering combinatorial explosion of present
and absent arguments, it's likely that your "single function"
is overburdened and should be broken up anyhow. Write functions
that do one thing well and reliably; avoid writing functions
that try to do everything for everybody all at once.
And extra overhead if not inlined (can a function be inlined if the body
is not visible to the compiler?)

Ahh, the blind worship of The Little Tin God persists even
to this day. But let us ask the priests of TLTG to describe for
us the overhead of detecting whether an argument has or has not
been omitted, and of supplying or suppressing the default. It's
zero, ri-i-i-ght?
I can't see a downside with the OP's implication that default arguments
should exist in C.

Function pointers come to mind as an area that would need
further thought. Also, default arguments in variadic functions
seem a potentially thorny issue.

In short: If you want C++ you know where to find it.
 
J

jacob navia

Michael Tsang a écrit :
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

Assume that you have written a library using plain standard C99 and you want
to extend the functionality of a function. However, the added functionality
requires some optional parameters. For example, in C++, you can change:

void foo(int a);

to

void foo(int a, const char *b = NULL);

and modify the definition. In this way, new functionality is added to foo
and the compatibility of old code (which calls foo with a single parameter)
is retained. However, in C99, you can't do this. You may consider writing a
new function in the library, however, if the new functionality only requires
very little changes in the original function, you may end up duplicating a
lot of code in the old function and the new function, and one more
identifier is now added into the namespace. Therefore, when more and more
functionality is added into the library, more and more identifier is added
and the code is finally unmaintainable (due to lots of duplicated code). A
new library version is therefore needed.
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.9 (GNU/Linux)

iEYEARECAAYFAkr5dTYACgkQG6NzcAXitM+n9wCgjlIMDxlzojl8vuEF44zcIFfX
+GsAn2JqI340NGAUyBADwjNko9noH2Cy
=GpjM
-----END PGP SIGNATURE-----

You are right. That is why the lcc-win C compiler provides
default function arguments as an extension.

I hope this extension will be added to the new C standard since also
the Pelle's C compiler features it. Both (Pelles C and lcc-win)
are derived from lcc.
 
S

Stephen Sprunk

Eric said:
....

Ahh, the blind worship of The Little Tin God persists even
to this day. But let us ask the priests of TLTG to describe for
us the overhead of detecting whether an argument has or has not
been omitted, and of supplying or suppressing the default. It's
zero, ri-i-i-ght?

AFAIK, if a C++ compiler sees this:

foo(x);

but it knows the function is declared like this:

void foo(int a, const char *b = NULL);

then the compiler automagically translates the call to this:

foo(x, NULL);

and the callee wouldn't know (or need to know) the difference. There
would be no runtime overhead--unlike most C++ features that folks might
want to copy into C. This one actually seems like a good idea with
virtually no downside.

However, this requires that you recompile all callers and callees when
you add "b" to the signature; if not, you still have potential problems.
You obviously also need a prototype declaration to make use of this
feature, but the same is true of variadic functions.
Function pointers come to mind as an area that would need
further thought.

Hmmm. Presumably the C++ folks found a way to make it work.
Also, default arguments in variadic functions
seem a potentially thorny issue.

Given that you have to name an argument to specify a default value for
it, and named arguments must come before the variable arguments, I don't
see any interaction there.

S
 
E

Eric Sosman

Stephen said:
Eric said:
[...]
Also, default arguments in variadic functions
seem a potentially thorny issue.

Given that you have to name an argument to specify a default value for
it, and named arguments must come before the variable arguments, I don't
see any interaction there.

void foo(int a, int b=0, ...);
foo (1, 2);

Is foo() invoked with b==2 and no variable arguments, or
with b==0 and one variable argument?

foo (1, "hello");

Is foo() invoked with b==0 and one variable argument, or
is there a compilation error because char* doesn't convert
to int?

Rules could certainly be formulated for these and other
cases, but somebody would actually need to formulate them;
they do not just leap full-armed from the brow of Jove.
 
L

ld

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

Assume that you have written a library using plain standard C99 and you want
to extend the functionality of a function. However, the added functionality
requires some optional parameters. For example, in C++, you can change:

void foo(int a);

to

void foo(int a, const char *b = NULL);

and modify the definition. In this way, new functionality is added to foo
and the compatibility of old code (which calls foo with a single parameter)
is retained. However, in C99, you can't do this. You may consider writing a
new function in the library, however, if the new functionality only requires
very little changes in the original function, you may end up duplicating a
lot of code in the old function and the new function, and one more
identifier is now added into the namespace. Therefore, when more and more
functionality is added into the library, more and more identifier is added
and the code is finally unmaintainable (due to lots of duplicated code). A
new library version is therefore needed.
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.9 (GNU/Linux)

iEYEARECAAYFAkr5dTYACgkQG6NzcAXitM+n9wCgjlIMDxlzojl8vuEF44zcIFfX
+GsAn2JqI340NGAUyBADwjNko9noH2Cy
=GpjM
-----END PGP SIGNATURE-----

#define foo(...) COS_PP_CAT_NARG(foo__,__VA_ARGS__)(__VA_ARGS__)
#define foo__1(a) foo(a,NULL)
#define foo__2(a,b) foo(a,b)

the macros can be included/added after or before the function
prototype:

void foo(int a, const char *b = NULL);

The macro COS_PP_CAT_NARG() can be found in the file:
http://cos.cvs.sourceforge.net/viewvc/cos/CosBase/include/cos/cpp/utils.h

of the (self sufficient) CPP module of the C Object System:
http://cos.cvs.sourceforge.net/viewvc/cos/CosBase/include/cos/cpp/

you may need to browse also the narg.h file to understand it.

This is a C99 solution easily extensible, no black magic nor compiler
specific feature.

a+, ld.
 
S

Stephen Sprunk

Eric said:
Stephen said:
Eric said:
[...]
Also, default arguments in variadic functions
seem a potentially thorny issue.

Given that you have to name an argument to specify a default value for
it, and named arguments must come before the variable arguments, I don't
see any interaction there.

void foo(int a, int b=0, ...);
foo (1, 2);

Is foo() invoked with b==2 and no variable arguments, or
with b==0 and one variable argument?

The former. Parameters are matched with arguments from left to right
until the parameter list is exhausted. If there are still any remaining
arguments to be filled, they get their default values--or a compile-time
error if one or more of them doesn't have a default.
foo (1, "hello");

Is foo() invoked with b==0 and one variable argument, or
is there a compilation error because char* doesn't convert
to int?

The latter; see above re: left-to-right matching.

I now remember why mixing variadic functions and default arguments isn't
a problem in C++: all default arguments must follow all non-default
arguments, i.e. default arguments must be at the end of the list. Since
variadic arguments must also be last and cannot be defaulted, a variadic
function cannot have _any_ default arguments.

(One might try to figure out a way to specify default variadic
arguments, but IIRC the C++ committee decided it wasn't worth the effort
and therefore the two features are mutually exclusive.)
Rules could certainly be formulated for these and other
cases, but somebody would actually need to formulate them;
they do not just leap full-armed from the brow of Jove.

We could simply steal the rules from C++, where all this has already
been sorted out. Since many compilers share a lot of logic for the two
languages, there is no sense reinventing wheels.

S
 
E

Eric Sosman

Stephen said:
[...]
We could simply steal the rules from C++, where all this has already
been sorted out. [...]

While we're at it, let's steal some rules from Snobol
and Perl and RPG. Oh, and PL/I -- perhaps the outstanding
example of an "everything anybody ever thought somebody might
ever want someday" language.

In other words, if you want C++ you know where to find it.
 
S

Seebs

Assume that you have written a library using plain standard C99 and you want
to extend the functionality of a function. However, the added functionality
requires some optional parameters.

It doesn't.
and modify the definition. In this way, new functionality is added to foo
and the compatibility of old code (which calls foo with a single parameter)
is retained.

I don't know exactly how the defaults are done, but I believe this involves
some amount of type-mangling.
However, in C99, you can't do this. You may consider writing a
new function in the library, however, if the new functionality only requires
very little changes in the original function, you may end up duplicating a
lot of code in the old function and the new function, and one more
identifier is now added into the namespace. Therefore, when more and more
functionality is added into the library, more and more identifier is added
and the code is finally unmaintainable (due to lots of duplicated code). A
new library version is therefore needed.

Yes, it is.

Actually, that's not quite true. You can use variadic functions, but only
if the earlier arguments tell you unambiguously whether there will be more
arguments. However, in any event, you are going to need to rebuild everything
which USES the library if you change the calling signature.

-s
 
S

Seebs

That's one extra parameter. As more are added, you need more wrapper
functions, more names, more docs, and multiple function signatures to keep
compatible.

Yes.

Which is a good reason not to do that.
And extra overhead if not inlined (can a function be inlined if the body is
not visible to the compiler?)

This is why the inline version of the trivial function goes in the header.
;)
I can't see a downside with the OP's implication that default arguments
should exist in C.

My guess is that it would impose a non-zero cost on all function calls.

Consider: I have access to the existing library, and I call foo(a). You
now extend the library. Can I use the new one without recompiling? If so,
there already has to be something in my call of foo(a) which indicates
no additional arguments -- that is likely to be an extra cost.

So let's assume that this always requires recompiling everything.

At that point... Hmm. The default argument presumably has to be visible when
I'm compiling the caller, or it won't work. Imagine that, in module foo,
I write:

extern int foo(int a, int b = 0) {
return a + b;
}

Now, what does a caller need to translate both foo(2) and foo(3, 4) correctly?
Presumably, at a minimum, access to that prototype... But how is the
prototype to be spelled? The prototype has to give the default value, or
at least indicate somehow that the second argument is optional. Probably
the latter.

Hmm. If we allow for this only on recompiles, and with a prototype in scope,
it's not too horrid, but it's pretty marginal syntactic sugar. I guess I
like it in languages that have it, but I have never much missed it when
writing C, because the sorts of things I do in C tend not to have defaults.

In short, I figure if you're at a phase in development where you're regularly
changing library calls, the correct thing is just to change the prototype
for foo, document the new argument, and make the callers change their code to
use the new version. Even if you have default arguments, they *MUST*
rebuild against the new headers anyway.

-s
 
S

Seebs

You are right. That is why the lcc-win C compiler provides
default function arguments as an extension.

How do you handle the various questions (interaction of default arguments
with variable arguments, etc.)? e.g., what has to happen if a function
has new default arguments added, etcetera?

-s
 
K

Keith Thompson

Eric Sosman said:
Stephen said:
[...]
We could simply steal the rules from C++, where all this has already
been sorted out. [...]

While we're at it, let's steal some rules from Snobol
and Perl and RPG. Oh, and PL/I -- perhaps the outstanding
example of an "everything anybody ever thought somebody might
ever want someday" language.

In other words, if you want C++ you know where to find it.

I think the suggestion is simply that *if* we were going to add
default arguments to C (and personally I think it's not a bad idea),
we might as well leverage the work that's already been done in C++.
The two languages will never be 100% compatible, but we can always
steal good ideas.

Remember that C got prototypes from C++.
 
B

bartc

Eric Sosman said:
bartc wrote:

["The lack of default function parameter in C99 makes compatibility
difficult."]
Ahh, the blind worship of The Little Tin God persists even
to this day.

I'm sorry, what do you mean by the Little Tin God? You mean, the pursuit of
performance (which is half the reason why C is used at all)?
But let us ask the priests of TLTG to describe for
us the overhead of detecting whether an argument has or has not
been omitted, and of supplying or suppressing the default. It's
zero, ri-i-i-ght?

I'm assuming this would be done at compile time.

(On one of my projects, I'm switching from compile-time default parameters,
to run time ones, but that is because of certain problems when variant types
are used; they needn't occur with C's static typing: this is when a missing
parameter is propagated through several function calls, where in each case
that parameter is defaulted; it's necessary the same default value is
specified each time. Otherwise, it would get expensive for a fast language
like C)

However, I see from your other reply that you're not interested in minor
language changes like this, because there is always another way to achieve
the same result.

C is so versatile however that half the language can disappear and it's
still possible to write the same programs, but no-one is complaining about
the features being there.
 
N

Nick

Seebs said:
At that point... Hmm. The default argument presumably has to be visible when
I'm compiling the caller, or it won't work. Imagine that, in module foo,
I write:

extern int foo(int a, int b = 0) {
return a + b;
}

Now, what does a caller need to translate both foo(2) and foo(3, 4) correctly?
Presumably, at a minimum, access to that prototype... But how is the
prototype to be spelled? The prototype has to give the default value, or
at least indicate somehow that the second argument is optional. Probably
the latter.

If not, you have the "interesting" property that you could compile two
different files which see slightly different prototypes (with different
values for the option). So in one file foo(10) would be - as in your
example - 10, but in another foo(10) would be 17 as that had picked up a
header which contained a prototype with a default of 7.
 
J

jacob navia

Seebs a écrit :
How do you handle the various questions (interaction of default arguments
with variable arguments, etc.)? e.g., what has to happen if a function
has new default arguments added, etcetera?

-s

They can't be used together. the ellipsis and default arguments are
incompatible.

Rationale:

The default arguments must be at the end of all other arguùents.
After the first default argument all others MUST be default
arguments. Ellipsis means an underterminate number
of arguments, without any defaults, so it can't happen after
any default argument.

Since ellipsis is ALWAYS the last argument, it can't be used with
default arguments.
 

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

No members online now.

Forum statistics

Threads
473,764
Messages
2,569,565
Members
45,041
Latest member
RomeoFarnh

Latest Threads

Top