簡體   English   中英

printf() 是否在 C 中分配內存?

[英]Does printf() allocate memory in C?

這個簡單的方法只是創建一個動態大小為 n 的數組,並用值 0 ... n-1 對其進行初始化。 它包含一個錯誤,malloc() 只分配了 n 而不是 sizeof(int) * n 字節:

int *make_array(size_t n) {
    int *result = malloc(n);

    for (int i = 0; i < n; ++i) {
        //printf("%d", i);
        result[i] = i;
    }

    return result;
}

int main() {
    int *result = make_array(8);

    for (int i = 0; i < 8; ++i) {
        printf("%d ", result[i]);
    }

    free(result);
}

當您檢查輸出時,您會看到它會按預期打印一些數字,但最后一個是胡言亂語。 然而,一旦我在循環中插入 printf() ,輸出就奇怪地正確了,即使分配仍然是錯誤的! 是否有某種與 printf() 相關的內存分配?

嚴格來說,要回答標題中的問題,答案將取決於實現。 一些實現可能會分配內存,而另一些可能不會。

盡管您的代碼中存在其他固有問題,我將在下面詳細說明。


注意:這最初是我對這個問題所做的一系列評論。 我認為評論太多了,並將他們轉移到這個答案。


當您檢查輸出時,您會看到它會按預期打印一些數字,但最后一個是胡言亂語。

我相信在使用分段內存模型的系統上,分配會“四舍五入”到一定大小。 即,如果您分配 X 個字節,您的程序確實將擁有這些 X 個字節,但是,您也將能夠(錯誤地)在 CPU 注意到您違反邊界並發送 SIGSEGV 之前(錯誤地)運行這些 X 字節一段時間.

這很可能是您的程序在您的特定配置中沒有崩潰的原因。 請注意,您分配的 8 個字節將僅涵蓋sizeof (int)為 4 的系統上的兩個整數。其他 6 個整數所需的其他 24 個字節不屬於您的數組,因此任何內容都可以寫入該空間,並且當您從那個空間讀取,你會得到垃圾,如果你的程序沒有先崩潰,那就是。

數字6很重要。 以后記住了!

神奇的部分是結果數組將包含正確的數字, printf 實際上只是再次打印每個數字。 但這確實改變了數組。

注意:以下是推測,我還假設您在 64 位系統上使用 glibc。 我要添加這個是因為我覺得它可能會幫助你理解為什么某些東西看起來可以正常工作,而實際上卻是不正確的。

它“神奇正確”的原因很可能與printf通過 va_args 接收這些數字有關。 printf可能正在填充剛剛超過數組物理邊界的內存區域(因為 vprintf 正在分配內存來執行打印i所需的“itoa”操作)。 換句話說,那些“正確”的結果實際上只是“看起來正確”的垃圾,但實際上,這正是 RAM 中的結果。 如果您嘗試在保留 8 字節分配的同時將int更改為long ,您的程序將更有可能崩潰,因為longint long

malloc 的 glibc 實現有一個優化,它在每次用完堆時從內核分配整個頁面。 這使它更快,因為與其在每次分配時要求內核提供更多內存,不如從“池”中獲取可用內存並在第一個“池”填滿時創建另一個“池”。

也就是說,就像堆棧一樣,malloc 的堆指針來自內存池,往往是連續的(或者至少非常接近)。 這意味着 printf 對 malloc 的調用可能會出現在您為 int 數組分配的 8 個字節之后。 然而,無論它如何工作,關鍵是無論結果看起來多么“正確”,它們實際上只是垃圾,並且您正在調用未定義的行為,因此無法知道會發生什么,或者程序會在不同情況下做其他事情,例如崩潰或產生意外行為。


所以我嘗試在有和沒有 printf 的情況下運行你的程序,但兩次結果都是錯誤的。

# without printf
$ ./a.out 
0 1 2 3 4 5 1041 0 

無論出於何種原因,沒有任何東西會干擾保存2..5的內存。 然而,有些東西干擾了保存67的內存。 我的猜測是,這是 vprintf 的緩沖區,用於創建數字的字符串表示。 1041將是文本, 0將是空終止符'\\0' 即使它不是 vprintf 的結果,在填充和打印數組之間也有一些東西正在寫入該地址。

