Question regarding prototypes for 0-ary functions.

  • Thread starter João Jerónimo
  • Start date
J

João Jerónimo

João Jerónimo said:
I came across a strange bug today. I had a function declared this way:
sthread_mon_t sthread_monitor_init();

Thank you for all the detailed answers, so common place in this newsgroup.

I know you guys are familiar with the standard, that's why I posted my
question here in this NG. I wanted a standard-based answer, and comp.lang.c
is surely the ideal place when we want standard-based answers.

Thank you,
JJ
 
T

Tim Rentsch

James Kuyper said:
Amandil said:
Amandil wrote:
...
[whether 'int printf();' allows calling printf() legally.]

6.5.2.2p6: "If the expression that denotes the called function has a
type that does not include a prototype, ... If the function is defined
with a type that includes a prototype, and either the prototype ends
with an ellipsis (, ...) or the types of the arguments after promotion
are not compatible with the types of the parameters, the behavior is
undefined."

"int printf()" is not a prototype; therefore if that declaration is in
scope at the point where the call to printf() occurs, the first
condition specified above has been met. The standard does not require
that printf() even be written in C, but it must behave as if it were
defined with a prototype ending with ellipsis; therefore the second
condition applies.

Surely it must be legal to call printf() if it is declared
as per the relevant parts in section 7, but the implication
going the other way isn't leaping out at me. Where do you
get that printf() must behave as if it were defined with
a prototype?

[If it is defined with a prototype, of course the prototype
must have an ellipsis, but that's not the question I'm asking.]
 
T

Tim Rentsch

jameskuyper said:
Amandil wrote:
...

Prototypes and the ability for users to define variadic functions were
added to C when it was first standardized. Prior to standardization,
printf() was a bizarre function which could do things that no user-
defined function could do. It did those things by using highly non-
portable hacks that depended upon details of the argument-passing
mechanisms used on each platform it was implemented on.


I believe that prohibition came in at pretty much the same time as
prototypes and the ability to have user-defined variadic functions.
The variadic function interface requires that there be some way for a
program to locate, at run time, an argument of arbitrary type, using
only the information about the types of the arguments that preceded
it. That imposes some serious constraints on the argument passing
mechanism; in particular, it can be problematic when arguments are
passed in registers rather than on a hardware stack.. The prohibition
on using an old-style function declaration for variadic functions
allows variadic functions and non-variadic functions to use different,
incompatible argument passing mechanisms.

This discussion got me to thinking - what's the intended
behavior for the two file program below?

foo.c
-----
int foo(x) int x; { return x; }

bas.c
-----
#include <stdio.h>
extern int foo( int, ... );

int main( void ){
printf( "foo(1) = %d\n", foo( 1 ) );
return 0;
}

Strictly conforming program, or undefined behavior?

Hint: consulting 6.7.5.3 fairly carefully might be a good idea here...
 
H

Harald van Dijk

James Kuyper said:
Amandil said:
Amandil wrote: [...]
[whether 'int printf();' allows calling printf() legally.]
[...]
Surely it must be legal to call printf() if it is declared as per the
relevant parts in section 7, but the implication going the other way
isn't leaping out at me. Where do you get that printf() must behave as
if it were defined with a prototype?

I think the idea is that it must behave as if it is defined with a type
compatible with int(const char *restrict, ...). Any unprototyped function
type happens to be incompatible with it. For other functions, there is no
problem if the implementation defines it without a prototype (as long as
it declares it with one).

I'm not sure if the standard actually forbids an unprototyped definition
of printf (except for the as-if rule), but if it allows it, then it also
says the behaviour is undefined if <stdio.h> is included or printf is
declared explicitly. In other words, if the standard allows a definition
of int printf(), the standard doesn't allow actually using it.
 
H

Harald van Dijk

This discussion got me to thinking - what's the intended behavior for
the two file program below?

foo.c
-----
int foo(x) int x; { return x; }

bas.c
-----
#include <stdio.h>
extern int foo( int, ... );

int main( void ){
printf( "foo(1) = %d\n", foo( 1 ) );
return 0;
}

Strictly conforming program, or undefined behavior?

Hint: consulting 6.7.5.3 fairly carefully might be a good idea here...

I suspect you're referring to:

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 and the type of each parameter shall
be compatible with the type that results from the application of the
default argument promotions.

This says that if foo is defined as int foo(int x, ...) { ... }, then
extern int foo(); is not a valid declaration, but not the other way
around.

