【问题标题】:How does C handle variable declarations after program terminations?C 如何在程序终止后处理变量声明?
【发布时间】:2021-01-24 20:49:31
【问题描述】:

我知道 C 不提供自动垃圾回收,这意味着每当有人使用 mallocrealloccalloc 分配内存时,他们必须在程序结束时释放它们,因此不会有泄漏。但是,我还没有看到任何人释放在变量声明期间分配的内存。例如,如果我执行int x = 10; 之类的操作,则在某处分配了一个用于保存值 10 的内存(我什至可以通过执行&x 来查看地址),但我从未在我的程序中释放这些内存,但似乎没有内存泄漏(如果我使用 valgrind 来检查),这让我认为 C 有某种垃圾收集,或者这是一个不同的故事?

【问题讨论】:

  • 它在EXE中预先分配,然后在程序卸载时释放。有很多指南展示了 C 的内存如何用于变量 - 例如静态、本地和动态分配的变量。你看过吗?
  • 我不知道要寻找什么,谢谢,我会知道寻找静态变量之类的东西。或者我问的问题是否有合适的话题。
  • 除非你在一个没有内存保护的深度嵌入式系统上,所有由malloc和朋友分配的存储空间作为进程终止的副作用自动释放,并且用手撕下来只是浪费时间。

标签: c memory-management memory-leaks


【解决方案1】:

虽然现有的答案很有帮助,但我认为您可能会误解更基本的东西。

我知道 C 不提供自动垃圾回收,这意味着每当有人使用 malloc、realloc 或 calloc 分配内存时,他们必须在程序结束时释放它们,以免泄漏。

其实不然。

问自己这个问题:当程序终止时,程序代码(可执行指令)所需的内存会发生什么变化?谁来释放它?

问题的答案显然不是程序本身,不是在它终止之后!对于普通程序(标准称为hosted),操作系统会分配一个进程,将内存与其关联,将可执行文件加载到该内存中,然后启动程序运行。它管理程序向它请求的资源,包括文件句柄和内存。当程序终止时,所有相关资源都将被丢弃,并释放内存以供其他用途。

当我们谈论垃圾收集和内存管理时,我们并不是在谈论从操作系统获取的资源,这些资源会在程序终止时释放。我们谈论的是程序临时获取、使用、释放,然后循环重新获取的内存。如果循环被打破(逐渐)并且可以释放的内存没有被释放,那么更多的可以被释放的内存循环地没有被释放,这就是我们所说的泄漏。垃圾回收是检测何时获取的无法再访问并自动释放它的过程。

如果我执行 int x = 10; 之类的操作,则会在某处分配用于保存值 10 的内存

是的。 C 将其称为定义,而不是声明。声明只是说某事存在;定义为它腾出空间。

如果变量是在函数外部定义的,或者在带有static 的函数中定义,它会在操作系统首次加载程序时分配一次。在函数中定义且未标记为静态的变量称为自动,并在函数执行期间“在堆栈上”分配。当函数终止时,它们会“从堆栈中弹出”。它可以被认为是一种原始的垃圾收集:因为函数的局部变量不能从函数外部引用,很明显,当函数终止时,它们可以被“收集”。 (我现在要掩饰一下,因为 100 名 CS 毕业生即将说这不是“垃圾收集”的意思,他们是对的。我只是想以你自己的方式回答你的问题。)

如果你花很多时间在 C 上,迟早你会遇到 recursion,函数调用它自己。每次这样做时,都会将更多自动变量添加到堆栈中。如果它没有找到停止的理由,你就会有无限递归,不久(通常是几秒钟)堆栈就会耗尽,并且会发生一些不好的事情。效果很像在无限循环中调用ma​​lloc(3),只不过内存来自堆栈而不是堆。

【讨论】:

  • 它知道有道理!当我问这个问题时,我在想如果在程序终止之前没有释放它,内存很难被泄露。我的意思是这仍然是一种泄漏,但不是我在谈论垃圾收集时所说的那种。谢谢!
【解决方案2】:

函数返回后,调用函数调整栈指针。由于堆栈指针已调整,因此位于堆栈上的调用函数的本地变量不再被视为堆栈的一部分。

在程序退出之前,全局变量和静态变量不会被销毁。此时,操作系统将记录内存不再属于该进程,并且可能会分配给其他进程,但通常不会将其归零。 在这两种情况下,RAM 都会保持其值直到被覆盖。

【讨论】:

  • 很好的答案,除了关于操作系统没有清零已释放内存的部分——用户空间进程无法判断这实际上何时发生,但操作系统 必须 擦除 RAM出于安全原因,在将其重新分配给另一个进程之前曾被一个进程使用过。
  • 按此,不是必须的stackoverflow.com/a/12716054/13826315,第3段
  • 第三段不正确。对于 Windows 3.1 和 95 可能是这样,但对于 NT 或任何 Unix 变体来说从未如此。
【解决方案3】:

