0

可能重复:
malloc(0) 的行为

我正在尝试了解 C 中的内存分配。所以我正在尝试使用malloc. 我为这个指针分配了 0 个字节,但它仍然可以保存一个整数。事实上,无论我在 的参数中放入什么数字malloc,它仍然可以容纳我给它的任何数字。为什么是这样?

#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    int *ptr = (int*)malloc(0);

    *ptr = 9;

    printf("%i", *ptr); // 9

    free(ptr);

    return 0;
}

它仍然打印 9,这是怎么回事?

4

5 回答 5

3

如果 size 为 0,则 malloc() 返回 NULL 或稍后可以成功传递给 free() 的唯一指针值。

我猜你遇到了第二种情况。无论如何,该指针只是错误地恰好位于您可以写入而不会产生分段错误的区域中,但您可能正在写入一些其他变量的空间,从而弄乱了它的值。

于 2013-01-31T21:47:07.987 回答
2

一方面,您的编译器可能会看到这两行背靠背并对其进行优化:

*ptr = 9;
printf("%i", *ptr);

使用这样一个简单的程序,您的编译器实际上可能正在优化整个内存分配/释放周期并改用常量。程序的编译器优化版本最终可能看起来更像是:

printf("9");

判断这是否确实发生的唯一方法是检查编译器发出的程序集。如果您想了解 C 的工作原理,我建议您在构建代码时明确禁用所有编译器优化。

关于您的特定malloc用法,请记住,如果分配失败,您将返回一个 NULL 指针。 在将它用于任何事情之前,请务必检查它的返回值。malloc盲目地取消引用它是使程序崩溃的好方法。

尼克发布的链接很好地解释了为什么malloc(0)看起来有效(注意“有效”和“似乎有效”之间的显着差异)。总结那里的信息,malloc(0)允许返回 NULL 或指针。如果它返回一个指针,则明确禁止您将其用于除将其传递给free(). 如果您确实尝试使用这样的指针,那么您将调用未定义的行为,并且无法判断结果会发生什么。它可能看起来对你有用,但这样做你可能会覆盖属于另一个程序的内存并破坏它们的内存空间。简而言之:不会发生什么好事,所以不要管那个指针,不要在malloc(0).

于 2013-01-31T22:08:32.747 回答
2

这里有很多很好的答案。但这绝对是未定义的行为。有些人声称未定义的行为意味着紫龙可能会飞出您的计算机或类似的东西......我失踪的离谱声称背后可能有一些历史,但我向您保证,无论何时紫龙都不会出现未定义的行为将是什么。

首先,让我提一下,在没有 MMU 的情况下,在没有虚拟内存的系统上,您的程序将可以直接访问系统上的所有内存,而不管其地址如何。在这样的系统上,malloc()它只是帮助您以有序的方式划分内存的人;系统实际上不能强制您只使用malloc()给您的地址。在有虚拟内存的系统上,情况稍微有点不同...嗯,好吧,很多不同。但是在您的程序中,您程序中的任何代码都可以访问通过 MMU 映射到真实物理内存的虚拟地址空间的任何部分。无论您是从 malloc() 获得地址,还是调用 rand() 并碰巧获得了位于程序映射区域中的地址,都无关紧要。如果它已映射且未标记为仅执行,则可以阅读它。如果它没有标记为只读,您也可以编写它。是的。即使你没有从malloc().

让我们考虑malloc(0)未定义行为的可能性:

  • malloc(0)返回 NULL。

好的,这很简单。在大多数计算机中确实有一个物理地址 0x00000000,甚至在所有进程中都有一个虚拟地址 0x00000000,但是操作系统故意不将任何内存映射到该地址,以便它可以捕获空指针访问。那里有一整页(通常是 4KB)根本没有映射,甚至可能超过 4KB。因此,如果您尝试通过空指针读取或写入,即使有一个偏移量,您也会碰到这些甚至没有映射的虚拟内存页面,并且 MMU 将抛出异常(硬件异常或中断) 被操作系统捕获,并声明一个 SIGSEGV(在 Linux/Unix 上)或非法访问(在 Windows 上)。

  • malloc(0)将有效地址返回到最小可分配单元的先前未分配的内存。

有了这个,你实际上得到了一块你可以合法地称之为你自己的真正的记忆,大小你不知道。你真的不应该在那里写任何东西(也可能不读),因为你不知道它有多大,就此而言,你不知道这是否是你正在经历的特殊情况(见下文例)。如果是这种情况,您获得的内存块几乎可以保证至少为 4 个字节,并且可能是 8 个字节甚至更大;这一切都取决于您的实现的最小可分配单元的大小。

  • malloc(0)有意返回 NULL 以外的未映射内存页的地址。

这对于实现来说可能是一个不错的选择,因为它允许您或系统跟踪并配对 malloc() 调用及其相应的 free() 调用,但本质上,它与返回 NULL 相同。如果您尝试通过此指针访问(读/写),您将崩溃(SEGV 或非法访问)。

  • malloc(0)返回“其他人”可能使用的其他映射内存页中的地址。

我发现商业上可用的系统不太可能采用这种方法,因为它只是为了隐藏错误而不是尽快将它们找出来。但如果是这样, malloc() 将返回一个指向您不拥有的内存某处的指针。如果是这种情况,当然,您可以随心所欲地对其进行写入,但是您会破坏其他代码的内存,尽管它会是您程序进程中的内存,因此您可以放心,您至少不会会踩到另一个程序的内存。(我听到有人准备说,“但它是 UB,所以从技术上讲它可以踩在其他程序的内存上。是的,在某些环境中,例如嵌入式系统,这是正确的。没有现代商业操作系统会让一个进程访问另一个进程的内存,就像简单地调用 malloc(0) 一样容易;事实上,如果不通过操作系统为你做这件事,你根本无法从一个进程到另一个进程的内存。) 无论如何,回到现实......这是“未定义行为”真正发挥作用的地方:如果您正在写入“其他人的记忆”(在您自己的程序的进程中),您将很难改变程序的行为- 预测方式。了解程序的结构以及所有内容在内存中的布局,这是完全可以预测的。但是从一个系统到另一个系统,事物会被布置在内存中(在内存中出现不同的位置),因此对一个系统的影响不一定与对另一个系统的影响相同,或者对同一系统的不同时间。

  • 最后......不,就是这样。真的,真的,只有这四种可能性。您可以为上述最后两个中的特殊情况子集点争论,但最终结果将是相同的。
于 2013-01-31T22:51:03.183 回答
1

您可以在此处找到未崩溃的malloc(0)/呼叫的答案:free()

零大小的malloc

关于*ptr = 9, 就像溢出缓冲区(例如 malloc'ing 10 个字节并访问第 11 个字节),您正在写入您不拥有的内存,这样做是在寻找麻烦。在这个特定的实现malloc(0)中,恰好返回一个指针而不是 NULL。

最重要的是,即使它似乎适用于一个简单的案例,也是错误的。

于 2013-01-31T21:49:10.247 回答
0

一些内存分配器具有“最小可分配大小”的概念。因此,即使您传递零,这也会返回指向例如字长内存的指针。您需要检查您的系统分配器文档。但是,如果它确实返回指向某个内存的指针,那么依赖它是错误的,因为该指针只应该被传递给传递 realloc() 或 free()。

于 2013-01-31T21:49:45.147 回答