Confusion in ANSI C's function concepts

V

vaib

hi all ,
It really seems that C never ceases to amaze . All this time
i've been doing C and i thought i was quite adept at it but i was
wrong . So without wasting any more time , here's the confusion .

I read in K&R that ANSI introduced the concept of function
prototyping in C and it was missing there initially ( it borrowed the
concept from C++ ) _but_ it did it make it compulsory to actually
include the function declaration in the program , the reason being -
so that older C code ( the ones missing the declarations ) could
run on newer compilers too . So the situation now is this - if there
is no function declaration corresponding to the function call and the
call does not say anything about the return type then the compiler
should assume a declaration with an int return type . for example ,
main()
{ fun(3) ;
return 0 ;
}
in this case the compiler assumes this declaration - int fun( the
standard says nothing about the parameters so thats implementation
dependent ) ;
and the code compiles without any hitch .

my question is what sort of declaration would the
compiler assume in the following case :
main()
{
void * p ;
p = fun(3) ;
}
in the above case the return type is mentioned as void * .


also what happens in this case :
main()
{
void * p ;
p = malloc(sizeof(int)) ;
return 0 ;
}
the above code also compiles and _executes_ successfully on
gcc .

few more regarding these concepts have cropped up lately in my mind
but i'll ask them as the thread proceeds .


Thanking in anticipation . Vaib .
 
B

Ben Pfaff

vaib said:
my question is what sort of declaration would the
compiler assume in the following case :
main()
{
void * p ;
p = fun(3) ;
}
in the above case the return type is mentioned as void * .

The compiler assumes that the return type is "int". The compiler
always makes this assumption in the absence of a function
declaration.

Also, there is no reason that fun() would have to have a return
type of void * for the above to be valid. Its return type could
be pointer to any object or incomplete type.
 
K

Keith Thompson

vaib said:
It really seems that C never ceases to amaze . All this time
i've been doing C and i thought i was quite adept at it but i was
wrong . So without wasting any more time , here's the confusion .

I read in K&R that ANSI introduced the concept of function
prototyping in C and it was missing there initially ( it borrowed the
concept from C++ ) _but_ it did it make it compulsory to actually
include the function declaration in the program , the reason being -
so that older C code ( the ones missing the declarations ) could
run on newer compilers too . So the situation now is this - if there
is no function declaration corresponding to the function call and the
call does not say anything about the return type then the compiler
should assume a declaration with an int return type .

For certain values of "now".

In the C89/C90 standard, which K&R2 describes, the situation is
basically what you've described. In the C99 standard, the "implicit
int" rule has been dropped, so the examples you show are illegal (more
precisely, they're constraint violations requiring diagnostics).
for example ,
main()
{ fun(3) ;
return 0 ;
}

Please use a more traditional code layout, even for short examples
like this.

main()
{
fun(3);
return 0;
}

In C90, the compiler will assume that fun is a function returning int
with a single parameter of type int (the latter because that's what
you passed it).
in this case the compiler assumes this declaration - int fun( the
standard says nothing about the parameters so thats implementation
dependent ) ;

No, it's not implementation dependent; the compiler's assumption about
the parameter types is based on the actual types of the arguments you
pass, after promotion. (For example, short is promoted to int and
float to double, so if you pass a float argument it will assume the
parameter is of type double.)
and the code compiles without any hitch .

Right -- but if you use C99 mode you'll at least get a warning.
You'll also probably get a warning on the declaration of main; use
"int main(void)" rather than "main()".
my question is what sort of declaration would the
compiler assume in the following case :
main()
{
void * p ;
p = fun(3) ;
}
in the above case the return type is mentioned as void * .

main()
{
void *p;
p = fun(3);
}

A C90 compiler will assume that fun takes one argument of type int and
returns a result of type int. In the absence of a visible
declaration, it *always* assumes a return type of int. There is no
implicit conversion from int to void*, so you should get at least a
warning even in C90 mode.
also what happens in this case :
main()
{
void * p ;
p = malloc(sizeof(int)) ;
return 0 ;
}
the above code also compiles and _executes_ successfully on
gcc .

main()
{
void *p;
p = malloc(sizeof(int));
return 0;
}

sizeof yields a result of type size_t, which is an alias (a typedef)
for some predefined unsigned integer type. Since sizeof is an
operator, not a function, the compiler knows this.

