c或c ++中main()函数的原型?

我正在阅读一个教程,我已经在那里阅读,我们无法定义void main()因为原型已经定义为int main() or int main(int argc , char *argv)这些只是两种有效的方法在C语言中定义主要function所以在这些原型设置的头文件或库文件中我怎么没有编译器给我错误的情况下float main()后面的机制是什么,我的意思是它是语法错误还是一些原型为main()定义?

请用简单的语言提出答案。

您的教程并非完全错误 ,但它描述了非常严格的实现的行为,比C标准要求实现更严格。

C标准所说的关于程序启动时调用的函数只是需要某些可能性才能工作。 它没有说其他事情不需要工作。 此外,C标准对编译器错误的描述很少。 现代C实现通常远远超出标准的诊断要求; 他们支持对他们接受的一系列程序的许多扩展也很常见。

在“托管”环境中,提供标准C库的所有function的环境,为程序调用函数的程序启动名称和签名int main(void)int main(int argc, char **argv) 符合要求 。 他们需要工作。 但是标准允许这个函数也以“其他一些实现定义的方式”声明,并且存在许多替代名称和签名:我将列出一些最常见的名称和签名。

  • int main(int argc, char **argv, char **envp)
  • void main(void)
  • int WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)

如果您的实现文档支持其中一个替代入口点名称或签名,则完全可以使用它。 (你的程序不会“严格遵守”,但几乎没有真正的程序“严格遵守”,所以不要担心。)

在一个提供所有标准C库的“独立”环境中,在程序启动时调用的函数的名称和签名留给实现 – 您可能必须使用像EFI_STATUS efi_main (EFI_HANDLE ImageHandle, EFI_SYSTEM_TABLE *SystemTable)这样古怪的东西EFI_STATUS efi_main (EFI_HANDLE ImageHandle, EFI_SYSTEM_TABLE *SystemTable) 。 但是, int main(void)通常工作,并且int main(int argc, char **argv)工作稍微不常见(在运行时接收的参数可能是垃圾)。


