现在的位置: 首页 > 综合 > 正文

Linux System Programming:Memory Management

2013年09月10日 ⁄ 综合 ⁄ 共 16295字 ⁄ 字号 评论关闭

 

原文链接:http://wiki.oss.org.tw/index.php/Linux_System_Programming%EF%BC%9AMemory_Management

 

 

目錄

 [隱藏]

前言

記憶體是最基本也最重要的資源.記憶體管理包括: 資源管理,資源分配(allocation),還有記憶體操作(manipulation)以及記憶體釋放(release).

 

進程位址空間(Process Address Space)

和現代作業系統一樣,Linux將其物理實體記憶體資源虛擬化,進程並不能直接在實體上找尋位址,而是由Linux核心為每一個進程維護一特殊的虛擬地址空間(virtual address space), 這個空間是線性的,從0到某個最大值.

 

頁面和頁面調度(Pages & Paging)

虛擬位址空間由許多頁面(pages)組成.作業系統體系結構及機型決定頁的數量(頁的大小是固定的).典型頁有4k(32位元系統)和8k(64位元系統).每個頁面都只有有效(valid)和無效(invalid)兩種狀態.一個有效頁和一個實體物理頁面或是是一些二級儲存介質相關連,像是一個swap分區或是一個硬碟上的檔案. 一個無效頁沒有跟任何位址空間關聯,表示沒有被分配使用. 存取無效頁面會導致錯誤產生.
一個進程不能使用一個處在二級儲存媒介中的頁面,除非這個頁面和物理實體中的頁面戶相關連.如過試著存取,那記憶體管理單元(MMU)就會產生一個頁面錯誤(page fault).
通常虛擬記憶體要比實體記憶體要來的大,核心通常需要經常把頁面從物理記憶體抽出(paging out)到二級儲存裝置.核心通常會把未來最不可能使用的頁面抽出,來達到最佳化性能.

 

Sharing and copy-on-write

虛擬記憶體中的多個頁面,甚至在被不同進程擁有的不同虛擬位址空間,也有可能被映射到同一個物理實體頁面.這樣允許不同的虛擬位址空間共享(share)實體物理記憶體上的資料.
當一個進程要寫入某個共享的可寫的頁面時,一是核心允許了其操作,一是MMU會擷取這次寫入操作並產生一個異常做為回應,核心會建立一個這個頁面的拷貝以供該進程進行寫入操作.我們稱這種方法為copy-on-write(COW).

允許讀取共享的資料可以有效的節省空間.當一個進程試圖寫入一個共享頁面的時候,就可以立刻獲得一份該頁面的拷貝, 這使得核心工作起來就像每個進程都始終擁有其私有的拷貝.COW是以頁面為單位,因此一個大檔案可以有效的被許多進程共享,並且進程只有在要寫入共享頁面的時候,才會取得一份拷貝.

 

記憶體區域 (Memory Regions)

核心將具有相同特徵的頁面組成區塊(blocks),例如有讀寫權限的.這些區塊我們就叫做記憶體區域(Memory Regions),區段(segments),或是映射(mappings).
下面是一個在每個進程都可以見到的記憶體區域:
  • 文字區段(text segment) : 包含進程的程式碼,字串,常態變數,及一些只可讀取的資料.Linux裡面,text segment均為只能讀取,並且直接從目標檔案直接映射到記憶體中.
  • 堆疊(stack) : 包括一個進程的堆疊.可以動態增長或縮短.包含了區域變數和函數傳回值.
  • 資料區段(data segment) : 也叫做堆積(heap).是一個進程的動態可讀寫的記憶體.
  • BSS區段(bss segment) : 包含了沒有被初始化的全域變數.這些變數包含了一些特殊值(像是全部為0)

 

動態記憶體分配(Allocating Dynamic Memory)

