The importance of prototypes

E

elzacho

It is my understanding that prototypes in C are purely optional. To my
experience, until today I guess, this has been the case (other than
eliminating compiler warning messages).

However, today I wrote a routine to search an array for values greater
than a certain limiting value (double) and something weird happened.
When debuging I noticed that I passed the limiting value to the routine
yet inside the function this value was garbage. This was very odd to
me and the value was something like 10^-315 for this double so it
looked like it was maybe an integer value which I thought was a double.
Thinking that there was some kind of type casting going on behind the
scenes, I added the prototype and the problem went away.

So, am I wrong and prototypes are mandatory, kinda like in C++? Does
the compiler automatically assume that unknown functions have only
integer arguments and thus puts in a type cast for me? This wouldn't
affect pointers or int, long, chars but only floats and doubles which
is kinda what I am seeing.

I am using gcc 3.3.4 if that makes a difference.

Thanks,
Zach
 
R

Richard Heathfield

elzacho said:
It is my understanding that prototypes in C are purely optional.

If you don't provide a prototype for a function, the compiler is required by
the Standard to assume that the function returns an int, and is unable to
do type checking on the arguments you pass to the function. Furthermore, if
you call a variadic function that does not have a valid function prototype
in scope, the behaviour of the program is undefined.

So ignore the fact that prototypes are sometimes optional, and always
provide the correct prototype (either by #include-ing the header that
contains it or by hacking it in yourself). By never omitting the
appropriate prototype, you can be sure that you will not suffer the
problems that can be caused by omitting the appropriate prototype!
 
P

Peter Nilsson

Richard said:
elzacho said:


If you don't provide a prototype for a function, the compiler is required
by the Standard to assume that the function returns an int,

Not if a declaration (sans prototype) is supplied, e.g. ...

double sin(); /* non-prototype declaration */

double f(double x) { return sin(x); }

[Sadly, IMHO,] C99 still allows code like this.
and is unable to do type checking on the arguments you pass to the function.
Furthermore, if you call a variadic function that does not have a valid
function prototype in scope, the behaviour of the program is undefined.

So ignore the fact that prototypes are sometimes optional, and always
provide the correct prototype (either by #include-ing the header that
contains it or by hacking it in yourself).

With stress on the former. :)
By never omitting the
appropriate prototype, you can be sure that you will not suffer the
problems that can be caused by omitting the appropriate prototype!

I'd be surprised if there was any ANSI/ISO C90 compiler that doesn't
have an option to enable 'required prototypes'.
 
K

Keith Thompson

Richard Heathfield said:
elzacho said:


If you don't provide a prototype for a function, the compiler is required by
the Standard to assume that the function returns an int, and is unable to
do type checking on the arguments you pass to the function. Furthermore, if
you call a variadic function that does not have a valid function prototype
in scope, the behaviour of the program is undefined.

One additional detail: If you provide a declaration that isn't a
prototype, you can specify the return type but not the argument types:

double func();
So ignore the fact that prototypes are sometimes optional, and always
provide the correct prototype (either by #include-ing the header that
contains it or by hacking it in yourself). By never omitting the
appropriate prototype, you can be sure that you will not suffer the
problems that can be caused by omitting the appropriate prototype!

Agreed. There is no reason *not* to provide prototypes for all
functions.

(C99 has stricter rules that C90. I don't remember the details, but
you should follow the stricter rules even if you're using a C90
compiler.)
 
C

Charles Richmond

elzacho said:
It is my understanding that prototypes in C are purely optional. To my
experience, until today I guess, this has been the case (other than
eliminating compiler warning messages).

However, today I wrote a routine to search an array for values greater
than a certain limiting value (double) and something weird happened.
When debuging I noticed that I passed the limiting value to the routine
yet inside the function this value was garbage. This was very odd to
me and the value was something like 10^-315 for this double so it
looked like it was maybe an integer value which I thought was a double.
Thinking that there was some kind of type casting going on behind the
scenes, I added the prototype and the problem went away.

So, am I wrong and prototypes are mandatory, kinda like in C++? Does
the compiler automatically assume that unknown functions have only
integer arguments and thus puts in a type cast for me? This wouldn't
affect pointers or int, long, chars but only floats and doubles which
is kinda what I am seeing.

I am using gcc 3.3.4 if that makes a difference.
Without using a prototype, if you have a function that takes a double
argument, and you pass it an int, you must cast the int to double.

#include <stdio.h>

double sqrt(); /* sqrt() takes an argument of type double */

int main()
{
double result;
int number;

number = 25;
printf("value is %f\n",sqrt(number)); /* <--- *** wrong *** */

number = 25;
printf("value is %f\n",sqrt((double) number)); /* <--- right!!! */
}

--
+----------------------------------------------------------------+
| Charles and Francis Richmond It is moral cowardice to leave |
| undone what one perceives right |
| richmond at plano dot net to do. -- Confucius |
+----------------------------------------------------------------+
 
