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字节,但是,你也能够(错误地)运行超过这些X字节一段时间,然后CPU注意到你违反了边界并发送了一个SIGSEGV 。

这很可能是您的程序在特定配置中没有崩溃的原因。 请注意,您分配的8个字节仅覆盖sizeof (int)为4的系统上的两个整数。其他6个整数所需的其他24个字节不属于您的数组,因此任何东西都可以写入该空间,当您从那个空间读取,如果你的程序没有先崩溃 ,你就会变得垃圾,就是这样。

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

神奇的部分是生成的数组内部会有正确的数字,printf实际上只是打印另一个数字。 但这确实改变了arrays。

注意:以下是推测,我也假设你在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 read of size 4invalid write of size 4invalid read of size 4 (4个字节是我系统上int的大小)。 它还提到你正在读取一个大小为0的块,该块位于一个8号块(malloc’d的块)之后。 这告诉你,你要经过arrays进入垃圾场。 您可能会注意到的另一件事是它从2个上下文生成了12个错误。 具体来说,这是写入上下文中的6个错误和读取上下文中的6个错误。 正是我之前提到的未分配空间的数量。

这是更正后的代码:

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