記憶體同樣可以透過自動或是靜態的變數獲得,但是任何記憶體管理系統的機制都是使用動態記憶體的方式配置(allocation),使用,以及最終的傳回. 動態記憶體是在進程執行(runtime)的時候才分配,而不是在編譯的時候就分配好, 分配的大小也只有在分配的時候才確定.
C語言不提供支援動態記憶體的變數,程式設計者透過一個指標pointer來對某段記憶體進行操作.C語言裡面最經典的存取動態記憶體的介面就是malloc():
#include <stdlib.h> 
void *malloc (size_t size);
呼叫成功會得到一個size大小的記憶體區域,並傳回一個指向這個記憶體第一個位址的指標.這記憶體的內容是未定義的,但不全為0.失敗的話傳回NULL,並設置errno為ENOMEM.
範例:
char *p; 
/* give me 2 KB! */ 
p = malloc (2048); 
if (!p) 
  perror (”malloc”);
或是如下範例:
struct treasure_map *map; 
/* 
 * allocate enough memory to hold a treasure_map 
    stucture 
 * and point ’map’ at it 
 */ 
map = malloc (sizeof (struct treasure_map)); 
if (!map) 
  perror (”malloc”);
C語言會把傳回值void指標轉成任何型別type,但是C++不會,要用下面的方式轉換:
char *name; 
/* allocate 512 bytes */ 
name = (char *) malloc (512); 
if (!name) 
perror (”malloc”);
避免將傳回值轉成void. 另外malloc可以傳回NULL,當傳回NULL就印出錯誤和終止程序,程式設計者將這種封裝叫做 xmalloc():
/* like malloc(), but terminates on failure */ 
void *xmalloc (size_t size) 
{ 
  void *p; 
  p = malloc (size); 
  if (!p) { 
      perror (”xmalloc”); 
      exit (EXIT_FAILURE); 
  } 
  return p; 
}

配置陣列(Allocating Arrays)

用陣列來處理動態記憶體.陣列長度是固定的, 但是元素數量是可以變化的. C語言提供calloc()函式:
#include <stdlib.h> 
void *calloc (size_t nr, size_t size);
呼叫calloc()成功會傳回一個指標, 指向一個可以存入整個陣列的記憶體區塊(nr個元素,每個元素大小為size個位元組), 下面有兩種方式結果都得到相同的記憶體大小:
int *x, *y; 
x = malloc (50 * sizeof (int)); 
if (!x) { 
        perror (”malloc”); 
        return -1; 
} 
y = calloc (50, sizeof (int)); 
if (!y) { 
        perror (”calloc”); 
        return -1; 
}
不過兩個函數還是有區別, calloc將分配的區域全部用0初始化.y的50個元素都為0,x陣列裡面的元素都是未定義.

Calloc失敗時會傳回NULL,並設errno為ENOMEM.

使用者也可以自行定義介面:
/* works identically to malloc( ), but memory is 
   zeroed */ 
void *malloc0 (size_t size) 
{ 
  return calloc (1, size); 
}
也可以將malloc()和xmalloc結合:
/* like malloc( ), but zeros memory and 
   terminates on failure */ 
void *xmalloc0 (size_t size) 
{ 
  void *p; 
  p = calloc (1, size); 
  if (!p) { 
           perror (”xmalloc0”); 
           exit (EXIT_FAILURE); 
  } 
  return p; 
}

 

調整配置記憶體的大小(Resizing Allocations)

使用realloc():
#include <stdlib.h> 
void *realloc (void *ptr, size_t size);
呼叫成功會將ptr指向的記憶體區域大小變為size位元組,傳回一個新空間的指標.
如果size為0,就跟在ptr上呼叫free()相同.
若ptr為NULL,結果就跟malloc()一樣.
若ptr不為NULL,就必須是之前呼叫的malloc(),calloc()或realloc()的傳回值.
若要縮小記憶體空間,就先使用calloc()來申請存放一個由兩個map結構所組成的陣列: struct map *p:
/* allocate memory for two map structures */ 
p = calloc (2, sizeof (struct map)); 
if (!p) { 
        perror (”calloc”); 
       return -1; 
} 
/* use p[0] and p[1]... */

 

