printf()是否在C中分配内存? [英] Does printf() allocate memory in C?

查看:139
本文介绍了printf()是否在C中分配内存?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

这个简单的方法只是创建一个动态大小为n的数组,并使用值0 ... n-1对其进行初始化.它包含一个错误,malloc()仅分配n个而不是sizeof(int)* n个字节:

This simple method just creates an array of dynamic size n and initializes it with values 0 ... n-1. It contains a mistake, malloc() allocates just n instead of sizeof(int) * n bytes:

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

When you check the output you will see that it will print some numbers as expected but the last ones are gibberish. However, once I inserted the printf() inside the loop, the output was strangely correct, even tho the allocation was still wrong! Is there some kind of memory allocation associated with printf()?

推荐答案

严格来说,要回答标题中的问题,答案将取决于它的实现.一些实现可能分配内存,而其他一些则可能没有.

Strictly, to answer the question in the title, the answer would be that it depends on the implementation. Some implementations might allocate memory, while others might not.

尽管您的代码中还存在其他固有的问题,我将在下面详细说明.

Though there are other problems inherent in your code, which I will elaborate on below.

注意:这最初是我对问题的一系列评论.我认为评论太多了,于是将他们转移到这个答案.

当您检查输出时,您会看到它会按预期打印一些数字,但最后一个是乱码.

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

我相信在使用分段内存模型的系统上,分配会向上舍入"到一定大小. IE.如果分配X个字节,则程序确实将拥有这X个字节,但是,在CPU注意到您违反边界并发送SIGSEGV之前,您还可以(不正确地)运行超过X个字节一会儿.

I believe on systems using a segmented memory model, allocations are "rounded up" to a certain size. I.e. if you allocate X bytes, your program will indeed own those X bytes, however, you'll also be able to (incorrectly) run past those X bytes for a while before the CPU notices that you're violating bounds and sends a SIGSEGV.

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

This is most likely why your program isn't crashing in your particular configuration. Note that the 8 bytes you allocated will only cover two ints on systems where sizeof (int) is 4. The other 24 bytes needed for the other 6 ints do not belong to your array, so anything can write to that space, and when you read from that space, you are going to get garbage, if your program doesn't crash first, that is.

数字6很重要.记得以后再用吧!

神奇的部分是,结果数组将在其中包含正确的数字,而printf实际上只是再次打印每个数字.但这确实会改变数组.

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.我将添加此内容,是因为我认为它可能会帮助您理解某些东西可能出现正常工作而实际上却不正确的可能原因.

Note: The following is speculation, and I'm also assuming you're using glibc on a 64-bit system. I'm going to add this because I feel it might help you understand possible reasons why something might appear to work correctly, while actually being incorrect.

绝对正确"的原因很可能与printf通过va_args接收这些数字有关. printf可能正在填充刚好超出阵列物理边界的内存区域(因为vprintf正在分配内存以执行打印i所需的"itoa"操作).换句话说,那些正确"的结果实际上只是看起来正确"的垃圾,但实际上,这恰好是RAM中的东西.如果尝试在保持8字节分配的同时将int更改为long,则由于long长于int,因此您的程序更有可能崩溃.

The reason it's "magically correct" most likely has to do with printf receiving those numbers through va_args. printf is probably populating the memory area just past the array's physical boundary (because vprintf is allocating memory to perform the "itoa" operation needed to print i). In other words, those "correct" results are actually just garbage that "appears to be correct", but in reality, that's just what happens to be in RAM. If you try changing int to long while keeping the 8 byte allocation, your program will be more likely to crash because long is longer than int.

malloc的glibc实现进行了优化,每次耗尽堆时,它都会从内核分配整个页面.这样可以更快,因为它可以从池"中获取可用的内存,而不必在每次分配时都向内核请求更多的内存,而当第一个池满时就可以创建另一个池".

The glibc implementation of malloc has an optimization where it allocates a whole page from the kernel every time it runs out of heap. This makes it faster because rather than ask the kernel for more memory on every allocation, it can just grab available memory from the "pool" and make another "pool" when the first one fills up.

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

That said, like the stack, malloc's heap pointers, coming from a memory pool, tend to be contiguous (or at least very close together). Meaning that printf's calls to malloc will likely appear just after the 8 bytes you allocated for your int array. No matter how it works, though, the point is that no matter how "correct" the results may seem, they are actually just garbage and you're invoking undefined behavior, so there's no way of knowing what's going to happen, or whether the program will do something else under different circumstances, like crash or produce unexpected behavior.

所以我尝试在有和没有printf的情况下运行您的程序,并且两次都结果不正确.

So I tried running your program with and without the printf, and both times, the results were wrong.

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

无论出于何种原因,没有任何事情会影响存储器2..5的保持.但是,某些东西干扰了保存67的内存.我的猜测是,这是vprintf的缓冲区,用于创建数字的字符串表示形式. 1041是文本,0是空终止符'\0'.即使不是vprintf的结果,也有某物正在写到填充和数组打印之间的那个地址.

For whatever reason, nothing interfered with the memory holding 2..5. However, something interfered with the memory holding 6 and 7. My guess is that this is vprintf's buffer used to create a string representation of the numbers. 1041 would be the text, and 0 would be the null terminator, '\0'. Even if it's not a result of vprintf, something is writing to that address between the population and the printing of the array.

# 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

这是有趣的部分.您没有在问题中提到程序是否崩溃.但是当我运行它时,它崩溃了. .

This is the interesting part. You didn't mention in your question whether your program crashed. But when I ran it, it crashed. Hard.

如果有可用的方法,最好与valgrind进行检查. Valgrind是一个有用的程序,可以报告您如何使用内存.这是valgrind的输出:

It's also a good idea to check with valgrind, if you have it available. Valgrind is a helpful program that reports how you're using your memory. Here is valgrind's output:

$ 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 4(4字节是我系统上int的大小).还提到您正在读取大小为8的块(您分配的块)之后的大小为0的块.这告诉您,您将要经过数组并进入垃圾区.您可能会注意到的另一件事是,它从2个上下文中生成了12个错误.具体来说,在写作环境中是 6 错误,在阅读环境中是 6 错误.正是我之前提到的未分配空间量.

As you can see, valgrind reports that you have an invalid write of size 4 and an invalid read of size 4 (4 bytes is the size of an int on my system). It's also mentioning that you're reading a block of size 0 that comes after a block of size 8 (the block that you malloc'd). This tells you that you're going past the array and into garbage land. Another thing you might notice is that it generated 12 errors from 2 contexts. Specifically, that's 6 errors in a writing context and 6 errors in a reading context. Exactly the amount of un-allocated space I mentioned earlier.

这是更正的代码:

#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的输出:

And here's valgrind's output:

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

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

Notice that it reports no errors and that the results are correct.

这篇关于printf()是否在C中分配内存?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