【问题标题】:about malloc() and free() in C关于 C 中的 malloc() 和 free()
【发布时间】:2011-04-14 19:15:07
【问题描述】:

我有以下 C 代码:

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

typedef struct node
{
    int a;
}node;

int main()
{
    node * n;

    printf("\n%d\n",n->a);

    n  = (node *) malloc ( sizeof( node ));

    printf("\n%d\n",n->a);
    n->a = 6;
    printf("\n%d\n",n->a);
    free(n);
    printf("\n%d\n",n->a);
    n->a = 4;
    printf("\n%d\n",n->a);
    return 0;
}

得到的输出是:

1314172

0

6

0

4

我的问题是即使在 free(n) 之后,为什么 n->a = 0 以及我们如何将其重新分配给任何值,例如 n->a = 4 ?

free 不会使 n 指向的内存块无效吗??

【问题讨论】:

  • 如果您担心清除数据,您可以在 free() 之后在同一空间上执行 calloc...但是,我想这违背了 free 的目的 :)
  • 为什么要使用这种未定义的行为来达到某种目的?
  • 一个更好的做法是在free后将变量设置为NULL。这将确保未定义的行为将成为已定义的行为(崩溃)

标签: c pointers malloc heap-memory free


【解决方案1】:
free(n);
n->a = 4; // is not guaranteed to work, might crash on some implementations

调用未定义的行为。

为什么 n->a = 0 以及我们如何将它重新分配给任何值,例如 n->a = 4 ?

那是因为未定义的行为意味着任何事情都可能发生。你不能依赖它。

P.S : 不要写这样的代码。

编辑

Jonathan 注意到您的代码早在您 free() 之前就调用了未定义行为,然后取消引用 n

node * n; //n is not initialized
printf("\n%d\n",n->a);  //dereferencing a wild pointer invokes UB.

【讨论】:

  • +1 确实。 free() 将释放分配的空间,以便可以通过其他操作再次重新分配它,但它不需要清理该内存空间。因此,在您的示例中,我相信您在 free() 之后打印的内容实际上只是之前对该内存空间的操作留下的垃圾。
  • 代码在free() 之前很久就遇到了未定义的行为;第一个 printf() 取消引用未初始化的指针。
【解决方案2】:

重用释放的指针类似于复制您租用的汽车的钥匙,归还汽车,然后在您将汽车还回租车地点后尝试使用该钥匙进入汽车。你也许可以侥幸逃脱,但你不应该这样做,而且它经常会给你带来麻烦。

【讨论】:

    【解决方案3】:

    不,free 可能会或可能不会对关联的内存做任何事情。但是引用它是无效的。其他进程/您的进程可能已经覆盖了内存,或者如果您不走运,数据可能与您留下的一样。

    确保不引用已释放的内存由您决定。

    【讨论】:

      【解决方案4】:

      当您取消引用指向未分配内存的指针时,行为是未定义的。这意味着它可以做任何事情。它可能会触发错误,也可能会执行其他操作。

      在大多数情况下,如果您free 一些内存,则该内存不会立即重新使用,并且不会立即被操作系统回收。这意味着您的应用程序仍然可以访问内存,并且仍将包含其先前的值。您通常可以毫无错误地对其进行读取或写入,但您不能依赖这种行为

      如果您在许多不同的环境中运行此代码,您会发现在某些环境中它始终有效,在某些环境中它总是触发错误,但在许多其他环境中它“大部分时间”都有效。这使得 C 很难调试! :)

      【讨论】:

        【解决方案5】:

        内存块被释放。这并不意味着您不能写信给它;这意味着你不能写信给它。机器不会阻止你这样做(通常,有一些工具,比如电围栏会阻止它)

        正如您将来会发现的那样;您最终会经常意外地这样做;通常会发生坏事(tm)

        【讨论】:

          【解决方案6】:

          在大多数针对具有 MMU 的硬件的操作系统(例如 x86)上,实际获取内存读取错误的唯一方法是应用程序地址未映射。由于 MMU 没有一个条目来告诉它在物理内存中的何处找到与应用程序请求的逻辑地址相关联的数据,因此它会导致中断并且操作系统会接管并对其进行处理。

          但大多数操作系统并没有为应用程序管理内存做太多工作,只是为其分配了所需大小的连续内存块。操作系统也不允许应用程序在 MMU 中乱七八糟,因为大多数 MMU 都不够聪明,无法让应用程序安全地做事而不会对其他程序产生负面影响(无论是意外还是恶意)。

          因此,当您 malloc() 某事时,如果您的应用程序还没有将其放置在其现有地址空间中的位置,它会要求操作系统提供更多信息,但是当您稍后 free() 它时,除非发生这种情况要位于应用程序地址空间的最后,您不能将其交还给操作系统以使该内存区域在您尝试读取它时导致错误。

          有一些方法可以做到这一点,例如使用mmap(),但这并不是使用该功能的正确理由。

          【讨论】:

          • 部分不真实。它不必位于应用程序地址空间的“末尾”。例如,在 x86 上,操作系统可以回收任何对齐的 4kB 块(“页面”)。虚拟内存的部分目的是对应用程序隐藏这种内存碎片。 (实际上在 x86 上,操作系统可以从各种页面大小中进行选择,但 4kB 是最常见的)。
          • 虽然硬件和操作系统支持它,但实际上,大多数分配器(例如 glibc 中的分配器)实际上并没有利用它。但是,没有什么可以阻止应用程序使用执行此操作的分配器。所有三层都需要合作才能使这种事情发挥作用。
          【解决方案7】:

          大多数操作系统都会分配一定的最小空间,该空间将等于或大于您请求的空间。 一旦您调用免费,此空间将不会回到操作系统,而是保留在您身边以供将来重复使用。因此,您可以摆脱上面所写的内容。

          但严格来说,这被归类为“未定义”行为。但大多数时候你可以侥幸逃脱。只是不要养成习惯;)

          【讨论】:

          • “不习惯”写入已释放的内存?我认为说“永远不要这样做”会更合适。认为您可以依赖未定义的行为是疯狂的。
          • 好吧,我说的比较轻松。如果有人在生产代码中写入他们需要释放内存的数据(除了一些随机数据或类似的东西),那么他们真的不适合开发团队;)你是对的,使用释放的内存纯粹是疯狂对于任何实用的东西。但是话又说回来,我们遇到了这种确切的情况,有一个工作代码(尽管以一种不确定的方式)。
          猜你喜欢
          • 2021-12-11
          • 1970-01-01
          • 2016-12-29
          • 2015-04-23
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2012-06-10
          • 1970-01-01
          相关资源
          最近更新 更多