然後修改記憶體大小,將一半空間還給作業系統:
/* we now need memory for only one map */ 
r = realloc (p, sizeof (struct map)); 
if (!r) { 
        /* note that ’p’ is still valid! */ 
        perror (”realloc”); 
        return -1; 
} 
/* use ’r’... */ 
free (r);
這個例子中, realloc()被呼叫後, p[0]被保留了下來,所有資料都維持不變.

釋放動態記憶體(Freeing Dynamic Memory)

不像自動配置的記憶體, 動態記憶體會永久佔有一個進程地址空間的一部分.所以程式設計者必須藥名白地將申請到的動態記憶體歸還給系統.
使用free():
#include <stdlib.h> 
void free (void *ptr);
呼叫free()會釋放ptr所指向的記憶體,但ptr必須是之前呼叫malloc(),calloc()或是realloc()的傳回值.

ptr可能為NULL,則free()當然就不做傳回的動作.

以下範例:
void print_chars (int n, char c) 
{ 
  int i; 
  for (i = 0; i < n; i++) { 
      char *s; 
      int j; 
      /* 
      * Allocate and zero an i+2 element array 
      * of chars. Note that ’sizeof (char)’ 
      * is always 1. 
      */ 
      s = calloc (i + 2, 1); 
      if (!s) { 
          perror (”calloc”); 
          break; 
    } 
    for (j = 0; j < i + 1; j++) 
        s[j] = c; 
    printf (”%s/n”, s); 
    /* Okay, all done. Hand back the memory. */ 
    free (s); 
  } 
}
這個例子我們將n個字元陣列分配記憶體, n個元素個數依次遞增,從兩個元素(2 bytes)一直增加到n+1個元素(n+1 bytes), 然後將每個陣列中的最後一個元素外的元素給值為c(最後一個byte為0),將結果列印, 將s釋放. 呼叫print_chars(),讓n為5, c為X, 可以得到以下結果:
X 
XX 
XXX 
XXXX 
XXXXX
注意這個例子沒有呼叫free()的結果,我們沒有辦法再對這個記憶體區塊進行操作.我們叫做這個程式設計錯誤為記憶體洩漏(memory leak).

另外常見錯誤就是釋放記憶體後再使用(use-after-free).

資料對齊(Alignment)

資料對齊是指將其位址跟由硬體決定的記憶體區塊間的關係做對應.
一個變數的位址是其大小的倍數時,叫做自然對齊(maturally aligned). 例如一個變數是32bit長度,他的位址是4(bytes)的倍數時—位址最低的兩個byte是0,那麼就是自然對齊. 對齊的規則是根據硬體而定.

配置要對齊的記憶體

多數情況下,編譯器和C語言函式庫會自動處理對齊問題.處理更大的邊界像是頁面的話,需要動態的對齊,可以使用posix_memalign()來處理:
/* one or the other -- either suffices */ 
#define _XOPEN_SOURCE 600 
#define _GNU_SOURCE 
#include <stdlib.h> 
int posix_memalign (void **memptr, 
                    size_t alignment, 
                    size_t size);
呼叫成功會傳回size bytes大小的動態記憶體,並保證是按照aligment對齊的.參數aligment必須是2的冪次,以及void指標大小的倍數.傳回的記憶體的位址保存在memptr裡,函數傳回0.
呼叫失敗則沒有配置記憶體, memptr值沒有被定義, 回傳回下列錯誤代碼之一:
EINVAL : 參數不是2的冪次,或不是void指標大小的倍數
ENOMEM : 沒有足夠記憶體滿足函式的要求.
注意這函式, errno不會被設置,會直接在傳回值中給出.
由posix_memalign()取得的記憶體透過free()釋放:
char *buf; 
int ret; 
/* allocate 1 KB along a 256-byte boundary */ 
ret = posix_memalign (&buf, 256, 1024); 
if (ret) { 
           fprintf(stderr, ”posix_memalign: %s/n”, 
                    strerror (ret)); 
           return -1; 
} 
/* use ’buf’... */ 
free (buf);
函數valloc()和malloc()功能一樣,但傳回的位址是頁面對齊的.
memalign()是以boundary bytes對齊,而boundary必須是2的次方.
下面例子兩個函式都傳回一個足夠大的記憶體去存一個ship結構,並且位址都在一個頁面的邊界上:
struct ship *pirate, *hms; 
pirate = valloc (sizeof (struct ship)); 
if (!pirate) { 
              perror (”valloc”); 
              return -1; 
} 
hms = memalign (getpagesize ( ), sizeof (struct 
   ship)); 
