Question regarding prototypes for 0-ary functions.

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

João Jerónimo

I came across a strange bug today. I had a function declared this way:
sthread_mon_t sthread_monitor_init();

But mistakenly called it as:
sthread_monitor_init(requests_monitor);

So, the return value was discarded and the object (a mutual-exclusive
monitor) was not initialized, which in turn caused a Segmentation Fault.
The problem is: the compiler[1] swallowed the source without complaining,
which felt strange, because the function was declared (or at least I
thought so) as taking no arguments, and so that call was malformed.

Then, I changed the prototype to:
sthread_mon_t sthread_monitor_init(void);
and this time the compiler complained about the signature mismatch.

Can you explain me this (subtle) difference between the two declarations? I
thought they meant the samething. I'm using -Wall and -std=c99, but not
-pedantic.

[1] gcc-4.1.2

JJ
 
A

Andrey Tarasevich

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();

But mistakenly called it as:
sthread_monitor_init(requests_monitor);

So, the return value was discarded and the object (a mutual-exclusive
monitor) was not initialized, which in turn caused a Segmentation Fault.
The problem is: the compiler[1] swallowed the source without complaining,
which felt strange, because the function was declared (or at least I
thought so) as taking no arguments, and so that call was malformed.

In C the above declaration does not declare the function as as taking no
arguments and does not introduce a prototype for the function. When a
function is declared with '()' parameter list, it means that it takes an
_unspecified_ number of arguments. The compiler will not check the
arguments when you call the function, and if you get it wrong the
behavior is undefined.
Then, I changed the prototype to:
sthread_mon_t sthread_monitor_init(void);
and this time the compiler complained about the signature mismatch.

This does declare the function as as taking no arguments and does
introduce a prototype.
Can you explain me this (subtle) difference between the two declarations? I
thought they meant the samething. I'm using -Wall and -std=c99, but not
-pedantic.

They mean the same thing in C++. But in C they are not the same, as
described above.
 
U

user923005

I came across a strange bug today. I had a function declared this way:
sthread_mon_t sthread_monitor_init();

But mistakenly called it as:
sthread_monitor_init(requests_monitor);

So, the return value was discarded and the object (a mutual-exclusive
monitor) was not initialized, which in turn caused a Segmentation Fault.
The problem is: the compiler[1] swallowed the source without complaining,
which felt strange, because the function was declared (or at least I
thought so) as taking no arguments, and so that call was malformed.

Then, I changed the prototype to:
sthread_mon_t sthread_monitor_init(void);
and this time the compiler complained about the signature mismatch.

Can you explain me this (subtle) difference between the two declarations? I
thought they meant the samething. I'm using -Wall and -std=c99, but not
-pedantic.

[1] gcc-4.1.2

From the C99 standard:
"A function prototype is a declaration of a function that declares the
types of its parameters"

If you do not supply the parameter information (and void is a subcase
of that) you have not provided a function prototype. Therefore, the
compiler has no idea what sort of arguments your function takes and
knows only the return type.
 
B

Ben Pfaff

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();

This is not a prototype. It is only a declaration. It does
not constrain the set of parameters that sthread_monitor_init()
takes by very much.
Then, I changed the prototype to:
sthread_mon_t sthread_monitor_init(void);
and this time the compiler complained about the signature mismatch.

This is a prototype. It means that sthread_monitor_init() has no
parameters.
 
A

Amandil

I came across a strange bug today. I had a function declared this way:
sthread_mon_t sthread_monitor_init();

But mistakenly called it as:
sthread_monitor_init(requests_monitor);

So, the return value was discarded and the object (a mutual-exclusive
monitor) was not initialized, which in turn caused a Segmentation Fault.
The problem is: the compiler[1] swallowed the source without complaining,
which felt strange, because the function was declared (or at least I
thought so) as taking no arguments, and so that call was malformed.

Then, I changed the prototype to:
sthread_mon_t sthread_monitor_init(void);
and this time the compiler complained about the signature mismatch.

Can you explain me this (subtle) difference between the two declarations? I
thought they meant the samething. I'm using -Wall and -std=c99, but not
-pedantic.

[1] gcc-4.1.2

JJ

Others have already replied. I just thought I'd quote directly:

6.7.5.3 (Function declarators, including prototypes)
14) An identifier list declares only the identifiers of the parameters
of the function. An empty list in a function declarator that is part
of a definition of that function specifies that the function has no
parameters. The empty list in a function declarator that is not part
of a definition of that function specifies that no information about
the number or types of the parameters is supplied.

