Are `int main()` and `int main(void)` equivalent prototypes in C23?

Are `int main()` and `int main(void)` equivalent prototypes in C23?


11

C23 introduced new semantics in function declarators:

6.7.6.3 Function declarators

[…]

13   For a function declarator without a parameter type list: the effect is as if it were declared with a parameter type list consisting of the keyword void. A function declarator provides a prototype for the function.

This seems to imply that a function definition with an empty parameter list can be written equivalently with () or (void).

Yet this equivalence does not seem guaranteed for the main function:

5.1.2.2.1 Program startup

The function called at program startup is named main. The implementation declares no prototype for this function. It shall be defined with a return type of int and with no parameters:

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

or with two parameters (referred to here as argc and argv, though any names may be used, as they are local to the function in which they are declared):

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

or equivalent or in some other implementation-defined manner.

This does not seem to guarantee that int main() { /* ... */ } is a valid definition for main, or does equivalent cover this variant?


It troubles me that the 2 examples in C17 that use the syntax int main() (in 6.5.3.4 and 6.7.6.3) have been changed to use int main(void) in the latest C23 draft.

6

  • 1

    On many traditional Unix systems, you can also declare main as int main(int argc, char **argv, char **envp), in which case envp is a NULL-terminated array of environment variable definitions. I just tried this on my Linux system, and it still works. Not sure what ANSI C has to say about it. It's probably considered non-standard.

    – Tom Karzes

    yesterday


  • 4

    @TomKarzes: this is covered by or in some other implementation-defined manner

    – chqrlie

    yesterday

  • 3

    The old wording in C11 §6.7.6.3 p14 was "An empty list in a function declarator that is part of a definition of that function specifies that the function has no parameters." So a definition of main as int main() { /*code here*/ } has been equivalent to int main(void) { /*code here*/ } at least as far back as C11.

    – user3386109

    yesterday


  • @user3386109: That's not how GCC or clang treats it with -std=c11. godbolt.org/z/5zf1dr1dK shows GCC accepting main(123) after an int main(){} definition with no warnings even at -Wall -Wextra -pedantic. Clang warns about deprecation but doesn't error, with the message saying it's only an error in C2x. GCC -std=c23 does error. It that item in the standard just talking about there being no named vars you can access to read any parameters that got passed? Otherwise GCC and Clang would be deviating from the C11 standard, perhaps intentionally, but then what's C23 changing?

    – Peter Cordes

    6 hours ago


  • @PeterCordes You've misconstrued my comment. I was addressing the question that the OP asked. The question specifically asks whether "int main() {/*...*/} is a valid definition for main" Emphasis on definition added. Notice also the emphasis on definition in my comment. Your comment is about whether a definition with an empty argument list acts as a prototype. The answer to that question in C11 is "No". In C23, the answer is "Yes".

    – user3386109

    6 hours ago


5 Answers
5


11

In C17 and earlier versions of the standard, int main() { … } does not provide a prototype for main() but is otherwise equivalent to int main(void) { … }.

In C23, int main() { … } does provide a prototype for main() and is fully equivalent, except for spelling, to int main(void) { … }.

The difference only matters if you call main() recursively — something that is allowed in C and disallowed in C++. With int main() in C17 or earlier, a recursive call like main(23, "elephants"); is allowed because there is no prototype specified for main() (assuming that the definition of main() is visible before the recursive call). With int main(void), that is not allowed because there is a prototype in scope that says "no arguments".

Note what is said in What should main() return in C and C++? That has extensive discussions, including that the C17 and earlier standards use both int main() and int main(void) in their (non-normative) examples. It also points out what Microsoft specifies for Windows systems and what Annex J "Common Extensions" mentions (both recognize int main(int argc, char **argv, char **envp)). Apple even has a fourth optional argument to main()int main(int argc, char **argv, char **envp, char **apple) that behaves like argv or envp. I need to update my answer for C23 sometime soon.