Let's assume size_t is unsigned long in the current implementation.
Then a C90 compiler will assume that malloc takes an argument of type
unsigned long (possibly declared as size_t, but that doesn't matter
for these purposes) and returns a result of type int. You then
attempt to assign this int result to an object of type void*, which is
a constraint violation requiring a diagnostic. gcc doesn't complain
about this by default, because with no options specified gcc is not a
conforming C90 compiler. Try invoking it with at least "-ansi
-pedantic", and possibly "-Wall -Wextra" in addition, and you'll get
at least one warning.

The best lesson to learn from this is not how compilers behave when
you write bad code like this, but how to write good code that the
compiler won't complain about. If you use a standard function,
*always* provide a #include directive for the appropriate header. If
you call your own function, *always* provide a prototype for it, not
just an old-style declaration, but a prototype that specifies the
types of the parameters. And if the compiler complains about a type
mismatch, adding a cast is almost never the right solution; more
often, it's like taping over a warning light on your car's dashboard.
 
K

Keith Thompson

Eric Sosman said:
Keith said:
[...]
main()
{
fun(3);
return 0;
}
In C90, the compiler will assume that fun is a function returning int
with a single parameter of type int (the latter because that's what
you passed it).
in this case the compiler assumes this declaration - int fun( the
standard says nothing about the parameters so thats implementation
dependent ) ;
No, it's not implementation dependent; the compiler's assumption
about
the parameter types is based on the actual types of the arguments you
pass, after promotion. (For example, short is promoted to int and
float to double, so if you pass a float argument it will assume the
parameter is of type double.)
[...]

It may be just a wording problem, but this doesn't seem quite right
to me. As I read it, you're saying that `fun(3)' implicitly declares
the function as `int fun(int)'. If that were so, then the fragment

fun(3); /* now we know `int fun(int)' */
fun(); /* ... or do we? */

... would require a diagnostic, because the second call omits the
argument that the first call's implicit declaration specifies. But
in fact no diagnostic is required, hence the first call does not
actually declare fun's parameters.

Obviously, at least one of the calls must be erroneous -- but a
C90 compiler is not obliged to diagnose either of them.

