C 如何处理程序终止后的变量声明?

How does C handle variable declarations after program terminations?

我知道 C 不提供自动垃圾回收,这意味着每当有人使用 mallocrealloccalloc 分配内存时,他们将不得不在最后释放它们的程序,所以不会有泄漏。但是,我还没有看到有人释放在 say 变量声明期间分配的内存。例如,如果我做类似 int x = 10; 的事情,某个地方正在分配一个内存来保存值 10(我什至可以通过 &x 看到地址),但我从来没有在我的程序中释放这样的内存,但似乎没有内存泄漏(如果我使用 valgrind 来检查),这让我认为 C 有某种垃圾收集,或者这是一个不同的故事?

whenever someone allocates memory [...] they will have to free them at the end of the program

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

if I do something like int x = 10;

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

leads me to think that C have some kind of garbage collection

C 中没有垃圾回收。

在一个函数returns之后,调用函数调整堆栈指针。由于调整了堆栈指针,位于堆栈上的调用函数的任何局部变量不再被视为堆栈的一部分。

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

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

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

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

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

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

C 当前没有任何类型的 dynamically-allocated 内存垃圾收集系统的原因有很多。该语言的未来版本 可能 创建一个新的 garbage-collected 动态内存存储 class,但我认为这不太可能。

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

I know that C doesn't offer automatic garbage collection which means whenever someone allocates memory using malloc, realloc or calloc, they will have to free them at the end of the program so there will be no leak.

事实并非如此。

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

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

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

if I do something like int x = 10;, somewhere a memory to hold the value 10 is being allocated

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

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

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

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

int x = 10;

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

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