C的main()函数的有效签名是什么?
在POSIX系统上,execve()
函数支持int main(int argc, char *argv[], char *envp[])
这种形式的main()
函数签名,其中新增的参数是环境变量,即一个由形如NAME=VALUE
的字符串组成的数组。
然而,这种说法并不完全准确。实际上,execve()
函数的环境变量参数与main()
函数的调用约定无关,而是用于初始化extern char **environ;
。
事实上,在实践中,许多基于POSIX系统的C实现确实将第三个envp
参数传递给main()
函数。我不确定POSIX本身是否将这种形式作为main()
函数的第三种有效签名进行了规定。你可以使用以下程序在GNU C上验证其实际工作情况:godbolt.org/z/9lie95(它将其argv
和envp
传递给execve("/usr/bin/env")
,因此你可以看到它继承了一个合理的环境而不是返回-EFAULT
)。但是,是的,这个回答描述得不正确,错误地暗示了execve
的存在意味着main()
的新签名。
根据上述内容,可以得出问题的原因是关于main()
函数的有效签名的描述不准确,解决方法是对问题进行修正并提供正确的信息。
C语言的main()函数的有效签名有哪些?这个问题的出现主要是因为不同的标准和编译器对于main()函数的签名有不同的要求。根据C99、C11和C18标准,在主机环境下,main()函数可以有两种有效的签名:int main(void)和int main(int argc, char *argv[])。这两种签名可以互换使用,例如int可以替换为typedef定义的int类型,argv的类型可以写成char **argv等。而C++98标准则要求main()函数的返回类型必须为int,但是其他类型可以是实现定义的,同时也要求和C标准一样有两种签名:int main()和int main(int argc, char *argv[])。C++03、C++11、C++14和C++17标准基本上和C++98标准相同。
此外,Unix系统还支持第三种main()函数的签名:int main(int argc, char **argv, char **envp)。这种签名允许在main()函数中获取环境变量的信息,通过envp参数可以访问环境变量。C标准将这种签名视为一种常见的扩展,并在附录J中对其进行了说明。
对于Microsoft C编译器,存在一些特殊情况。根据Microsoft VS 2010编译器的官方文档,main()函数的声明语法可以是int main()或int main(int argc, char *argv[], char *envp[]),也可以声明为返回void类型,但是返回void类型的main()函数无法使用return语句返回退出码,必须使用exit()函数。需要注意的是,Microsoft的文档并没有提到C和C++标准规定的只有两个参数的main()函数签名。
最后,关于int main()和int main(void)是否相同的问题,从C++的角度来看,这两种写法是等价的,没有区别。而从C的角度来看,这两种写法有细微的差别,主要体现在递归调用main()函数的情况下。在C中,int main()不提供main()函数的原型,但只有在递归调用main()函数时才会注意到这一点。而int main(void)明确表示函数不接受任何参数,如果尝试提供参数调用该函数,将会导致编译错误。
总结起来,C语言的main()函数的有效签名取决于不同的标准和编译器,常见的有效签名有int main(void)和int main(int argc, char *argv[]),而C++语言的main()函数的有效签名要求返回类型为int,但其他类型可以是实现定义的。此外,Unix系统还支持第三种签名int main(int argc, char **argv, char **envp),而Microsoft C编译器有一些自己的规定。对于int main()和int main(void)的区别,C++中没有区别,而C中只有在递归调用main()函数的情况下才会有细微的差别。
C的main()函数的有效签名有哪些?
C11标准明确提到了以下两个签名:
int main(void);
int main(int argc, char* argv[]);
此外,它还提供了更多(实现定义的)可能性。
相关的文本(第5.1.2.2.1节,但这个特定方面与C99没有变化)规定:
程序启动时调用的函数名为main。实现不为此函数声明原型。它应该定义为返回类型为int且没有参数的函数:
int main(void) { /* ... */ }
或者带有两个参数(这里称为argc和argv,尽管可以使用任何名称,因为它们是在声明它们的函数中局部的):
int main(int argc, char *argv[]) { /* ... */ }
或者以某种其他实现定义的方式。
如果声明了它们,main函数的参数必须符合以下约束:
请注意,这适用于托管环境,即C程序中通常见到的环境。无操作系统的自由环境(如嵌入式系统)的约束要少得多,如同标准的5.1.2.1节所述:
在无操作系统的自由环境中(其中C程序的执行可能不受任何操作系统的任何好处),程序启动时调用的函数的名称和类型是实现定义的。除了第4条所要求的最小集合外,任何可供自由程序使用的库设施都是实现定义的。
那么int main(int argc, const char* argv[]);呢?
根据标准第5.1.2.2.1节的规定:“参数argc和argv以及argv数组指向的字符串必须可以被程序修改……”。因此,似乎在签名中使用const是无效的。
为了使您的答案更具未来性,请提到“当前标准”是什么。
我在问题中已经提到了这一点,提到了C11中的文本并指出C99几乎相同。但是我会根据您的建议,在第一段中再次重申。谢谢。
有人可能会辩解int main(int argc, char* const argv[])是有效的,即将参数本身声明为const。我认为这是可以辩解的,因为它与假定的隐式声明int main(int argc, char* argv[]);是兼容的。
具体而言,文本“argc和argv以及argv数组指向的字符串必须可以被程序修改”可能适用,尽管它可以被解读为“只有argv和argv[N]指向的字符,而不是组成argv的个别指针”。这就是为什么在标准中不应该使用不规范的语言的原因。
我认为我们都同意argv(正确地)是可修改的(因为它是像所有函数参数一样的局部副本)-但是函数“实现”可以声明自己不修改这个局部变量,通过将其声明为const,而不改变函数签名。标准的意思是argv不指向const内存,argv中的指针也不指向const内存(也就是说我们可以说++argv是理所当然的,但标准要求我们也可以说++*argv,甚至++**argv(如果argc > 0)。