当将main函数声明为`int main(void)`时,传递命令行参数时为什么不会出现错误?

14

案例1:

void hello(void) {
    //something
}

int main()
{
    hello(1); //error
    return 0;
}

案例2:

int main(void) {
    //something
    return 0;
}

执行:

./a.out something something //No error, Why?

为什么没有错误?main不会接受任何参数。那么为什么可以从命令行提供参数呢?


你为什么会期望出现错误? - Oliver Charlesworth
因为我们将参数传递给主函数,所以编译器不会介入,但在运行时/加载时为什么没有错误? - Jagdish
2
Shell总是将其参数发送给程序 - 即使您没有提供任何参数(通常它会发送argv [0])。 main函数与hello函数不属于同一种类型。 - Jongware
我也很想听答案。运行时肯定有什么东西使用这些参数调用main函数。那么,当您定义一个与标准不同的main函数时,为什么没有链接器错误呢?(即使main(float)只生成警告,也没有错误。而且只有在使用-Wall选项时才会出现。) - Mr Lister
1
出于好奇心,我进行了一个小实验。我编译了同样的程序两次,一次使用每个不同的签名。(如果你感兴趣,是VS2013,因为我手头有这个版本。)忽略调试信息和时间戳,两个生成的文件完全相同,字节一模一样。这表明编译器要么用标准的void签名替换了它(这不会改变你的程序),要么聪明到知道如果你不使用args也没关系。(当然,我们期望编译器知道这一点。 :) ) - jpfx1342
5个回答

14
因为C编译器和命令行解释器(或用于调用程序的任何东西)是不同的东西。
C语言允许以各种方式声明main()函数。
命令行解释器会将任何参数提供给程序。如果程序忽略它们,那就不关它的事。
命令行解释器甚至不知道你使用了C来编译你的程序。在我的电脑上,程序可以用C、C ++、Objective-C、Objective-C ++、Swift、Fortran、Ada等语言编写。每个编译器可能会做或不做接受来自命令行的命令的事情。

9
不检查规范或编译结果,由于C运行时将获取参数并将其传递给main(),因此不会引发错误。但是这种类型的main()将忽略传递的参数,如果调用者的职责是清理用作参数的内存(堆栈),那么就像获取一些参数而不在代码中使用它们一样,它不会引起任何问题。此代码不会在C中引发错误:
void hello(); // in C, the compiler won't check arguments

int main() {
    hello(1); //no error
    return 0;
}

void hello(void) {
    //something 
}

3
调用main函数的函数是由C标准库提供的一个桥接程序,它并不知道也不关心main函数的声明方式,因此传递给main的参数将被清除出栈。这种情况不仅局限于main函数,而是所有C语言函数的普遍调用约定。(使用类似__stdcall的编译器扩展可以改变调用约定,但如果将其应用于main或任何由C标准库调用的其他函数,则会导致问题。) - zwol
2
@Jongware 啊,你可能想问的另一件事情。argv 最初是从哪里来的?内核将它与程序一起加载到内存中,作为 execve 操作的一部分,并构造一个初始调用帧,告诉 C 库的启动代码在哪里找到它。它通常位于堆栈的顶部,但不一定是,而且永远不会被释放。 - zwol
1
@Jongware 如果C库的启动代码从初始堆栈帧返回,程序将崩溃。必须调用exit。请参见https://sourceware.org/git/?p=glibc.git;a=blob;f=csu/libc-start.c的最后部分。 - zwol
2
@zwol:那一定是OP正在寻找的答案。运行时启动始终告诉C代码在哪里找到参数——无论是否有参数。而且它并不关心C代码是否真的费心去检查。 - Jongware
1
你不能指望这种代码能够编译和运行而没有错误。根据C11标准,6.5.2.2.6:“如果参数的数量不等于形参的数量,则行为是未定义的。”你正在依赖于实现的怪癖,而不同的实现或任何类型的优化都可能破坏这段代码。 - user2357112
显示剩余4条评论

5
因为./a.out something something并没有直接调用您的主函数,而是由C运行时库调用主函数。命令行参数由加载器/C运行时在堆栈(开头处)的某个区域放置。您可以自行决定是否要访问这些参数。
此外,正如其中一条评论所指出的那样,至少一个命令行参数总是会被传递(程序名称./a.out),因此您可能也会对这种情况下的错误感到疑惑。

3
请注意,ISO C 规定了 main 两种可能的签名: int main(void)int main(int, char *[]),以及类似于 int main(int, char **) 的等效版本,因为数组衰减为指针。这在此处有更详细的说明。 可以通过考虑相反的问题来回答这个问题:C 运行时怎么知道要调用哪个 main 函数?C 没有重载解析!这在此处有解释。简言之,其他的被推入但没有被访问,因为 C 没有给出这样做的迹象。

2
当您使用gcc program_name.c编译程序时,编译器将报告可能的编译时警告或错误。由于命令行参数在编译时未传递,因此编译器不知道它,并且程序会忽略该参数。
对于hello,编译器知道此函数的原型并期望在其调用中不传递任何参数,因此如果传递任何参数,则会报告错误。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接