2016-07-23 75 views
1

我正在阅读教程,我已经阅读那里,我们不能定义void main()作为原型已经定义为int main() or int main(int argc , char *argv)这些只是两个有效的方式来定义C中的主要功能。 所以在哪个头文件或库文件这些原型设置为 我不知道如何编译器给出错误float main()后面的机制是什么,我的意思是它是一个语法错误或为main()定义的一些原型?c或C++中main()函数的原型?

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

+0

这里没有任何原型。这个限制是编译器本身的一部分。 – HolyBlackCat

+1

“这些只是C中定义主函数的两种有效方式” - > _almost_。这些(和等价物)只有两个_defined_有效的方法。其他可能存在的每个实施可能会失去可移植性, – chux

+0

因为每个发布的答案都包含事实错误,所以会投票删除投票。所以这篇文章造成的伤害比好的更多。如果没有错误地引用这些标准,请参阅标准实际所说的内容[阅读本文](https://stackoverflow.com/a/31263079/584518)。 – Lundin

回答

-1

棒的标准形式,

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

int main(void) 

,如果你从一个编译器的移动程序到另一个你不会遇到问题。
这就是说,ISO/IEC 9899:201X - > 5.1.2.2.1(程序启动)启动状态:

称为在程序启动的功能被命名为主力。 实现声明没有原型这个函数。它应 与的INT返回类型和不带参数进行定义:

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

或两个参数(这里称为argc和argv,虽然任何 名称可以使用,因为它们是地方于它们是 声明的功能):

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

或等同物; 10)或某种其他实施方式定义的 方式。 。


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

+0

问题中没有提到托管环境。您通过引用托管环境的子章节来错误地引用标准,从而忽略了标准中独立环境的整个章节。 – Lundin

0

我不认为有主函数的原型。这是您的程序的入口点,它由标准定义。

例如C标准定义是这样:

5.1.2.2.1计划启动

1名为在程序启动的功能被命名为主力。 实现声明此函数没有原型。应当带有int返回类型和不带参数来 定义:

int main(void){} 

或具有两个参数(此处称为argc和argv,虽然任何名字>可以使用,因为它们是本地的在它们 声明功能):

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

或等同物; 9)或在一些其它实现定义的方式。

+0

问题中没有提到托管环境。您通过引用托管环境的子章节来错误地引用标准,从而忽略了标准中独立环境的整个章节。 – Lundin

0

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

在对于main签名它说(5.1.2.2.1):

称为在程序启动的功能被命名为主力。该实现没有声明这个函数的原型。它应为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) { /* ... */ } 
+0

如果'int main()','int main(int argc,char ** argv)'等有效,建议添加add。 – chux

+0

问题中没有提到托管环境。您通过引用托管环境的子章节来错误地引用标准,从而忽略了标准中独立环境的整个章节。 – Lundin

2

你的教程是不完全,但它描述了一个非常严格执行的行为,比C标准严格要求的实现成为。

C标准中关于程序启动时调用的函数的说法就是某些可能性需要工作。它并不是说需要其他东西而不是才能工作。另外,C标准对编译器错误的说法很少。现代C实现通常超出标准对诊断的要求;支持他们接受的一套程序的许多扩展也是很常见的。

在“托管”的环境,一个提供所有的标准C库,让启动名称和签名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)之类的古怪东西。 但是,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是几乎没有挑剔别人的代码,因为它可能是,很多的事情,从现代的眼光看,应该是错误仅仅是警告,并如果你从头开始编写新的代码并使用GCC,我建议基本上总是使用-std=gnu11 -Wall -Wextra -Wpedantic -Werror选项,但不是-std=c11,因为这会使关闭您可能需要的扩展名,并且可以也暴露系统头文件中的错误)。

  • prog内存无法链接。这是什么情况,举例来说,如果你试图弥补自己的名字,而不是调用它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' 
    

    这是更神秘的错误,你可以走出连接器中的一个,所以我将它解开一点。你有没有想过如何调用main?这是令人惊讶的简单:有C库提供的功能,通常被称为_start,其最后一行是一样的东西

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

    由于历史原因,该功能不与libc.so大部分C库的捆绑。它位于一个单独的目标文件crt1.o中,编译器在被要求链接一个程序时会自动引入(就像它自动在-lc上一样)。因此,当您未定义main时,从_startmain的引用不满足,并且链接失败。

    (OK,如何_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返回的值成为该程序的exit status,其出现在壳可变$?。如果main已正确声明为返回int,并且末尾有return 0;,则echo $?将打印0.从哪里来的?可能是puts的返回值,编译器在从main返回之前没有打扰从返回值寄存器中清除。

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


有关退出状态的一些注解,主要用于学究:

  1. 在C++中,并用C开始与1999年的标准,你在技术上允许省略任何明确return 0;main只要你声明正确,但我认为依靠这是糟糕的风格。

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

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

    在实践中,返回至少0,1和2以及其中EXIT_SUCCESS != 0和/或EXIT_FAILURE != 1早已走到天空中的一大桶的实现是安全的,所以不用担心它。

+0

谢谢zwol的回答,它非常有帮助,也非常有知识。 –

+0

此答案不正确,不完整。如果这是编译器使用的形式,那么'void main(void)'没有未定义的行为。实现定义的行为。这是独立环境和完全可接受的标准C的常见形式,见5.1.2.1。重要的是要注意的是__的形式是由编译器选择的,而不是由程序员选择的。如果程序员偏离编译器使用的main()的形式,我们只会得到UB。 – Lundin

+0

至于gcc,用于独立环境的正确编译器选项是“-freestanding”,它可以有效地将所有这种main()格式的信息变成不相关的。 – Lundin