invoke function without declaration


S

Stanley Rice

Dear all

Once again I come across some question about linking. In f1.c, I have
the following function definition.
----------------f1.c------------
#include <stdio.h>
void my_print(float a, int b)
{
printf("%f\t%d\n", a, b);
}

int add(int a, int b)
{
return a + b;
}

In main.c, I call the two function without declaration.
----------------main.c-------------
int main(void)
{
int a = add(1, 2);
printf("%d\n", a);
my_print(2.0, a);

return 0;
}

Notice here, in file main.c, I don't include any headers, say, 'f1.h',
'stdio.h', and don't declare anything deliberately, trying to get what
the result would like to be.

In my machine, with compiler gcc on ubuntu, I got some warnings while
compiling the code, and the binary file runs happily, printing the
following lines:
3
0.000000 1073741824

From the result, I got that
1. the function 'add' and the standard library function 'printf'
performs well, because the first line printed is 3.
2. the function 'my_print' doesn't work, the printed line obviously
shows.

All three function are defined external without declaration in file
main.c. But why the first two works well, but the last one fails? Is
it undefined in ISO C?

Thanks in advance.
 
Ad

Advertisements

B

bartek szurgot

Dear all

Once again I come across some question about linking. In f1.c, I have
the following function definition.
----------------f1.c------------
#include <stdio.h>
void my_print(float a, int b)
{
printf("%f\t%d\n", a, b);
}

int add(int a, int b)
{
return a + b;
}

In main.c, I call the two function without declaration.
----------------main.c-------------
int main(void)
{
int a = add(1, 2);
printf("%d\n", a);
my_print(2.0, a);

return 0;
}

Notice here, in file main.c, I don't include any headers, say, 'f1.h',
'stdio.h', and don't declare anything deliberately, trying to get what
the result would like to be.

In my machine, with compiler gcc on ubuntu, I got some warnings while
compiling the code, and the binary file runs happily, printing the
following lines:
3
0.000000 1073741824

From the result, I got that
1. the function 'add' and the standard library function 'printf'
performs well, because the first line printed is 3.
2. the function 'my_print' doesn't work, the printed line obviously
shows.

All three function are defined external without declaration in file
main.c. But why the first two works well, but the last one fails? Is
it undefined in ISO C?

Thanks in advance.

when C does not know the exact type of arguments/return values it
assumes 'int'. as long as arguments are in fact ints, it works. by when
they differ, problems arise, since compiler put different types on
stack, and tries to take different inside the function.

it gets even funnier when architecture changes. your code gives
different results on ia32 and amd64.

real life situation - about a week ago a friend of mine found similar
bug in code he develops. for some reason it crashed on amd64, while
working stable on ia32. some debugging shown that stdlib.h was not
included, thus malloc was not defined, thus compiler assumed it returns
'int' instead of 'void*'. it worked well on ia32, since there, on GCC
sizeof(int)==sizeof(void*). on the amd64 however
sizeof(int)<sizeof(void*) and program used some random memory space,
instead of newly allocated block.

