Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

A question about the type of a non-prototyped function name.

4 views
Skip to first unread message

Ronald F. Guilmette

unread,
Aug 5, 1993, 1:58:40 PM8/5/93
to
I am having a bit of a disagreement with one implementor regarding the
following bit of code, and whether or not it violates a semantic rule.

I hope that some of the folks here might be able to give me more insight
into this issue than I now have.

-----------------------------------------------------------------------------
void foo (i, j) int i; int j; { }
void (*p) (int i);

void test ()
{
p = foo; /* semantic rule violation? */
}
-----------------------------------------------------------------------------

With regard to this example (which I may have even asked about before),
there seems to be some a conflict in the standard.

Section 3.5.4.3 (classic):

"For two function types to be compatible...

...If one type has a {prototype-style} parameter type list and
the other type is specified by a function definition that contains
a (possibly empty) identifier list {i.e. non-prototype style},
both shall argee in the number of parameters..."

The above rule (which appears in a semantics section) would seem to indicate
that following its definition, the name of a function which is defined using
the old-style (non-prototyped) function heading syntax should effectively
act (in some respects at least) as if it had been prototyped.

However in 3.7.1 (classic) we have the statement:

"The declarator in a function definition specifies the name of the
function being defined and the identifiers of its parameters. If
the declarator includes a parameter type list {i.e. a prototype},
the list also specifies the types of the parameters; such a
declarator also serves as a function prototype for later calls
to the same function in the same translation unit."

The fact that this statement explicitly says that a prototyped function
definition acts like a prototyped function declaration (for later calls)
while NOT saying that same thing about NON-PROTOTYPED function definitions
appears significant. Specifically, it seems that the intention was that
the names of functions defined using the old-style function heading syntax
(i.e. non-prototyped) SHOULD NOT subsequently be treated as if they had been
prototyped (at least as far as calls are concerned).

The two rules quoted above thus appear to lead to an odd situation, in which
the names of functions which have been defined without prototypes must, at
one and the same time, themselves have types which are BOTH prototyped types
(in some cases) and also non-prototyped types (in other cases).

More specifically, it would seem that the name `foo' in the example above
should be considered to have type `void (*)(int, int)' if it is used in
certain contexts (e.g. assignment to, or comparison with another value
whose type is pointer-to-function) while it should be considered to have
the (unprototyped) type `void (*)()' when used in a call.

Have I missed anything? Does the name `foo' really have two different
types at points following its definition?

--

-- Ronald F. Guilmette ------------------------------------------------------
------ domain address: r...@netcom.com ---------------------------------------
------ uucp address: ...!uunet!netcom.com!rfg -------------------------------

Ronald F. Guilmette

unread,
Aug 5, 1993, 2:21:45 PM8/5/93
to
In article <rfgCBA...@netcom.com> r...@netcom.com (Ronald F. Guilmette) writes:
>I am having a bit of a disagreement with one implementor regarding the
>following bit of code, and whether or not it violates a semantic rule.

Regarding my earlier message on this topic, I have to clarify that I
misspoke.

I said the code in question might contain a violation of a semantic rule.
That was incorrect. The semantic rules (from 3.5.4.3) merely attempt to
define the semantics of the situation, especially as they relate to the
compatability of various (function) types. If (in the assignment) the
two function types are not compatible (for assignment) however, then
that would constitute a constraint violation (which would require a
diagnostic).

So the question really is "Must a standard conforming implementation
issue a diagnostic for the code in question?"

