Why is it important to share functions' declarations?

  • Thread starter grishin-mailing-lists
  • Start date
G

grishin-mailing-lists

Hi guys,

Writing a program I found that if a function returns char* it's
necessary to have its declaration within the compilation unit.

A simple example
/*----------------------------------------------*/
/* filename: casting_test.c */
#include <stdio.h>
char *Foo_int(const char *c)
{
return (char *)c;
}

int main(int argc, char *argv[])
{
char s[] = "Hi. It's a test string.";
char *n = NULL;
char *m = NULL;

n = Foo_ext(s); /* line #15 */
m = Foo_int(s);
puts(n);
puts(m);
return 0;
}
/*----------------------------------------------*/

Foo_ext is in separate Foo.c file.
Compiling this
gcc casting_test.c Foo.c
casting_test.c: In function `main':
casting_test.c:15: warning: assignment makes pointer from integer
without a cast
This message a misleading one because
if I add
char *Foo_ext(const char *c);
to casting_test.c (above main(...))
then no warnings appear.
It works well in both cases.
 
I

Ike Naar

Writing a program I found that if a function returns char* it's
necessary to have its declaration within the compilation unit.

It's necessary if the return type is anything other than int,
and it's always recommended.
A simple example
/*----------------------------------------------*/
/* filename: casting_test.c */
#include <stdio.h>
char *Foo_int(const char *c)
{
return (char *)c;
}

int main(int argc, char *argv[])
{
char s[] = "Hi. It's a test string.";
char *n = NULL;
char *m = NULL;

n = Foo_ext(s); /* line #15 */
m = Foo_int(s);
puts(n);
puts(m);
return 0;
}
/*----------------------------------------------*/

Foo_ext is in separate Foo.c file.
Compiling this
gcc casting_test.c Foo.c
casting_test.c: In function `main':
casting_test.c:15: warning: assignment makes pointer from integer
without a cast
This message a misleading one because
if I add
char *Foo_ext(const char *c);
to casting_test.c (above main(...))
then no warnings appear.

If you leave out the declaration of Foo_ext,
the compiler must assume that Foo_ext returns int.
So the warning is correct: you are assigning an int to a char* without a cast.
It works well in both cases.

It may *appear* to work well in your case, but in fact the behaviour
is undefined. It may misbehave badly on, for instance, a machine on which
pointers and ints have different sizes. An example of such a machine is
the Intel Itanium, where your program crashes with a segmentation fault.

The recommended way to handle this, is to have a header file,
say Foo.h, that contains a declaration of Foo_ext.

Include Foo.h everywhere where Foo_ext is used.
Also include Foo.h in Foo.c itself. This ensures that the declaration of
Foo_ext in Foo.h matches the definition of Foo_ext in Foo.c; if the types
don't match, you'll get a diagnostic when you compile Foo.c .
 
E

Eric Sosman

Writing a program I found that if a function returns char* it's
necessary to have its declaration within the compilation unit.

It's necessary if the return type is anything other than int,
and it's always recommended.
[...]

Just to clarify: There are two sets of rules, depending on
whether the compiler follows the current ("C99") or older ("C90"
or "C89" or "ANSI") version of the Standard:

C90: A declaration is optional if and only if the function
takes a fixed number of parameters of "non-promotable"
types, and returns an int value. If the function takes
a variable number of arguments (has "..." in the parameter
list), or if any parameters are of "promotable" types
like float or char, or if the function does not return
an int, a declaration is required.

C99: A declaration is required, period.
 
G

grishin-mailing-lists

Thanks guys!

PS 2 Ike Naar: I wonder did you really run my program on Itanium?
 
B

Barry Schwarz

On Sun, 2 May 2010 02:55:59 -0700 (PDT),
Hi guys,

Writing a program I found that if a function returns char* it's
necessary to have its declaration within the compilation unit.

A simple example
/*----------------------------------------------*/
/* filename: casting_test.c */
#include <stdio.h>
char *Foo_int(const char *c)
{
return (char *)c;
}

int main(int argc, char *argv[])
{
char s[] = "Hi. It's a test string.";
char *n = NULL;
char *m = NULL;

n = Foo_ext(s); /* line #15 */
m = Foo_int(s);
puts(n);
puts(m);
return 0;
}
/*----------------------------------------------*/