K

Keith Thompson

Peter Nilsson said:
Richard Heathfield wrote: [...]
By never omitting the
appropriate prototype, you can be sure that you will not suffer the
problems that can be caused by omitting the appropriate prototype!

I'd be surprised if there was any ANSI/ISO C90 compiler that doesn't
have an option to enable 'required prototypes'.

Or at least warn about their absence.
 
E

elzacho

Thanks for the help guys,

Browsing around in gnu.gcc.bug I see that in gcc if no prototype is
given, the function passes all arguments as ints (no matter the local
data type or the function declarations desired data type). I am
wondering if this is the way the C standard designated it, or if gcc is
taking a liberty here.

This seems silly as this is just memory which I would expect would be
directly copied to the stack if no prototype is given to say otherwise,
not casted behind the scenes.

Zach
 
C

Christian Bau

Charles Richmond said:
Without using a prototype, if you have a function that takes a double
argument, and you pass it an int, you must cast the int to double.

#include <stdio.h>

double sqrt(); /* sqrt() takes an argument of type double */

int main()
{
double result;
int number;

number = 25;
printf("value is %f\n",sqrt(number)); /* <--- *** wrong *** */

number = 25;
printf("value is %f\n",sqrt((double) number)); /* <--- right!!! */
}

Just wondering: Would the compiler have to complain about this? Even
without the prototype, it is obvious that one of the two calls must be
wrong - the compiler just cannot know which one.
 
K

Keith Thompson

elzacho said:
Thanks for the help guys,

Browsing around in gnu.gcc.bug I see that in gcc if no prototype is
given, the function passes all arguments as ints (no matter the local
data type or the function declarations desired data type). I am
wondering if this is the way the C standard designated it, or if gcc is
taking a liberty here.

I suspect you've misunderstood what was said in gnu.gcc.bug.

