【问题标题】:Always check malloc'ed memory?总是检查 malloc 的内存?
【发布时间】:2010-12-28 19:39:15
【问题描述】:

我经常发现自己在做以下事情(在非关键组件中):

some_small_struct *ptr=(some_small_struct *) malloc(sizeof(some_small_struct));
ptr->some_member= ...;

换句话说,我为一个小结构动态分配内存,我直接使用它而不检查 malloc'ed 指针。我知道程序总是有可能无法获得它要求的内存(呃!),但请考虑以下几点:

如果程序甚至无法从 堆,也许还有更大的问题迫在眉睫,毕竟没关系。

此外,如果处理空指针更加恶化了不稳定的情况怎么办? (例如,尝试记录条件调用甚至更多不存在的资源等)

我的推理是否理智(足够)?

更新

  1. “safe_malloc”函数在调试时很有用,在其他情况下也很有用
  2. +X 访问可以隐藏 NULL 指针的根本原因
  3. 在 Linux 上,“乐观内存分配”会影响 OOM(内存不足)情况

【问题讨论】:

  • 最佳实践意味着所有系统的强度和稳定性。应用它们取决于我们。
  • 找到一个相关问题(我喜欢 Reed Copsey 的贡献):stackoverflow.com/questions/691402/…

标签: c architecture system


【解决方案1】:

分配失败可能有多种原因。你做什么(和可以做什么)部分取决于分配失败。

真正失忆是灾难性的。除非您为此制定了周密的计划,否则您可能无能为力。 (例如,您可以预先分配紧急保存和关机所需的所有资源。)

但许多分配失败与内存不足无关。碎片会导致分配失败,因为即使有足够的可用内存,也没有足够的连续空间可用。这个问题特别提到了一个“小结构”,所以这可能与真正的内存不足情况一样糟糕。 (但代码是不断变化的。今天的小结构明天可能会成为怪物。如果它这么小,你真的需要堆内存还是从堆栈中获取内存?)

在多线程世界中,分配失败通常是暂时的情况。您的适度分配可能会在这一微秒内失败,但也许一个占用内存的线程即将释放一个大缓冲区。因此,恢复策略可能涉及延迟和重试。

您如何(以及是否)处理分配失败也取决于应用程序的类型。如果您正在编写一个复杂的文档编辑器,并且崩溃意味着失去用户的工作,那么花费更多的精力来处理这些故障是值得的。如果您的应用程序是事务性的,并且每次更改都以增量方式应用于持久存储,那么崩溃对用户来说只是轻微的不便。即便如此,也应该考虑日志记录。如果您的应用程序经常遇到分配失败,那么您可能有一个错误,您需要日志来了解并跟踪它。

最后,您必须考虑测试。分配失败很少见,因此在您的测试中执行恢复代码的机会非常小——除非您已采取措施通过人为强制失败来确保测试覆盖率。如果您不打算测试您的恢复代码,那么可能不值得编写它。

