c语言 - "int main(void *framep)"的目的是什么?



在C中,main()函数只接受零个或两个参数。如果我们提供两个参数,那么第一个参数必须是int类型。

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

但是,我在浏览OpenBSD时看到了以下代码。

int main(void *framep){} 

它在 C 中有效吗?

GCC 编译器给出以下警告:

prog.c:3:5: warning: first argument of 'main' should be 'int' [-Wmain]
int main(void *p) {
^~~~
prog.c:3:5: warning: 'main' takes only zero or two arguments [-Wmain]

它的目的是什么?

在 Linux 上,在链接期间,库函数_start将链接到预期存在于代码中的函数main()

然后传统上,您的main由带有int argc, char *argv[]、参数数量(包括程序名称)和实际参数(加上尾随NULL)的_start调用。

但是,在其他一些实现中,可能不需要以这种方式调用main,或者出于性能原因,按照不同的格式使用减少参数数量来调用它。

main()是我们程序的起始函数,argc, argv传递,但毕竟,它只是一个 C 函数,可以传递其他东西,只要该实现的约定是已知的并被接受的。

哎呀,这不是一个普通的程序,而是一个内核,所以 main 的正常规则并不真正适用。当程序启动时,不存在传递参数值的环境,并且 main 的返回值也不会被使用,因为当内核退出时,不存在任何其他内容。一条评论说,该定义被修改为仅应对 gcc 要求:

返回 int,所以 gcc -Werror 不会抱怨

这在 C11 的 N1256 草案 5.1.2.1 独立环境中是明确的:

在独立环境中(C程序执行可以在没有任何的情况下进行 操作系统的好处),在程序中调用的函数的名称和类型 启动是实现定义的。独立式图书馆设施 程序,除了条款 4 要求的最小集之外,是实现定义的。

程序终止在独立环境中的效果是实现定义的。

在内核启动时,没有操作系统仍然存在,因此它实际上在独立环境中运行。这可能意味着还需要使用特殊标志进行编译......

在您提供的链接中,主函数中使用framep

不,这不是标准的。

正如您已经看到的那样,GCC 会发出警告,但值得注意的是,clang 会抛出一个错误:

error: first parameter of 'main' (argument count) must be of type 'int'
int main(void *framep){} 
^
1 error generated.

从标准:

5.1.2.2.1 程序启动 1

程序启动时调用的函数名为 main。该实现没有为此声明原型 功能。它应使用返回类型 int 和 no 参数:int main(void) { /* ... */ }

具有两个参数(此处称为 argc 和 argv,尽管可以使用任何名称,因为它们是函数的本地 他们被声明):

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

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

你用g++编译它来得到这些错误,如果你用gcc编译它,你不会得到它们。

$ gcc test.c
$ g++ test.c
test.c:3:5: warning: first argument of 'int main(void*)' should be 'int' [-Wmain]
int main(void *framep)
^~~~
test.c:3:5: warning: 'int main(void*)' takes only zero or two arguments [-Wmain]

这很重要,因为 C 不认为参数类型(或数字!)是函数类型的一部分(而C++这样)。 原因有很多,其中包括在 C 中,调用者会清理参数,因此如果他指定了太多参数,他也会清理它们。 在C++中,被调用方清理参数,因此如果他清理了错误的号码,您最终会得到损坏的堆栈。

关于为什么你可以选择使用int main(void *framep):在 C 的调用约定中,参数被推送到堆栈上,然后进行调用,这将返回地址放在下一个。 然后,被调用方通常会推送 EBP 的旧值,然后将堆栈指针移动到 EBP 中作为新堆栈帧的"基本指针"。然后移动堆栈指针以分配被调用方中任何自动(局部)变量的空间。即,堆栈如下所示:

Arg n
Arg n-1
...
Arg 1
Return Addr
Old EBP
Callee locals

现在假设我们要检查函数的返回地址,或者读取推送的先前帧指针 (Old EBP)。 如果我们在汇编中编写,我们只是相对于当前帧指针(EBP)取消引用。 但我们是用 C 语言编写的。 获取引用的一种方法是获取第一个参数的地址。 也就是说,&framep,这是Arg1住在堆栈上的地方。 因此(&framep)[-2]应该是一个指向存储的先前帧指针(Old EBP)的void *

(注意:我假设是英特尔架构,其中所有推送到堆栈都由硬件扩展到指针大小。