if (!hms) { 
           perror (”memalign”); 
           free (pirate); 
           return -1; 
} 
/* use ’pirate’ and ’hms’... */ 
free (hms); 
free (pirate);
這兩個函式獲得的記憶體,在Linux裡面都可以用free()釋放.

其他對齊注意事項

對於非標準型別來說:
  • 1 一個結構的對齊要求和它成員中最大的那個型別是一樣的. 例如一個結構中最大的是一個4 bytes對齊的32bit整數.那麼這個結構至少要以4 bytes對齊.
  • 2 結構也引入對padding(留白)的需求.這用來確定每個成員型別都符合各自的對齊要求.所以若一個char(一個byte對齊)型別後跟著一個int(4 bytes對齊),編譯器會自動插入3 bytes來填充,保證int以4 bytes對齊.
注意型別轉換:
char greeting[] = ”Ahoy Matey”; 
char *c = greeting[1]; 
unsigned long badnews = *(unsigned long *) c;
一個unsigned long可能以4或8 bytes為邊界對齊.而c當然只以1 byte為邊界對齊,因為當c被強制型別轉換之後在進行讀取,會導致對齊錯誤.這樣會導致性能下降或是系統崩潰.

 

匿名記憶體映射(Anonymous Memory Mappings)

Glibc裡的記憶體配置使用資料區段以及記憶體映射. 實現malloc()最經典的方式就是將資料分為一個系列大小的2的次方的區塊,傳回最小的那個區塊來滿足請求. 釋放則是將這個區塊標記為未使用,若相鄰區域是空閒的,就會被合成更大的分區.若堆疊的頂是空的, 系統就可用brk()來降低中斷點(break point),讓堆疊變小,將記憶體返回給作業系統.
這種我們叫做夥伴記憶體分配演算法(buddy memory allocation scheme). 好處是快跟簡單,壞處是會產生兩種類型的碎片.
當使用記憶體區塊大於請求的大小時產生內部碎片(Internal fragmentation),導致記憶體使用率降低. 外部碎片(external fragmentation)是空閒的記憶體空間合起來可以滿足一個請求,但是沒有一個單獨的空間可以滿足這個請求。這樣同樣會導致記憶體利用不足,或是分配失敗。另外這個演算法會使一個記憶體的分配'栓'(pin)住另外一個,導致glibc不能將釋放的記憶體返回給作業系統。
不過這不是問題,對於較大的記憶體分配,glibc不使用heap,而是建立一個匿名記憶體映射(anonymous memory mapping).匿名記憶體映射類似file-based mappings,不過不是用檔案的形式,只是一塊已經用0初始化的大塊記憶體來給使用者使用. 可以想像為單獨為某次分配而使用的heap.因為這種映射不是基於映射heapp,,所以不會在記憶體內產生碎片。
使用匿名記憶體映射的好處:
  • 1 不用擔心碎片
  • 2 匿名儲存記憶體映射大小可以調整
  • 3 每個配置(allocation)存在於獨立的記憶體映射,不用管管全域的的heap
  • 4 每個記憶體映射都是頁面大小的整數倍
  • 5 建立一個新的記憶體映射比比heapp中返回記憶體的負載要大.越小的配置這樣的問題也明顯。
所以, glibc的的malloc())使用資料區段資料區段(data segment)來滿足較小的配置的配置,使用匿名記憶體映射來作較大的配置的配置。兩者臨界點是可以調整的,一般來說是一般來說是128kb.

建立匿名記憶體映射

使用使用mmap())建立,使用minmap()銷毀:
#include <sys/mman.h> 
void * mmap (void *start, 
             size_t length, 
             int prot, 
             int flags, 
             int fd, 
             off_t offset); 