If one type has a parameter type list and the other type is speciï¬ed
by a function deï¬nition that contains a (possibly empty) identiï¬er
list, both shall agree in the number of parameters, and the type of
each prototype parameter shall be compatible with the type that
results from the application of the default argument promotions to
the type of the corresponding identiï¬er.

The definition of foo says it takes one parameter, while the declaration
says it takes a variable number of arguments. Are you making a (correct)
distinction between parameter and argument here -- which seems like an
oversight to me -- or did you mean something else?
 
L

lawrence.jones

Harald van D??k said:
The definition of foo says it takes one parameter, while the declaration
says it takes a variable number of arguments.

And parameters -- 6.7.5.3p9:

If the list terminates with an ellipsis (, ...), no information
about the number or types of the parameters after the comma is
supplied.
 
H

Harald van Dijk

And parameters -- 6.7.5.3p9:

If the list terminates with an ellipsis (, ...), no information about
the number or types of the parameters after the comma is supplied.

Thanks for that. I was looking at something entirely different: doesn't
the definition of parameter say that

int foo(int bar, ...);

only bar -- and no part of the ellipsis -- is a parameter?

3.15
1 parameter
formal parameter
formal argument (deprecated)
object declared as part of a function declaration or definition that acquires a value on
entry to the function, or an identifier from the comma-separated list bounded by the
parentheses immediately following the macro name in a function-like macro definition

The ellipsis is not and does not contain any declaration, right?
 
L

lawrence.jones

Harald van D??k said:
doesn't the definition of parameter say that

int foo(int bar, ...);

only bar -- and no part of the ellipsis -- is a parameter?

The definitions in the standard are general linguistic definitions like
you'd find in a dictionary, not exacting mathematical definitions, so
don't expect them to cover all the obscure details.
 
T

Tim Rentsch

Harald van =?UTF-8?b?RMSzaw==?= said:
James Kuyper said:
Amandil wrote:
Amandil wrote:
[...]
[whether 'int printf();' allows calling printf() legally.]
[...]
Surely it must be legal to call printf() if it is declared as per the
relevant parts in section 7, but the implication going the other way
isn't leaping out at me. Where do you get that printf() must behave as
if it were defined with a prototype?

I think the idea is that it must behave as if it is defined with a type
compatible with int(const char *restrict, ...). Any unprototyped function
type happens to be incompatible with it. For other functions, there is no
problem if the implementation defines it without a prototype (as long as
it declares it with one).

I think all the Standard requires is that printf() be callable /if/
there is an ellipsis-including-prototype in scope. I don't see any
text in the Standard that shows printf() will be callable /only if/
there is an ellipsis-including-prototype in scope.

I'm not sure if the standard actually forbids an unprototyped definition
of printf (except for the as-if rule), but if it allows it, then it also
says the behaviour is undefined if <stdio.h> is included or printf is
declared explicitly. In other words, if the standard allows a definition
of int printf(), the standard doesn't allow actually using it.

Two possibilities:

One, as discussed in another posting, it's possible that a K&R-style
function definition may legally be called through a declaration
that includes a prototype with ellipsis.

Two, printf() might be defined not in C but in assembly or some such,
and which the implementation knows will work with both an non-prototype
declaration and with an ellipsis-including-prototype. This follows the
usual rule for library functions - as long as they are callable using
the methods prescribed in the Standard, they're allowed to use special
implementation magic to make them work.

As possibility (two) definitely allows functions like printf() to
be /workable/ with non-prototype declaractions, does 7.1.4 p 2

Provided that a library function can be declared without reference to
any type defined in a header, it is also permissible to declare the
function and use it without including its associated header.

mean that such declarations must actually /work/? Since 7.1.4p2
grants what could be seen as an explicit definition, there would
be undefined behavior only if there were an explicit statement
somewhere contradicting that. I haven't found any such contradicting
statement yet; just wondering if someone else has noticed something
I've missed.
 
T

Tim Rentsch

Harald van =?UTF-8?b?RMSzaw==?= said:
I suspect you're referring to:

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 and the type of each parameter shall
be compatible with the type that results from the application of the
default argument promotions.

This says that if foo is defined as int foo(int x, ...) { ... }, then
extern int foo(); is not a valid declaration, but not the other way
around.

If one type has a parameter type list and the other type is speciï¬ed
by a function deï¬nition that contains a (possibly empty) identiï¬er
list, both shall agree in the number of parameters, and the type of
each prototype parameter shall be compatible with the type that
results from the application of the default argument promotions to
the type of the corresponding identiï¬er.

The definition of foo says it takes one parameter, while the declaration
says it takes a variable number of arguments. Are you making a (correct)
distinction between parameter and argument here -- which seems like an
oversight to me -- or did you mean something else?