I believe that the answer is clearly "yes", but that still leaves open the
question of the type of `foo', when the name `foo' is used directly in a
call. In such cases, must a diagnostic also be issued if the number,
order, or types of the actual parameters do not match those required by
the (prototyped?) type of `foo'?

For reference, I am providing the code (and relevant quotations) again (below).

-- rfg


-----------------------------------------------------------------------------
void foo (i, j) int i; int j; { }
void (*p) (int i);

void test ()
{

p = foo; /* diagnostic required? */
foo (99); /* diagnostic required? */
}
-----------------------------------------------------------------------------

Section 3.5.4.3 (classic):

"For two function types to be compatible...

...If one type has a {prototype-style} parameter type list and
the other type is specified by a function definition that contains
a (possibly empty) identifier list {i.e. non-prototype style},
both shall argee in the number of parameters..."

Section 3.7.1 (classic):

"The declarator in a function definition specifies the name of the
function being defined and the identifiers of its parameters. If
the declarator includes a parameter type list {i.e. a prototype},
the list also specifies the types of the parameters; such a
declarator also serves as a function prototype for later calls
to the same function in the same translation unit."

--

Clive Feather

unread,
Aug 6, 1993, 9:06:50 AM8/6/93
to
In article <rfgCBA...@netcom.com> r...@netcom.com (Ronald F. Guilmette) writes:
> void foo (i, j) int i; int j; { }
> void (*p) (int i);
>
> void test ()
> {
> p = foo; /* diagnostic required? */
> foo (99); /* diagnostic required? */
> }

A diagnostic is required for the first line, as the types are not
compatible (ISO 6.5.4.3, quoted by RFG). A diagnostic is not required for
the second line (ISO 6.3.2.2): "the number and types of arguments are
not compared with those of the parameters in a function definition that
does not include a function prototype declarator".

--
Clive D.W. Feather | IXI Ltd (an SCO company) | If you lie to the compiler,
cl...@x.co.uk | Vision Park | it will get its revenge.
Phone: +44 223 236 555 | Cambridge CB4 4ZR | - Henry Spencer
Fax: +44 223 236 466 | United Kingdom |

Ronald F. Guilmette

unread,
Aug 7, 1993, 2:57:44 AM8/7/93
to
In article <CBC9r...@x.co.uk> cl...@x.co.uk (Clive Feather) writes:
>In article <rfgCBA...@netcom.com> r...@netcom.com (Ronald F. Guilmette) writes:
>> void foo (i, j) int i; int j; { }
>> void (*p) (int i);
>>
>> void test ()
>> {
>> p = foo; /* diagnostic required? */
>> foo (99); /* diagnostic required? */
>> }
>
>A diagnostic is required for the first line, as the types are not
>compatible (ISO 6.5.4.3, quoted by RFG). A diagnostic is not required for
>the second line (ISO 6.3.2.2): "the number and types of arguments are
>not compared with those of the parameters in a function definition that
>does not include a function prototype declarator".

So at some point (presumably after the end of its full declarator) the
name `foo' actually has two different types simultaneously???

This is some truly weird s***!

Michael J. Eager

unread,
Aug 7, 1993, 7:51:23 PM8/7/93
to
In article 2...@netcom.com, r...@netcom.com (Ronald F. Guilmette) writes:
>I am having a bit of a disagreement with one implementor regarding the
>following bit of code, and whether or not it violates a semantic rule.
>
>I hope that some of the folks here might be able to give me more insight
>into this issue than I now have.
>
>-----------------------------------------------------------------------------
>void foo (i, j) int i; int j; { }
>void (*p) (int i);
>
>void test ()
>{
> p = foo; /* semantic rule violation? */
>}
>-----------------------------------------------------------------------------
>
>With regard to this example (which I may have even asked about before),
>there seems to be some a conflict in the standard.
>
>Section 3.5.4.3 (classic):
>
> "For two function types to be compatible...
>
> ...If one type has a {prototype-style} parameter type list and
> the other type is specified by a function definition that contains
> a (possibly empty) identifier list {i.e. non-prototype style},
> both shall argee in the number of parameters..."
>
>The above rule (which appears in a semantics section) would seem to indicate
>that following its definition, the name of a function which is defined using
>the old-style (non-prototyped) function heading syntax should effectively
>act (in some respects at least) as if it had been prototyped.

You're reading words which aren't there. It says nothing about an unprototyped
function declaration acting as if it is prototyped. It says that to be for
two function types to be compatible, the number of parameters must match.
That's all.