Foo_ext is in separate Foo.c file.
Compiling this
gcc casting_test.c Foo.c
casting_test.c: In function `main':
casting_test.c:15: warning: assignment makes pointer from integer
without a cast
This message a misleading one because
if I add
char *Foo_ext(const char *c);
to casting_test.c (above main(...))
then no warnings appear.

The message is not misleading at all. If you omit the declaration
(also called a prototype), under the C89 standard the compiler is
required to assume the function returns an int. Since your assignment
then attempts to assign this int to a pointer and since you don't have
a cast and since there is no implicit conversion between int and
pointer, you have violated a constraint and the message identifies
exactly which constraint.
It works well in both cases.

No, it only appears to work well when you omit the prototype. You
have actually invoked undefined behavior. Think about systems where
int and pointer do not have the same size or where int and pointer are
not returned in the same way. One of the most insidious forms of
undefined behavior is to appear to work as intended.

By the way, adding the cast would do nothing to remove the undefined
behavior.

Even in those cases where the function returns int, it is always
desirable to include a prototype so the compiler can assist you by
insuring your arguments match (or can be converted to match) the
parameters the function will actually process.
 
K

Keith Thompson

Eric Sosman said:
Writing a program I found that if a function returns char* it's
necessary to have its declaration within the compilation unit.

It's necessary if the return type is anything other than int,
and it's always recommended.
[...]

Just to clarify: There are two sets of rules, depending on
whether the compiler follows the current ("C99") or older ("C90"
or "C89" or "ANSI") version of the Standard:

C90: A declaration is optional if and only if the function
takes a fixed number of parameters of "non-promotable"
types, and returns an int value. If the function takes
a variable number of arguments (has "..." in the parameter
list), or if any parameters are of "promotable" types
like float or char, or if the function does not return
an int, a declaration is required.

And if you violate this rule, the consequence is undefined behavior.
The compiler will warn you about it if you're lucky. If you're
using a C compiler that doesn't (claim to) conform to C99,
you should read its documentation to find out how to make sure
you get enough warnings.
C99: A declaration is required, period.

And if you violate this rule, it's a constraint violation.
The compiler *must* issue a diagnostic (which can be either a
warning or a fatal error message).
 
E

Eric Sosman

[...] If you omit the declaration
(also called a prototype), under the C89 standard the compiler is
required to assume the function returns an int.[...]

Nomenclature quibble: A declaration is not "also called a
prototype," except by people who don't know better. A function
may be declared either with or without a prototype:

double f(); /* declaration, no prototype */
void g(int, double); /* declaration with prototype */
char *h(short s); /* declaration with prototype */

An undeclared function clearly has no prototype in its non-
existent declaration. A declared function may have a prototype
or may lack one.

Strong recommendations: (1) Declare all functions before
use, and (2) include prototypes in the function's declaration
and definition. There are some odd corner cases involving
function pointers where (2) may be inconvenient enough to
jettison, but (1) is very nearly mandatory. (Under C99 rules,
it *is* mandatory.)
 
B

Ben Bacarisse

Barry Schwarz said:
Writing a program I found that if a function returns char* it's
necessary to have its declaration within the compilation unit.

A simple example
/*----------------------------------------------*/
/* filename: casting_test.c */
#include <stdio.h>
char *Foo_int(const char *c)
{
return (char *)c;
}

int main(int argc, char *argv[])
{
char s[] = "Hi. It's a test string.";
char *n = NULL;
char *m = NULL;

n = Foo_ext(s); /* line #15 */
m = Foo_int(s);
puts(n);
puts(m);
return 0;
}
If you omit the declaration
(also called a prototype), under the C89 standard the compiler is
required to assume the function returns an int.

It's probably worth pointing out that not all declarations are
prototypes:

char *Foo_ext();

provides a declaration of Foo_ext but not a prototype. Your "also
called a prototype" might be taken to suggest otherwise.

<snip>
 
I

Ike Naar

PS 2 Ike Naar: I wonder did you really run my program on Itanium?

Yes.

$ uname -m
ia64
$
$ cat foo.c
char const *foo(char const *s) { return s; }
$
$ cat main_with_proto.c
#include <stdio.h>
char const *foo(char const *);
int main(void) { puts(foo("hi")); return 0; }
$
$ cc main_with_proto.c foo.c
$ ./a.out
hi
$
$ cat main_without_proto.c
#include <stdio.h>
int main(void) { puts(foo("hi")); return 0; }
$
$ cc main_without_proto.c foo.c
main_without_proto.c: In function `main':
main_without_proto.c:2: warning:
passing arg 1 of `puts' makes pointer from integer without a cast
$ ./a.out
Segmentation fault
$
 

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,535
Members
45,007
Latest member
obedient dusk

Latest Threads

Top