如果為了安全起見堆是零初始化的,那麼為什麼堆棧只是未初始化呢?
在我的 Debian GNU/Linux 9 系統上,執行二進製文件時,
- 堆棧未初始化,但
- 堆是零初始化的。
為什麼?
我假設零初始化可以提高安全性,但是,如果對於堆,那麼為什麼不也用於堆棧呢?堆棧也不需要安全性嗎?
據我所知,我的問題並不特定於 Debian。
範例 C 程式碼:
#include <stddef.h> #include <stdlib.h> #include <stdio.h> const size_t n = 8; // -------------------------------------------------------------------- // UNINTERESTING CODE // -------------------------------------------------------------------- static void print_array( const int *const p, const size_t size, const char *const name ) { printf("%s at %p: ", name, p); for (size_t i = 0; i < size; ++i) printf("%d ", p[i]); printf("\n"); } // -------------------------------------------------------------------- // INTERESTING CODE // -------------------------------------------------------------------- int main() { int a[n]; int *const b = malloc(n*sizeof(int)); print_array(a, n, "a"); print_array(b, n, "b"); free(b); return 0; }
輸出:
a at 0x7ffe118997e0: 194 0 294230047 32766 294230046 32766 -550453275 32713 b at 0x561d4bbfe010: 0 0 0 0 0 0 0 0
當然,C 標準不要求
malloc()
在分配記憶體之前清除記憶體,但我的 C 程序只是為了說明。這個問題不是關於 C 或 C 標準庫的問題。相反,問題是關於為什麼核心和/或執行時載入程序將堆歸零而不是堆棧歸零的問題。另一個實驗
我的問題是關於可觀察到的 GNU/Linux 行為而不是標准文件的要求。如果不確定我的意思,那麼試試這段程式碼,它會呼叫進一步的未定義行為(*未定義,*即就 C 標準而言)來說明這一點:
#include <stddef.h> #include <stdlib.h> #include <stdio.h> const size_t n = 4; int main() { for (size_t i = n; i; --i) { int *const p = malloc(sizeof(int)); printf("%p %d ", p, *p); ++*p; printf("%d\n", *p); free(p); } return 0; }
我機器的輸出:
0x555e86696010 0 1 0x555e86696010 0 1 0x555e86696010 0 1 0x555e86696010 0 1
就 C 標準而言,行為是未定義的,所以我的問題與 C 標準無關。呼叫
malloc()
不需要每次都返回相同的地址,但是由於每次呼叫malloc()
確實碰巧返回相同的地址,有趣的是,堆上的記憶體每次都歸零。相比之下,堆棧似乎並沒有歸零。
我不知道後面的程式碼會在你的機器上做什麼,因為我不知道 GNU/Linux 系統的哪一層導致了觀察到的行為。你可以嘗試一下。
更新
@Kusalananda 在評論中觀察到:
對於它的價值,您最近的程式碼在 OpenBSD 上執行時會返回不同的地址和(偶爾)未初始化(非零)數據。這顯然沒有說明您在 Linux 上看到的行為。
我的結果與 OpenBSD 上的結果不同確實很有趣。顯然,我的實驗發現的不是核心(或連結器)安全協議,正如我所想的那樣,而僅僅是一個實現工件。
有鑑於此,我相信@mosvy、@StephenKitt 和@AndreasGrapentin 下面的答案一起解決了我的問題。
另請參閱 Stack Overflow:為什麼 malloc 將 gcc 中的值初始化為 0?(信用:@bta)。
malloc() 返回的儲存不是零初始化的。永遠不要假設它是。
在您的測試程序中,這只是一個僥倖:我想
malloc()
剛剛獲得了一個新的塊mmap()
,但也不要依賴它。例如,如果我以這種方式在我的機器上執行您的程序:
$ echo 'void __attribute__((constructor)) p(void){ void *b = malloc(4444); memset(b, 4, 4444); free(b); }' | cc -include stdlib.h -include string.h -xc - -shared -o pollute.so $ LD_PRELOAD=./pollute.so ./your_program a at 0x7ffd40d3aa60: 1256994848 21891 1256994464 21891 1087613792 32765 0 0 b at 0x55834c75d010: 67372036 67372036 67372036 67372036 67372036 67372036 67372036 67372036
您的第二個範例只是
malloc
在 glibc 中公開實現的工件;如果您使用大於 8 字節的緩衝區重複malloc
/ ,您將清楚地看到只有前 8 個字節被清零,如以下範常式式碼所示。free
#include <stddef.h> #include <stdlib.h> #include <stdio.h> const size_t n = 4; const size_t m = 0x10; int main() { for (size_t i = n; i; --i) { int *const p = malloc(m*sizeof(int)); printf("%p ", p); for (size_t j = 0; j < m; ++j) { printf("%d:", p[j]); ++p[j]; printf("%d ", p[j]); } free(p); printf("\n"); } return 0; }
輸出:
0x55be12864010 0:1 0:1 0:1 0:1 0:1 0:1 0:1 0:1 0:1 0:1 0:1 0:1 0:1 0:1 0:1 0:1 0x55be12864010 0:1 0:1 1:2 1:2 1:2 1:2 1:2 1:2 1:2 1:2 1:2 1:2 1:2 1:2 1:2 1:2 0x55be12864010 0:1 0:1 2:3 2:3 2:3 2:3 2:3 2:3 2:3 2:3 2:3 2:3 2:3 2:3 2:3 2:3 0x55be12864010 0:1 0:1 3:4 3:4 3:4 3:4 3:4 3:4 3:4 3:4 3:4 3:4 3:4 3:4 3:4 3:4