? APUE 一書的第七章學習筆記。
程式終止
有 8 種方式可以使得程式終止,5 種為正常方式:
- Return from
main
- Calling
exit()
- Calling
_exit
or_Exit
- Return of the last thread from its start routine
- Calling
pthread_exit
from the last thread
3 種非正常終止方式:
- Calling
abort
- Receipt of a signal
- Response of the last thread to a cancellation request
退出函式
函式原型:
#include <stdlib.h>
void exit(int status);
void _Exit(int status);
#include <unistd.h>
void _exit(int status);
區別:_exit/_Exit
會立即進入核心;exit
先執行清理處理(對所有開啟的 Stream 執行 fclose
),後進入核心。
exit(k)
相當於在 main
函式中 return k
,最近一個程式的退出碼可以在 Shell 中使用 echo $?
獲取。
回撥函式 atexit
按照 ISO C 標準,一個程式最多可以登記 32 個回撥函式,這些函式稱為終止處理程式 (exit handler),由 exit
來呼叫。
函式原型:
int atexit(void (*func)(void));
// Returns: 0 if OK, nonzero on error
exit
呼叫終止處理函式的順序與登記順序相反(_exit/_Exit
則不會呼叫);如果登記多次,也會被呼叫多次。
例子:
void test1() { printf("A "); }
void test2() { printf("B "); }
int main()
{
atexit(test1);
atexit(test1);
atexit(test2);
// 如果呼叫下面 2 個函式,則不會有輸出
// _exit(0), _Exit(0)
}
// Output: B A A
下圖展示了一個 C 程式如何啟動和終止的過程,也顯示了 exit, _exit, _Exit, atexit
這 4 個函式的關係。
環境表
環境表 (Environment List), 與命令列引數 argv
一樣,是一個 char*
陣列。
下列程式可以列印所有的環境引數:
#include <stdio.h>
extern char **environ;
int main()
{
int i;
for (i = 0; environ[i] != NULL; i++)
puts(environ[i]);
}
輸出一大片:
TERM=xterm-256color
SHELL=/bin/bash
...
USER=sinkinben
LS_COLORS=rs=0:di=01;...
PATH=...
MAIL=/var/mail/sinkinben
PWD=/home/sinkinben/workspace/apue
HOME=/home/sinkinben
...
_=./a.out
C 儲存佈局
如下圖所示,一個 C 程式由以下部分組成:
- text 段:這是 CPU 執行的機器指令部分。text 段通常是隻讀(防止意外修改)和可共享的,即使是頻繁執行的程式,它們的 text 段在記憶體中只會存在 1 個副本。(難怪我的 Mac 開機第一次呼叫 clang++ 會特別慢)
- 初始化資料段(簡稱資料段):包括初始化的全域性變數和
static
修飾的變數。 - 未初始化的資料段(簡稱 bss 段):在程式開始執行前,由
exec
初始化為 0 或者空指標。(確實,我還以為是隨機值) - 棧:區域性變數,函式呼叫。
- 堆:動態記憶體分配。
這是一種典型的邏輯佈局,但不是所有的實現都是如此,具體取決於實際的 OS 和硬體。對於 32 位 Intel x86 架構的 Linux,text 段從 0x80480000 開始,棧從 0xC0000000 開始向低地址增長。
可以通過 size
命令獲取一個 C 程式的各個段大小,dec, hex
分別是前 3 個數字的總和的十進位制和十六進位制:
$ size /bin/bash
text data bss dec hex filename
997958 36496 23480 1057934 10248e /bin/bash
共享庫
共享庫 (Shared Libraries): 對於一些常用的公共函式庫,只需要在所有程式都可引用的儲存區儲存一份副本。程式第一次呼叫某個庫函式的同時,用動態連結的方式將程式與庫函式相連結。這就減少了可執行檔案的大小,但增加了一些執行時開銷。
使用 Shared Libraries 的另外一個優點是:可以動態更新某個庫的版本,而不需要重新對使用該庫的程式重新連結。
使用 gcc -static
可指定生成的可執行檔案使用靜態連結。
$ gcc test.c ; size ./a.out
text data bss dec hex filename
1099 544 8 1651 673 ./a.out
$ gcc test.c -static ; size ./a.out
text data bss dec hex filename
823142 7284 6360 836786 cc4b2 ./a.out
動態記憶體分配
相關函式:
#include <stdlib.h>
void *malloc(size_t size);
void *calloc(size_t nobj, size_t size);
void *realloc(void *ptr, size_t newsize);
// All three return: non-null pointer if OK, NULL on error
void free(void *ptr);
作用:
malloc
: 申請指定位元組數的記憶體,該記憶體的值不確定。calloc
: 為指定數量,指定長度的物件分配記憶體,並初始化為 0 。realloc
: 增加或者減少ptr
指向記憶體區的長度。當增加長度時,可能需要將之前的資料拷貝到另外一個足夠大的記憶體區(也有可能是在原有基礎上增加一段連續記憶體),新增區域的初始值不確定。
環境變數
getenv
#include <stdlib.h>
char *getenv(const char *name);
// Returns: pointer to value associated with name, NULL if not found
獲取環境變數。例子:
#include <stdio.h>
#include <stdlib.h>
int main()
{
puts(getenv("JAVA_HOME"));
}
// Output: /usr/local/java/jdk1.7
putenv, setenv, unsetenv
相關 API:
#include <stdlib.h>
int putenv(char *str);
// Returns: 0 if OK, nonzero on error
int setenv(const char *name, const char *value, int rewrite);
int unsetenv(const char *name);
// Both return: 0 if OK, −1 on error
改變當前程式以及後續產生的子程式的環境變數(實際上是修改程式的環境表)。作用分別如下:
putenv
: 把name=value
的環境變數新增到環境表,如果name
已存在,則刪除原來的定義。setenv
: 將name
設定為value
。rewrite = 0/1
表示是否覆蓋已有的name
(如果有的話)。unsetenv
: 刪除name
,如果不存在則什麼都不做。
getrlimit, setrlimit
每個程式都有一組資源限制,可以通過 getrlimit, setrlimit
進行查詢和修改:
#include <sys/resource.h>
int getrlimit(int resource, struct rlimit *rlptr);
int setrlimit(int resource, const struct rlimit *rlptr);
// Both return: 0 if OK, −1 on error
resource
是形如 RLIMIT_CPU
的一組巨集定義。
結構體 rlimit
的定義如下:
struct rlimit {
rlim_t rlim_cur; /* soft limit: current limit */
rlim_t rlim_max; /* hard limit: maximum value for rlim_cur */
};
修改操作需要遵循以下規則:
cur <= max
- 可以降低
rlim_max
,但必須大於等於rlim_cur
. - 只有超級使用者程式可以提高
rlim_max
。
修改資源限制會應當當前程式和它的子程式,所以 Shell 中一般會內建 ulimit
命令來修改當前 Shell 的資源限制。