int munmap (void *start, size_t length);
因為不需要打開檔案,所以建立匿名記憶體映射更簡單.兩者差別在於有無匿名標記匿名標記(signifying that the mapping is anonymous).
void *p; 
p = mmap (NULL,                         /* do not care where */ 
           512 * 1024,                  /* 512 KB */ 
           PROT_READ | PROT_WRITE,      /* read/write */ 
           MAP_ANONYMOUS | MAP_PRIVATE, /* anonymous, private */ 
           -1,                          /* fd (ignored) */ 
           0);                          /* offset (ignored) */ 
if (p == MAP_FAILED) 
         perror ("mmap"); 
else 
         /* 'p' points at 512 KB of anonymous memory... */
參數說明如下:
  • 1 start參數設為參數設為NULL,意味匿名記憶體映射可以讓核心安排在任意位址上
  • 2 prot參數同時設置PROT_READ和PROT_WRITE bits, 使得映射可以讀寫.
  • 3 flags參數設置MAP_ANONYMOUSS使映射為匿名,設置MAP_PRIVATE使得映射為私有的.
  • 4 假如MAP_ANONYMOUSS被設置,那fd,offset參數會被忽略.
使用匿名記憶體映射的好處,就是已經分配好的頁面全部用全部用0進行了初始化. 核心使用核心使用copy-on-write將記憶體映射到一個為0的頁面上,也避免了額外的開銷。
int ret; 
/* all done with ’p’, so give back the 512 KB 
   mapping */ 
ret = munmap (p, 512 * 1024); 
if (ret) 
perror (”munmap”);

映射到/dev/zero

像BSD並沒有MAP_ANONYMOUSS,使用特殊的檔案叫做/dev/zero來實現
void *p; 
int fd; 
/* open /dev/zero for reading and writing */ 
fd = open (”/dev/zero”, O_RDWR); 
if (fd < 0) { 
            perror (”open”); 
            return -1; 
} 
/* map [0,page size) of /dev/zero */ 
p = mmap (NULL, /* do not care where */ 
          getpagesize ( ), /* map one page */274 – 
                         内存  理 
                     8 
          PROT_READ | PROT_WRITE, /* map 
             read/write */ 
          MAP_PRIVATE, /* private mapping */ 
          fd, /* map /dev/zero */ 
          0); /* no offset */ 
if (p == MAP_FAILED) { 
                      perror (”mmap”); 
                      if (close (fd)) 
                      perror (”close”); 
                      return -1; 
} 
/* close /dev/zero, no longer needed */ 
if (close (fd)) 
    perror (”close”); 
/* ’p’ points at one page of memory, use it... */
這種需要開啟檔案,所以還是匿名記憶體映射速度比較快.

 

進階記憶體配置(Advanced Memory Allocation)

使用mallopt():
#include <malloc.h> 
int mallopt (int param, int value);
mallopt()會將param確定的儲存管理相關參數設為value.成功時傳回一個非0值,失敗傳回0.
Linux目前支援六種param值,定義在<malloc.h>中:
M_CHECK_ACTION : 環境變數 MALLOC_CHECK 的變數值
M_MMAP_MAX : 滿足動態記憶體請求的最大記憶體映射數量
M_MMAP_THRESHOLD : 決定是用匿名記憶體映射還是記憶體區段來滿足記憶體分配請求
M_MXFAST  : fast bin的最大size.用bytes為單位.
M_TOP_PAD : 調整資料區段的長度而使用的padding bytes數量
XPG標準中定義了mallopt(),並指定另外三個參數M_GRAIN, M_KEEP,和M_NLBLKS,不過實際上沒有作用.下表定義了所有合法參數,還有其預設值與可接受的範圍:
Parameter      Origin         Default value        Valid values Special values 
M_CHECK_ACTION                0 
               Linux-specific                      0–2 
M_GRAIN        XPG standard   Unsupported on Linux >= 0 
M_KEEP         XPG standard   Unsupported on Linux >= 0 
M_MMAP_MAX                      64 * 1024 
                 Linux-specific                      >= 0   0 disables use of mmap( ) 