现在,如果您有托管环境并且使用未记录的入口点函数名称和/或签名可以工作,会发生什么? C标准说你的程序在这种情况下有不确定的行为任何事情都可以发生。 实际发生的一些常见事情是:

  • 编译器确实发出错误,或者至少发出警告。 当它执行此操作时,它没有从任何头文件中获取正确的原型; 相反,正确的原型据说内置于编译器中,在编译器自己的源代码中定义。 现在,许多C库函数和main都是这种情况。 示范:

     $ cat > test.c <<\! extern int exit(int); // wrong, `exit` should return `void` void main(void) {} // wrong, `main` should return `int` ! $ gcc -fsyntax-only -std=gnu11 -Wall test.c test.c:1:12: warning: conflicting types for built-in function 'exit' test.c:2:6: warning: return type of 'main' is not 'int' 

    (由于历史原因,海湾合作委员会对人们的代码并不是那么挑剔;从现代的角度来看,许多错误只是警告,而不是默认的警告。如果你'从头开始编写新代码并使用GCC,我建议使用-std=gnu11 -Wall -Wextra -Wpedantic -Werror选项基本上总是。不是-std=c11 ,因为这会关闭你可能需要的扩展,并且可以还会暴露系统头文件中的错误。)

  • 该程序无法链接。 例如,如果您尝试组建自己的名称而不是将其命名为main ,则会发生这种情况:

     $ cat > test.c <<\! extern int puts(const char *); void my_program_starts_here(void) { puts("hello world"); } ! $ gcc -std=gnu11 -Wall test.c /usr/lib/gcc/x86_64-linux-gnu/5/../../../x86_64-linux-gnu/crt1.o: In function `_start': (.text+0x20): undefined reference to `main' 

    这是你可以从链接器中获得的更加神秘的错误之一,所以我会稍微解压缩它。 你有没有想过主叫如何? 它非常简单:C库提供了一个函数,通常称为_start ,其最后一行是类似的

     exit(main(argc, argv, environ)); 

    由于历史原因,此函数未与libc.so的大量C库捆绑在一起。 它位于一个单独的目标文件crt1.o ,当被要求链接程序时,编译器会自动拉入(就像它在-lc上自动处理一样)。 因此,当您不定义main ,对_start main的引用不满足且链接失败。

    (好吧, _start如何被调用?这就是你进入更深层次魔法的地方。问另一个问题。)

  • 最后,程序可以编译和链接很好,甚至似乎工作正常 - 但看起来更难,你发现它是行为不端。 如果在Unix系统上使用void main(void)会发生这种情况。 (首先,Windows以外的所有托管环境都是Unix系统。)

     $ cat > test.c <<\! extern int puts(const char *); void main(void) { puts("hello world"); } ! $ gcc -std=gnu11 test.c $ ./a.out hello world 

    没有-Wall ,不是编译器的窥视,程序运行良好......还是做到了?

     $ ./a.out ; echo $? hello world 12 

    应该从main返回的值成为程序的退出状态 ,它出现在shell变量$? 。 如果main已被正确声明为返回int ,并且return 0; 最后, echo $? 会印出0. 12来自哪里? 可能它是puts的返回值,编译器在从main返回之前没有打算从返回值寄存器中清除。

    很容易没有注意到这个错误,但这一个错误,第一个尝试编写涉及程序的shell脚本的人会对你感到恼火。


关于退出状态的一些脚注,主要针对学生:

  1. 在C ++和C语言中,从1999年标准开始,技术上允许您省略任何显式return 0;main结束时,只要你正确宣布它,但我认为依靠这是糟糕的风格。

  2. 在很多但不是所有的Unix实现中, $?的值是$? 将只是从main返回的值的低七或八位。 这是用于检索子进程waitpid的退出状态的系统调用的限制。

  3. 严格符合 ISO C的程序只能从main返回三个值:0, EXIT_SUCCESSEXIT_FAILURE ; 后两个常量在stdlib.h中声明。 从main返回零的效果保证与返回EXIT_SUCCESS效果相同,但不保证相等。

    在实践中,返回至少0,1和2是安全的,并且EXIT_SUCCESS != 0和/或EXIT_FAILURE != 1实现早已进入天空中的大桶,所以不要担心关于它。

我认为主要function没有原型。 它是您的程序的入口点,它由标准定义。

例如c标准用这种方式定义它:

5.1.2.2.1程序启动

1程序启动时调用的函数名为main。 该实现声明此函数没有原型。 它应该使用返回类型int并且没有参数来定义:

 int main(void){} 

或者使用两个参数(这里称为argc和argv,尽管可以使用任何名称>,因为它们是声明它们的函数的本地参数):

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

或等效的; 9)或其他一些实施定义的方式。

C11标准规定什么是有效和无效的C(旧标准具有相同的规则)。

关于main签名,它说( 5.1.2.2.1 ):

程序启动时调用的函数名为main。 该实现声明此函数没有原型。 它应该使用返回类型int并且没有参数来定义:

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

或者使用两个参数(这里称为argc和argv,尽管可以使用任何名称,因为它们是声明它们的函数的本地名称):

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

或等效或以某种其他实现定义的方式。

因此,除了一些其他实现定义的方式 ,唯一有效的签名是那两个。 如果实现定义了不同的签名,则可以自由使用该签名,前提是您不介意丢失某些代码可移植性。

因此,对于没有额外内容的编译器,以下内容均无效:

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

坚持标准forms,

 int main(int argc, char* argv[]) 

要么

 int main(void) 

如果将程序从一个编译器移动到另一个编译器,则不会遇到问题。
也就是说, ISO / IEC 9899:201x – > 5.1.2.2.1 (程序启动)启动状态:

程序启动时调用的函数名为main。 该实现声明此函数没有原型 。 它应该使用返回类型int并且没有参数来定义:

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

或者使用两个参数(这里称为argc和argv,尽管可以使用任何名称,因为它们是声明它们的函数的本地名称):

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

或同等学历; 10)或以某种其他实现定义的方式。 。


10)因此,int可以被定义为int的typedef名称替换,或者argv的类型可以写为char ** argv,依此类推。