>
>However in 3.7.1 (classic) we have the statement:
>
> "The declarator in a function definition specifies the name of the
> function being defined and the identifiers of its parameters. If
> the declarator includes a parameter type list {i.e. a prototype},
> the list also specifies the types of the parameters; such a
> declarator also serves as a function prototype for later calls
> to the same function in the same translation unit."
>
>The fact that this statement explicitly says that a prototyped function
>definition acts like a prototyped function declaration (for later calls)
>while NOT saying that same thing about NON-PROTOTYPED function definitions
>appears significant. Specifically, it seems that the intention was that
>the names of functions defined using the old-style function heading syntax
>(i.e. non-prototyped) SHOULD NOT subsequently be treated as if they had been
>prototyped (at least as far as calls are concerned).

They weren't prototyped. So they are not treated as if they were.

>
>The two rules quoted above thus appear to lead to an odd situation, in which
>the names of functions which have been defined without prototypes must, at
>one and the same time, themselves have types which are BOTH prototyped types
>(in some cases) and also non-prototyped types (in other cases).

You seem to be assuming more from compatiblity than is stated in the standard.
Two types being compatible does not mean that they are identical.

>
>More specifically, it would seem that the name `foo' in the example above
>should be considered to have type `void (*)(int, int)' if it is used in
>certain contexts (e.g. assignment to, or comparison with another value
>whose type is pointer-to-function) while it should be considered to have
>the (unprototyped) type `void (*)()' when used in a call.
>
>Have I missed anything? Does the name `foo' really have two different
>types at points following its definition?

No. 'foo' only has one type. What is true is that when you assign 'foo'
to a pointer-to-function, the types are compatible (not identical) if (among
other things) the number of parameters are the same.

Note that if foo had a prototype, the requirements on compatibility are tighter.
But there is still no requirement for the types to be identical.

For example:

void foo (const int i, const int j) { }
void (*f) (int i, int j) = foo;

Clearly, the types of foo and f are different. Clearly, they are compatible.
---
Michael J. Eager Michae...@eagercon.com
Eager Consulting (415) 325-8077
1960 Park Boulevard, Palo Alto, CA 94306-1141

Michael J. Eager