M_MMAP_THRESHOLD                128 * 1024 
                 Linux-specific                      >= 0   0 disables use of the heap 
M_MXFAST                        64 
                 XPG standard                        0 – 80 0 disables fast bins 
M_NLBLKS         XPG standard   Unsupported on Linux >= 0 
M_TOP_PAD                       0 
                 Linux-specific                      >= 0   0 disables padding
程式必須在呼叫malloc()或是其他記憶體分配函數前使用mallopt():
/* use mmap( ) for all allocations over 64 KB */ 
ret = mallopt (M_MMAP_THRESHOLD, 64 * 1024); 
if (!ret) 
    fprintf (stderr, ”mallopt failed!/n”);

使用malloc_usable_size( )和malloc_trim( )來調整性能(Fine-Tuning)

Linux提供兩個用來控制glibc記憶體分配系統的底層函數:
#include <malloc.h> 
size_t malloc_usable_size (void *ptr);
呼叫 malloc_usable_size()成功時, 傳回指向動態記憶體實際大小的指標.因為glibc可能擴大動態記憶體來適應一個可存在的區塊或匿名記憶體映射.如下例:

 

size_t len = 21; 
size_t size; 
char *buf; 
buf = malloc (len); 
if (!buf) { 
          perror (”malloc”); 
          return -1; 
} 
size = malloc_usable_size (buf); 
/* we can actually use ’size’ bytes of ’buf’... */
第二個函式允許程序強制glibc歸還所有可釋放的動態記憶體給核心:
#include <malloc.h> 
int malloc_trim (size_t padding);
呼叫malloc_trim()成功時,資料區段會盡可能的收縮,但是padding bytes被保留下來,然後傳回1.失敗時傳回0. 一般來說當空閒的記憶體達到 M_TRIM_THRESHOLD bytes時,會使用M_TOP_PAD數目來作padding.

 

記憶體配置除錯(Debugging Memory Allocations)

程式可以設置MALLOC_CHECK_ 環境變數來啟動記憶體子系統的除錯功能, 可以直接執行指令:
$ MALLOC_CHECK_=1 ./rudder
若設為0,則系統會忽略所有錯誤, 若設為1,訊息會被輸出到標準錯誤stderr. 若設為2,進程會立刻通過abort()終止.

 

獲得統計數據

使用mallinfo():
#include <malloc.h> 
struct mallinfo mallinfo (void);
mallinfo()將統計數據存到mallinfo結構中.這個結構是通過值傳回,而非指標. 結構定義在<malloc.h>:

 

/* all sizes in bytes */ 
struct mallinfo { 
        int arena;    /* size of data segment used by malloc */ 
        int ordblks; /* number of free chunks */ 
                                                       Debugging Memory Allocations | 263 
   int smblks;   /* number of fast bins */ 
   int hblks;    /* number of anonymous mappings */ 
   int hblkhd;   /* size of anonymous mappings */ 
   int usmblks;  /* maximum total allocated size */ 
   int fsmblks;  /* size of available fast bins */ 
   int uordblks; /* size of total allocated space */ 
   int fordblks; /* size of available chunks */ 
   int keepcost; /* size of trimmable space */ 
};
用法如下:
struct mallinfo m; 
m = mallinfo(); 
printf (”free chunks: %d/n”, m.ordblks);
Linux也提供stats()函數,將與記憶體相關的統計數據輸出到標準錯誤輸出(stderr):
#include <malloc.h> 
void malloc_stats (void);
在記憶體操作頻繁的程式中往往會產生一些較大的數字:
Arena 0: 
system bytes = 865939456 
in use bytes = 851988200 
Total (incl. mmap): 
system bytes = 3216519168 
in use bytes = 3202567912 
max mmap regions = 65536 
max mmap bytes = 2350579712

 

基於堆疊的配置(Stack-Based Allocations)

