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
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章