7

  • Hmm, As I read "An empty list in a function declarator that is part of a definition of that function specifies that the function has no parameters." (C11 §6.7.6.3 14) says to me that foo() { .... }, as a function definition that a later foo(23, "elephants"); is an invalid call. I would expect int main() { ... } and a later call like main(23, "elephants"); to also be invalid.

    – chux – Reinstate Monica

    22 hours ago

  • 1

    Side note: Why can you call main recursively in C but not in C++? Although not super useful, I could devise a C program: #include <stdio.h> int main(int argc,char **argv) { printf("%s: %sn",argv[0],argv[argc - 1]); if (argc > 2) main(argc - 1,argv); return 0; } Build it and run it with: ./program hello world goodbye galaxy and get: ./program: galaxy ./program: goodbye ./program: world ./program: hello Why won't this work with C++? It seems to be some arbitrary rule. What's the rationale for this rule?

    – Craig Estey

    21 hours ago

  • 2

    @CraigEstey Say rather an obsolete rule. Cfront-era C++ implementations injected code at the beginning of main to run global constructors, and that code was not idempotent, so recursively calling main would run the global constructors again and hilarity would ensue. I imagine the rule persists today largely because nobody in WG21 thinks it's important enough to bother changing.

    – zwol

    21 hours ago

  • 1

    @CraigEstey: MinGW puts a call to a __main init function at the top of main: in the asm even in C mode, I think instead of having their libc init stuff run from DLL on-load hooks. Presumably it's idempotent at least in C mode, perhaps with an already-initialized check of a static flag. What library has the __main function reference in gcc assembly output / How does GCC implement __attribute__((constructor)) on MinGW? (GCC supports static-constructor functions even in C mode, so maybe not libc init)

    – Peter Cordes

    5 hours ago


  • 1

    @PeterCordes: They can't put their libc stuff in DLL on-load hooks because of -static. There's no such thing as an EXE on-load hook. Now why they didn't put that call in crt0.o (or its moral equivalent) I have no idea.

    – Joshua

    2 hours ago


3

All code fragments in the standard are considered to be examples, and, therefore, non-normative.

The normative requirement is what the text says: "[main] shall be defined with a return type of int and with no parameters[, or …]". In C2023, int main() { ... } defines main with a return type of int and with no parameters, so it meets the requirement.

As pointed out in the comments on the question, C2011 had language that meant a definition int main() { ... } would have, under that standard, defined main with a return type of int and with no parameters, although a declaration int main(); would not have declared main with no parameters. I cannot conveniently check C1999 or earlier from this computer.

11

  • Re “All code fragments in the standard are considered to be examples”: Code in examples are non-normative because examples are non-normative. There are fragments of code not in examples, used in exposition, and they are integral to the normative part of the standard. We cannot say the int main(void) { /* ... */ } in 5.1.2.2.1 is example code and not expositive code because then the int main(int argc, char *argv[]) { /* ... */ } would also be non-normative and then there is nothing in the normative standard that says the first parameter has type int and the second has type char *[].

    – Eric Postpischil

    13 hours ago


  • Notably the parts you quote from 5.1.2.2.1 are for hosted systems – you are quoting the hosted system sub-chapter, which is only normative for strictly conforming hosted programs. Whereas 6.7.6.3 is normative for all C programs. For example a C23 freestanding implementation might declare main as void main () and that will be equivalent to void main (void).

    – Lundin

    10 hours ago

  • @EricPostpischil No, really, all code fragments are non-normative examples. It does not actually say that anywhere in the text of the standard but this has been the consistent interpretation of the committee since before C1999. (It follows that the case you cite is a defect — the requirement for the first parameter of main to be int and the second to be char *[] needs to be lifted to normative text.)

    – zwol

    10 hours ago


  • @Lundin That seems unrelated to the point made by my answer.

    – zwol

    10 hours ago

  • No it isn't, because the text you claim is normative isn't normative to C but normative to the sub category of hosted C programs. Though I realize you inherited this from the question itself.

    – Lundin

    9 hours ago



2

Let’s at first consider how the function main shall be declared according to the C17 Standard. The C17 Standard, section 6.7.6.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.