notice that similar problems may arise if you do not include some header
(say: aaa.h), that you use calls from, but other header you do include
(say bbb.h) does that (i.e. bbb.h includes aaa.h). it will work on your
machine, but when moved to different implementation of library with
bbb.h file, this implicit include may not take place (i.e. bbb.h may
DON'T include aaa.h), users of that system will have, possibly
nontrivial, problems with your code...

summary is short. do NOT never, ever use functions that were not
declared. also always include headers of the functions that you use,
even if you "know" they are already included by some other header.
 
S

Stanley Rice

when C does not know the exact type of arguments/return values it
assumes 'int'. as long as arguments are in fact ints, it works. by when
they differ, problems arise, since compiler put different types on
stack, and tries to take different inside the function.

it gets even funnier when architecture changes. your code gives
different results on ia32 and amd64.

real life situation - about a week ago a friend of mine found similar
bug in code he develops. for some reason it crashed on amd64, while
working stable on ia32. some debugging shown that stdlib.h was not
included, thus malloc was not defined, thus compiler assumed it returns
'int' instead of 'void*'. it worked well on ia32, since there, on GCC
sizeof(int)==sizeof(void*). on the amd64 however
sizeof(int)<sizeof(void*) and program used some random memory space,
instead of newly allocated block.

notice that similar problems may arise if you do not include some header
(say: aaa.h), that you use calls from, but other header you do include
(say bbb.h) does that (i.e. bbb.h includes aaa.h). it will work on your
machine, but when moved to different implementation of library with
bbb.h file, this implicit include may not take place (i.e. bbb.h may
DON'T include aaa.h), users of that system will have, possibly
nontrivial, problems with your code...

summary is short. do NOT never, ever use functions that were not
declared. also always include headers of the functions that you use,
even if you "know" they are already included by some other header.

--
pozdrawiam serdecznie / best regards,
Bartek 'BaSz' Szurgot

http://www.baszerr.org

I found a similar question in comp.lang.c FAQ list.Qustion 1.25

Functions which are called without a declaration in scope, perhaps
because the first call precedes the function's definition, are assumed
to be declared as if by:

extern int f();
That is, an undeclared function is assumed to return int, and to
accept an unspecified number of arguments (though there must be a
fixed number of them and none may be ``narrow'').

The statement above is not consistant with what you said,
'when C does not know the exact type of arguments/return values it
assumes 'int'. as long as arguments are in fact ints, '

The points is that what on earth the type of argument in the function
which is not declared is, int or not specified at all?
As you can see that even I pass an int variable to the function
that requires an int type, the result is confusing.
Does the standard say something about this, I got a draft, but
don't know where to get the point.
 
B

Ben Bacarisse

I found a similar question in comp.lang.c FAQ list.Qustion 1.25

Functions which are called without a declaration in scope, perhaps
because the first call precedes the function's definition, are assumed
to be declared as if by:

extern int f();
That is, an undeclared function is assumed to return int, and to
accept an unspecified number of arguments (though there must be a
fixed number of them and none may be ``narrow'').
Does the standard say something about this, I got a draft, but
don't know where to get the point.

Most of what you want is in 6.5.2.2 paragraph 6.

I don't think it's productive to try to find out about these sorts of
rules by trying things out. It's very easy to think that, because
something works, it's valid C. For example, if you'd left out my_print
you might have concluded that the program is correct when, in fact,
calling a variadic function (like printf) without a prototype in scope
is undefined.
 
K

Keith Thompson

Stanley Rice said:
I found a similar question in comp.lang.c FAQ list.Qustion 1.25

Functions which are called without a declaration in scope, perhaps
because the first call precedes the function's definition, are assumed
to be declared as if by:

extern int f();
That is, an undeclared function is assumed to return int, and to
accept an unspecified number of arguments (though there must be a
fixed number of them and none may be ``narrow'').
[...]

That's no longer the case in C99. In C99, a call to an undeclared
function is a constraint violation. (It's a bad idea in either version
of the language.)
 
Ad

Advertisements

J

James Kuyper

Dear all

Once again I come across some question about linking. In f1.c, I have
the following function definition.
----------------f1.c------------
#include <stdio.h>
void my_print(float a, int b)
{
printf("%f\t%d\n", a, b);
}

int add(int a, int b)
{
return a + b;
}

In main.c, I call the two function without declaration.
----------------main.c-------------
int main(void)
{
int a = add(1, 2);
printf("%d\n", a);
my_print(2.0, a);

return 0;
}

Notice here, in file main.c, I don't include any headers, say, 'f1.h',
'stdio.h', and don't declare anything deliberately, trying to get what
the result would like to be.

main.c relies upon the implicit int rules from C90. They cause add() to,
in effect, be implicitly declared as taking two arguments of type 'int'
and returning an int. They cause printf() in main.c to be treated as if
took a char* and an int as arguments, and returns an int. They cause
my_print() to be treated as if it took a double and an int as arguments,
and returns an int.

These implicit declarations are correct for add(), but not for printf()
or my_print(). As a result, the behavior of your program is undefined.
The implementation is not required to issue a diagnostic for such code.

Since the problem occurs due to a mismatch between the implicit
declaration in one translation unit, and the actual definition in
another translation unit, the compiler cannot detect this mismatch. The
linker could have enough information to detect it, but it doesn't need
that information in order to do it's job, and therefore doesn't
necessarily have access to such information.
In my machine, with compiler gcc on ubuntu, I got some warnings while
compiling the code,

Good. However, it would have been better to compile in C99 mode, where
the implicit int rule has been dropped. Your program provides a prime
example of why it was dropped. At least one diagnostic message for this
code due to missing declarations is mandatory in C99, and gcc would
almost certainly refuse to continue after issuing that message.

and the binary file runs happily, printing the
following lines:
3
0.000000 1073741824

From the result, I got that
1. the function 'add' and the standard library function 'printf'
performs well, because the first line printed is 3.

The printf() call in main() has undefined behavior; that it appeared to
work is a coincidence; it didn't have to work. And even though it
appeared to work, it might have malfunctioned in some way that caused
something else to go wrong.
2. the function 'my_print' doesn't work, the printed line obviously
shows.

It may seem obvious, but it's wrong. The function my_print() should work
perfectly, if called correctly, and if stdout is writeable; it's the
call to my_print() inside main() which is defective, not my_print() itself.
All three function are defined external without declaration in file
main.c. But why the first two works well, but the last one fails? Is
it undefined in ISO C?

Yes.
 
A

André Gillibert

Stanley Rice said:
Dear all

Once again I come across some question about linking. In f1.c, I have
the following function definition.
----------------f1.c------------
#include <stdio.h>
void my_print(float a, int b)
{
printf("%f\t%d\n", a, b);
}

int add(int a, int b)
{
return a + b;
}

In main.c, I call the two function without declaration.
----------------main.c-------------
int main(void)
{
int a = add(1, 2);
printf("%d\n", a);
my_print(2.0, a);

return 0;
}

Implicit declarations are a constraint violation in C99 but valid
in C90.
In C90, my_print(2.0, a) implicitly returns int, and has to take two
arguments: The first is of type double (float is promoted to
double) while the second is of type int. Since the prototype of
my_print doesn't match, the behavior is undefined, even in C90.
 
K

Keith Thompson

James Kuyper said:
Good. However, it would have been better to compile in C99 mode, where
the implicit int rule has been dropped. Your program provides a prime
example of why it was dropped. At least one diagnostic message for this
code due to missing declarations is mandatory in C99, and gcc would
almost certainly refuse to continue after issuing that message.

Almost, but not quite. gcc's diagnostic for a call to an undeclared
function, even with "-std=c99 -pedantic", is merely a warning; after
printing the warning, it proceeds under C90 rules. You can make it a
fatal error by specifying "-pedantic-errors".

This is conforming, of course; the standard doesn't distinguish between
fatal errors and non-fatal warnings; it only requires diagnostics.

[...]
 
K

Keith Thompson

Acid Washed China Blue Jeans said:
If you don't declare a function f, it is assumed to be
int f()
which passes all arguments with var-args rules.

Not exactly, if by "var-args rules" you mean as if calling a variadic
function (one with ", ..." in its declaration).

Under C90 rules, the implicit declaration is for a function that returns
int with a fixed number of parameters corresponding to the promoted
types of the actual arguments in the call. So for

my_print(2.0, a);

the implicit declaration is

int my_print(double, int);

*not*

int my_print(...);

(which isn't even a valid declaration; variadic functions must have at
least one non-variadic parameter).
So every float argument is promoted to double. However your function expects a
float which on most machines is passed in different manner and binary format.
Much confusion will occur.

Right.
 
P

Peter Nilsson

Keith Thompson said:
Not exactly, if by "var-args rules" you mean as if calling a variadic
function (one with ", ..." in its declaration).

If they meant default argument promotions are applied to all
arguments,
then that _is_ how they're passed.
Under C90 rules, the implicit declaration is for a function that
returns int with a fixed number of parameters corresponding to the
promoted types of the actual arguments in the call.

No, the implicit declaration is simply int f(), i.e. a function
returning int with an unspecified number of parameters.
 So for

    my_print(2.0, a);

the implicit declaration is

    int my_print(double, int);

No, it's int my_print(). [Which, BTW, isn't compatible with the
definition
in the OP's post above.]
*not*

    int my_print(...);

(which isn't even a valid declaration; variadic functions must have
at least one non-variadic parameter).

True. But to demonstrate that there are no implicit parameters in the
implicit declaration consider...

#include <stdio.h>

int main(void)
{
int foo();
int i = 42;
int *ip = &i;
const int *cip = &i;

foo(ip);
foo(cip);

return 0;
}

int foo(int *cip)
{
printf("%d\n", *cip);
return 0;
}

If the first call to foo imposed an implicit declaration with fixed
parameters based on the promoted argument type, then the second call
to foo() would violate a constraint. It doesn't under C90, and
wouldn't under C99 if foo had been declared int foo() prior to
calling.
 
Ad

Advertisements

K

Keith Thompson

Peter Nilsson said:
If they meant default argument promotions are applied to all
arguments,
then that _is_ how they're passed.

Yes and no. The default argument promotions are applied in the
same way; the calling convention may be quite different. That's why
calling a variadic function with no visible prototype has undefined
behavior, even in C90. (I still don't know what the previous poster
meant by "var-args rules".
No, the implicit declaration is simply int f(), i.e. a function
returning int with an unspecified number of parameters.

You're right, thanks for the correction.

C90 6.3.2.2 says:

If the expression that precedes the parenthesized argument list
in a function call consists solely of an identifier, and if
no declaration is visible for this identifier, the identifier
is implicitly declared exactly as if, in the innermost block
containing the function call, the declaration

extern int identifier () ;

appeared.

(The effect of the nonexistent rule that I described would be similar to
the actual rule in the case of a single call.)

[snip]
 
Ad

Advertisements

J

James Kuyper

If they meant default argument promotions are applied to all
arguments,
then that _is_ how they're passed.

It's more than just the argument promotions. Variadic functions are
allowed to use an ABI that is incompatible with that used by
non-variadic functions, even when they take arguments of the same
promoted type:

"For two function types to be compatible, ... the parameter type lists,
if both are present, shall agree ... in use of the ellipsis terminator.
.... If one type has a parameter type list and the other type is
specified by a function declarator that is not part of a function
definition and that contains an empty identifier list, the parameter
list shall not have an ellipsis terminator ..." (6.7.5.3p15).

Consider the following code:

define.c:
int foo(char *format, ...)
{
// Definition of f()
}

int bar(
char *format,
int i,
double d
)
{
}

main.c:
int foo();
int bar(char *, ...);

int main(int argc, char *argv[])
{
short s;
float f;
// Set things up

int a = foo(argv[1], s, f);
int b = bar(argv[2], s, f);
return 0;
}

The call to f() has undefined behavior because of 6.5.2.2p6: "... If the
expression that denotes the called function has a type that does not
include a prototype, ... [and] the function is defined with a type that
includes a prototype, and ... the prototype ends with an ellipsis (,
....) ... the behavior is undefined. ..."

In addition, both calls have undefined behavior because of 6.5.2.2p9:
"If the function is defined with a type that is not compatible with the
type (of the expression) pointed to by the expression that denotes the
called function, the behavior is undefined."
 

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

Top