The word 'argument' is correct, part of the compound term 'default
argument promotions', from 6.5.2.2 p 6.

I read both the definition and the declaration as having one
parameter, per 3.15

parameter
formal parameter
formal argument (deprecated)
object declared as part of a function declaration or definition that
acquires a value on entry to the function, or an identifier from the
comma-separated list bounded by the parentheses immediately following
the macro name in a function-like macro definition

The definition for 'foo' declares one object (i.e., variable), and the
declaration for 'foo' declares one object. So, by the Standard's
terminology, each has one parameter.

Since the number of parameters matches, and the other conditions of
6.7.5.3 p 15 are met, the types are compatible, and the function call
should be allowed under the conditions given in 6.5.2.2 (in particular
paragraphs 7 and 9). Right?
 
K

Kaz Kylheku

James Kuyper said:
Amandil said:
Amandil wrote: ...
[whether 'int printf();' allows calling printf() legally.]

6.5.2.2p6: "If the expression that denotes the called function has a
type that does not include a prototype, ... If the function is defined
with a type that includes a prototype, and either the prototype ends
with an ellipsis (, ...) or the types of the arguments after promotion
are not compatible with the types of the parameters, the behavior is
undefined."

"int printf()" is not a prototype; therefore if that declaration is in
scope at the point where the call to printf() occurs, the first
condition specified above has been met. The standard does not require
that printf() even be written in C, but it must behave as if it were
defined with a prototype ending with ellipsis; therefore the second
condition applies.

Surely it must be legal to call printf() if it is declared
as per the relevant parts in section 7, but the implication
going the other way isn't leaping out at me. Where do you
get that printf() must behave as if it were defined with
a prototype?

If you don't #include <stdio.h>, you can use printf as your own file scope
identifier with internal linkage. That works precisely because there isn't any
file scope declaration of printf unless you include the header, and because
the name is not reserved for internal linkage.

If you use the standard printf, with or without your own declaration,
I suppose the implementation /can/ whip up an implicit prototype for it anyway.
That's because implementations can produce arbitrary diagnostics. The
implicit prototype would only serve as the means to diagnose the program and
possibly stop translating it. It wouldn't be used to silently change the
meaning of a correct construct.

E.g.

extern int printf(); /* inadequate, silently replaced by prototype */
int (*pprintf)(int) = printf; /* diagnostic, perhaps translation stops */

Also:

extern int printf(int); /* wrong, but matches pointer type below */
int (*pprintf)(int) = printf; /* diagnostic, perhaps translation stops */

I.e. uses of printf which are wrong due to a bad declaration can be diagnosed
based on knowing what the good declaration is. That is fair game.

We have no way of knowing whether there is a prototype in effect because
the implementation is a black-box. We can only guess from the diagnostics that
it produces that, aha, it must be using knowledge about the function /as if/
that knowledge had been introduced by a prototype.

Who knows how that works; maybe the implementation could have some internal
concept of a ``supplementary prototype'', as an attribute of a function
separate from the ``manifest prototype'' that is actually written, and work
with them both.
 
K

Kaz Kylheku

James Kuyper said:
Amandil wrote:
Amandil wrote:
[...]
[whether 'int printf();' allows calling printf() legally.]
[...]
Surely it must be legal to call printf() if it is declared as per the
relevant parts in section 7, but the implication going the other way
isn't leaping out at me. Where do you get that printf() must behave as
if it were defined with a prototype?

I think the idea is that it must behave as if it is defined with a type
compatible with int(const char *restrict, ...). Any unprototyped function
type happens to be incompatible with it. For other functions, there is no
problem if the implementation defines it without a prototype (as long as
it declares it with one).

I'm not sure if the standard actually forbids an unprototyped definition
of printf (except for the as-if rule), but if it allows it, then it also
says the behaviour is undefined if <stdio.h> is included or printf is
declared explicitly. In other words, if the standard allows a definition
of int printf(), the standard doesn't allow actually using it.

The standard does not allow implementations to support programs that
redefine standard library external names using external linkage.

You have to be clear whether you are talking about a definition or declaration,
and with what kind of linkage.
 
K

Kaz Kylheku

Harald van =?UTF-8?b?RMSzaw==?= said:
Amandil wrote:
Amandil wrote:
[...]
[whether 'int printf();' allows calling printf() legally.]
[...]
Surely it must be legal to call printf() if it is declared as per the
relevant parts in section 7, but the implication going the other way
isn't leaping out at me. Where do you get that printf() must behave as
if it were defined with a prototype?