That is empty parentheses can be used in a function definition when the function has an empty identifier list. But relative to the function main the Standard requires that it will be declared with a parameter type list.

From the C grammar:

5 If, in the declaration "T D1", D1 has the form

D ( parameter-type-list )

or

D ( identifier-listopt )

and the type specified for ident in the declaration "T D" is
"derived-declarator-type-list T", then the type specified for ident is
"derived-declarator-type-list function returning the unqualified
version of T"

As it is seen the parameter type list may not be optional. It is the identifier list that may be optional.

Further (p. #10):

10 The special case of an unnamed parameter of type void as the only
item in the list specifies that the function has no parameters.

That is the function main without parameters shall be declared with a parameter list that contains unnamed parameter of type void.

From the C17 Standard (5.1.2.2.1 Program startup):

1 The function called at program startup is named main. The
implementation declares no prototype for this function. It shall be
defined with a return type of int and with no parameters
:

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

or with two parameters (referred to here as argc and argv, though any
names may be used, as they are local to the function in which they are
declared):

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

or equivalent;10) or in some other implementation-defined manner.

Now let’s read the footnote 10 that elaborates what means the word equivalent:

10)Thus, int can be replaced by a typedef name defined as int, or the
type of argv can be written as char ** argv, and so on.

There is nothing said that the function declaration of the function main with an empty parameter list may be rewritten with a function declaration with an empty identifier list.

That is the Standard requires that the function main shall be declared with the function prototype. And examples of such declarations of main are presented in the quote provided above.

From the C17 Standard (6.9.1 Function definitions)

Semantics

7 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, the list also specifies the
types of all the parameters; such a declarator also serves as a
function prototype for later calls to the same function in the same
translation unit.
If the declarator includes an identifier list,166)
the types of the parameters shall be declared in a following
declaration list. In either case, the type of each parameter is
adjusted as described in 6.7.6.3 for a parameter type list; the
resulting type shall be a complete object type.

Pay attention to that the verb shall in the C standard means the following

  1. Conformance

1 In this document, "shall" is to be interpreted as a requirement on
an implementation or on a program
; conversely, "shall not" is to be
interpreted as a prohibition.

What is changed in the C23 Standard relative to the declaration of main? Nothing! If you will read the section 5.1.2.2.1 Program startup of the C23 Standard Draft you will see that there are used the same text relative to the declaration of main without parameters.

Yes, now in the C23 Standard a function declaration with empty parentheses in the function declarator has another meaning. The parameter type list can be optional. The identifier list is excluded from function declarators. But the declaration of main satisfies the same requirements as in the C17 Standard. It shall be declared as written in the C23 Standard like

int main( void )

and even the footnote stays the same as in the C17 Standard. There is no a word that the function main may be declared like

int main()

in the section 5.1.2.2.1 Program startup of the C23 Standard.

The form of the declaration of main with empty list of parameters was preserved in the C23 Standard in the same way as in the C17 Standard.

And @zwol mistakenly claims relative to the declaration of main that "All code fragments in the standard are considered to be examples, and, therefore, non-normative." The section 5.1.2.2.1 Program startup of the both Standards, the C17 Standard and the C23 Standard, has normative description of how the function main shall be declared.

You should pay attention to that it will be a serious change in the normative description of main from preceding C Standards and the C23 Standard. And the C23 Standard should reflect that in its description "what documents, for all intents and purposes, have been applied to this draft (C23)". However in this list of documents nothing is said about changes in the requirements for declaration of the function main. I found only the following document that has some relation "N2432 Remove support for function definitions with identifier lists".

If to adopt that I am mistaken then as a consequence of that it may be concluded that the C23 Standard has a defect because this section was transferred from the C17 Standard without any changes but its meaning was changed. At least in the C23 Standard there should be added a note or a footnote in this section that clarifies changes relative to the previous C Standard.