堆疊(stack)是用來存放程式的自動變數(automatic variables).
當然也可以使用堆疊來作動態記憶體配置:
#include <alloca.h> 
void * alloca (size_t size);
呼叫alloca(),成功會傳回一個指向size bytes大小的記憶體指標.失敗時有的回傳NULL,不過基本上都不可能失敗,失敗就是堆疊超出.
下面例子是在系統配置目錄裡打開一個特定的檔案,這個函數必須申請一個新的緩衝區,複製系統配置路徑到達這個緩衝區,然後將提供的檔案名稱接到緩衝區的後面:
int open_sysconf (const char *file, int flags, 
   int mode) 
{ 
  const char *etc = SYSCONF_DIR; /* ”/etc/” */ 
  char *name; 
  name = alloca (strlen (etc) + strlen (file) + 
     1); 
  strcpy (name, etc); 
  strcat (name, file); 
  return open (name, flags, mode); 
}
在open_sysconf函式傳回時,從alloca()分配到的記憶體隨著堆疊的收縮而被自動釋放.我們不需作任何釋放工作.
下面是通過malloc()實現相同功能的函數:
int open_sysconf (const char *file, int flags, 
   int mode) 
{ 
    const char *etc = SYSCONF_DIR; /* ”/etc/” */ 
    char *name; 
    int fd; 
    name = malloc (strlen (etc) + strlen (file) + 
       1); 
    if (!name) { 
                perror (”malloc”); 
                return -1; 
    } 
    strcpy (name, etc); 
    strcat (name, file); 
    fd = open (name, flags, mode); 
    free (name); 
    return fd; 
}
注意不能使用由alloca()得到的記憶體來作為一個函式呼叫的參數,因為分配到的記憶體會被當作參數保存在函式的堆疊中,下面這樣不行:
/* DO NOT DO THIS! */ 
ret = foo (x, alloca (10));

 

在堆疊中複製字串(Duplicating Strings on the Stack)

alloca()常見用法是用來臨時複製一個字串:
/* we want to duplicate ’song’ */ 
char *dup; 
dup = alloca (strlen (song) + 1); 
strcpy (dup, song); 
/* manipulate ’dup’... */ 
return; /* ’dup’ is automatically freed */
Linux提供strdup()來將一個特定的字串複製到堆疊中:
#define _GNU_SOURCE 
#include <string.h> 
char * strdupa (const char *s); 
char * strndupa (const char *s, size_t n);
呼叫strdupa()會傳回一個s的拷貝,stmdupa()會複製s中的n個bytes.若s大於n,那複製s前n個bytes,後面自動加上一個空bytes.這些函式都具有alloca()的優點.

變數長度的陣列(Variable-Length Arrays)

C99導入變數長度陣列(variable-length arrays,VLAs)先看例子:
for (i = 0; i < n; ++i) { 
    char foo[i + 1]; 
    /* use ’foo’... */ 
}
每次loop中,foo都被動態建立,並在loop結束後自動釋放,若用alloca(),那記憶體空間要到函數傳回時才被釋放.
使用VLA最多n個bytes,alloca()會用掉n*(n+1)/2個bytes, 我們可以這樣重寫我們的open_sysconf()函式:
int open_sysconf (const char *file, int flags, 
   int mode) 
{ 
    const char *etc; = SYSCONF_DIR; /* ”/etc/” */ 
    char name[strlen (etc) + strlen (file) + 1]; 
    strcpy (name, etc); 
    strcat (name, file); 
    return open (name, flags, mode); 
}

選擇適當的記憶體分配機制

大部分情況下malloc()是最好地選擇,以下作點總結:
  • malloc( ) : 簡單方便好用, 缺點是傳回的記憶體用0初始化
  • calloc( ) : 讓配置陣列變得容易.用0初始化記憶體. 缺點若非配置陣列會很複雜
  • realloc( ) : 可以重新配置已分配的空間大小, 缺點是只能用來配置已分配的空間大小
  • brk( )和sbrk( ) : 允許對heap深入控制. 缺點是大多用在底層匿名記憶體映射: 使用簡單可共享,適合大空間分配. 缺點就是不適合小分配
  • posix_memalign( ) : 分配的記憶體按照任何合理大小進行對齊. 缺點是比較新,可移植性是個問題
  • memalign( ) and valloc( ) : 比 posix_memalign( )常見, 但是POSIX標準,對齊控制不如 posix_memalign( )
  • alloca( ) : 配置很快,不需要知道確切大小,適合小型配置. 缺點就是不適合大配置變數長度陣列 : 與alloca()相似,但是退出loop時就會釋出記憶體空間. 缺點就是只能用來分配陣列

 

