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

Proposal: State explicitly that int main() { /* ... */ } is valid

37 views
Skip to first unread message

Keith Thompson

unread,
Nov 30, 2011, 1:24:59 PM11/30/11
to
C99 5.1.2.2.1 says that the main function shall be defined with a return
type of int and with no parameters:

int main(void) { /* ... */ }

or with two parameters:

int main(int argc, char *argv[]) { /* ... */ }

or equivalent; or in some other implementation-defined manner. (There
are more details; see the standard for the full requirement.)

I've argued that this:

int main() { /* ... */ }

violates this requirement, and thus has undefined behavior. Even though
it's in some sense equivalent *as a definition* to the first form, it's
not equivalent *as a declaration*. For example, this:

int main(void) {
if (0) main(42);
}

requires a diagnostic, but this:

int main() {
if (0) main(42);
}

does not.

On the other hand, I've never heard of a compiler that will reject
int main() { /* ... */ }
or that will make it behave differently from the standard-approved
definition (other than the lack of a diagnostic for an incorrect
recursive call). So it's *practically* portable, whether the
standard says so or not. And rejecting "int main()" would break
a number of old (and new!) programs.

Therefore I suggest that, in the C201X standard (if it's not too late),
wording should be added saying that, for purposes of that section,
int main() { /* ... */ }
is "equivalent" to
int main(void) { /* ... */ }
This could be a footnote, or even an addition to the existing footnote.

We don't necessarily need to resolve the question of whether it's
equivalent under C99 rules.

An aside: The wording of 5.1.2.2.1 could imply that main()
must always return int, even if it's defined "in some other
implementation-defined manner". 5.1.2.2.3 starts with "If the return
type of the main function is a type compatible with int ...", which
implies that it can be something other than int. It would be good,
IMHO, to tweak 5.1.2.2.1 to make it clear that implementation-defined
forms of main needn't return int.

What I'd *really* like to do is remove old-style function
declarations and definitions from the language; after all, they've
been officially obsolescent since 1989. But if we're not going to
do that, it would be nice to resolve the status of "int main()",
and declaring it to be valid would be the least disruptive solution.

--
Keith Thompson (The_Other_Keith) ks...@mib.org <http://www.ghoti.net/~kst>
"We must do something. This is something. Therefore, we must do this."
-- Antony Jay and Jonathan Lynn, "Yes Minister"

Joel C. Salomon

unread,
Nov 30, 2011, 6:11:42 PM11/30/11
to
On 11/30/2011 01:24 PM, Keith Thompson wrote:
> What I'd *really* like to do is remove old-style function
> declarations and definitions from the language; after all, they've
> been officially obsolescent since 1989.

There's at least one still-valid use-case:

float sum(float a[], size_t n);

float sum(a, n)
float a[n]; size_t n;
{ /* ... */ }


I'm tempted to make this C99 version:

float sum99(size_t n, float a[/* static? */ n])
{ /* ... */ }

but, as the comment indicates, I'm not sure whether it would be valid;
IIRC the [static n] notation requires n to be a constant. Anyhow, if
the interface is frozen then an old-style definition is the way to go.

Old-style declarations might be safely removed though, I think.

--Joel
Message has been deleted

Jun Woong

unread,
Dec 1, 2011, 3:40:27 AM12/1/11
to
Keith Thompson <ks...@mib.org> wrote:
[...]
> I've argued that this:
>
>     int main() { /* ... */ }
>
> violates this requirement, and thus has undefined behavior.  Even though
> it's in some sense equivalent *as a definition* to the first form, it's
> not equivalent *as a declaration*.  For example, this:
>
>     int main(void) {
>         if (0) main(42);
>     }
>
> requires a diagnostic, but this:
>
>     int main() {
>         if (0) main(42);
>     }
>
> does not.
>

Should requiring a diagnostic count as a part of being equivalent?
Not that I'm saying it should not, but just asking if there is any
implication in the text of the standard.

> On the other hand, I've never heard of a compiler that will reject
>     int main() { /* ... */ }
> or that will make it behave differently from the standard-approved
> definition (other than the lack of a diagnostic for an incorrect
> recursive call).  So it's *practically* portable, whether the
> standard says so or not.  And rejecting "int main()" would break
> a number of old (and new!) programs.
>
> Therefore I suggest that, in the C201X standard (if it's not too late),
> wording should be added saying that, for purposes of that section,
>     int main() { /* ... */ }
> is "equivalent" to
>     int main(void) { /* ... */ }
> This could be a footnote, or even an addition to the existing footnote.
>

Agreed, but this reminds me of a post I had wrote before about
defects in the standard's description on old-style declarations. It
was filed as a DR by Clive Feather and the committee's response to it
was they did not care about old-style declarations any longer. So I
guess that "int main()" would not make its way into the standard even
if the deprecated feature survives in C201X.

> We don't necessarily need to resolve the question of whether it's
> equivalent under C99 rules.
>
[...]
>
> What I'd *really* like to do is remove old-style function
> declarations and definitions from the language; after all, they've
> been officially obsolescent since 1989.  But if we're not going to
> do that, it would be nice to resolve the status of "int main()",
> and declaring it to be valid would be the least disruptive solution.

In some cases, it is necessary to declare a function pointer that is
able to capture different (but similar) function signatures. Of
course, removing old-style function declarations does not stop me
from doing that, but casts, a union with designated initializers or
dummy parameters as placeholders need to be introduced, which is more
verbose than old-style declarations anyway. (Does C now stop trusting
programmers? ;-)


--
Jun, Woong (woong.jun at gmail.com)

Keith Thompson

unread,
Dec 1, 2011, 9:53:01 AM12/1/11
to
Woong Jun <woon...@gmail.com> writes:
> Keith Thompson <ks...@mib.org> wrote:
> [...]
>> I've argued that this:
>>
>>     int main() { /* ... */ }
>>
>> violates this requirement, and thus has undefined behavior.  Even though
>> it's in some sense equivalent *as a definition* to the first form, it's
>> not equivalent *as a declaration*.  For example, this:
>>
>>     int main(void) {
>>         if (0) main(42);
>>     }
>>
>> requires a diagnostic, but this:
>>
>>     int main() {
>>         if (0) main(42);
>>     }
>>
>> does not.
>>
>
> Should requiring a diagnostic count as a part of being equivalent?
> Not that I'm saying it should not, but just asking if there is any
> implication in the text of the standard.

In my opinion, yes. The standard doesn't provide a definition for
"equivalent", so we fall back to the ordinary English meaning.
Requiring vs. not requiring a diagnostic is a big difference,
and I'd say it's more than enough to say that they're not equivalent.

[snip]

>> What I'd *really* like to do is remove old-style function
>> declarations and definitions from the language; after all, they've
>> been officially obsolescent since 1989.  But if we're not going to
>> do that, it would be nice to resolve the status of "int main()",
>> and declaring it to be valid would be the least disruptive solution.
>
> In some cases, it is necessary to declare a function pointer that is
> able to capture different (but similar) function signatures. Of
> course, removing old-style function declarations does not stop me
> from doing that, but casts, a union with designated initializers or
> dummy parameters as placeholders need to be introduced, which is more
> verbose than old-style declarations anyway. (Does C now stop trusting
> programmers? ;-)

I've never had a need to do that. Have you? C++ programmers seem to
get along ok without such a feature.

And if old-style function declarations and definitions are going to stay
in the language, in what sense are they "obsolescent"? Has the
committee changed its mind, and decided they're so vital we can't get
along without them? (Or, more realistically, that the benefit of
removing them doesn't justify the cost?)

C201X has a set of optional features. Maybe old-style function
declarations and definitions can be made optional?

--
Keith Thompson (The_Other_Keith) ks...@mib.org <http://www.ghoti.net/~kst>
Will write code for food.

Jun Woong

unread,
Dec 2, 2011, 2:42:01 AM12/2/11
to
Keith Thompson <ks...@mib.org> wrote:
My compiler code has a table that consists of tokens, their various
properties and functions that should be invoked while constructing
expression trees. Those functions have different signatures depending
on the oprators they deal with. Because they all have the same return
type and similar parameters, I decided to depend on the old-style
declaration that stops type-checking on parameters. I know what
function I should call in a specific spot in code, so undefined
behavior from mismatched parameters never occurs.

The tree-construction functions can be grouped into three, IIRC, so I
considered to use a union that has three function pointer members
with each of the signatures. As you know, however, C90 does not
support designated initializers and I wanted my code to be
C90-compatible. Or I could introduce a generic function pointer with
prototype declaration, but this means I should put casts between
function pointers when initializing the table and invoking the
functions, which I didn't like.

What I really needed was a way to stop a compiler from checking
parameters without casts, which the old-style declaration provided.
I'm not defending the old-style declarations here, but just saying
that they *can* be useful in some cases (at least to me).

> C++ programmers seem to
> get along ok without such a feature.
>

I think C++ is in different situation than C because it has enforced
strict type checking from its start.

> And if old-style function declarations and definitions are going to stay
> in the language, in what sense are they "obsolescent"?  Has the
> committee changed its mind, and decided they're so vital we can't get
> along without them?  (Or, more realistically, that the benefit of
> removing them doesn't justify the cost?)

I don't think the reason they have survived since C90 is not because
they are vital and am also wondering the why. Compilers will wisely
accept them for a good while as they do for implicit int if dropped in
C201X.

Keith Thompson

unread,
Dec 2, 2011, 4:16:41 PM12/2/11
to
Jun Woong <wo...@icu.ac.kr> writes:
> Keith Thompson <ks...@mib.org> wrote:
>> Woong Jun <woong....@gmail.com> writes:
>> > Keith Thompson <ks...@mib.org> wrote:
[...]
Fair enough. But *if* a new C standard dropped old-style function
declarations (and I note that you're not depending on old-style function
*definitions*), then (a) your code would continue to work for many
years, as most compilers would at least continue to support the old
behavior as an option, and (b) there's an alternative that doesn't
require unions or disabling type-checking.

Any pointer-to-function type can be converted (only by an explicit
cast) to any other pointer-to-function type and back again; the
round-trip conversion is guaranteed to give you a valid pointer. So
you can use, say, "void (*)(void)" as a generic pointer-to-function
type, and convert explicitly as needed (since you always know, at the
point of the call, what the actual type is). A contrived example:

#include <stdio.h>
#include <math.h>

/* Generic pointer-to-function type */
typedef void (*funcptr)(void);

/* Specific pointer-to-function types */
typedef double (*unary)(double);
typedef double (*binary)(double, double);

struct func {
funcptr addr;
char *name;
int count;
};

struct func table[] = {
{ (funcptr)sqrt, "sqrt", 1 },
{ (funcptr)fmod, "fmod", 2 }
};

int main(void) {
double x = 7.0;
double y = 4.0;
double result;
for (int i = 0; i < 2; i ++) {
switch (i) {
case 0:
printf("calling %s(%g)\n", table[0].name, x);
const unary f1 = (unary)table[0].addr;
result = f1(x);
break;
case 1:
printf("calling %s(%g, %g)\n", table[1].name, x, y);
const binary f2 = (binary)table[1].addr;
result = f2(x, y);
break;
}
printf(" result = %g\n", result);
}
return 0;
}

Output:

calling sqrt(7)
result = 2.64575
calling fmod(7, 4)
result = 3

[...]

Shao Miller

unread,
Jan 19, 2012, 1:45:04 PM1/19/12
to
On 11/30/2011 13:24, Keith Thompson wrote:
>
> What I'd *really* like to do is remove old-style function
> declarations and definitions from the language; after all, they've
> been officially obsolescent since 1989. But if we're not going to
> do that, it would be nice to resolve the status of "int main()",
> and declaring it to be valid would be the least disruptive solution.
>

I have a limited use case for empty parentheses, that does come in handy:

/* Any old type */
typedef void sometype;

typedef sometype sometype_f();

typedef sometype nifty_f(int, int);

typedef sometype spiffy_f(int, void *);

sometype_f my_nifty;
nifty_f my_nifty;
sometype my_nifty(int x, int y) {
(void) x;
(void) y;
return;
}

sometype_f my_spiffy;
spiffy_f my_spiffy;
sometype my_spiffy(int x, void * y) {
(void) x;
(void) y;
return;
}

int main(void) {
sometype_f * fptr;
spiffy_f * spiffy_ptr;

/* 'fptr' is kind of like a 'void *' */
spiffy_ptr = fptr = my_nifty;

return 0;
}

- Shao Miller

Hans-Bernhard Bröker

unread,
Jan 19, 2012, 4:45:10 PM1/19/12
to
On 19.01.2012 19:45, Shao Miller wrote:

> I have a limited use case for empty parentheses, that does come in handy:

Unfortunately, your use case is useless.

[...]
> int main(void) {
> sometype_f * fptr;
> spiffy_f * spiffy_ptr;
>
> /* 'fptr' is kind of like a 'void *' */
> spiffy_ptr = fptr = my_nifty;

Non-prototype declarations don't make any difference with that. It is
already allowed to store any arbitrary function pointer value in any
arbitrary function pointer typed object, regardless of the functions
pointer types' signatures. You can completely skip your non-prototyped
sometype_f type and fptr object, and just write

spiffy_ptr= my_nifty;

And anyway, no, there is no such thing as a generic function pointer,
nor a particular need for one: every function pointer is already
generic. Just about the only thing you're allowed to do with a
function pointer value stored in a wrong-type function pointer object is
to cast it back to the original type, anyway, so casting function
pointers is pretty pointless exercise to begin with.

Shao Miller

unread,
Jan 19, 2012, 8:22:11 PM1/19/12
to
On 1/19/2012 16:45, Hans-Bernhard Bröker wrote:
> On 19.01.2012 19:45, Shao Miller wrote:
>
>> I have a limited use case for empty parentheses, that does come in handy:
>
> Unfortunately, your use case is useless.
>

Oh?

> [...]
>> int main(void) {
>> sometype_f * fptr;
>> spiffy_f * spiffy_ptr;
>>
>> /* 'fptr' is kind of like a 'void *' */
>> spiffy_ptr = fptr = my_nifty;
>
> Non-prototype declarations don't make any difference with that.

"Simple assignment" @ N1256, 6.5.16.1p1, third item down, seems to be
relevant. It refers to "pointers to qualified or unqualified versions
of compatible types".

"Compatible type and composite type" @ 6.2.7p1 suggests that types are
compatible "if their types are the same." But it refers to some
additional rules, too...

"Funtion declarators (including prototypes)" @ 6.7.5.3p15 suggests that
function types are compatible if:
- They return the same type
- Have the same number of arguments
- Have the same use or lack of an ellipsis
- Parameters have compatible types
- and more

Do you suggest that 'void (int, int)' is a compatible type to 'void
(int, void *)'? If so, could you please direct me to whichever part of
C Standard I've missed?

> It is
> already allowed to store any arbitrary function pointer value in any
> arbitrary function pointer typed object, regardless of the functions
> pointer types' signatures.

Are you referring to "Pointers" @ 6.3.2.3p8? You say "to store," and
I'm wondering where that comes from.

> You can completely skip your non-prototyped
> sometype_f type and fptr object, and just write
>
> spiffy_ptr= my_nifty;
>

I'd appreciate if you could point out the supporting Standard C text for
that.

> And anyway, no, there is no such thing as a generic function pointer,
> nor a particular need for one: every function pointer is already
> generic. Just about the only thing you're allowed to do with a function
> pointer value stored in a wrong-type function pointer object is to cast
> it back to the original type, anyway, so casting function pointers is
> pretty pointless exercise to begin with.

I beg to differ. I believe that when you use an empty parameter list
for a function type, the remaining criteria to for that type to be
compatible with another function type is for them to return the same type.

It's pretty handy to be able to do:

void foo(void * vp) {
/* ... */
return;
}

int main(void) {
double d = 3.14;
double * dp = &d;

foo(dp);
return 0;
}

and I find it just as handy to be able to pass some
pointer-to-function-returning-xxx-and-having-params-yyy to a function
accepting a pointer-to-function-returning-xxx-and-unspecified-params,
without issue.

- Shao Miller

Keith Thompson

unread,
Jan 19, 2012, 8:57:55 PM1/19/12
to
Hans-Bernhard Bröker <HBBr...@t-online.de> writes:
> On 19.01.2012 19:45, Shao Miller wrote:
>> I have a limited use case for empty parentheses, that does come in handy:
>
> Unfortunately, your use case is useless.
>
> [...]
>> int main(void) {
>> sometype_f * fptr;
>> spiffy_f * spiffy_ptr;
>>
>> /* 'fptr' is kind of like a 'void *' */
>> spiffy_ptr = fptr = my_nifty;
>
> Non-prototype declarations don't make any difference with that. It is
> already allowed to store any arbitrary function pointer value in any
> arbitrary function pointer typed object, regardless of the functions
> pointer types' signatures. You can completely skip your
> non-prototyped sometype_f type and fptr object, and just write
>
> spiffy_ptr= my_nifty;

Not if they're of different pointer-to-function types.

A function pointer of one type may be converted to any other function
pointer type and back again without loss of information, but the
conversion cannot be done implicitly; you need a cast.

[...]

--
Keith Thompson (The_Other_Keith) ks...@mib.org <http://www.ghoti.net/~kst>
Will write code for food.

James Kuyper

unread,
Jan 19, 2012, 11:35:47 PM1/19/12
to
On 01/19/2012 04:45 PM, � wrote:
> On 19.01.2012 19:45, Shao Miller wrote:
>
>> I have a limited use case for empty parentheses, that does come in handy:
>
> Unfortunately, your use case is useless.
>
> [...]
>> int main(void) {
>> sometype_f * fptr;
>> spiffy_f * spiffy_ptr;
>>
>> /* 'fptr' is kind of like a 'void *' */
>> spiffy_ptr = fptr = my_nifty;
>
> Non-prototype declarations don't make any difference with that. It is
> already allowed to store any arbitrary function pointer value in any
> arbitrary function pointer typed object, regardless of the functions
> pointer types' signatures. You can completely skip your non-prototyped
> sometype_f type and fptr object, and just write
>
> spiffy_ptr= my_nifty;

I can't tell from the code fragment you've written what the relevant
function types are, I've got the OP killfiled. However, depending upon
what they were, there could be a real (though debatably useful)
advantage to using fptr as an intermediate step.

Your suggested replacement would a constraint violation unless spiffy_f
is compatible with my_nifty 6.5.16l1; an explicit conversion is required
to bypass that constraint. If they aren't compatible, but sometype_f is
compatible with both, then using fptr as an intermediate step makes the
relevant conversions occur implicitly. For instance, if sometype_f is
declared as

typedef int sometype_f();

then sometype_f* is compatible with any pointer to a function type that
returns an 'int' and accepts a fixed number of arguments. Personally, I
think that using a cast to make the conversion explicit would be a
better way to handle this.

> And anyway, no, there is no such thing as a generic function pointer,
> nor a particular need for one: every function pointer is already
> generic. Just about the only thing you're allowed to do with a
> function pointer value stored in a wrong-type function pointer object is
> to cast it back to the original type, anyway, so casting function
> pointers is pretty pointless exercise to begin with.

That's not quite true; a pointer to a function returning a given type
with an unspecified number of parameters (which can only be done using
an unprototyped function declaration) can be used to call any function
with the same return type that is defined as accepting a fixed number of
parameters, so long as the actual call using that pointer involves
arguments whose number and promoted types match the function's
definition. Example:

#include <stdio.h>
enum type {VOID, INT, PCHAR, ETC};
int call_func(int (*func)(), enum type t)
{
switch(t)
{
case VOID: return func();
case INT: return func(1);
case PCHAR: return func("two");
default:
fprintf(stderr, "Invalid type in call_func():%d\n", t);
return -1;
}
}

All of the following are legal calls; none require a cast:
call_func(getchar, VOID);
call_func(putchar, INT);
call_func(remove, PCHAR);

The fact that this works only for functions with the same return type,
and can't work for variadic functions makes such function pointers less
generic than void* pointers are for object types; but the convenience
factor is the same.

--
James Kuyper

Hans-Bernhard Bröker

unread,
Jan 20, 2012, 4:33:24 PM1/20/12
to
On 20.01.2012 02:57, Keith Thompson wrote:
> Hans-Bernhard Bröker<HBBr...@t-online.de> writes:

>> Non-prototype declarations don't make any difference with that. It is
>> already allowed to store any arbitrary function pointer value in any
>> arbitrary function pointer typed object, regardless of the functions
>> pointer types' signatures. You can completely skip your
>> non-prototyped sometype_f type and fptr object, and just write
>>
>> spiffy_ptr= my_nifty;

> Not if they're of different pointer-to-function types.
>
> A function pointer of one type may be converted to any other function
> pointer type and back again without loss of information, but the
> conversion cannot be done implicitly; you need a cast.

Ooops, sorry, missed that.

Hans-Bernhard Bröker

unread,
Jan 20, 2012, 4:49:40 PM1/20/12
to
On 20.01.2012 02:22, Shao Miller wrote:

[...]
> and I find it just as handy to be able to pass some
> pointer-to-function-returning-xxx-and-having-params-yyy to a function
> accepting a pointer-to-function-returning-xxx-and-unspecified-params,
> without issue.

Passing those function pointers is at most half the job, though.

You always have to pass the information about their exact signature,
too, or at the end of all that passing the pointer around, you won't
eventually be able to call the function correctly. And as far as the
passing itself goes, you can just as well cast them to some common type,
say (void (*)(void)), for the way, and be done with that. Non-prototype
function pointers don't add any value to that.

Shao Miller

unread,
Jan 20, 2012, 6:42:04 PM1/20/12
to
I believe you're mistaken. Although he's chosen not to read my posts
based on a misunderstanding, I don't mind saying that Mr. James Kuyper
is pretty good at adding value to some discussions, like this one.
Please see his post regarding using such function pointers to call
functions and using the argument promotions.

There're multiple points of handiness, not just the two examples I've
given. They all make use of the same property, though: Type compatibility.

Suppose you wish to define a function 'test' and one of the arguments is
to be a function pointer to a function with the same type as 'test'.
How might you accomplish that? Or a pointer to such a function pointer,
perhaps?

I _did_ say it was a _limited_ use case. :)

#include <stdio.h>

/*** Types */
typedef void void_f();
typedef void_f ** fjref_t;
typedef void fj_f(fjref_t, int);
typedef fj_f * fj_t[1];
struct funcject;

/*** Function declarations */
static void_f test;

/* Compatible with above */
static fj_f test;

/*** Struct/union definitions */
struct funcject {
/* Handler */
void_f * func[1];
/* Other stuff */
int x;
float y;
};

/*** Objects */
struct funcject my_funcject = { { test }, 13, 3.14 };
static fjref_t foo = (void *) &my_funcject;

/*** Function definitions */
/* Actual definition also compatible */
static void test(fj_t fj, int x) {
struct funcject * const this = (void *) fj;

printf("We have: %d and %f\n", this->x, this->y);
this->x = x;
puts("I just set x");
return;
}

int main(void) {
/* Pretend all we know about is 'foo' */
(*foo)(foo, 42);
(*foo)(foo, 42);

Shao Miller

unread,
Jan 20, 2012, 6:44:00 PM1/20/12
to
On 1/20/2012 18:42, Shao Miller wrote:
>
> /*** Objects */
> struct funcject my_funcject = { { test }, 13, 3.14 };
> static fjref_t foo = (void *) &my_funcject;
>

Oops. Had the 'static' on the wrong line, there.

Shao Miller

unread,
Jan 20, 2012, 6:49:38 PM1/20/12
to
Blargh. I forgot what I was doing. There's supposed to be one less
cast, too. Casting is such nasty business. ;)

#include <stdio.h>

/*** Types */
typedef void void_f();
typedef void_f ** fjref_t;
typedef void fj_f(fjref_t, int);
typedef fj_f * fj_t[1];
struct funcject;

/*** Function declarations */
static void_f test;

/* Compatible with above */
static fj_f test;

/*** Struct/union definitions */
struct funcject {
/* Handler */
fj_t func;
/* Other stuff */
int x;
float y;
};

/*** Objects */
struct funcject my_funcject = { { test }, 13, 3.14 };
static fjref_t foo = my_funcject.func;
0 new messages