在C语言中,char*如何被释放?

10

所以我在读一些课程代码时对C语言中变量是如何释放的有些困惑。

给定的代码是:

#include<stdio.h>
main () {
    int n=0; 
    char *p = "hello world";
    while (*p!= 0)  {   // *p != '\0';
        putc(*p, stdout);
        p++;
    }
    printf("\np = %d", *p);
    printf("\np = %d\n", p);
}

所以我知道你不需要为 char* 释放任何内存,因为没有进行malloc分配,但我不明白为什么这段代码不会泄漏内存...如果你对字符串进行指针递增操作,从而将指针移动到下一个内存块(1字节),那么你不是失去了最初的引用和所有被你递增过的引用点吗?如果没有引用点,这段内存怎么能被回收,除非在这种操作发生之前由编译器保存了一个引用点。我希望能在这方面得到一些见解!


3
当变量超出作用域时,它们会被释放。堆内存在有人释放它时才会被释放。编译代码中的静态字符串(即字面常量)会一直存在于程序运行期间。你担心的空间是一个字面字符串。 - Hot Licks
7个回答

13
释放内存的任务由该内存的所有者承担。仅因为您有指向某个内存区域的指针并不意味着您“拥有”该内存,因此也不意味着您负责释放它。
字符串文字“hello world”是具有静态存储期的对象。它保存在静态内存中。静态内存始终由运行时环境拥有。运行时环境知道存储在静态内存中的数据。运行时环境知道何时必须对其进行释放(这很容易,因为静态内存基本上“永不”被释放-只要您的程序运行,它就存在)。
因此,您使用指针p并没有真正拥有静态区域中的任何内存。您只是碰巧使用指针p引用该内存。您不需要担心该内存的释放。当时机成熟时(即程序结束时),它将被正确释放,并且无需您和指针p的帮助即可完成。您可以随意更改指针p,可以使其指向完全不同的内存位置,或者可以毫不保留地丢弃它。简单来说,没有人关心您的指针p。
在C程序中您唯一可能“拥有”的内存是您自己使用malloc(或其他动态内存分配函数)分配的内存。因此,您必须记住最终调用free来释放您自己分配的内存(并且必须确保知道malloc返回的原始值以传递给free)。所有其他类型的内存(例如静态或自动)都不是您所拥有的,这意味着释放它不是您的责任,并且保留原始指针值完全没有必要。

12
你没有泄漏任何内存,因为你没有动态分配任何内存。内存泄漏来自未释放动态分配的内存。局部分配的内存(如char *p)或静态分配的内存(如字符串"hello world"最初指向的p)不能导致泄漏。

这是因为它们在未来的 '}' 超出范围,对吧? - Plasmarob
1
对于块作用域变量,它们会在未来的 } 处超出作用域;对于文件作用域或全局作用域变量,它们会在程序退出时由操作系统释放。(或者我应该说“具有内部或外部链接的文件作用域变量”?也许这更准确。) - Jonathan Leffler

3
您没有动态分配任何新的内存,因此不需要释放它。

2
字符串字面值"hello world"是程序本身的一部分。当计算"hello world"表达式时,程序实际上获得了指向自身的一部分的指针。在程序运行时,该内存不能被释放;这等同于在程序中留下“空洞”。该内存与程序本身的生命周期相同。
在C语言中,程序员不需要管理与程序具有相同生命周期的内存:这由启动程序的环境进行外部管理(或误管理),并在程序终止时处理后果。
当然,内存仍然需要被管理;只是责任不在C程序身上。(至少,在提供托管实现的C语言环境中不是这样。对于某些嵌入式系统的规则可能会有所不同!)
在嵌入式程序中,字符串字面值(以及程序的其余部分)实际上可以存在于ROM中。因此可能真的没有什么需要清理的。该指针是一个地址,它指向芯片上的某个永久位置(或几个芯片)。

