我正在阅读一本教程,我已经在那里阅读,我们无法定义void main()
因为原型已被定义为int main() or int main(int argc , char *argv)
这些只是在C中定义主要功能的两种有效方式。
所以在哪个头文件或库文件中设置这些原型
我不知道编译器如何在float main()
背后隐藏机制时给出错误,我的意思是它是语法错误还是为main()定义了一些原型?
请用简单的语言提出答案。
答案 0 :(得分: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库,在程序启动时调用的函数的名称和签名留给实现 - 您可能必须使用像{一样的古怪的东西{1}}。
但是,EFI_STATUS efi_main (EFI_HANDLE ImageHandle, EFI_SYSTEM_TABLE *SystemTable)
通常工作,int main(void)
工作的常见程度较低(运行时收到的参数可能是垃圾)。
现在,如果您有一个托管环境并且使用了一个没有记录的入口点函数名称和/或签名,会发生什么? C标准表示您的程序在这种情况下具有未定义的行为 - 允许发生任何。实际发生的一些常见事情是:
编译器确实发出错误,或者至少发出警告。当它执行此操作时,它没有从任何头文件中获取正确的原型;相反,正确的原型被称为内置于编译器,在编译器自己的源代码中定义。现在,许多C库函数以及int main(int argc, char **argv)
都是这种情况。演示:
main
(由于历史原因,GCC对人们的代码并不是那么挑剔;从现代的角度来看,应该错误的许多事情仅仅是警告默认情况下,甚至没有警告。如果您从头开始编写新代码并使用GCC,我建议基本上始终使用$ 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’
选项。但不是-std=gnu11 -Wall -Wextra -Wpedantic -Werror
,因为转动您可能需要的 off 扩展,并且还可以在系统标头中公开错误。)
程序无法链接。例如,如果您尝试组建自己的名称,而不是将其称为-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库提供了一个函数,通常称为main
,其最后一行类似于
_start
由于历史原因,此功能未与exit(main(argc, argv, environ));
中的大量C库捆绑在一起。它位于一个单独的目标文件libc.so
中,当被要求链接程序时,编译器会自动拉入(就像它在crt1.o
上自动处理一样)。因此,当您不定义-lc
时,main
对main
的引用不满意,且链接失败。
(好的,_start
如何被召唤?这是你进入更深层魔法的地方。再问一个问题。)
最后,程序可以编译和链接正常甚至出现以正常工作 - 但看起来更难,你发现它是行为不端。如果在Unix系统上使用_start
会发生这种情况。 (首先,Windows以外的所有托管环境都是Unix系统。)
void main(void)
没有$ 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
返回的值将成为程序的exit status,它出现在shell变量main
中。如果$?
已正确声明返回main
,并且最后有int
,那么return 0;
会打印0. 12来自哪里?可能它是echo $?
的返回值,编译器在从puts
返回之前没有打算从返回值寄存器中清除。
很容易没有注意到这个错误,但 是一个错误,第一个尝试编写涉及程序的shell脚本的人会对你感到厌烦。
关于退出状态的一些脚注,主要针对学生:
在C ++和C语言中,从1999年标准开始,只要您正确声明,就可以在main
的末尾省略任何明确的return 0;
,但我认为依靠这种风格很差。
在很多但不是所有的Unix实现中,main
中显示的值只是从$?
返回的值的低七或八位。这是用于检索子进程的退出状态waitpid
的系统调用的限制。
严格符合 ISO C程序只能返回main
:0,main
和EXIT_SUCCESS
中的三个值;后两个常量在EXIT_FAILURE
中声明。从stdlib.h
返回零的效果保证与返回main
的效果相同,但值不保证是平等的。
在实践中,至少返回0,1和2是安全的,并且EXIT_SUCCESS
和/或EXIT_SUCCESS != 0
的实现早已进入天空中的大桶,所以不要担心。
答案 1 :(得分:0)
我认为主要功能没有原型。它是您的计划的切入点,由标准定义。
例如c标准用这种方式定义:
5.1.2.2.1程序启动
1程序启动时调用的函数名为main。该 实现声明此函数没有原型。应该是 使用返回类型int定义并且没有参数:
int main(void){}
或有两个参数(这里称为argc和argv,但可以使用任何名称&gt;,因为它们是函数的本地函数) 宣布):
int main(int argc, char *argv[]){}
或等效的; 9)或其他一些实现定义的方式。
答案 2 :(得分:0)
C11 Standard指示什么是有效和无效的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) { /* ... */ }
答案 3 :(得分:-1)
坚持标准表格,
int main(int argc, char* argv[])
或
int main(void)
如果将程序从一个编译器移动到另一个编译器,则不会遇到问题 也就是说, ISO / IEC 9899:201x - &gt; 5.1.2.2.1 (程序启动)启动状态:
程序启动时调用的函数名为main。该 实现为此函数声明无原型。它应该 使用返回类型int 并且没有参数来定义:
int main(void) { /* ... */ }
或有两个参数(这里称为argc和argv,尽管如此) 可以使用名称,因为它们是它们所在的函数的本地名称 声明):
int main(int argc, char *argv[]) { /* ... */ }
或等效的; 10)或其他一些实现定义的 方式。 。
。
。 10)因此,int可以用a代替 typedef name定义为int,或者argv的类型可以写为 char ** argv,等等。