printf()是否在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则由于long长于int ,程序将更有可能崩溃。

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将是空终止符'' 。 即使它不是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的大小)。 另外还提​​到您正在读取大小为8的块(您使用malloc'd的块)之后的大小为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);
链接地址: http://www.djcxy.com/p/80211.html

上一篇: Does printf() allocate memory in C?

下一篇: Size of struct NOT equal to the size of its content