0
简而言之:因为程序本身很短。你可以在其中进行任何malloc,但实际上不会发生任何泄漏,因为所有内存都会在进程结束时立即返回给操作系统。在你的例子中,泄漏并没有发生,因为变量p指向一个字面字符串,该字符串位于内存的数据段中(即它是一个常量,写入可执行文件中)。这种内存无法被释放,因为其空间是固定的。实际上,这不是问题是错误的,因为一个非常大的可执行文件,其中有许多大的常量,可能具有显着的内存占用,但无论如何这不被称为泄漏,因为内存使用可能很大,但它不会随时间增加,这是内存泄漏的主要问题(因此称为泄漏)。

我觉得在不解释原因的情况下给踩不礼貌。 - Giulio Franco
在现代通用操作系统中,当程序终止时,所有的内存泄漏都会被清理掉。内存泄漏是由于程序动态分配了内存,但在终止之前没有释放该内存所导致的。(在一些旧的操作系统上,如原始的AmigaOS,以及我认为较早版本的Mac OS(9版本及更早版本)也可能存在一些脆弱性。在嵌入式操作系统中,甚至在现代的当前版本中,规则可能会有所不同。)你的回答暗示int main(void){void*vp=malloc(10);}不会发生内存泄漏;实际上它会发生内存泄漏! - Jonathan Leffler
是的,好的... 我指的是特定情况,假设是Win/Linux/OSX。如果你使用了malloc,就应该使用free释放它,在现代操作系统中也应该如此,因为你或其他人可能决定将你的程序嵌入并在一个循环中运行你的主函数,从而将不泄漏变成泄漏。 - Giulio Franco

0

当您在本地声明变量时,编译器知道每个变量需要多少空间,并且在运行程序时,每个本地变量(以及每个函数调用)都被放置在堆栈上。在返回语句之后(或void函数的}括号),每个本地变量都从堆栈中弹出,因此您不必释放它。

当您调用new运算符(或纯C中的malloc)时,编译器不知道数据的大小,因此内存在堆上运行时分配。

我解释这个原因是因为每当您调用new或malloc(或calloc)时,您有责任释放您不再使用的内存。


局部变量的释放可以解释为什么您不需要释放指针本身,但它并不能真正解释为什么您不必分配指针所指向的内存。 - templatetypedef

0
除了其他答案之外,在C中递增指针不会创建或丢失“引用”,也不会导致指针所指向的内存的任何复制或其他更改。在这种情况下,指针只是一个恰好指向静态分配的内存区域的数字。
递增指针不会改变指针曾经指向的字节。 "H"仍然存在。但是程序现在认为字符串以“e”开头。(它知道字符串的结尾在哪里,因为按照惯例,C中的字符串以null结尾。)
没有检查指针是否指向您认为应该指向的内容,或者是否指向任何有效区域。程序本身可能会丢失内存区域(例如,如果您设置p = 0),或者将p递增到字符串的末尾之外,但编译器不会跟踪此事(或防止它),也不会释放用于字符串的内存。
如果将指针更改为指向内存中的“错误”位置,则会发生有趣(坏)的事情-例如页面故障、堆栈溢出和核心转储。

如果您正在动态分配内存并且在不保存对初始节点的引用的情况下增加指针,则最有可能会丢失引用。如果您正在实现链表,并且只是决定先将头部向前移动几个插槽而不释放节点,那么我不认为这不构成对内存块的引用丢失,因为您将无法释放它并且会导致后续泄漏...在这种情况下,我们使用静态分配的内存,当其生命周期结束时从堆栈中弹出,但不是一般情况。 - PandaRaid
是的,如果你遍历一个链表(且该链表没有反向/父指针),并且不断地向该链表添加元素,那么可能会出现内存泄漏(无法释放内存)的情况。通过修改指针,你可以“丢失”任何内存“引用”。但是泄漏意味着有一个不断增长的(动态)内存池,通常是从malloc()中获得的。 - david25272

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接