記憶體操作(Manipulating Memory)

C語言提供很多對記憶體的操作.

Setting Bytes

最常用的就是memeset():
#include <string.h> 
void * memset (void *s, int c, size_t n);
memset()將s指向n個bytes開始的區域設為c, 並傳回指標s.該函式常被用來將一塊記憶體清空設為0:
/* zero out [s,s+256) */ 
memset (s, ’/0’, 256);

Comparing Bytes

使用memcmp():
#include <string.h> 
int memcmp (const void *s1, const void *s2, 
   size_t n);
比較s1和s2兩個前面n個bytes, 若兩個記憶體相同就傳回0, 若s1<s2就傳回一個小於0的數,反之傳回大於0的數值.
程式設計者若要比較結構,就必須一個一個比較結構中的每一個元素.如下面例子:
/* are two dinghies identical? */ 
int compare_dinghies (struct dinghy *a, struct 
   dinghy *b) 
{ 
    int ret; 
    if (a->nr_oars < b->nr_oars) 
        return -1; 
    if (a->nr_oars > b->nr_oars) 
        return 1; 
    ret = strcmp (a->boat_name, b->boat_name); 
    if (ret) 
        return ret; 
    /* and so on, for each member... */ 
}

Moving Bytes

使用mememove()複製src的前n個bytes到dst,傳回dst:
#include <string.h> 
void * memmove (void *dst, const void *src, 
   size_t n);
也可使用memcpy:
#include <string.h> 
void * memcpy (void *dst, const void *src, size_t  n);
除了dst和src間不能重疊,基本和memmove()一樣.
另外一個安全的複製函式為memcpy():
#include <string.h> 
void * memccpy (void *dst, const void *src, int 
   c, size_t n);
memccpy()和memcpy()類似,但發現c byte在src指向的前n個bytes時會停止複製.它傳回指向dst中c後一個byte的指標,當沒有找到c時傳回NULL.
最後我們用mempcpy()來跨過複製的記憶體:
#define _GNU_SOURCE 
#include <string.h> 
void * mempcpy (void *dst, const void *src, 
   size_t n);
mempcpy()和memcpy()幾乎一樣,區別在於memeccpy()傳回的是指向被複製的記憶體最後一個byte的下一個byte的指標.

Searching Bytes

使用memechr()和memrchr():
#include <string.h> 
void * memchr (const void *s, int c, size_t n);
函數memechr()從s指向的n個byte中尋找c, c將被轉換為unsigned char:
#define _GNU_SOURCE
#include <string.h> 
void * memrchr (const void *s, int c, size_t n);
函數傳回符合c的第一個byte的指標,若沒找到c就傳回NULL.
memrchr()與memchr()類似,但是他是從s指向的記憶體開始反向搜尋n個bytes.
另外用複雜搜索的話使用 memmem()函式:
#define _GNU_SOURCE 
#include <string.h> 
void * memmem (const void *haystack, 
               size_t haystacklen, 
               const void *needle, 
               size_t needlelen);
memmem()函式在指向長度為haystacklen 的記憶體區塊haystack中尋找,並傳回第一塊和長為needlelen的needle匹配的子區塊的指標. 或在haystack找不到needle,就傳回NULL.

Frobnicating Bytes

Linux C函式庫提供了資料加密的介面:
#define _GNU_SOURCE 
#include <string.h> 
void * memfrob (void *s, size_t n);
memfrob()將s指向的位置開始的n個bytes, 每個都與42進行XOR操作來進行加密,函數傳回s.
再做一次memefrob()就可以將其轉換回來:
memfrob (memfrob (secret, len), len);
這僅是一個對字串的簡單處理函式. 不適合用於資料加密.

 


抱歉!评论已关闭.