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() 相关的内存分配?

您为数组分配了 8 个字节,但您存储了 8 个 int,每个至少有 2 个字节(可能是 4 个),因此您正在写入已分配内存的末尾。这样做会调用未定义的行为。

当你调用未定义的行为时,任何事情都可能发生。您的程序可能会崩溃,可能会显示意外的结果,或者可能看起来工作正常。一个看似无关的变化可以改变上述哪些动作发生。

修复内存分配,您的代码将按预期工作。

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

未指定printf()是否在执行其工作过程中分配任何内存。如果任何给定的实现这样做都不足为奇,但没有理由假设它会这样做。此外,如果一个实现可以实现,那也不能说明不同的实现是否可以实现。

printf() 在循环内时您看到不同的行为并没有说明任何问题。该程序通过超出已分配对象的边界表现出未定义的行为。一旦这样做,all 后续行为是未定义的。您不能对未定义的行为进行推理,至少不能在 C 语义方面进行推理。一旦未定义的行为开始,程序 就没有 C 语义。这就是 "undefined" 的意思。

严格来说,要回答标题中的问题,答案是这取决于实现。一些实现可能会分配内存,而另一些则可能不会。

虽然你的代码本身还有其他问题,我会在下面详细说明。


注:这本来是我对问题的一系列评论。我认为评论太多了,并将他们移至此答案。


When you check the output you will see that it will print some numbers as expected but the last ones are gibberish.

我相信在使用分段内存模型的系统上,分配 "rounded up" 到一定大小。 IE。如果您分配 X 字节,您的程序确实会拥有这些 X 字节,但是,您也可以(错误地)运行 在 CPU 注意到您之前通过这些 X 字节一段时间违反界限并发送 SIGSEGV。

这很可能就是您的程序在您的特定配置下没有崩溃的原因。请注意,您分配的 8 个字节将仅覆盖 sizeof (int) 为 4 的系统上的两个整数。其他 6 个整数所需的其他 24 个字节不属于您的数组,因此任何内容都可以写入 space,当您从 space 读取时,如果您的程序没有首先 崩溃 ,那么您将得到垃圾。

数字 6 很重要。以后记住!

The magic part is that the resulting array will then have the correct numbers inside, the printf actually just prints each number another time. But this does change the array.

注意:以下是猜测,我还假设您在 64 位系统上使用 glibc。我要添加这个,因为我觉得它可能会帮助您理解为什么某些东西 看起来 可以正常工作,但实际上是不正确的。

它是 "magically correct" 的原因很可能与 printf 通过 va_args 接收这些号码有关。 printf 可能正在填充刚好超过数组物理边界的内存区域(因为 vprintf 正在分配内存以执行打印 i 所需的 "itoa" 操作)。换句话说,那些 "correct" 结果实际上只是 "appears to be correct" 的垃圾,但实际上,这恰好是 RAM 中的结果。如果您尝试在保持 8 字节分配的同时将 int 更改为 long,您的程序将更有可能崩溃,因为 longint 长。

malloc 的 glibc 实现有一个优化,它在每次 运行 超出堆时从内核分配整个页面。这使它更快,因为它不是在每次分配时都要求内核提供更多内存,而是可以从 "pool" 中获取可用内存,并在第一个内存满时再创建一个 "pool"。

也就是说,就像堆栈一样,来自内存池的 malloc 的堆指针往往是连续的(或者至少非常接近)。这意味着 printf 对 malloc 的调用可能会出现在您为 int 数组分配的 8 个字节之后。但是,无论它如何工作,关键是无论结果看起来如何 "correct",它们实际上只是垃圾,并且您正在调用未定义的行为,因此无法知道会发生什么,或者程序是否会在不同情况下执行其他操作,例如崩溃或产生意外行为。


所以我尝试 运行使用 printf 和不使用 printf 连接你的程序,但两次的结果都是错误的。

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

无论出于何种原因,没有任何东西干扰 2..5 的记忆。但是,某些东西干扰了保存 67 的内存。我的猜测是,这是用于创建数字的字符串表示形式的 vprintf 缓冲区。 1041 是文本,0 是空终止符,'[=28=]'。即使它不是 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 4 和一个 invalid read of size 4(4 个字节是我系统上 int 的大小)。它还提到您正在读取大小为 0 的块,该块位于大小为 8 的块(您 malloc 的块)之后。这告诉您,您将越过阵列进入垃圾场。您可能会注意到的另一件事是它从 2 个上下文中生成了 12 个错误。具体来说,这是写作上下文中的 6 错误和阅读上下文中的 6 错误。正是我前面提到的 un-allocated space 的数量。

这是更正后的代码:

#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)

请注意,它没有报告任何错误并且结果是正确的。