- C和C++安全編碼(原書第2版)
- (美)Robert C.Seacord
- 1577字
- 2020-10-30 17:56:42
2.4.3 動態分配函數
第二種內存管理模型(由被調用者分配,由調用者釋放)是由ISO/IEC TR 24731-2定義的動態分配函數實現。ISO/IEC TR 24731-2定義了許多標準C字符串處理函數的替代品,這些替代品使用動態分配的內存,以確保不會發生緩沖區溢出。因為使用這樣的函數需要引入隨后的釋放緩沖區的額外調用,所以這些函數更適用于新的開發,而不是改造現有代碼。
在一般情況下,因為在ISO/IEC TR 24731-2中描述的函數,總是自動調整緩沖區大小以容納所需的數據,所以這些函數更好地確保了不會發生緩沖區溢出問題。但是,使用動態內存分配的應用程序,可能會遭受拒絕服務攻擊,因為其中的數據會一直存在,直到內存耗盡。它們也更容易出現動態內存管理錯誤,這也可能導致安全漏洞。
例2.1可以使用動態分配函數實現,如例2.7中所示。
例2.7 使用函數getline()從stdin中讀入數據
01 #define __STDC_WANT_LIB_EXT2__ 1 02 #include <stdio.h> 03 #include <stdlib.h> 04 05 void get_y_or_n(void) { 06 char *response = NULL; 07 size_t len; 08 09 puts("Continue? [y] n: "); 10 if ((getline(&response, &len, stdin) < 0) || 11 (len && response[0] == 'n')) { 12 free(response); 13 exit(0); 14 } 15 free(response); 16 }
此程序對于任何輸入都具有已定義的行為,包括一個假定,即假定一個非常長的、需要耗盡所有可用內存才能容納的行,應被視為一個“no”回應。因為對getline()函數動態地分配response緩沖區,所以程序必須調用free()來釋放已分配的內存。
ISO/IEC TR 24731-2允許在不相應地打開文件的情況下定義流。這種類型的流從內存緩沖區取得輸入或把輸出寫入到內存緩沖區。例如,GNU C庫使用這些流來實現sprintf()和sscanf()函數。
與內存緩沖區相關的流和與外部文件關聯的文本文件流,具有相同的操作。此外,數據流的方向也是用完全相同的方式確定的。
你可以明確地使用fmemopen()、open_memstream()或open_wmemstream()函數創建一個字符串流。這些函數允許你對字符串或內存緩沖區執行I/O操作。fmemopen()和open_memstream()函數在<stdio.h>中被聲明,如下所示。
1 FILE *fmemopen( 2 void * restrict buf, size_t size, const char * restrict mode 3 ); 4 FILE *open_memstream( 5 char ** restrict bufp, size_t * restrict sizep 6 );
open_wmemstream()函數是在<wchar.h>中定義的,并具有以下簽名:
FILE *open_wmemstream(wchar_t **bufp, size_t *sizep);
fmemopen()函數打開一個流,使你可以讀取或寫入指定的緩沖區。open_memstream()函數打開一個面向字節的流來寫入一個緩沖區,而open_wmemstream()函數創建一個面向寬字符的流。當用fclose()關閉流或用fflush()刷新流時,bufp和sizep被更新,以包含緩沖區的指針及其大小。只要沒有進一步的輸出流發生,這些值仍然有效。如果執行額外的輸出,必須再次刷新流來存儲新的值,才能再次使用它們。一個空字符被寫入緩沖區的末尾,但它存儲在sizep中的size值中不包括它。
通過調用fmemopen()、open_memstream()或open_wmemstream()創建的一個與內存緩沖區相關聯的流的輸入和輸出操作,發生在內存緩沖區的范圍內,受限于實現。對于用open_memstream()或open_wmemstream()打開的流的情況,內存區域動態增長,以適應必要的寫操作。對于輸出,在刷新或關閉操作期間,數據從函數setvbuf()提供的緩沖區移動到內存流。如果沒有足夠的內存來增長內存區域,或者操作需要訪問相關內存區域以外的地方,相關的操作失敗。
例2.8中的程序在第6行打開一個流來寫入到內存。
例2.8 打開一個流來寫入內存
01 #include <stdio.h> 02 03 int main(void) { 04 char *buf; 05 size_t size; 06 FILE *stream; 07 08 stream = open_memstream(&buf, &size); 09 if (stream == NULL) { /* handle error */ }; 10 fprintf(stream, "hello"); 11 fflush(stream); 12 printf("buf = '%s', size = %zu\n", buf, size); 13 fprintf(stream, ", world"); 14 fclose(stream); 15 printf("buf = '%s', size = %zu\n", buf, size); 16 free(buf); 17 return 0; 18 }
在第10行把字符串“hello”寫入到流,并且在第11行刷新該流。fflush()的調用更新buf和size,以便第12行的printf()函數輸出:
buf = 'hello', size = 5
在第13行把字符串".world"寫入流后,在第14行關閉流。關閉流的同時也更新buf和size,以便第15行的printf()函數輸出:
buf = 'hello, world', size = 12
size是緩沖區的累計(總數)大小。open_memstream()函數提供了一個更安全的寫入內存機制,因為它采用了根據需要動態分配內存的方法。但是,它確實要求調用者來釋放分配的內存,如例子的第16行所示。
在安全關鍵的系統中,往往是不允許動態分配的。例如,MISRA標準要求,“不得使用動態堆內存分配”[MISRA 2005]。一些安全關鍵系統在初始化過程中可以利用動態內存分配,但在操作過程中不允許。例如,航空電子軟件在初始化飛機時可以動態地分配內存,但在飛行過程中不允許。
動態分配函數從廣泛應用的現有實現中取得,許多這類函數都包含在POSIX中。
- Mastering NetBeans
- DBA攻堅指南:左手Oracle,右手MySQL
- TypeScript入門與實戰
- Java EE 6 企業級應用開發教程
- NLTK基礎教程:用NLTK和Python庫構建機器學習應用
- Python Geospatial Development(Second Edition)
- Java Web開發詳解
- Learning Material Design
- Python 3 數據分析與機器學習實戰
- Java Hibernate Cookbook
- C語言程序設計
- Android從入門到精通
- Practical Time Series Analysis
- 米思齊實戰手冊:Arduino圖形化編程指南
- RPA開發:UiPath入門與實戰