return -1 和 255

环境

  • 系统:macOS M1

  • Shell:Zsh

  • 编译器:clang++ 13.1.6

return -1 和 255

一个简单的 C++ 小程序:

int main() { return -1; }

编译执行后,查看程序的退出码为 255:

❯ echo $?
255

为什么程序里返回的是 -1,但是系统中查看的退出码却是 255 呢?

分析

我们知道如果是非零的退出码,表示命令执行失败,尝试更多的情况:

Return Exit Code
-1 255
-2 254
-10 246
1 1

基于此,我们先做一些猜测:

  1. 退出码可能是一个 unsigned 的类型,长度可能是 1 个字节。

  2. 上述情况中,-1 和 255, -2 和 254,以及 -10 和 246 之间应该存在某种规律,可能是一种强制类型转换。

针对以上猜测,我们需要进一步了解负数在内存中是如何存储的。

负数的表示

在计算机中,数值统一使用补码来表示和存储。其中,正数的补码和其原码相同,负数的补码是将其原码除符号位外,其余取反后加 1。

以 -1 为例,假设它为 signed char 类型,那么它的原码为 1000 0001,补码为 1111 1111,如果将其强制转换为 unsigned char 类型,它的十进制表示的就是 255。

其它情况的转换结果如下:

signed char 原码 补码 unsigned char
-1 1000 0001 1111 1111 255
-2 1000 0010 1111 1110 254
-10 1000 1010 1111 0110 246
1 0000 0001 0000 0001 1

我们也可以使用程序计算上述结果,以 Python 为例:

>>> import array
>>> arr = array.array("b", [-1, -2, -10, 1])
>>> arr_v = memoryview(arr)
>>> arr_v.cast("B").tolist()
[255, 254, 246, 1]

fork 和 exec

简单来说,当我们在 Shell 中执行上述编译后的二进制可执行文件时,Shell 会先 fork 出一个子进程,然后在子进程中执行命令,命令的退出状态会再被父进程(Shell)收集。

不包含 Shell 的内建命令,如 cdalias 等,这些内建命令不会创建子进程。

收集命令结果

Shell 本质上也是一个程序,可以通过 C 语言库函数中的 wait() 方法收集子进程返回的结果。

pid_t   wait(int *status)

其中,status 的主要字段结构如下:

15        8        7        6       0
+-----------------------------------+
|  退出码  | core dump 标识位 |  信号  |
+-----------------------------------+

一般而言,程序退出的方式有两种:

  1. 程序正常结束,例如:return 或者 exit 等;

  2. 程序异常终止,例如:CTRL + C 或者 kill 等;

我们可以通过一些程序对上述两种情况做一些测试。

  1. 程序正常结束:
#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <unistd.h>

int main() {
  pid_t c_pid;
  int status;

  c_pid = fork();
  if (c_pid == 0) {
    exit(-10); /* 子进程退出 */
  } else {
    wait(&status); /* 收集子进程的状态 */
  }

  printf("Child exit code: %d\n", WEXITSTATUS(status));

  return 0;
}

编译执行上述程序:

Child exit code: 246

这里我们使用了宏函数 WEXITSTATUS 来获取退出码,在我的系统中,它的实际含义为:

#define _W_INT(w)       (*(int *)&(w))
#define WEXITSTATUS(x)  ((_W_INT(x) >> 8) & 0x000000ff)
  1. 程序异常终止:
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <unistd.h>

int main() {
  pid_t c_pid;
  int status;

  c_pid = fork();
  if (c_pid == 0) {
    for (;;) /*死循环*/
      ;
  } else {
    kill(c_pid, SIGKILL);

    wait(&status);
  }

  printf("Child exit signal: %d\n", WTERMSIG(status));

  return 0;
}

编译执行上述程序:

Child exit signal: 9

这里我们向子进程发送 SIGKILL 信号,杀死进程,和 kill -9 命令的效果相似。并且,可以通过 WTERMSIG 宏函数解析出导致进程终止的信号值。

俗成的约定

类似 HTTP 服务中的 404、501 这样的状态码,针对 Shell 中命令的退出码也有一些默认俗成的约定

Exit Code Number Meaning Example Comments
1 Catchall for general errors var1 = 1/0 Miscellaneous errors, such as "divide by zero" and other impermissible operations
126 Command invoked cannot execute /dev/null Permission problem or command is not an executable
127 "command not found" illegal_command Possible problem with $PATH or a typo
128 Invalid argument to exit exit 3.14159 exit takes only integer args in the range 0 - 255 (see first footnote)
128+n Fatal error signal "n" kill -9 $PPID of script $? returns 137 (128 + 9)
130 Script terminated by Control-C Ctl-C Control-C is fatal error signal 2, (130 = 128 + 2, see above)
255* Exit status out of range exit -1 exit takes only integer args in the range 0 - 255

原文链接: https://www.cnblogs.com/luizyao/p/17053342.html

欢迎关注

微信关注下方公众号,第一时间获取干货硬货;公众号内回复【pdf】免费获取数百本计算机经典书籍

    return -1 和 255

原创文章受到原创版权保护。转载请注明出处:https://www.ccppcoding.com/archives/312269

非原创文章文中已经注明原地址,如有侵权,联系删除

关注公众号【高性能架构探索】,第一时间获取最新文章

转载文章受原作者版权保护。转载请注明原作者出处!

(0)
上一篇 2023年2月16日 下午12:19
下一篇 2023年2月16日 下午12:20

相关推荐