C 语言标准定义了 4 种不同的存储持续时间,它们指定了如何为各种对象管理内存:

  • 具有静态存储持续时间的对象在程序启动时分配并在程序退出时释放(即对象的存储在整个生命周期内保持静态程序,所以你不必担心它)。在文件范围(任何主体或函数之外)或使用 static 关键字声明的任何内容都具有静态存储持续时间。

  • 具有自动存储持续时间的对象在程序执行进入声明它们的块时分配,然后在程序执行退出该块时释放(即分配和释放是“自动的”,你不必担心它)。在没有static_Thread_local 关键字的函数或块中声明的任何内容都具有自动存储持续时间。这是您的int x = 10; 所属的组。大多数人会将此类对象称为“在堆栈上”,这足以作为简写描述,但请注意实际实现可能比这要复杂得多。重要的是行为,而不是通常如何实现该行为。堆栈使auto 存储持续时间易于实现,但它们并不是唯一的方法。

  • 具有线程存储持续时间的对象在线程启动时分配并在线程退出时释放。使用 _Thread_local 关键字声明的任何内容都具有线程存储持续时间。由于线程和线程语义是该语言中相对较新的补充,因此这并不真正符合上述“自动与静态”的区别,但与其他两个一样,您不必担心管理该内存。

  • 具有已分配存储持续时间的对象通过调用malloccallocrealloc 进行分配,并在调用free 时释放。这些是您唯一需要注意的关于内存泄漏的事情,以及如果 C 有垃圾收集器,垃圾收集器将管理什么。大多数人会将此类对象称为“在堆上”,这可以作为简写描述,但实际实现可能要复杂得多。同样,重要的是行为,而不是行为的实现方式。

C 目前没有任何类型的用于动态分配内存的垃圾收集系统的原因有很多。该语言的未来版本可能创建一个新的垃圾收集动态内存存储类,但我认为这不太可能。

【讨论】:

    【解决方案4】:

    每当有人分配内存 [...] 他们必须在程序结束时释放它们

    这根本不是真的,您不需要在程序结束时释放进程内部分配的内存以避免内存泄漏。

    如果我做类似int x = 10;的事情

    这根本不分配内存,它使用线程已经分配的堆栈内存(或.text 部分,如果它是全局的)来存储值。

    让我认为 C 有某种垃圾收集功能

    C 中没有垃圾收集。

    【讨论】:

    • 定义确实会分配内存,无论它们是静态的并在某些数据段中分配,还是自动并在堆栈中分配。典型的线程实现确实为堆栈分配了一个内存区域,但是当函数被调用和返回时,该堆栈的一部分被进一步分配和释放。堆栈指针的每次修改都是一次内存分配或解除分配,即使它可能是一个函数调用所需的所有堆栈内存的捆绑分配。
    • 我.. 不相信您是对的,但请随时显示来源以支持您的主张。据我所知,线程堆栈空间被分配一次,然后一成不变,这就是为什么会出现堆栈溢出异常的原因——预先分配的堆栈空间已满,无法增加。
    • C 2018 6.7 5:“标识符的定义是该标识符的声明:- 对于一个对象,导致为该对象保留存储空间;……”
    • Re:“只是增加和减少”:您认为分配是什么?分配只是簿记。您通常会将分配与malloc 提供的动态分配混淆。即使在那里,它也只是簿记:malloc 可能有大量可用内存,并且在发出请求时,它所需要做的就是调整它拥有的一些记录。堆栈指针是一样的;它记录了哪些内存已分配给特定用途,哪些未分配。
    • 啊,那么它是语义。我所说的内存分配是指操作系统给你的内存。如果没有malloc 的变体,您将永远无法访问超过堆栈空间(由操作系统分配)和各个部分(由操作系统根据加载程序的要求分配)。堆栈变量永远无法逃脱操作系统提供的堆栈内存块,但malloc 请求的内存可以并且将会。
    【解决方案5】:

    在 C 中有两种分配内存的方法:在堆栈上或在堆上。当你写这样的东西时:

    int x = 10;
    

    这是一个堆栈分配。一旦程序退出声明发生的范围,它就会被释放。例如,如果上面的行出现在一个函数中,它会在函数结束时被释放。

    如果你使用 malloc 之类的东西来声明一个变量,那就是堆分配。当您存在声明它的范围时,它不会自动释放。如果你从不调用相应的清理代码,它会一直保留在内存中,直到程序终止(此时它当然会被程序外部的东西清理掉)。

    【讨论】:

    • C 有五个存储持续时间,而不是两个:静态、线程、自动、分配和临时。在解释 C 语义时,最好使用 C 标准中的术语,例如“自动”而不是“堆栈”。堆栈非常常用来实现自动存储,但在专门的环境中可能会使用其他方法。并且很少使用真正的堆结构来实现分配的存储持续时间; malloc 系列例程可能具有经过数十年研究和开发的复杂算法。
    • @EricPostpischil 你是对的,但人们仍然指的是堆栈和堆,即使他们用词不当。堆不是实际的堆,但这是一个实现细节。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-10-22
    • 2019-06-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-08-22
    • 1970-01-01
    相关资源
    最近更新 更多