8捺萌、內(nèi)存分配
ISO C
指定了三種內(nèi)存分配的方式:
#include <stdlib.h>
void *malloc(size_t size);
void *calloc(size_t nobj, size_t size);
void *realloc(void *ptr, size_t newsize);
這三種函數(shù)如果返回的指針是非空的表示分配成功,如果出錯了那么返回的是 null
.
void free(void *ptr);
malloc
分配指定字節(jié)大小的內(nèi)存追驴,內(nèi)存里面的數(shù)據(jù)是不確定的。calloc
分配nobj
個特定大小(size
)的對象责循,內(nèi)存里面的數(shù)據(jù)被初始化為0贵试。-
realloc
用來增加或者減小之前分配的內(nèi)存區(qū)域琉兜。如果是增加的話,可能會把原先的分配的區(qū)域移動到別的地方然后在后面增加多出來的內(nèi)存毙玻,增加的內(nèi)存的內(nèi)容不確定并返回新內(nèi)存塊的指針豌蟋,如果原來的區(qū)域后面有足夠的空間來增加額外的內(nèi)存的話就不需要移動內(nèi)存了。參數(shù)是最終內(nèi)存的總大小桑滩。如果ptr
為空那么realloc
的行為和malloc
的行為是一樣的梧疲。三種分配函數(shù)返回的指針的對齊方式保證適合任何的數(shù)據(jù)類型。因為函數(shù)返回的是
void*
所以如果我們包含了stdlib.h
那么我們不用強制將這個函數(shù)返回的指針轉(zhuǎn)換成為我們想要的類型运准。 -
free
使用來釋放之前分配的內(nèi)存的幌氮。分配函數(shù)是使用
sbrk
系統(tǒng)來進行實現(xiàn)的。實際上分配的空間要比指定的空間大一些胁澳,因為需要存放空間大小等信息讓free
正確地執(zhí)行该互。如果只分配不釋放會導致內(nèi)存泄露,如果釋放了沒有分配的內(nèi)存听哭,那么會導致致命的錯誤慢洋。
由于這些函數(shù)導致的內(nèi)存錯誤很難跟蹤,所以有些系統(tǒng)提供了這些函數(shù)的可以進行一些額外檢測的版本(當 alloc
或者 free
時)陆盘∑粘铮可以通過包含一些庫文件或者有時候也可以通過特定的編譯選項來打開這些額外檢查的特性。
一些其他的內(nèi)存分配函數(shù):
除了前面提到的 malloc
, free
等標準分配函數(shù)隘马,還有一些替代的內(nèi)存分配函數(shù)太防。有些系統(tǒng)只提供了標準的分配函數(shù),開發(fā)者可以自己選擇下載其他的替代分配函數(shù)來使用酸员。
-
libmalloc
SVR4-based
的系統(tǒng)蜒车,例如solaris
,包含libmalloc
庫,這個庫提供了一系列的和ISO C
內(nèi)存分配函數(shù)相匹配的接口幔嗦。libmalloc
庫包括mallopt
函數(shù)酿愧,這個函數(shù)允許進程設置特定的變量來控制存儲的分配(???)。還有一個函數(shù)叫做mallinfo
邀泉,可以用來提供內(nèi)存分配器的統(tǒng)計信息嬉挡。 -
vmalloc
這是一種允許進程對不同的內(nèi)存區(qū)域使用不同的技術分配內(nèi)存的內(nèi)存分配器钝鸽。出了
vmalloc
之外,這個庫也提供了ISO C
內(nèi)存分配函數(shù)的模擬版本庞钢。 -
quick-fit
以前標準的分配算法使用的是最佳適配或者最先適配分配策略拔恰。
Quick-fit
比這兩種方法都快,但是它會消耗更多的內(nèi)存基括。這個算法基于把內(nèi)存分成不同大小的緩存塊颜懊,然后基于緩存的大小,在free list
上面維護沒有使用的緩存风皿。在一些ftp
站點上面有基于quick-fit
實現(xiàn)的malloc
和free
河爹。 -
alloca Function
還有一個值得一提的函數(shù)就是
alloca
.這個函數(shù)和malloc
具有一樣的調(diào)用次序,然而它不是從heap
上面分配內(nèi)存而是從當前函數(shù)所處堆棧上面分配內(nèi)存的揪阶。這樣的有時就是我們不用釋放空間了昌抠,當函數(shù)結(jié)束的時候內(nèi)存的空間會自動地被釋放掉。alloca
函數(shù)導致堆棧楨的大小增加鲁僚。這樣的缺點是有些系統(tǒng)不支持alloca
函數(shù)炊苫,因為對于它們而言不可能在調(diào)用函數(shù)之后再增加堆棧楨的大小。然而許多的軟件包還是使用了它冰沙,因為這個函數(shù)在許多系統(tǒng)上都有實現(xiàn)侨艾。
譯者注
下面是一個使用 sbrk
的例子:
/*測試用sbrk分配內(nèi)存
* #include <unistd.h>
* int brk(void *addr);
* void *sbrk(intptr_t increment);
* brk和sbrk用來改變程序數(shù)據(jù)段的結(jié)尾地址。如果地址增加了表示為這個進程分配內(nèi)存了拓挥,如果地址減少了表示給這個進程釋放內(nèi)存了唠梨。
* sbrk把程序的結(jié)尾地址增加increment,如果increment的值為0會返回當前程序的結(jié)尾地址。返回先前的程序結(jié)尾侥啤,如果之前已經(jīng)增加過那么返回上次的新的程序結(jié)尾(不是設置完了之后的)当叭,失敗了就返回-1,同時errno被設置為ENOMEM.
*
* 一個可能的輸出結(jié)果是:
* current break is:153415680
* after add break is:153415680
* after sub break is:153415690
* current break is:153415680
*
* malloc就是用這個系統(tǒng)調(diào)用實現(xiàn)的。
* */
#include <stdio.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
void *cur = sbrk(0);
printf("current break is:%d\n",(int)cur);//
void *newadd1 = sbrk(10);
printf("after add break is:%d\n",(int)newadd1);//
void *newsub1 = sbrk(-10);
printf("after sub break is:%d\n",(int)newsub1);//
cur = sbrk(0);
printf("current break is:%d\n",(int)cur);//
return 0;
}
原文參考
9盖灸、再談環(huán)境變量
我們前面說了環(huán)境變量一般的形式是:"name=value"蚁鳖,unix內(nèi)核從來不會檢查這些字符串,對于這些字符串的解釋讓程序自己來做赁炎。shell中就維護了大量的內(nèi)部變量例如:HOME,USER,MAILPATH等等醉箕。
ISO C定義了一些可以獲取環(huán)境變量值的函數(shù),但是標準說值的內(nèi)容是由實現(xiàn)定義的徙垫。
#include <stdlib.h>
char *getenv(const char *name);
返回和name相關的環(huán)境變量的值的指針讥裤,如果沒有這個環(huán)境變量那么返回NULL。需要注意的是這個函數(shù)返回"name=value"字符串姻报。我們一般調(diào)用getenv來獲得特定的值,而不是直接訪問environ己英。有些環(huán)境變量是特定的標準(例如XSI)才有的,而不是所有的標準才有這樣的環(huán)境變量。
有時候我們想要修改一個環(huán)境變量或者添加一個環(huán)境變量吴旋,但是并不是所有的系統(tǒng)都支持這樣的功能剧辐。下面的函數(shù)就和這個相關:
#include <stdlib.h>
int putenv(char *str);
int setenv(const char *name, const char *value,int rewrite);
int unsetenv(const char *name);
putenv函數(shù)以一個"name=value"字符串做為輸入寒亥,把這個字符串放到環(huán)境列表中去邮府。如果name已經(jīng)存在了荧关,那么舊的定義會被移除。
setenv函數(shù)設置name為value,如果name已經(jīng)存在了褂傀,那么
- 如果重寫的值非0忍啤,會先把已經(jīng)定義的name給移除;
- 如果重寫的值為0仙辟,那么已經(jīng)存在的name定義不會被移除同波,name也不會被設置為新值也不會有任何錯誤發(fā)生。
unsetenv函數(shù)會把任何定義的name給移除叠国。如果沒有相應的定義也不會出現(xiàn)錯誤未檩。
注意putenv和setenv的不同之處。setenv必須分配一塊memory來創(chuàng)建name=value來根據(jù)參數(shù)創(chuàng)建字符串粟焊,putenv直接把傳遞給它的字符串自由地傳遞到environment.在linux和solaris中冤狡,putenv的實現(xiàn)會把我們傳遞給它的地址直接放到environment list中去。這時候项棠,如果傳遞一個在stack上面的字符串將會出現(xiàn)錯誤悲雳。
修改環(huán)境變量列表的操作實際很復雜。前面已經(jīng)說過香追,環(huán)境變量列表是指針數(shù)組合瓢,指針元素指向了實際的"name=value"字符串。環(huán)境變量字符串一般都存放在進程內(nèi)存空間的頂部透典,在對陣的上面晴楔。如果刪除一個字符串很簡單,我們只需要找到該字符串在環(huán)境變量列表中相應的指針峭咒,然后把所有后面的指針向下移動一個單元就行了税弃。但是如果添加一個字符串或者修改已經(jīng)存在的字符串就很困難了。堆棧頂部的空間是不能被擴展的讹语,因為它一直在進程地址空間的頂部钙皮,并且不能向上擴展;它也不能向下擴展顽决,因為下面的stack不能被move短条。
如果我們修改一個已經(jīng)存在的名字:
- 如果新value的size比原來的小或者等于原來的value,那么我們可以把新的字符串拷貝替換舊的字符串才菠。
- 如果新value比舊大茸时,我們必須為新的字符串分配空間,把新的字符串拷貝到相應的空間赋访,然后把環(huán)境變量列表中相應的指針修改成指向新分配的空間可都。
如果我們想要增加一個環(huán)境變量缓待,那么情況更為復雜.首先,我們的調(diào)用malloc為"name=value"分配空間渠牲,然后把字符串拷貝到這個空間旋炒。
- 然后,如果是我們第一次添加新的變量签杈,我們需要調(diào)用malloc分配一個新的指針列表瘫镇。我們把舊的環(huán)境變量指針列表內(nèi)容拷貝到這個新的列表里面,然后把我們定義的新的環(huán)境變量的地址放到這個列表指針的最后答姥。注意铣除,如果原來的環(huán)境變量列表是在堆棧的頂部,我們需要把指針列表移動到heap中去鹦付。但是大多數(shù)在這個堆棧上面的列表的指針還是指向了name=value字符串尚粘。
- 如果我們不是第一次將新的環(huán)境變量添加到環(huán)境變量列表中去,那么我們需要知道我們已經(jīng)在heap中為這個列表分配了空間敲长,所以我們只需要調(diào)用realloc來分配可以容納更多指針的空間郎嫁。這個指向新定義環(huán)境變量的指針存放在列表的結(jié)尾,其后是null指針潘明。
實際我們修改環(huán)境變量的時候行剂,只能影響當前進程以及當前進程的子進程的環(huán)境變量。
原文中的參考資料中,給出了有關這些函數(shù),以及相應的一些環(huán)境變量在系統(tǒng)上面的支持魂角。這里不再給出,需要查看的讀者可以從下面給出的參考網(wǎng)址中找到相應的內(nèi)容铲觉。
譯者注
原文參考
10、關于跳轉(zhuǎn)函數(shù)
在c語言中吓坚,我們不能夠使用goto跳轉(zhuǎn)到另外一個函數(shù)的標簽中去,如果我們想要實現(xiàn)這樣的功能撵幽,我們需要使用setjmp和longjmp。
#include <setjmp.h>
int setjmp(jmp_buf env);
void longjmp(jmp_buf env, int val);
我們需要在跳轉(zhuǎn)的地方調(diào)用setjmp,其參數(shù)保存了跳轉(zhuǎn)時候的各種信息礁击,并且因為是從不同函數(shù)的跳轉(zhuǎn)所以一般是全局變量盐杂。
setjmp的返回值,直接調(diào)用它返回的是0哆窿,通過longjmp返回的链烈,是非0。
我們在需要跳轉(zhuǎn)的時候挚躯,調(diào)用longjmp函數(shù)强衡,這個函數(shù)有兩個參數(shù),一個是先前設置setjmp的變量码荔,一個是用來設置跳轉(zhuǎn)之后setjmp的返回值漩勤。
一個使用這兩個函數(shù)的例子:
#include <setjmp.h>
jmp_buf jmpbuffer;
int main(void)
{
...
if (setjmp(jmpbuffer) != 0)
printf("error");
while (fgets(line, MAXLINE, stdin) != NULL)
do_line(line);
exit(0);
}
...
void cmd_add(void)
{
int token;
...
if (token < 0) /* an error has occurred */
longjmp(jmpbuffer, 1);
/* rest of processing for this command */
}
當main函數(shù)執(zhí)行的時候感挥,我們調(diào)用setjmp,這個函數(shù)記錄跳轉(zhuǎn)時候需要保存的信息,之后返回0越败。當我們調(diào)用do_line的時候触幼,do_line會調(diào)用cmd_add,然后cmd_add調(diào)用logjmp,調(diào)用logjmp導致跳轉(zhuǎn)到了main調(diào)用setjmp的地方,并且setjmp返回值變?yōu)閘ongjmp的第2個參數(shù)指定的1.
跳轉(zhuǎn)的時候哪些變量保存眉尸?哪些變量不保存域蜗?假設setjmp之前定義了變量并且賦值,再setjmp噪猾,之后再修改變量值;之后筑累,調(diào)用某些函數(shù)導致longjmp袱蜡。這時候,longjmp將返回setjmp的位置慢宗,我們這里假設關注的變量有5個種類:全局變量坪蚁,靜態(tài)變量,寄存器變量镜沽,自動變量敏晤,volatile變量,一般來說缅茉,具體會保存那些變量嘴脾,以來于具體情況。多數(shù)的實現(xiàn)不會將自動變量和寄存器變量恢復蔬墩,但是標準說他們的值是不確定的译打。如果你有一個自動變量,并且你不想讓它在跳轉(zhuǎn)的時候被回滾拇颅,那么應該把它定義成為volatile屬性奏司。全局變量和靜態(tài)變量的值在進行l(wèi)ongjmp的時候不會回滾。具體參見參考資料里面的例子樟插。從例子中看韵洋,跳轉(zhuǎn)之后確定不被恢復的是全局,靜態(tài)和volatile變量黄锤。
譯者注
原文參考
11搪缨、資源的限制
每個進程都有各種資源的限制,使用下列函數(shù)可以獲得當前進程的各種資源的限制:
#include <sys/resource.h>
int getrlimit(int resource, struct rlimit *rlptr);
int setrlimit(int resource, const struct rlimit *rlptr);
這兩個函數(shù)屬于XSI擴展以及Single Unix標準猜扮。進程資源的限制是在系統(tǒng)初始化的時候被進程0來建立起來的勉吻。每種實現(xiàn)的資源種類可能會有所不同。
資源的結(jié)構有兩個成員:軟資源限制旅赢;硬資源限制齿桃。修改資源的限制遵循如下的規(guī)則:
- 進程可以在小于等于硬資源限制的前提下修改軟資源限制惑惶。
- 進程可以減少它的硬資源限制,硬資源限制不能小于軟資源限制短纵,普通用戶減少硬資源限制之后不能夠反向修改了(也就是增加了).
- 只有超級用戶可以提高硬資源的限制带污。
資源限制影響當前的進程以及它的子進程。這也就是說設置資源的應該編譯到shell中去以便影響我們的所有進程香到。例如命令:ulimit或者c shell的limit命令鱼冀。更多的信息,參見下面網(wǎng)址中給出的參考資料悠就。
譯者注
原文參考
12千绪、總結(jié)
在知道UNIX系統(tǒng)中進程控制相關的特性之前,需要對UNIX系統(tǒng)環(huán)境中梗脾,一個C程序的運行環(huán)境有所了解荸型。本章我們看到如何啟動和終止進程,以及如何傳遞參數(shù)列表和環(huán)境變量炸茧。盡管兩者在內(nèi)核中并不知道這些瑞妇,但是調(diào)用者調(diào)用exec,執(zhí)行新的程序梭冠,卻是通過內(nèi)核傳遞這些的辕狰。
我們也看到了一個C程序的典型內(nèi)存布局,以及一個進程如何能夠動態(tài)分配和釋放內(nèi)存控漠。因為涉及到了內(nèi)存的分配問題蔓倍,所以很有必要來詳細對這些操作環(huán)境變量的函數(shù)。我們也看到了setjmp和longjmp函數(shù)润脸,它們提供了一個在進程中實現(xiàn)非本地化程序分支(跳轉(zhuǎn))的方法柬脸。我們最后通過介紹各種系統(tǒng)實現(xiàn)提供的資源限制,來結(jié)束本章毙驯。