unread,
Aug 7, 1993, 8:16:27 PM8/7/93
to
In article 4...@netcom.com, r...@netcom.com (Ronald F. Guilmette) writes:
>In article <rfgCBA...@netcom.com> r...@netcom.com (Ronald F. Guilmette) writes:
>
>So the question really is "Must a standard conforming implementation
>issue a diagnostic for the code in question?"
>
>I believe that the answer is clearly "yes", but that still leaves open the
>question of the type of `foo', when the name `foo' is used directly in a
>call. In such cases, must a diagnostic also be issued if the number,
>order, or types of the actual parameters do not match those required by
>the (prototyped?) type of `foo'?
>
>For reference, I am providing the code (and relevant quotations) again (below).
>
>-- rfg
>
>
>-----------------------------------------------------------------------------
>void foo (i, j) int i; int j; { }
>void (*p) (int i);
>
>void test ()
>{
> p = foo; /* diagnostic required? */

Yes. The two types are not compatible. Specifically, the number of parameters
do not match.


> foo (99); /* diagnostic required? */

No. 3.3.2.2 Function calls, constraints, requires that if the function has a
type which includes a prototype, that the number of arguments agree with the number
of parameters. Nothing is said about non-prototyped functions.


>}
>-----------------------------------------------------------------------------
>
>Section 3.5.4.3 (classic):
>
> "For two function types to be compatible...
>
> ...If one type has a {prototype-style} parameter type list and
> the other type is specified by a function definition that contains
> a (possibly empty) identifier list {i.e. non-prototype style},
> both shall argee in the number of parameters..."

Requires the first diagnostic.

>
>Section 3.7.1 (classic):
>
> "The declarator in a function definition specifies the name of the
> function being defined and the identifiers of its parameters. If
> the declarator includes a parameter type list {i.e. a prototype},
> the list also specifies the types of the parameters; such a
> declarator also serves as a function prototype for later calls
> to the same function in the same translation unit."

Seems irrelevant. The function doesn't have a prototype. So there is nothing
to "serve as a function prototype".


Yes, you can have a clever compiler which remembers non-prototype function definitions
and issues warning messages. But you cannot act as if there was a prototype. In
particular, if there is no prototype, standard promotions must be done. With a
prototype, this is not necessary.

Take another example:

void foo (i, j) float i, j; { }


void (*f) (int i, int j) = foo;

float x=1.414, y=3.14;

foo(x, y);

Everything about the function pointer assignement is irrelevant to the call.

No diagnostics are to be issued. Like it or not, 'f' and 'foo' are compatible.

The call to 'foo' must promote both x and y to double before calling the function.
It may _not_ act as if there were a prototype in scope, since none exists.

John F. Woods

unread,
Aug 10, 1993, 12:24:23 PM8/10/93
to
ea...@eagercon.com (Michael J. Eager) writes:
> void foo (i, j) float i, j; { }
> void (*f) (int i, int j) = foo;

>No diagnostics are to be issued. Like it or not, 'f' and 'foo' are compatible.

"For two function types to be compatible, ...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,
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 identifier."

E.g. if you mix old-style and new-style declared functions, the types of the
arguments have to match, remembering that old-style functions don't "really"
take float arguments (etc.). 'int' is not compatible with 'double', so the
types of 'f' and 'foo' aren't compatible.

Philip Lantz

unread,
Aug 10, 1993, 8:20:20 PM8/10/93
to
>void foo (i) int i; { }
>void (*p) (double i);

>
>void test ()
>{
> p = foo; /* diagnostic required? */
>}

The fact that the standard requires a diagnostic for this astonishes
me, even though I have followed the arguments and looked up the
relevant sections. I do not believe that the committee intended that a
compiler must remember the parameter types of a function that is
defined without a prototype.

I believe that the paragraph of 3.5.4.3 defining compatible function
types was written with the purpose of rendering undefined a call to a
function via an function designator with an incompatible type.
(Section 3.3.4 (Cast operators): "If a converted pointer is used to
call a function that has a type that is not compatible with the type of
the called function, the behavior is undefined.)

I think that the fact that it also requires a diagnostic for an assignment
was overlooked.

Any opinions?

Philip Lantz
p...@cirrus.ht.intel.com

David M. Kristol

unread,
Aug 11, 1993, 9:33:37 AM8/11/93
to

I implemented much of the front-end for (what is now) USL's ANSI C
compiler. My office-mate was Dave Prosser, who was redactor for the
ANSI Standard. He came back from a Standards meeting at some point and
told me about this addition. I grumbled, wrung my hands, and added the
necessary code to the front-end. The change was certainly intentional
-- they knew what they did.

I think the warning (in our case) provides a service to the user,
by warning that the types are, in fact, incompatible. The USL
compiler does not prevent you from doing it ("The Spirit of C"),
just points out that you may be making a mistake.

Dave Kristol

John F. Woods

unread,
Aug 11, 1993, 11:27:58 AM8/11/93
to
p...@cirrus.ht.intel.com (Philip Lantz) writes:
>>void foo (i) int i; { }
>>void (*p) (double i);
>>void test ()
>>{
>> p = foo; /* diagnostic required? */
>>}
>The fact that the standard requires a diagnostic for this astonishes
>me, even though I have followed the arguments and looked up the
>relevant sections. I do not believe that the committee intended that a
>compiler must remember the parameter types of a function that is
>defined without a prototype. ...

>I think that the fact that it also requires a diagnostic for an assignment
>was overlooked.

I think the committee was generally of the opinion that mixing old-style and
new-style function declarations was a bad idea. I cannot see any reason why
one would actively not want a diagnostic for

void foo(i) int i; {}
void (*p)(double i) = foo;

when it is unquestionably required for

void foo(int i) {}
void (*p)(double i) = foo;

The compiler must already be able to remember the argument types of prototyped
functions; requiring it to also remember the argument types of defined non-
prototyped functions is not a significant burden.

On the other hand, the Standard does not require a diagnostic in the case of

void foo(i) int i; {}

...
foo(1, 2);

but explicitly says the results are undefined; the same knowledge of the
"effective" prototype of foo() that comes from the apparent requirement for
a diagnostic in the first question would enable the compiler to be able to
diagnose this as well.

Clive Feather

unread,
Aug 12, 1993, 10:50:21 AM8/12/93
to
In article <rfgCBD...@netcom.com> r...@netcom.com (Ronald F. Guilmette) writes:
>In article <CBC9r...@x.co.uk> cl...@x.co.uk (Clive Feather) writes:
>>In article <rfgCBA...@netcom.com> r...@netcom.com (Ronald F. Guilmette) writes:
>>> void foo (i, j) int i; int j; { }
>>> void (*p) (int i);
>>> void test ()
>>> {
>>> p = foo; /* diagnostic required? */
>>> foo (99); /* diagnostic required? */
>>> }
>> A diagnostic is required for the first line, as the types are not
>> compatible (ISO 6.5.4.3, quoted by RFG). A diagnostic is not required for
>> the second line (ISO 6.3.2.2): "the number and types of arguments are
>> not compared with those of the parameters in a function definition that
>> does not include a function prototype declarator".
> So at some point (presumably after the end of its full declarator) the
> name `foo' actually has two different types simultaneously???