That's exactly right. In C90, if you're not using prototypes (or in
pre-ANSI C where you couldn't use prototypes), getting the argument
types right in a function call is entirely up to the programmer. If
you get it wrong, the behavior is undefined; no diagnostic is
required.

A sufficiently clever compiler could warn about the fact that it's
seen two incompatible implicit declarations for the same function, but
it's not required to do so.

A call to a unprototyped function doesn't create an implicit
declaration that remains visible after the call. The compiler
generates code to throw the argument values in the general direction
of the function; if they don't land in the right place, that's the
programmer's problem.

Remember that in pre-ANSI C, there was nothing special about printf;
it was just another function. <varargs.h>, the predecessor of
<stdarg.h>, was available, but it didn't affect the *declaration* of
printf. If the compiler couldn't complain about
printf("hello\n");
printf("x = %d\n", x);
then it would have no basis for complaining about
fun(3);
fun();

(In one of my first C programs, written about 10 years before the ANSI
standard, I made a mistake and passed the wrong number of arguments to
a function. The compiler didn't complain. When I realized what was
going on, I was *horrified*. Most of my previous programming had been
in Pascal.)
 
H

Harald van Dijk

Keith said:
[... concerning `fun(3)' without a declaration ...] A call to a
unprototyped function doesn't create an implicit declaration that
remains visible after the call. [...]

Are you sure of that? I don't have a C90 at hand for study,
and of course in C99 the rules changed.

I argued earlier that `fun(3)' did not magically declare
`int fun(int)', which your earlier post seemed to imply (though as I
said, it might just have been a misreading). However, all the compilers
I've seen have behaved as if a different declaration did in fact appear
out of thin air: `int fun()'. Specifically,

int main(void) {
fun(3);
return 0;
}

double fun(int x) { return x * 0.5; }

... generates a diagnostic indicating that the definition of fun()
conflicts with a prior declaration. So if this isn't just a matter of
compilers trying to be helpful and informative, it means that the
implicit declaration has "staying power" beyond the immediate context.

The implicit declaration does remain, but your example doesn't show it.
The declaration of fun has block scope, so it's not visible when the
definition of fun is encountered. Your example is one of compilers trying
to be helpful and informative. But, consider this program:

int main(void) {
int (*fp)();
fp = fun; /* #1 */
{ fun(); }
fp = fun; /* #2 */
fun();
fp = fun; /* #3 */
return 0;
}

the assignments marked #1 and #2 are invalid, while the assignment marked
#3 is valid. #1 is invalid because fun is undeclared, but #2 is invalid
for the same reason. #3, on the other hand, is valid, because the implicit
declaration of function fun remains visible for the rest of the block.
 
K

Keith Thompson

Eric Sosman said:
Keith said:
[... concerning `fun(3)' without a declaration ...]
A call to a unprototyped function doesn't create an implicit
declaration that remains visible after the call. [...]

Are you sure of that? I don't have a C90 at hand for study,
and of course in C99 the rules changed.

I argued earlier that `fun(3)' did not magically declare
`int fun(int)', which your earlier post seemed to imply (though
as I said, it might just have been a misreading). However, all
the compilers I've seen have behaved as if a different declaration
did in fact appear out of thin air: `int fun()'. Specifically,

int main(void) {
fun(3);
return 0;
}

double fun(int x) { return x * 0.5; }

... generates a diagnostic indicating that the definition of fun()
conflicts with a prior declaration. So if this isn't just a matter
of compilers trying to be helpful and informative, it means that
the implicit declaration has "staying power" beyond the immediate
context.

Assuming a C90-conforming compiler, it's just being helpful; the
diagnostic is not required.

But now that I've actually checked the C90 standard, I see that my
description was a bit off (though I think the effect is about the
same).

C90 6.3.2.2:

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.

with a footnote:

That is, an identifier with block scope declared to have external
linkage with type function without parameter information and
returning an int. If in fact it is not defined as having type
``function returning int'', the behavior is undefined.

So there is an implicit declaration, but it's not visible outside the
innermost block containing the call, and it provides no parameter
information.

Later in that same section in C90:

If the expression that denotes the called function has a type that
does not include a prototype, the integral promotions are
performed on each argument and arguments that have type float are
promoted to double. These are called the _default argument
promotions_. If the number of arguments does not agree with the
number of parameters, the behavior is undefined. If the function
is defined with a type that does not include a prototype, and the
types of the arguments after promotion are not compatible with
those of the parameters after promotion, the behavior is
undefined. If the function is defined with a type that includes a
prototype, and the types of the arguments after promotion are not
compatible with the types of the parameters, or if the prototype
ends with an ellipsis (,... ), the behavior is undefined.

Even without implicit declations, non-prototype function declarations
can cause similar problems. For example, this requires no diagnostic,
even in C99, though it's certain to invoke undefined behavior:

int main(void)
{
extern void func();
func();
func(42);
func("hello");
return 0;
}
 
D

David Thompson

vaib wrote:


It shouldn't. Since malloc() has not been declared, it is
assumed to return an int and the compiler should object to your
attempt to convert that int to a pointer. A diagnostic is required;
I speculate that you are running gcc in some kind of lenient mode.
Depends on quite what you mean by 'should[n't]'.

A diagnostic is required, but need be no more than a warning. If you
ignore or suppress (or leave suppressed) the warning it's Undefined
Behavior; the compiler can proceed to generate code to do whatever it
likes. On many machines (and implementations) where 'int' and 'void*'
are both a word, and use the same return mechanism, an easy thing to
do is just use the assumed-int-actually-voidptr return value, and it
does work. This is not only permitted, but arguably reasonable.
But it is not required, and (thus) should not be relied on.

And as both you and Keith noted (but I snipped) you should request and
at least think about diagnostics. Even when you aren't forced to.

And nearby on Wed, 24 Sep 2008 12:58:28 -0700, Keith Thompson
{
fun(3);
return 0;
}

In C90, the compiler will assume that fun is a function returning int
with a single parameter of type int (the latter because that's what
you passed it).


No, it's not implementation dependent; the compiler's assumption about
the parameter types is based on the actual types of the arguments you
pass, after promotion. (For example, short is promoted to int and
float to double, so if you pass a float argument it will assume the
parameter is of type double.)
It's not impl _defined_, but it can be impl _dependent_; the default
argument promotions for narrow unsigned integer types depends on the
ranges, (mostly) chosen by the impl, for several of the integer types.

<snip other points>

- formerly david.thompson1 || achar(64) || worldnet.att.net
 

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,769
Messages
2,569,582
Members
45,070
Latest member
BiogenixGummies

Latest Threads

Top