In other words, int printf() declares printf() to be a function
returning an int. This declaration says absolutely nothing about its
arguments. To declare a function taking no arguments, use a
declaration like this (this one is also called a prototype):
void abort(void);

This is still not a definition, which is the actual source code for
the function.

Note that the above chapter & verse are from the 9/7/07 draft of the
standard. A footnote at the end of the above quote refers one to
section 6.11, Future Language Direction:

6.11.6 Function declarators
1 The use of function declarators with empty parentheses (not
prototype-format parameter
type declarators) is an obsolescent feature.

In other words, don't do this in your program, but it's still legal.

Happy coding!

-- Marty Amandil (In pursuit of certain undomesticated semi-aquatic
avians)
 
J

James Kuyper

Ben said:
This is not a prototype. It is only a declaration. It does
not constrain the set of parameters that sthread_monitor_init()
takes by very much.

To be precise, the only constraint it implies is that the function does
NOT take a variable number of arguments. The behavior is undefined if
you use such a declaration to call a function that takes a variable
number of arguments (6.5.2.2p6).
 
J

James Kuyper

Amandil wrote:
....
In other words, int printf() declares printf() to be a function
returning an int.

Note: because printf() takes a variable number of arguments, if you use
such a declaration to call printf(), the behavior is undefined (6.5.2.2p6).
 
A

Amandil

Amandil wrote:

...


Note: because printf() takes a variable number of arguments, if you use
such a declaration to call printf(), the behavior is undefined (6.5.2.2p6).

I'm sorry, but I do not believe I made a mistake. I'm pretty sure K&R2
uses "int printf()", and I didn't see anything in the paragraph you
mentioned supporting your statement. The paragraph I quoted says "...
specifies no information ...". That would seem to include saying "Oh,
but it's a constant number of arguments."
I suppose Keith or Chris will have the final word on this point.

-- Marty Amandil
 
P

Peter Nilsson

Amandil said:
I'm sorry, but I do not believe I made a mistake.
I'm pretty sure K&R2 uses "int printf()",

I'm pretty sure they didn't.
and I didn't see anything in the paragraph you
mentioned supporting your statement.

Try 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.
...I suppose Keith or Chris will have the final word
on this point.

No, WG14 has the final word. :)
 
J

James Kuyper

Amandil said:
I'm sorry, but I do not believe I made a mistake. I'm pretty sure K&R2
uses "int printf()", and I didn't see anything in the paragraph you
mentioned supporting your statement. The paragraph I quoted says "...
specifies no information ...". That would seem to include saying "Oh,
but it's a constant number of arguments."

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.
 
P

Peter Nilsson

Jack Klein said:
Did you actually bother to look at the very specific
chapter and verse reference that James supplied?

I did. AFAICS James' argument is a non sequitur.
Disputing someone who quotes a specific reference to the
standard without even bothering to look it
up is rather foolish, in this group.

True, but he clearly states that he has read it.
In 6.5.2.2 Function calls, the first three sentences of
paragraph 6 specifically says:

"If the expression that denotes the called function has
a type that does not include a prototype,

That matches the case in point.
the integer promotions are performed on each argument,
and arguments that have type float are promoted to
double. These are called the default argument promotions.

This is incidental.
If the number of arguments does not equal the number of
parameters, the behavior is undefined.

This is the only point of relevance, but you'll note the
constraint of 6.5.2.2p2 which talks about the number of
arguments agreeing with the parameters for prototyped
functions, including variadic ones.

I ask, do the following calls agree with the prototype?