16

  • Foot note 10 is not certainly an elaboration of "what means the word equivalent:" It is a least an example of equivalent. Other than that most of the answer is good.

    – chux – Reinstate Monica

    13 hours ago


  • @chux-ReinstateMonica In any case the equivalence does not mean in C17 declaring main with an empty identifier list. It shall be declared with a parameter list including the empty parameter list.

    – Vlad from Moscow

    13 hours ago


  • Re “But relative to the function main the Standard requires that it will be declared with a parameter type list”: No such assertion appears in C 2017.

    – Eric Postpischil

    13 hours ago

  • Re “As it is seen the parameter type list may not be optional. It is the identifier list that may be optional”: The grammar makes no such distinction. These two options for a direct-declarator establish that a direct-declarator may be “direct-declarator ( parameter-type-list ), “direct-declarator ( identifier-list )”, or “direct-declarator ( )”. No distinction between whether ( ) means the parameter-type-list is missing or the identifier-list is missing is made either by the grammar rules or by the text of the standard.

    – Eric Postpischil

    12 hours ago


  • 1

    Re “Further (p. #10):” / 10 The special case of an unnamed parameter of type void as the only item in the list specifies that the function has no parameters.” / “That is the function main without parameters shall be declared with a parameter list that contains unnamed parameter of type void.”: That passage from paragraph 10 says if the declarator has the form D(void), then it specifies the function has no parameters, not that if the function has no parameters, its declarator must have the form D(void), and it says nothing about what form main must have.

    – Eric Postpischil

    12 hours ago


1

Are int main() and int main(void) equivalent prototypes in C23?

Yes, for function declarations.

C23 draft N1570 has the revised specification:

For a function declarator without a parameter type list: the effect is as if it were declared with a parameter type list consisting of the keyword void. A function declarator provides a prototype for the function. C23 § 6.7.6.3 13

I understand this to apply to all functions including main().

The specifications about main do not restrict this.


0

What is normative in the C standard:

  • 6.7.6.3 is normative for all function declarators.
  • The entry point for a C program is always a function in all C systems (see 5.1.2).
  • If main is the entry point for a C program, then int main () and int main (void) are equivalent as per C23 6.7.6.3.

Notably:

  • Examples, including code examples are not normative. Neither are notes or foot notes. These are all examples of informative parts. For details check: Are notes and examples in the core language specification of the C++ Standard non-normative?

  • Chapter 5.1.2.2.1 is only normative for strictly conforming hosted system programs. It bears no relevance for a general C program.

  • As noted in the answer by @zwol, the parts in 5.1.2.2.1 that are normative (for hosted system programs) is the text "It shall be defined with a return type of int and with no parameters" and not the example.

  • Furthermore, 5.1.2.2.1 has the normative text "or equivalent". This was always meant for covering cases such as int main (int foo, char* bar[]) or int32_t main (void) in a system where int == int32_t, and so on. Equivalent examples where only coding style or naming changes.

    As it happens, in C23 this "or equivalent" part will also apply to int main (void) vs int main () since they are equivalent forms.

  • 5.1.2.2.1 also has the text "or in some other implementation-defined manner".
    Similarly 5.1.2.1 for freestanding systems: "the name and type of the function called at program startup are
    implementation-defined".

    This means that as long as the function obeys the rules for how functions are declared, it can be named anything and declared in any manner, as long as this is documented by the implementation (compiler). However, a program relying on such will no longer be strictly conforming (C17 or C23 4 §5) since it relies on implementation-defined means; a freestanding program can therefore never be strictly conforming.

    It is however possible to in a conforming program (hosted/freestanding) to create a function with any name, return type or parameters as the entry point of the program, as long as the compiler documents and supports such.

    A program entry function with a format int function_name() still has to obey 6.7.6.3 however.

Common example of a conforming hosted system function (impl.defined):

int WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd);

Common example of a conforming freestanding system function (impl.defined):

void main (void);

1

  • Even if examples aren't normative, their existence would strongly suggest to me imply an intention and expectation that implementations will behave in the indicated fashion any time there isn't a very strong and compelling reason for doing otherwise.

    – supercat

    7 hours ago



Leave a Reply

Your email address will not be published. Required fields are marked *