【讨论】:

    【解决方案2】:

    假设您在 Linux/MaxOs/Windows 或其他虚拟内存系统上运行,那么...检查 malloc 返回值的唯一原因是您是否有释放足够内存以允许程序运行的策略继续运行。信息丰富的消息将有助于诊断问题,但前提是您的程序导致了内存不足的情况。 通常它不是你的程序,你的程序唯一能做的就是尽快退出。

    assert(ptr != NULL);
    

    会做所有这些事情。我通常的策略是在 malloc 周围有一个层 这个在里面。

    void *my_malloc(size_t size)
    {
        void *ptr = malloc ( size );
        assert(ptr != NULL);
        return *ptr;
    }
    

    然后你调用 my_malloc 而不是 malloc。在开发过程中我使用了一个有利于调试的内存分配库。之后,如果内存不足 - 我会收到一条消息。

    【讨论】:

    • 别忘了使用对应的my_free!否则是一个错误;)
    • 这个assert 没用:malloc 最有可能失败的情况是生产,而不是你的测试。在生产中,通常会发布发布版本,它定义了NDEBUG,这使得所有asserts 都没有操作。
    【解决方案3】:

    我一直认为处理 malloc 的返回或任何其他系统调用的返回是重要且最好的。尽管在现代系统(除了嵌入式系统)中,这种情况很少见,除非您的代码使用太多内存,否则它总是更安全。

    在系统调用失败后继续执行代码可能会导致损坏、崩溃等等,除了让您的程序看起来很糟糕之外。

    另外,在 linux 中,分配给进程的内存是有限的。尝试在一个进程中创建 1000 个线程,并在每个线程中分配一些内存,那么您可以轻松模拟内存不足的情况。 :)

    总是更好地检查系统调用返回值!

    【讨论】:

      【解决方案4】:

      取决于平台。例如,在 Linux 上(默认情况下)检查 NULL 没有多大意义:

      http://linux.die.net/man/3/malloc

      默认情况下,Linux 遵循乐观的内存分配策略。这意味着当 malloc() 返回非 NULL 时,不能保证内存确实可用。这是一个非常糟糕的错误。如果发现系统内存不足,一个或多个进程将被臭名昭著的 OOM 杀手杀死。

      【讨论】:

      • Malloc 在地址空间已满时仍然可以返回 NULL。
      • 它被称为memory overcommit,它可以被禁用所以这不是真的。
      • @boraalper4:我写了“默认”,这很清楚地暗示它可以被禁用。那么哪一部分是不正确的呢?
      • @Nemanja Trifunovic:“检查 NULL 没有多大意义”;如果它可以被禁用,那么检查 NULL 是有意义的。 (当然你可能不想每次都检查返回值。)
      【解决方案5】:

      在 C 的情况下,它取决于平台。如果你在一个内存很少的嵌入式平台上,你应该总是检查,尽管如果它确实失败了你会做什么更难说。在具有虚拟内存的现代 32 位操作系统上,系统可能会在承认内存不足之前变得无响应并崩溃。在这种情况下,对 malloc 的调用永远不会返回,因此检查其值的实用程序变得毫无意义。

      在 C++ 的情况下,您应该使用 new 而不是 malloc,在这种情况下会在用尽时引发异常,因此检查返回值是没有意义的。

      【讨论】:

      • 但是在 C 的情况下你应该经常检查。无响应和可恢复总比崩溃好。
      • @Martin:像什么?我真的很好奇,因为我不知道 exit() 可能为我提供什么价值。请解释一下。
      • @Martin 至少在类似 UNIX 的操作系统上,内存访问冲突可能比干净退出要好,因为这样您将获得核心转储。
      • @Mattin 核心也会告诉你内存不足的地方,假设你在 malloc 后立即使用指针,正如 OP 询问的那样。
      • "在具有虚拟内存的现代 32 位操作系统上,系统可能会在承认内存不足之前变得无响应并崩溃。在这种情况下,对 malloc 的调用永远不会返回,因此检查其价值的实用性变得没有意义。”这根本不是真的,malloc 可能会因为地址空间中没有足够的(连续的)内存而失败,这并不意味着系统内存不足:实际上 32 位操作系统上的虚拟地址空间已启动到 4 GB 宽,而物理内存可以更多,不包括交换空间。所以,检查返回值。
      【解决方案6】:

      我会说不。 使用 NULL 指针会使程序崩溃(可能)。
      但是检测到它并做一些智能的事情就可以了,你也许可以从内存不足的情况中恢复过来。

      如果您正在执行大型操作,请设置一些全局错误标志并开始展开堆栈并释放资源。希望这些资源中的一种或多种会占用您的内存,并且您的应用程序将恢复正常。

      这当然是一个 C 问题,在 C++ 中借助异常和 RAII 自动处理。
      由于 new 不会返回 NULL,因此检查没有意义。

      【讨论】:

      • @Martin:系统(正如 Neil 也指出的那样)可能会对任何恢复都可能是毫无意义的操作无响应。如果某些东西消耗内存的速度超过了可以有意义地减轻的速度,我认为没有意义。
      • C++ 设法做到了。如果某件事正在快速吞噬记忆,那么就停止做某事。释放所有资源待办事项恢复正常状态然后报错。使用工作 C 可以像 C++ 一样处理错误。在最坏的情况下调用 exit() 并干净地关闭。因为您可能会为正确编码而产生段错误不会让您的客户对您的能力充满信心。
      • @Martin: (1) 程序 X 可能不是吃内存的那个,它可能是程序 Y。(2) 你调用 exit() 的建议得到确认 (3) 回到我的初始点:如果我的程序(比如 X)甚至不能抓取一些 16 字节来操作结构,我很确定主机的错误日志无论如何都会填满我的其他讨厌的东西。有问题的客户会忙于处理其他问题,而您所指的segfault 可能不是他最关心的问题。
      • @jldupont。 1) 如果我们在嵌入式平台中,X 和 Y 可能会交互。但是你肯定需要在那里检查。如果我们在 Windows 上,X 对内存的使用不会影响 Y 的可用内存。
      • 对于那些说内存不足时无法做有用事情的人,请三思。关键是提前计划。分配内存专门用于在程序启动时处理此类问题。调用 myexit() 时使用此内存。如果您需要调用可能需要更多内存的第三方代码,请释放您早先分配的内存块,其唯一目的是解除分配/可能/有助于回收可以使用的内存。
      【解决方案7】:

      是的,内存不足几乎肯定预示着其他故障即将到来。 但是您如何确定在分配失败和最终崩溃之间不会发生损坏的输出?

      您对每个程序、每次进行编辑的确定程度如何。

      捕捉您的错误,以便您可以知道您按时崩溃了。

      【讨论】:

      • +1:用于解决不确定性。这个策略加上safemalloc 可能是一个很好的组合。谢谢。
      【解决方案8】:

      此外,如果处理空指针更加剧了不稳定的情况呢??

      我不明白为什么它会加剧这种情况。
      无论如何,在为 windows ptr->some_member 编写代码时会抛出访问冲突,因此您会立即看到问题,因此我认为没有理由检查返回值,除非您的程序有机会释放内存。 对于不能很好地处理空指针(抛出异常)的平台,忽略这些点是很危险的。

      【讨论】:

      • +1:完全正确。哪些平台不处理空指针? (可能除了嵌入式系统)
      • 即使ptr 是空指针(即它的值位于地址空间的保留区域中),ptr->fooptr[42] 也不一定如此
      • @Christoph: 好点。也许增加的调整是先访问+0
      • 我认为 some_small_struct 的大小不会为 1 Mb(或者 NULL 指针分配内存分区的确切值是多少?)
      • @ironic:这是特定于平台的;如果您确信代码将永远不会耗尽所有平台上的保留空间,请随时放弃对NULL 的检查;同时,我将继续为每个foo = malloc(sizeof *foo) 加上if(!foo) [...],其中[...] 类似于return NULLabort()log("out of memory")、...
      【解决方案9】:

      可以在启动时分配较大的内存块,当您遇到内存不足的情况时可以释放它并使用它来优雅地关闭。

      【讨论】:

      • @Eclipse:当然可以(“恒定内存”模式),但这不是问题所在。
      • 这实际上在 Linux 上不起作用,它会很高兴地过度使用内存(另见 OOM 杀手)。您的“可释放”块将是地址空间的保留部分,没有实际内存支持它,因此当您释放它时,情况不会好转。 ::叹息::
      • 您将有一定程度的堆栈内存剩余(否则您会遇到堆栈溢出,而不是从 malloc 返回 NULL)。除非同时满足这两个条件,否则只要在释放保留内存之前不需要分配更多堆空间,处理这种情况就不会有问题。
      • 可以保留您的安全块,然后访问它的每个内存页面以强制内存管理器连接一些后备内存。他们你又把这个把戏拿回来了。
      【解决方案10】:

      至少我会在里面放一个assert(ptr != NULL),这样你就会得到一个有意义的错误。

      【讨论】:

      • @Evan:但是,就我而言,这不会触发一系列无论如何都无法支持的事件吗?
      • @jldupont:是的,但请考虑一下:想象一下 ptr->some_member =... 不会导致段错误,但它会覆盖很久以后导致段错误的内容。一个非常讨厌的错误被很好地隐藏了。断言会立即捕获它。
      • @jldupont:是的,但是如果访问 *NULL 错误的事件并不总是这样,访问 *(NULL + 4) 可能不会。
      • 断言没有意义,因为它只会在测试环境中中止,而内存不足的情况在实际情况中更为常见。断言只会告诉您它可能会出现内存不足的情况,或者您会在调试模式下发布您的产品吗?
      • 不过,在这种情况下,我还是会选择一个 SafeMalloc() 函数来执行(断言)检查,而不是把断言放在所有地方。
      猜你喜欢
      • 2019-06-10
      • 2017-02-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-03-02
      • 2020-03-19
      • 2010-12-11
      相关资源
      最近更新 更多