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
的内存。 但是,有些东西会干扰内存6
和7
。 我的猜测是,这是vprintf的缓冲区,用于创建数字的字符串表示形式。 1041
是文本, 0
将是空终止符'