int printf(const char *, ...);
printf("Hello\n");
printf("%s\n", "Hello");
printf(""%s %s\n", "Hello", "World");

If they do, then in which one does the number of arguments
agree with the number of parameters, per the constraint?
If all of them, then I ask how relevant the substantive
statement of 6.5.2.2p6 is in clinching James' point?

I see no reason to think that removing the parameter and
ellipses from the prototype above would change the agreeance
or otherwise of the number of arguments and parameters.
If the function is defined with a type that
includes a prototype, ...

Which it doesn't in this case, so that can be skipped.
And if you had read one paragraph further in the section
you quoted, you would find that the first three sentences
of 6.7.5.3 Function declarators (including prototypes),
paragraph 15 state:

"For two function types to be compatible, both shall
specify compatible return types. Moreover, the parameter
type lists, if both are present, shall agree in the number
of parameters and in use of the ellipsis terminator;
corresponding parameters shall have compatible types.
***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."

Which simply defines compatible types for function types.
In and of itself, it doesn't preclude calling a function
through an incompatible signature.
That third sentence, highlighted by me with the asterisks,
is word-for-word identical with ANSI 89/ISO 90. The
wording of the first quote is a little bit different in
ANSI 89/ISO 90, but it still says that calling a variadic
function without a correct prototype in scope yields
undefined behavior.

No, it doesn't. 6.5.2.2p9 (n1256) says it. But that wasn't
cited by either James or yourself.
 
B

Ben Pfaff

James Kuyper said:
To be precise, the only constraint it implies is that the function
does NOT take a variable number of arguments. The behavior is
undefined if you use such a declaration to call a function that takes
a variable number of arguments (6.5.2.2p6).

I think that there is more to it. Without a prototype for it in
scope, it is not possible to call a function that has a parameter
of type float or of an integer type narrower than int. No?
 
J

James Kuyper

Ben Pfaff wrote:
....
I think that there is more to it. Without a prototype for it in
scope, it is not possible to call a function that has a parameter
of type float or of an integer type narrower than int. No?

You're correct, I left that out. More precisely, the behavior is
undefined if you use such a declaration to call such a function.
 
J

James Kuyper

Peter said:
....
I did. AFAICS James' argument is a non sequitur. ....

This is the only point of relevance, but you'll note the
constraint of 6.5.2.2p2 which talks about the number of
arguments agreeing with the parameters for prototyped
functions, including variadic ones.

I ask, do the following calls agree with the prototype?

int printf(const char *, ...);
printf("Hello\n");
printf("%s\n", "Hello");
printf(""%s %s\n", "Hello", "World");

If they do, then in which one does the number of arguments
agree with the number of parameters, per the constraint?
If all of them, then I ask how relevant the substantive
statement of 6.5.2.2p6 is in clinching James' point?

I see no reason to think that removing the parameter and
ellipses from the prototype above would change the agreeance
or otherwise of the number of arguments and parameters.

The requirement that the number of arguments match the number of
parameters applies only to functions not defined with a prototype ending
in an ellipsis. For functions that are so defined, the behavior is
undefined when called without a matching prototype in scope. When such a
function is called using such a prototype, the requirement that the
number of parameters match the number of arguments is waived (6.5.2.2p8).

"... and either the prototype ends with an ellipsis (, ...) or ..."
Which it doesn't in this case, so that can be skipped.

On what basis do you reach that conclusion? At the vary least, it is
certainly permitted for the C standard library to define printf() with
prototype that ends with an ellipsis. More to the point, if it defines
printf() using strictly conforming C code, it must be defined using such
a prototype, because such a prototype is the only way in strictly
conforming C code to provide the functionality required of printf().

I believe that the intent of the prototypes declared by the standard
library headers is that the corresponding functions must behave as if
they were defined with those prototypes, even if the actual definition
of the function is provided in some entirely different language, such as
assembler. However, I will concede that this is debatable, since I can't
cite any text which explicitly says so. Even with that concession, it is
at the very least non-portable to write code that will have undefined
behavior if the implementation chooses to define printf() with a the
same prototype as the one declared in <stdio.h>.
 
J

James Kuyper

Lorenzo said:
I miss the usefulness of a declaration of a function that takes
_unspecified_ number of arguments and I guess is not a syntax error only for
historical reasons. Maybe is always there for "creative" uses of function
pointers but
it seems useless to me anyway...

It is there mainly for historical reasons. Prior to standardization,
this was the ONLY way to declare functions, including printf().

However, one case where this feature is actually necessary is when
declaring a function which takes a parameter which is a pointer to a
function of the same type. There are many ways of handling this in C,
but they all involve, somewhere along the line, a declaration that
leaves the number and types of the parameters unspecified:

void func();
void func( void(*)() );
void func( void(*)( void(*)() ) );
etc.

To declare func() without using that feature would require infinite
recursion.
 
A

Amandil

Did you actually bother to look at the very specific chapter and verse
reference that James supplied?  Disputing someone who quotes a
specific reference to the standard without even bothering to look it
up is rather foolish, in this group.


I most certainly did. ("I didn't see anything in the paragraph you
quoted...") I just didn't see how it applied in my case. I simply
misunderstood the paragraph, which, like many parts of the standard,
is written in the same gobbledygook language the tax code is written
in. Perhaps, if he had quoted and highlighted, as you did, I wouldn't
have erred.
In 6.5.2.2 Function calls, the first three sentences of paragraph 6
specifically says:

"If the expression that denotes the called function has a type that
does not include a prototype, the integer 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 equal the number of parameters, the
behavior is undefined. 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."

And if you had read one paragraph further in the section you quoted,
you would find that the first three sentences of 6.7.5.3 Function
declarators (including prototypes), paragraph 15 state:

"For two function types to be compatible, both shall specify
compatible return types. Moreover, the parameter type lists, if both
are present, shall agree in the number of parameters and in use of the
ellipsis terminator; corresponding parameters shall have
compatible types. ***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."

That third sentence, highlighted by me with the asterisks, is
word-for-word identical with ANSI 89/ISO 90.  The wording of the first
quote is a little bit different in ANSI 89/ISO 90, but it still says
that calling a variadic function without a correct prototype in scope
yields undefined behavior.

That is true, but I didn't get that far. I just looked at the first
sentence and didn't think it applicable to the point in question.

That being said, I still think I remember K&R2 using "int printf()",
but it's been awhile, and I don't have a copy to check back to - if I
did, I would have given a page number - so I won't argue the point
with Peter. My memory isn't perfect.

I would imagine, though, that K&R1 would have had to use "int printf
()" (or perhaps it didn't, seeing as printf() returns an int, which is
the default). However, I'm not old enough to remember K&R1, so I may
be very wrong. I would appreciate, though, an explanation of this
requirement that variadic functions have a prottoype, rather than a
bland declaration, and when did this requirement come to exist.

-- Marty Amandil (Apologizing, as usual, for making a mistake)
 
J

jameskuyper

Amandil wrote:
....
I would imagine, though, that K&R1 would have had to use "int printf
()" (or perhaps it didn't, seeing as printf() returns an int, which is
the default). However, I'm not old enough to remember K&R1, so I may
be very wrong. ...

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 would appreciate, though, an explanation of this
requirement that variadic functions have a prottoype, rather than a
bland declaration, and when did this requirement come to exist.

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.
 
K

Keith Thompson

Amandil said:
That being said, I still think I remember K&R2 using "int printf()",
but it's been awhile, and I don't have a copy to check back to - if I
did, I would have given a page number - so I won't argue the point
with Peter. My memory isn't perfect.

I would imagine, though, that K&R1 would have had to use "int printf
()" (or perhaps it didn't, seeing as printf() returns an int, which is
the default). However, I'm not old enough to remember K&R1, so I may
be very wrong. I would appreciate, though, an explanation of this
requirement that variadic functions have a prottoype, rather than a
bland declaration, and when did this requirement come to exist.