I think the idea is that it must behave as if it is defined with a type
compatible with int(const char *restrict, ...). Any unprototyped function
type happens to be incompatible with it. For other functions, there is no
problem if the implementation defines it without a prototype (as long as
it declares it with one).

I think all the Standard requires is that printf() be callable /if/
there is an ellipsis-including-prototype in scope. I don't see any
text in the Standard that shows printf() will be callable /only if/
there is an ellipsis-including-prototype in scope.

If there is a prototype in scope which doesn't have the ellipsis, the
call is invalid since the funtion types are incompatible.
(6.5.2.2 (9) and 6.7.5.3 (15)).

If there is a non-prototype declaration in scope (empty parameter list), then
this is not compatible with a variadic function (as you note above), according
to 6.7.5.3 (15). Consistent with this is that the function call is undefined
specifically according to 6.5.2.2 (6), and more generally according to
6.5.2.2 (9).

The remaining possibility is that there is no declaration at all.
This is ruled out in C99 as a constraint violation: 6.5.2.2 (1).
Two possibilities:

One, as discussed in another posting, it's possible that a K&R-style
function definition may legally be called through a declaration
that includes a prototype with ellipsis.

I don't see how since their types are incompatible by 6.7.5.3 (15),
and 6.5.2.2 (9) covers the general case of call and definition type
incompatibility leading to undefined behavior.
 
H

Harald van Dijk

Amandil wrote:
Amandil wrote:
[...]
[whether 'int printf();' allows calling printf() legally.]
[...]
Surely it must be legal to call printf() if it is declared as per the
relevant parts in section 7, but the implication going the other way
isn't leaping out at me. Where do you get that printf() must behave
as if it were defined with a prototype?

I think the idea is that it must behave as if it is defined with a type
compatible with int(const char *restrict, ...). Any unprototyped
function type happens to be incompatible with it. For other functions,
there is no problem if the implementation defines it without a
prototype (as long as it declares it with one).

I'm not sure if the standard actually forbids an unprototyped
definition of printf (except for the as-if rule), but if it allows it,
then it also says the behaviour is undefined if <stdio.h> is included
or printf is declared explicitly. In other words, if the standard
allows a definition of int printf(), the standard doesn't allow
actually using it.

The standard does not allow implementations to support programs that
redefine standard library external names using external linkage.

The standard does allow implementations to support such programs, but
does not require them to.

But I wasn't talking about that.
You have to be clear whether you are talking about a definition or
declaration, and with what kind of linkage.

I was talking about a definition of int printf() _as provided by the
implementation_, rather than a definition of
int printf(FILE *, const char *restrict, ...). Can an implementation that
defines it as int printf() -- and does not rely on the as-if rule for
this -- meet all requirements of the standard?
 
T

Tim Rentsch

Kaz Kylheku said:
Harald van =?UTF-8?b?RMSzaw==?= said:
Amandil wrote:
[...]
[whether 'int printf();' allows calling printf() legally.]
[...]
Surely it must be legal to call printf() if it is declared as per the
relevant parts in section 7, but the implication going the other way
isn't leaping out at me. Where do you get that printf() must behave as
if it were defined with a prototype?

I think the idea is that it must behave as if it is defined with a type
compatible with int(const char *restrict, ...). Any unprototyped function
type happens to be incompatible with it. For other functions, there is no
problem if the implementation defines it without a prototype (as long as
it declares it with one).

I think all the Standard requires is that printf() be callable /if/
there is an ellipsis-including-prototype in scope. I don't see any
text in the Standard that shows printf() will be callable /only if/
there is an ellipsis-including-prototype in scope.

If there is a prototype in scope which doesn't have the ellipsis, the
call is invalid since the funtion types are incompatible.
(6.5.2.2 (9) and 6.7.5.3 (15)).

If there is a non-prototype declaration in scope (empty parameter list), then
this is not compatible with a variadic function (as you note above), according
to 6.7.5.3 (15). Consistent with this is that the function call is undefined
specifically according to 6.5.2.2 (6), and more generally according to
6.5.2.2 (9).

The remaining possibility is that there is no declaration at all.
This is ruled out in C99 as a constraint violation: 6.5.2.2 (1).
Two possibilities:

One, as discussed in another posting, it's possible that a K&R-style
function definition may legally be called through a declaration
that includes a prototype with ellipsis.

I don't see how since their types are incompatible by 6.7.5.3 (15),
and 6.5.2.2 (9) covers the general case of call and definition type
incompatibility leading to undefined behavior.

(1) This statement begs the question about whether two types really
are compatible or not, which is a more subtle question than just
citing the section numbers will answer, as explained in my other more
recent posting.