If you call a function with no prototype in scope, certain type
promotions, called the "default argument promotions", are applied.
Integer types shorter than int are promoted to either int or unsigned
int, and float is promoted to double. All other types (including all
pointer types) are unchanged. If the promoted type is incompatible
with the type that the function expects, the call invokes undefined
behavior. (I'm not sure that "incompatible" is the correct term here,
but that's the general idea.)

As far as I know, gcc implements this correctly.
 
A

Anonymous 7843

Browsing around in gnu.gcc.bug I see that in gcc if no prototype is
given, the function passes all arguments as ints (no matter the local
data type or the function declarations desired data type). I am
wondering if this is the way the C standard designated it, or if gcc is
taking a liberty here.

I think you are misinterpreting the situation slightly.
In the absense of a prototype, SOME kinds of arguments are
promoted to ints but not all. For example, float is promoted
to double, and pointers just continue to be pointers.

I would expect that gcc's implementation of the argument promotion
concept is robust and correct.
This seems silly as this is just memory which I would expect would be
directly copied to the stack if no prototype is given to say otherwise,
not casted behind the scenes.

The current implementations of C are best used with protoypes, as
others have reminded you already.

Perhaps you haven't lived through it, but there was an age when
prototypes did not exist and it was remarkably easy to pass
the wrong types, size, or number of arguments to a function.

The promotion rules were designed to at least cover some of
the basic types so that use of floats vs. doubles and shorts vs. ints
wasn't too painful. Co-incidentally, the promotion rules for
expressions played nicely into the promotion rules for arguments
so that expressions being used as arguments were fairly painless.

The other aspect is that arguments were not always passed on
a stack. If a function took an int, two floats and a pointer
it's possible on some architectures that all the items would
be passed in via registers. Passing shorts as ints (e.g.)
was often faster since it avoided extra code to mask off the
extra bits or the use of slower CPU instructions that essentially
did the same thing.
 
E

elzacho

Keith, you are right. This is more logical and does not contrast what
was said in gcc.bug. Still, what they said explained the odd behavior,
and what you said only makes it more confusing...

For the life of me I cannot reproduce this is a smaller example which
is postable.
 
K

Keith Thompson

Christian Bau said:
Just wondering: Would the compiler have to complain about this? Even
without the prototype, it is obvious that one of the two calls must be
wrong - the compiler just cannot know which one.

The compiler is not obligated to complain about either call. The
first one invokes undefined behavior; it's not a syntax error or a
constraint violation.

Since sqrt() happens to be defined in the standard library, a clever
compiler could complain only about the first call. It could even add
an implicit conversion to double; since the call invokes undefined
behavior; this is one of the infinitely many legal ways to handle it.
(This would mask the error, so I wouldn't consider it a good idea.)

For a function that's not part of the standard library, a compiler is
certainly allowed to issue a diagnostic indicating that at least one
of the two calls must be incorrect. I don't know whether any
compilers are actually required to do so.
 
R

Richard Tobin

elzacho said:
Browsing around in gnu.gcc.bug I see that in gcc if no prototype is
given, the function passes all arguments as ints (no matter the local
data type or the function declarations desired data type).

This seems rather unlikely, since it would break many existing
programs, and gcc is a very widely-used compiler.

In the absence of a prototype, the default promotions are used.
Roughly, integer types smaller than int are promoted to int,
and float is promoted to double.
This seems silly as this is just memory which I would expect would be
directly copied to the stack if no prototype is given to say otherwise,

No, they are promoted as described above.

-- Richard
 
K

Keith Thompson

elzacho said:
Keith, you are right. This is more logical and does not contrast what
was said in gcc.bug. Still, what they said explained the odd behavior,
and what you said only makes it more confusing...

For the life of me I cannot reproduce this is a smaller example which
is postable.

Please provide some context when you post a followup. Don't assume
that your readers have easy access to the article to which you're
replying. (Blame the broken groups.google.com interface for making it
unnecessarily difficult to post properly.)

If you want to post a followup via groups.google.com, don't use
the broken "Reply" link at the bottom of the article. Click on
"show options" at the top of the article, then click on the
"Reply" at the bottom of the article headers.

Here's an example:

#include <stdio.h>

double func();

int main(void)
{
double d1 = func(42);
double d2 = func(42.0);
printf("d1 = %g\n", d1);
printf("d2 = %g\n", d2);
return 0;
}

double func(double arg)
{
return arg * 2.0;
}

And here's the output I got:

d1 = 1.00718e+292
d2 = 84

In the first call to func, the int value 42 was passed to the
function, which treated it as a double. I lied to the compiler, and
it got its revenge.

In the second call, the argument was already of type double, so there
was no problem.

This can be fixed by changing the line
double func();
to
double func(double arg);
which gives this output:

d1 = 84
d2 = 84

With a proper prototype in scope, the compiler knows that func()
expects a double argument, so it implicitly converts the int value 42
to double before the call.

Note that for functions like printf() that take a variable number (and
type) of arguments, you can run into similar problems even if you have
a proper prototype. You just have to be careful that the arguments
are of the correct type, applying explicit casts if necessary -- and
don't expect the compiler to catch your errors for you. For example:

#include <stdio.h>

int main(void)
{
int d = 42;
printf("d = %g\n", d); /* WRONG! */
return 0;
}

produced the following output:

d = 2.07508e-322

even though I had the correct prototype for printf() (declared in
<stdio.h>). The fix is either to change the "%g" to "%d", or, if I
really want to convert the int value to double before printing it,
use an explicit cast:

printf("d = %g\n", (double)d);
 
R

Randy Howard

elzacho wrote
(in article
Keith, you are right. This is more logical and does not contrast what
was said in gcc.bug. Still, what they said explained the odd behavior,
and what you said only makes it more confusing...

Simple solution. Always use prototypes. Problem solved.
 
A

aegis

Richard said:
elzacho said:


If you don't provide a prototype for a function, the compiler is required by
the Standard to assume that the function returns an int, and is unable to
do type checking on the arguments you pass to the function.

Only for c89/90.
Furthermore, if
you call a variadic function that does not have a valid function prototype
in scope, the behaviour of the program is undefined.

6.7.2#2
All declarations that refer to the same object or function shall have
compatible type; otherwise, the behavior is undefined.

the same applies to c89/90. If you do not have a prototype present for
a c89/90 compliant compiler, then you will invoke undefined behavior in
any instance where a function is declared as returning an object type
other than 'int'.
i.e., if you provide no prototype for using 'strchr' and strchr is
declared as char *strchr(const char *s, int c) { } in some other
translation unit that you link with, then you invoke undefined
behavior.


HTH
 
R

rep_movsd

Hi

If you are writing a small "one file" program, you can simply structure
your code so that no function calls another which has not been defined
physically before it ( within the file )
Thus you can avoid prototypes, or even a header file.

But such programs tend to be very few.

Regards
Vivek
 
K

Keith Thompson

rep_movsd said:
If you are writing a small "one file" program, you can simply structure
your code so that no function calls another which has not been defined
physically before it ( within the file )
Thus you can avoid prototypes, or even a header file.

But such programs tend to be very few.

You should still include prototypes as part of the definition.

For example:

void func1(int x, const char *y)
{
...
}

void func2(void)
{
func1(42, "foobar");
}
 
R

Richard Heathfield

aegis said:
Only for c89/90.

Sure. But then I have yet to encounter a conforming implementation of C99.
Comeau is perhaps closest, but even they claim only partial support for
C99. And gcc appears to have abandoned all efforts at conformance.
Microsoft never even bothered to try. So I see little point in providing
C99 answers. If people want to know about C99 specifically, I expect
they'll ask about it specifically.
 

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
474,169
Messages
2,570,919
Members
47,458
Latest member
Chris#

Latest Threads

Top