Nonsense. At all times, foo has the type:

void function without prototype with int and int parameters

The standard says that:
(a) this is not compatible with "void function with int parameter"
(b) that a diagnostic is required for the first line, as assignments must
involve compatible types (except where stated otherwise, which does
not apply here)
(c) that no diagnostic is required for the second line, as calls where
the function has type "... function without prototype ..." do not
require the parameters to be checked.

Christopher R. Volpe

unread,
Aug 12, 1993, 1:41:51 PM8/12/93
to
In article <1993Aug11.1...@allegra.att.com>, d...@allegra.att.com (David M. Kristol) writes:
|> In article <CBKJL...@ibeam.intel.com> p...@cirrus.ht.intel.com (Philip Lantz) writes:
|> >>void foo (i) int i; { }
|> >>void (*p) (double i);
|> >>
|> >>void test ()
|> >>{
|> >> p = foo; /* diagnostic required? */
|> >>}
|> >

|> >Any opinions?


|>
|> I implemented much of the front-end for (what is now) USL's ANSI C
|> compiler. My office-mate was Dave Prosser, who was redactor for the
|> ANSI Standard. He came back from a Standards meeting at some point and
|> told me about this addition. I grumbled, wrung my hands, and added the
|> necessary code to the front-end. The change was certainly intentional
|> -- they knew what they did.
|>
|> I think the warning (in our case) provides a service to the user,
|> by warning that the types are, in fact, incompatible. The USL
|> compiler does not prevent you from doing it ("The Spirit of C"),
|> just points out that you may be making a mistake.

If the compiler must carry around type information about the function foo
in order to catch compatibility problems with p, then why is it not
also required to diagnose improper calls through foo, such as foo("hello")?

|>
|> Dave Kristol

--

Chris Volpe
G.E. Corporate R&D
vol...@crd.ge.com

Jutta Degener

unread,
Aug 14, 1993, 4:47:34 PM8/14/93
to
vo...@bart.crd.ge.com (Christopher R. Volpe) writes:
>
> If the compiler must carry around type information about the function foo
> in order to catch compatibility problems with p, then why is it not
> also required to diagnose improper calls through foo, such as foo("hello")?

Good question. I don't know the definite answer (I wasn't there), but
it might have been this:

If someone writes an assignment of an old-style function to a prototyped
function pointer (or vice versa), they are using function prototypes,
and must be aware of the guarantees and demands that this involves.

But if, in an old-style program, someone writes a three-argument function
and calls it with only two arguments (e.g., open()), this might very well
be intentional, if dubious. Forcing an error at this point will break old
code. A protective compiler is already free to issue all the warnings it
likes.

So why don't the compilers that have to warn about assignments of incomplete
types also warn us about calls to unprototyped (but declared and defined)
functions with the wrong number of arguments, or at least offer an option
to do so?

Beats me.

Jutta Degener (ju...@cs.tu-berlin.de)

0 new messages