(2) You snipped out possibility (two), which renders the question of
whether or not the two types are compatible moot. Did you miss that?
 
T

Tim Rentsch

Harald van =?UTF-8?b?RMSzaw==?= said:
On Tue, 02 Dec 2008 17:31:06 -0800, Tim Rentsch wrote:
This discussion got me to thinking - what's the intended behavior for
the two file program below?

foo.c
[...]
I read both the definition and the declaration as having one parameter,
per 3.15

So did I, but this is wrong. See lawrence jones's message
<[email protected]>.

The posting I think you're talking about is this one:

If I understand you correctly, you interpret this remark to mean that
the condition (that is, the requirement that the two types shall agree
in the number of parameters) is not met.

I don't find this argument convincing, because of how 6.7.5.3 p 15 is
worded. Consider these two sentences from that paragraph:

Moreover, the parameter type lists, if both are present, shall
agree in the number of parameters and in use of the ellipsis
terminator; ...

If one type has a parameter type list and the other type is
specified by a function definition that contains a (possibly
empty) identifier list, both shall agree in the number of
parameters, ...

The first sentence clearly distinguishes the condition of agreeing in
the number of parameters from the condition of use of the ellipsis
terminator; if having an ellipsis terminator affected whether two
types agree in the number of parameters then there would be no reason
to state it as a separate requirement.

Since a statement about ellipsis terminators is included in the
first case but not the second, it seems reasonable to conclude that
agreement in the number of parameters is affected only by the number
of parameters explicitly declared. Conversely, if having or not
having an ellipsis terminator were important for determining
compatibility, that would have been mentioned explicitly in the
second case, as it was in the first; since it was not, having or
not having an ellipsis termintor doesn't affect compatibility in
this case.
 
T

Tim Rentsch

The definitions in the standard are general linguistic definitions like
you'd find in a dictionary, not exacting mathematical definitions, so
don't expect them to cover all the obscure details.

If the standard is intended to mean something different from what its
text plainly says, that is a defect and it should be corrected by
revising the standard.

Certainly there should be some leeway in cases where the wording
is ambiguous or can reasonably be read in more than one way.
However that's not the case here; the proposed interpretation
is clearly at odds with the plain text in the standard.
 
K

Kaz Kylheku

The standard does allow implementations to support such programs, but
does not require them to.

Oops, yes. I wrote allow instead of require!

In fact this is not an uncommon extension.
But I wasn't talking about that.


I was talking about a definition of int printf() _as provided by the
implementation_, rather than a definition of
int printf(FILE *, const char *restrict, ...). Can an implementation that
defines it as int printf() -- and does not rely on the as-if rule for

Which specific instance of the as-if rule? The as-if principle is broad.

You mean, if the implementation actually just implements printf
in the old style, using exactly the same mechanisms that are offered
to the user-defined program, and no hidden magic?

Just because old-style functions are incompatible with variadic funtions
doesn't mean that such combinations are required to fail.

In some C compilers, the calling conventions and the handling of
varaidic arguments is such that an old-style definition
can handle the variable arguments just fine.

This means that not only can an implementation define printf
using an old-style definition, but that implementation will likely
allow your own programs to get away with the same thing.

Variable-argument handling in fact grew out of the permissiveness
of old-style functions: that there was no checking on the
number of arguments passed, and the calling conventions of compilers
were such that the superfluous arguments were just ignored by the called
function. Someone hacked up a tool to get at the extra arguments
in a somewhat disciplined way, and this became <varargs.h>

Today you have to use the prototype ellipsis. This allows for
variadic functions to use special conventions. I.e. if you
have a machine with lots of registers, then a 10 argument
call might just use registers for all the arguments.
But a 10 argument call to a variadic function with only
two required arguments might use two registers for those
arguments, and put the rest into memory that can be represented
as a va_list and iterated over.
this -- meet all requirements of the standard?

Which requirements do you suspect couldn't be met if an old-style definition of
printf just worked naively?
 
H

Harald van Dijk

Which specific instance of the as-if rule? The as-if principle is broad.

You mean, if the implementation actually just implements printf in the
old style, using exactly the same mechanisms that are offered to the
user-defined program, and no hidden magic?

Basically, yes. Except that this is done on an implementation where
unprototyped functions don't use the same calling convention as prototyped
variadic functions. (And my point was that the standard might not
specifically disallow it, but if it doesn't, since the only way to use
printf would be by avoiding <stdio.h> and the correct declaration, it
would be absurd for an implementation to try this.)
 

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,998
Messages
2,570,242
Members
46,834
Latest member
vina0631

Latest Threads

Top