这个简单的方法只是创建一个动态大小为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()相关的内存分配?
3 回答
严格地说,要回答 Headers 中的问题,答案是它取决于实施 . 某些实现可能会分配内存,而其他实现可能不会 .
虽然您的代码中存在其他固有问题,我将在下面详细说明 .
Note: this was originally a series of comments I made on the question. I decided that it was too much for a comment, and moved them to this answer.
我相信使用分段内存模型的系统,分配被“四舍五入”到一定的大小 . 即如果你分配X字节,你的程序确实会拥有那些X字节,但是,在CPU注意到你违反了边界并发送了一个SIGSEGV之前,你也能够(错误地)运行这些X字节一段时间 .
这很可能是您的程序在特定配置中没有崩溃的原因 . 请注意,您分配的8个字节仅覆盖
sizeof (int)
为4的系统上的两个整数 . 其他6个整数所需的其他24个字节不属于您的数组,因此任何内容都可以写入该空间,当您从中读取时空间,如果你的程序没有先崩溃,你就会得到垃圾,就是这样 .数字6很重要 . 请记住以后再说!
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.
这是"magically correct"最有可能与
printf
通过va_args接收这些数字有关 .printf
可能正在填充数组物理边界之外的内存区域(因为vprintf正在分配内存以执行打印i
所需的"itoa"操作) . 换句话说,那些"correct"结果实际上只是垃圾"appears to be correct",但实际上,这恰好是在RAM中发生的事情 . 如果您尝试在保持8字节分配的同时将int
更改为long
,则程序将更有可能崩溃,因为long
长于int
.malloc的glibc实现有一个优化,它在每次堆耗尽时从内核分配整个页面 . 这使它更快,因为它不是要求内核在每次分配时获得更多内存,而是可以从“池”中获取可用内存,并在第一个填充时创建另一个“池” .
也就是说,像堆栈一样,malloc的堆指针来自内存池,往往是连续的(或者至少非常靠近) . 这意味着printf对malloc的调用可能会出现在为int数组分配的8个字节之后 . 然而,无论它是如何工作的,重点是无论结果看起来多么“正确”,它们实际上只是垃圾而你正在调用未定义的行为,因此无法知道将会发生什么,或者是否程序将在不同情况下执行其他操作,例如崩溃或产生意外行为 .
所以我尝试使用和不使用printf运行你的程序,两次,结果都是错误的 .
无论出于何种原因,没有任何东西干扰了持有
2..5
的记忆 . 然而,有些东西干扰了持有6
和7
的记忆 . 我的猜测是这是vprintf的缓冲区,用于创建数字的字符串表示 .1041
将是文本,0
将是空终止符,'\0'
. 即使它不是vprintf的结果,也有人在数据的填充和打印之间写入该地址 .这是有趣的部分 . 您的问题中没有提到您的程序是否崩溃 . 但当我跑它时,它崩溃了 . Hard .
如果你有可用的话,与valgrind一起检查也是一个好主意 . Valgrind是一个有用的程序,可以报告你如何使用你的记忆 . 这是valgrind的输出:
如你所见,valgrind报告你有
invalid write of size 4
和invalid read of size 4
(4个字节是我系统中int的大小) . 它's also mentioning that you'正在读取一个大小为0的块,该块在大小为8的块之后(你的malloc 'd). This tells you that you'经过数组并进入垃圾场的块 . 你可能注意到的另一件事是它从2个上下文产生了12个错误 . 具体来说 . ,写入上下文中的 6 错误和读取上下文中的 6 错误 . 正好是我前面提到的未分配空间的数量 .这是更正后的代码:
这是valgrind的输出:
请注意,它报告没有错误,结果是正确的 .
是否
printf()
在执行其工作的过程中分配了任何内存是未指定的 . 如果任何给定的实现都这样做,那就不足为奇了,但是没有理由认为它确实如此 . 而且,如果一个实现确实如此,那就说明不同的实现是否有效 .当
printf()
在循环内部时,您会看到不同的行为 . 程序通过超出已分配对象的边界来展示未定义的行为 . 一旦这样做,所有后续行为都是未定义的 . 你不能推理未定义的行为,至少不是C语义 . 一旦未定义的行为开始,程序就没有C语义 . 这就是"undefined"的含义 .您为数组分配了8个字节,但是存储了8个
int
,每个字节至少有2个字节(可能是4个字节),所以您要写入已分配内存的末尾 . 这样做会调用未定义的行为 .当您调用未定义的行为时,任何事情都可能发生 . 您的程序可能会崩溃,它可能会显示意外的结果,或者它似乎可以正常工作 . 看似无关的变化可以改变上述哪种行为发生 .
修复内存分配,您的代码将按预期工作 .