The point is to allow compilers to use a different calling convention
for variadic functions. For example, non-variadic functions might
take as many arguments as possible in registers, while variadic
functions take all arguments corresponding to the "..." on the stack.

Without a prototype, a call such as
printf("x = %d\n", x);
appears to be a call to an ordinary function that takes a char* and an
int, and the compiler will pass both arguments in registers, resulting
in va_arg() grabbing some uninitialized garbage off the stack.

On the other hand, pre-ANSI compilers had to use consistent calling
conventions for ordinary and variadic functions (since prototypes
didn't exist), and many (most?) modern C compilers still do so -- so a
call to printf without a visible prototype is still likely to "work".
But the point is that it's not guaranteed, and can fail badly if the
compiler takes advantage of the new (well, 19 year old) permission.
 
M

Martien Verbruggen

I'm pretty sure they didn't.


Try 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.

Also see 6.7.5.3p15, which explains what compatible function types are,
and includes the following explicit reference to the situation
under discussion:

... 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. ...

Martien
 
J

João Jerónimo

Keith said:
The point is to allow compilers to use a different calling convention
for variadic functions. For example, non-variadic functions might
take as many arguments as possible in registers, while variadic
functions take all arguments corresponding to the "..." on the stack.

Actually, at least for the only architecture whose ABIs I know relativily
well, the last known argument has to be passed on the stack too, because
otherwise the variable argument list wouldn't be able to initialize itself
from the pointer to the last known argument.

This is only a clever guess. :) I've not checked it.

JJ
 

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,744
Messages
2,569,482
Members
44,901
Latest member
Noble71S45

Latest Threads

Top