# with printf
$ ./a.out
*** Error in `./a.out': free(): invalid next size (fast): 0x0000000000be4010 ***
======= Backtrace: =========
/lib/x86_64-linux-gnu/libc.so.6(+0x77725)[0x7f9e5a720725]
/lib/x86_64-linux-gnu/libc.so.6(+0x7ff4a)[0x7f9e5a728f4a]
/lib/x86_64-linux-gnu/libc.so.6(cfree+0x4c)[0x7f9e5a72cabc]
./a.out[0x400679]
/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xf0)[0x7f9e5a6c9830]
./a.out[0x4004e9]
======= Memory map: ========
00400000-00401000 r-xp 00000000 08:02 1573060                            /tmp/a.out
00600000-00601000 r--p 00000000 08:02 1573060                            /tmp/a.out
00601000-00602000 rw-p 00001000 08:02 1573060                            /tmp/a.out
00be4000-00c05000 rw-p 00000000 00:00 0                                  [heap]
7f9e54000000-7f9e54021000 rw-p 00000000 00:00 0 
7f9e54021000-7f9e58000000 ---p 00000000 00:00 0 
7f9e5a493000-7f9e5a4a9000 r-xp 00000000 08:02 7995396                    /lib/x86_64-linux-gnu/libgcc_s.so.1
7f9e5a4a9000-7f9e5a6a8000 ---p 00016000 08:02 7995396                    /lib/x86_64-linux-gnu/libgcc_s.so.1
7f9e5a6a8000-7f9e5a6a9000 rw-p 00015000 08:02 7995396                    /lib/x86_64-linux-gnu/libgcc_s.so.1
7f9e5a6a9000-7f9e5a869000 r-xp 00000000 08:02 7999934                    /lib/x86_64-linux-gnu/libc-2.23.so
7f9e5a869000-7f9e5aa68000 ---p 001c0000 08:02 7999934                    /lib/x86_64-linux-gnu/libc-2.23.so
7f9e5aa68000-7f9e5aa6c000 r--p 001bf000 08:02 7999934                    /lib/x86_64-linux-gnu/libc-2.23.so
7f9e5aa6c000-7f9e5aa6e000 rw-p 001c3000 08:02 7999934                    /lib/x86_64-linux-gnu/libc-2.23.so
7f9e5aa6e000-7f9e5aa72000 rw-p 00000000 00:00 0 
7f9e5aa72000-7f9e5aa98000 r-xp 00000000 08:02 7999123                    /lib/x86_64-linux-gnu/ld-2.23.so
7f9e5ac5e000-7f9e5ac61000 rw-p 00000000 00:00 0 
7f9e5ac94000-7f9e5ac97000 rw-p 00000000 00:00 0 
7f9e5ac97000-7f9e5ac98000 r--p 00025000 08:02 7999123                    /lib/x86_64-linux-gnu/ld-2.23.so
7f9e5ac98000-7f9e5ac99000 rw-p 00026000 08:02 7999123                    /lib/x86_64-linux-gnu/ld-2.23.so
7f9e5ac99000-7f9e5ac9a000 rw-p 00000000 00:00 0 
7ffc30384000-7ffc303a5000 rw-p 00000000 00:00 0                          [stack]
7ffc303c9000-7ffc303cb000 r--p 00000000 00:00 0                          [vvar]
7ffc303cb000-7ffc303cd000 r-xp 00000000 00:00 0                          [vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0                  [vsyscall]
012345670 1 2 3 4 5 6 7 Aborted

這是有趣的部分。 您沒有在問題中提到您的程序是否崩潰。 但是當我運行它時,它崩潰了。

如果您有可用的 valgrind 進行檢查,這也是一個好主意。 Valgrind 是一個有用的程序,可以報告您如何使用您的記憶。 這是 valgrind 的輸出:

$ valgrind ./a.out
==5991== Memcheck, a memory error detector
==5991== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
==5991== Using Valgrind-3.11.0 and LibVEX; rerun with -h for copyright info
==5991== Command: ./a.out
==5991== 
==5991== Invalid write of size 4
==5991==    at 0x4005F2: make_array (in /tmp/a.out)
==5991==    by 0x40061A: main (in /tmp/a.out)
==5991==  Address 0x5203048 is 0 bytes after a block of size 8 alloc'd
==5991==    at 0x4C2DB8F: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==5991==    by 0x4005CD: make_array (in /tmp/a.out)
==5991==    by 0x40061A: main (in /tmp/a.out)
==5991== 
==5991== Invalid read of size 4
==5991==    at 0x40063C: main (in /tmp/a.out)
==5991==  Address 0x5203048 is 0 bytes after a block of size 8 alloc'd
==5991==    at 0x4C2DB8F: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==5991==    by 0x4005CD: make_array (in /tmp/a.out)
==5991==    by 0x40061A: main (in /tmp/a.out)
==5991== 
0 1 2 3 4 5 6 7 ==5991== 
==5991== HEAP SUMMARY:
==5991==     in use at exit: 0 bytes in 0 blocks
==5991==   total heap usage: 2 allocs, 2 frees, 1,032 bytes allocated
==5991== 
==5991== All heap blocks were freed -- no leaks are possible
==5991== 
==5991== For counts of detected and suppressed errors, rerun with: -v
==5991== ERROR SUMMARY: 12 errors from 2 contexts (suppressed: 0 from 0)

如您所見,valgrind 報告您有invalid write of size 4invalid write of size 4invalid read of size 4 (4 個字節是我系統上 int 的大小)。 它還提到您正在讀取大小為 8 的塊(您分配的塊)之后的大小為 0 的塊。 這告訴您,您將越過陣列並進入垃圾區。 您可能會注意到的另一件事是它從 2 個上下文生成了 12 個錯誤。 具體來說,這是在寫入上下文中的6 個錯誤和在讀取上下文中的6 個錯誤。 正是我之前提到的未分配空間量。

這是更正后的代碼:

#include <stdio.h>
#include <stdlib.h>

int *make_array(size_t n) {
    int *result = malloc(n * sizeof (int)); // Notice the sizeof (int)

    for (int i = 0; i < n; ++i)
        result[i] = i;

    return result;
}

int main() {
    int *result = make_array(8);

    for (int i = 0; i < 8; ++i)
        printf("%d ", result[i]);

    free(result);
    return 0;
}

這是 valgrind 的輸出:

$ valgrind ./a.out
==9931== Memcheck, a memory error detector
==9931== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
==9931== Using Valgrind-3.11.0 and LibVEX; rerun with -h for copyright info
==9931== Command: ./a.out
==9931== 
0 1 2 3 4 5 6 7 ==9931== 
==9931== HEAP SUMMARY:
==9931==     in use at exit: 0 bytes in 0 blocks
==9931==   total heap usage: 2 allocs, 2 frees, 1,056 bytes allocated
==9931== 
==9931== All heap blocks were freed -- no leaks are possible
==9931== 
==9931== For counts of detected and suppressed errors, rerun with: -v
==9931== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

請注意,它沒有報告錯誤並且結果是正確的。

printf()在執行其工作的過程中是否分配任何內存是未指定的。 如果任何給定的實現這樣做並不奇怪,但沒有理由假設它確實如此。 此外,如果一個實現是這樣,那說明不同的實現是否是這樣。

printf()在循環內時,你看到不同的行為告訴你什么。 該程序通過超出已分配對象的邊界表現出未定義的行為。 一旦這樣做,所有后續行為都是未定義的。 您不能對未定義的行為進行推理,至少不能在 C 語義方面進行推理。 一旦未定義的行為開始,程序就沒有C 語義。 這就是“未定義”的意思。

您為數組分配了 8 個字節,但存儲了 8 個int ,每個至少為 2 個字節(可能為 4 個),因此您寫入的內容超出了分配的內存末尾。 這樣做會調用未定義的行為。

當您調用未定義的行為時,任何事情都可能發生。 您的程序可能會崩潰,可能會顯示出意外的結果,或者可能看起來工作正常。 一個看似無關的變化可能會改變上述哪些動作發生。

修復內存分配,您的代碼將按預期工作。

int *result = malloc(sizeof(int) * n);

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM