【问题标题】:Call exit() without argument不带参数调用 exit()
【发布时间】:2019-08-18 11:34:11
【问题描述】:

在“UNIX 环境中的高级编程”第 199 页(第 7.3 节)中,我读到:

如果 (a) 这些函数 [exit, _exit, _Exit] 中的任何一个在没有退出状态的情况下被调用

我写了一个简单的程序来验证这一点:

#include <stdio.h>
#include <unistd.h>

int main() {
    exit();
}

但是gcc source.c 抱怨:

source.c:5:5: 错误:函数“退出”的参数太少

这怎么可能?他们的意思是“直接从程序集调用”吗?

编辑:段落说:

所有三个出口函数都需要一个整数参数,我们称之为出口 状态。大多数 UNIX 系统 shell 提供了一种检查进程退出状态的方法。 如果 (a) 这些函数中的任何一个在没有退出状态的情况下被调用,(b) main 会返回 没有返回值,或 (c) 主函数未声明返回整数,则 进程的退出状态未定义。

【问题讨论】:

  • 我没有可用的 APUE 副本(假设它甚至与您正在查看的版本相同),但引用比该句子片段更多的上下文可能有助于获得答案。

标签: c linux


【解决方案1】:

如果您忘记了原型(ANSI C 发明)并简单地将 exit 声明为采用 K&R 样式中未指定数量的参数

void exit();/*unspecified number of parameters*/

那你就可以了

void exit();/*unspecified number of parameters*/
int main()
{
    exit(1);
}

这是一个格式良好的程序。

使用这种类型的声明,编译器不会阻止您完全省略参数:

void exit();
int main()
{
    exit();
}

但这在技术上是未定义的 C。

实际上,exit 在单独的翻译单元(标准库)中,因此编译器无法将其弄乱,并且上述代码是否会崩溃或返回一些垃圾值取决于您的机器在代码中执行的操作尝试读取具有未指定内容的寄存器或内存位置。 (exit 将尝试抓取的未指定内容可能是“陷阱表示”,然后您会崩溃,否则它将简单地抓取并使用一些垃圾整数值。)

【讨论】:

  • void exit(); 这是欺骗编译器的方法。告诉编译器接受我的声明而不是定义。
  • “使用这种类型的声明,编译器不会阻止您完全省略参数:”实际上并非如此,因为编译器可以阻止您的未定义行为。
【解决方案2】:

所以我认为您的书想说的是,“如果您的程序在没有定义退出状态的情况下退出,则退出状态是未定义的”。 (当我这样说的时候听起来很明显有点荒谬,但我们就是这样。)

那么问题是,“程序如何在没有定义退出状态的情况下退出?”

一种方法是在不返回值的情况下从main() 的末尾脱落。 (曾几何时,实际上给了你一个随机退出状态,尽管现在 C 定义了一个特殊的例外,即如果你忘记从 main 返回一个值,编译器会悄悄地插入 return 0; for你。)

然后另一种在没有定义退出状态的情况下退出的方法是调用exit而不提供值——即调用exit()。再一次,自从(似乎)那本书写完之后,事情又发生了变化。曾几何时,C 没有函数原型,因此如果您在范围内调用没有原型的函数,编译器也不会抱怨。

但即使在今天,如果你写

/* deliberately no proper #include line here */

int main()
{
    exit();
}

您可能会得到一个至少有点令人惊讶的、不一定是 0 的退出状态。如果这对您来说不够随机,您可以尝试通过将其更改为来增加几率

int main()
{
    puts("Hello, world!");
    exit();
}

【讨论】:

  • 我无法编译它们:source.c:3:5: warning: implicit declaration of function ‘exit’ [-Wimplicit-function-declaration] exit(); ^~~~ source.c:3:5: warning: incompatible implicit declaration of built-in function ‘exit’ source.c:3:5: note: include ‘&lt;stdlib.h&gt;’ or provide a declaration of ‘exit’ source.c:3:5: error: too few arguments to function ‘exit’
  • @bicup 老实说,我也不能,出于同样的原因。
【解决方案3】:

编辑包含 3 个声明:

所有三个退出函数都需要一个整数参数,我们称之为退出状态。大多数 UNIX 系统 shell 提供了一种检查进程退出状态的方法。如果

a)在没有退出状态的情况下调用这些函数中的任何一个,

b) main 做一个没有返回值的返回,

c)主函数未声明返回整数

进程的退出状态未定义。

None 这 3 种情况都不是那么简单。让我们按顺序解决它们。

首先a)。如果您没有包含 &lt;stdlib.h&gt;,则可以在 C89 中不带参数地调用 exit()。然后exit 将被隐式假定为int exit() 类型,带有未声明的参数。这将导致未定义的行为,并且任何事情都可能发生。在 C99+ 中没有隐式函数声明,因此您必须使用错误类型显式声明 exit 才能在没有参数的情况下调用它。如果包含&lt;stdlib.h&gt;,则在没有参数的情况下调用exit() 是违反约束的,编译器必须对此进行诊断。该程序是无效程序。然而,编译器也可以成功编译无效程序。在这种情况下,行为是未定义的。

b) 如果 main 确实使用了没有值的 return,则它违反了约束,对于返回 int 的函数必须 return 类型为 int 的值。在这种情况下,编译器必须对其进行诊断,并可能拒绝编译程序。然而,它可以成功编译这个无效程序,例如 a)。但是,如果实现使用 C99+ 并且省略 return 语句 并且执行继续到最后一个 }main 就像 main 结束return 0;,即行为是明确定义的。

c) 如果main 函数没有声明为返回int(不仅仅是任何整数),那么main 必须使用其他一些实现定义的原型,并且在那个情况下,实现可能应该定义行为。如果原型不是任何实现定义的类型,则行为是undefined

现在,问题是在这些情况下未定义行为的限制是什么?它不一定是有界的——实际上任何事情都是允许的,包括程序崩溃就像是通过信号,或者编译器拒绝编译程序,或者程序打电话给exit,根本不回来。当然这是一个执行质量问题,大多数编译器可能会拒绝编译程序并且如果一个程序被成功编译,那么生成的可执行文件确实会返回一个未定义的退出状态。

【讨论】:

    【解决方案4】:

    即使你使用汇编,如果函数需要一个参数,也不可能调用一个带参数的函数。

    1. 如果参数需要在 CPU 寄存器中:每个寄存器中总是有一个值。它可能是未知的,但在大多数情况下,它可以由调用之前的控制流来确定。

    2. 如果参数应该在堆栈上:堆栈指针指向的内存中总是有值。同上。

      我能想到的只有一种情况是不同的。如果堆栈的内存区域受内存管理单元保护并且exit() 无法访问参数,因为它超出了区域的限制。

    所以你总是有一个退出状态;但你可能不知道它的价值。

    编辑

    如果某些系统具有@EricPostpischil 描述的架构,exit() 将检测丢失的退出状态。不知道这样的系统,我只能猜测接下来会发生什么。可能是被调用的exit() 将返回'0'(零)给调用进程,它也可能选择另一个定义的值,或者它可能会取一些随机值。

    在大多数情况下,结果是一样的:退出状态没有定义。但是有一个,因为调用进程收到一个。

    【讨论】:

    • Unix 已被移植到许多架构中,并且有些架构以不同的方式传递参数,例如在调用中包含参数计数,这样就可以传递零个参数。
    猜你喜欢
    • 1970-01-01
    • 2019-10-19
    • 2020-08-06
    • 2020-07-29
    • 1970-01-01
    • 2020-11-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多