无法在C中释放常量指针

92

我如何释放一个const char*?我使用malloc分配了新的内存,在尝试释放它时,总是会收到 "不兼容的指针类型" 错误。

引起这个错误的代码大致如下:

char* name="Arnold";
const char* str=(const char*)malloc(strlen(name)+1);

free(str); // error here

6
基本上是一个C语言问题。free()的签名应该是void free(const void* p);。在C++中已经被修复(使用delete)。 - MSalters
9
@James Kingsbery:interned strings,或许可以这么说:一旦您初始化了字符缓冲区,之后将其视为const char*是很有意义的。提问者真的需要我们的批准才能遇到问题吗?;-) - Steve Jessop
7
然而,这根本没有任何意义。一旦内存被分配给str,就无法通过str来更改它,这意味着当malloc()抓取它时,内存中包含的内容是永久的。不加转换就无法将名称复制到其中。 (此外,将字符串文字分配给char *不好,因为尝试修改字符串文字是未定义的行为。我认为您只是混淆了const。) - David Thornley
4
你得到的 const char * 可能会在填充内容后从 char * 转换而来,例如从 const char* foo() { char* s = malloc(...); strcpy(s, ...); return s; } 这样的函数中。 - musiphil
在C语言中无法释放常量指针:在给定的代码片段中,const指针在哪里? - haccks
显示剩余4条评论
12个回答

117

有几个人发布了正确的答案,但出于某种原因他们一直在删除它。你需要将其转换为非const指针;free使用的是void*,而不是const void*

free((char*)str);

29
能运行,但是将const转换为非const是代码异味的一种征兆。 - el.pescado - нет войне
16
为什么要将指针转换为char类型?为什么不直接使用free((void *) str)来释放内存? - philant
3
@el. 当然可以,但这似乎有些不必要。我不会保留两个指向同一位置的指针,一个是const而另一个不是,只是为了在最后释放非const的那个而不使用邪恶的强制转换。 - Michael Mrozek
61
我记得在Linux内核中有一个内存释放函数接受一个const指针作为参数,有人问Linus为什么,他解释说这个函数既从概念上又从实践上都不会修改指向的值,它只是使用该指针查找内存块并释放它。我认同他的观点,因此认为free()函数的规范是不正确的。但遗憾的是,它已经成为标准了。 - rmeador
20
如果"释放"的概念发生了改变,那么声明一个const int,并离开它被声明的作用域,是否可以?这样会“释放”自动变量,释放资源并使指向它的指针无效。只是free需要非const参数是一种怪异现象,并不是来自上级的命令。在极少数情况下,如果你的指针只有一个非const的操作就是释放,那么实际上使用const指针(然后进行强制类型转换)可能比非const指针更有益,因为你可能会意外地修改非const指针。 - Steve Jessop
显示剩余10条评论

30
你的代码被反转了。 这个:
char* name="Arnold";
const char* str=(const char*)malloc(strlen(name)+1);

应该看起来像这样:

const char* name="Arnold";
char* str=(char*)malloc(strlen(name)+1);
const 存储类型告诉编译器,一旦分配(动态或静态),您不打算修改内存块。释放内存是一种修改。请注意,您不需要转换 malloc() 的返回值,但这只是一个附注。
动态分配内存(根据 name 的长度)并告诉编译器您没有使用它的意义不大。请注意,"使用" 意味着向其中写入某些内容,然后(可选)稍后释放它。
将存储类型强制转换为其他类型不能解决您最初反转存储类型的问题 :) 它只是使警告消失了,而那个警告试图告诉您一些信息。
如果代码被反转(应该是这样),free() 将按预期工作,因为您实际上可以 修改 分配的内存。

8
OP问如何释放指向const限定类型的指针-附加的代码示例反映了他的问题,而您的解释与之相矛盾。顺便说一句,指向类型的const限定符并不影响或表达任何关于对/使用分配对象的意图,它只影响通过此指针执行的操作。一旦丢弃指向const的限定符,就可以修改已分配的对象。 - Dror K.
1
@DrorK。尽管如此,这是最有用的答案,至少对我来说是这样,因为我犯了与OP相同的错误。大多数遇到这个问题的人可能也同样困惑,所以我认为这实际上是最好的答案。 - Michael Dorst
1
我同意Michael Dorst的观点,这是最好的答案。对于StackOverflow的规定如此严格地按字面意思解释,告诉别人如何“更好地错误编程”没有任何意义(请参见其他答案和Puppy的答案的评论)。然而,使用“free”会使这个答案更完整。 - Poikilos

6

将指针分配到常量是没有意义的,因为您将无法修改其内容(除非使用丑陋的技巧)。

尽管如此,gcc仅对以下内容发出警告:

//
// const.c
//

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

int main(void)
{
    const char *p = malloc(100);

    free(p);
    return 0;
}

$ gcc -Wall const.c -o const
const.c: In function ‘main’:
const.c:8: warning: passing argument 1 of ‘free’ discards qualifiers from pointer target type
$ 

你使用的是哪个编译器?

16
这里有一个情况,你可能想要释放一个指向常量的指针:char const* s = strdup("hello"); free(s); - bobbogo
2
@bobbogo:是的,虽然很难想象为什么你首先要创建一个字符串字面量的常量副本。 - Paul R
13
你可能希望复制一个即将被库代码释放或修改的字符串。你不会修改你的副本,因此将其标记为const。 - bobbogo

4

有些情况下,您可能需要释放const*的指针。但是,除非在同一个函数中分配/赋值,否则最好不要这样做,否则可能会破坏程序。请看下面的代码示例,这是一个真实的例子。我在函数声明中使用const来说明我没有更改参数的内容。但是,它被重新分配(使用strdup函数)为一个小写的副本,需要进行释放。

char* tolowerstring(const char *to_lower)
{
    char* workstring = strdup(to_lower);
    for(;workstring != '\0'; workstring++)
        *workstring = tolower(workstring);
    return workstring;
}

int extension_checker(const char* extension, const char* to_check)
{
    char* tail = tolowerstring(to_check);
    extension = tolowerstring(extension);

    while ( (tail = strstr( tail+1, extension)) ) { /* The +1 prevents infinite loop on multiple matches */
        if ( (*extension != '.' ) && ( tail[-1] != '.'))
            continue;
        if ( tail[strlen(extension)] == '\0') {
            free(tail);
            free( (char*) extension);
            return 1;
        }
    }
    free(tail);
    free( (char *) extension);
    return 0;
}

3

将malloc分配的指针转换为const是没有意义的。任何接受const指针的函数都不应该负责释放传递给它的内存。


struct foo { const char *bar; ... } 这样的代码怎么处理呢?这表示指向 foo->bar 的内存应该被视为不可变的(而 struct foo 的其他成员可能是可变的)。这对于保证程序的正确性非常有用。但当对象首次初始化时,可能需要对 bar 进行 malloc。如果要释放这样的对象,则需要一种方法来释放 bar - uncleremus
@uncleremus 这是一个关于从哪个角度来看的问题。foo->bar指向的内存应该被接收者视为不可变的。但是,foo->bar指向的内存对于拥有它的人来说不应该被视为不可变的,因为他们需要释放该内存,这绝对构成了一种变异。因此,您需要为自己保留一个可变版本,同时向其他人展示一个不可变的接口。 - Puppy
你是否建议使用 union?我猜 struct foo { union { const char *bar; char *__bar; }; } 可以工作。 - uncleremus
@uncleremus,我建议您应该有两个完全不同的结构体,一个用于内部使用,另一个用于外部使用。 - Puppy
即使是“所有者”代码也可能只需要在一个地方(析构函数)修改bar元素,而可能会传递foo对象并可能修改其他成员。即使在拥有结构体的代码中,使用const来保护bar免受错误修改也是可取的。在析构函数中只需要强制转换掉const即可。 - uncleremus

2
几个答案建议只是将 const 转换为 char*。但正如 el.pescado 上面所写的,
casting const 到非 const 是代码味道的一种症状。
有编译器警告来防止这种情况,例如gcc中的 -Wcast-qual,我发现这非常有用。如果您确实有一个释放 const 指针的有效情况(与许多人在此处写的相反,nlstd指出了一些有效情况),您可以定义一个宏来完成此操作,如下所示:
#define free_const(x) free((void*)(long)(x))

这至少适用于gcc。双重转换使逻辑 -Wcast-qual 不会将其检测为“去除const的强制转换”。不用说,这个宏应该小心使用。实际上,它只应用于在同一函数中分配的指针。


5
应该用intptr_t替换long。 - Good Person

1
我认为真正的答案是,free应该接受一个const指针参数,并且NULL应该被定义为一个const指针。这似乎是标准中的一个错误。释放一个const指针应该如下实现:
free(p);
p = NULL;

我不明白在这种情况下编译器如何生成错误的代码,因为常量指针p已经不再可访问,所以它所指向的对象是否是const、有效或其他都无关紧要。它是const类型,所以寄存器或其他任何地方都不可能有脏副本。将一个const指针设置为另一个值是有效的,而该值为NULL并不重要,因为先前的值已不可访问。

1

我可能错了,但我认为问题在于const。像这样将指针转换为非常量:

free((char *) p);

因为使用 const,你就是在说:不要改变这个指针所指向的数据


4
free函数不会改变指针本身,它释放指针所指向的内存块。这是语言规范中的一个错误。free函数应该清晰地接受一个常量指针。 - Axel Gneiting
6
@Axel const 表示你不能更改存储对象的内容,而不是指针实际的值...释放所指向的内存是非常重大的变化!(顺便说一句,认为规范是错误的[已经错误了30多年],突然发现你是正确的,而所有审查委员会成员都是错误的,似乎有点自负,不是吗?) - fortran
10
@fortran: 这不是傲慢的表现,而只是意见上的普通分歧。在C++中,可以对“const char*”使用“delete”,因此,如果这是一个大争议,则两个标准作者中的一个必须是错误的。实际上,我认为这并不重要——强制转换以释放指针并不是什么危机。 - Steve Jessop
3
const char* 表示被指向的内容是一个常量,不可更改。它并 没有 意味着指针本身不能更改。 - JeremyP
4
@Axel Gneiting:我从未说过指针会被更改。const表示此位置上的数据不应更改。但是,如果您释放内存,则可以覆盖此位置上的数据,因此更改数据。 - Felix Kling
显示剩余3条评论

0
如果您正在讨论纯C并且完全控制内存分配,您可以使用以下技巧将(const char *)转换为(char *),这不会在编译器中给您任何警告:
const char *const_str = (const char *)malloc(...);
char *str = NULL;

union {
  char *mutable_field_p;
  const char *const_field_p;
} u;

u.const_field_p = const_str;
str = u.mutable_field_p;

现在你可以使用free(str);来释放内存。
但是要小心,这是非常危险的操作,只能在严格控制的环境下使用(例如分配和释放字符串的库,但不允许用户修改它们)。否则,当有人将编译时的"STRING"传递给你的free函数时,你的程序将崩溃。

2
你为什么要使用这个丑陋的hack和毫无意义的联合体,而不是直接使用一个简单的强制类型转换来改变const属性呢? - underscore_d
我没有测试过,但这个答案完全符合C标准,并且不会触发编译器的警告,甚至包括gcc的-Wcast-qual。而且它在像CHERRI这样的指针来源硬件上也能正常工作,只要你不进行指针-<足够大小的整数>-指针的转换:这取决于优化器是否注意到它始终是一个NOP,并且不会破坏处理器保留的来源元数据。 - anonymous
编辑窗口丢失了。我刚在gcc 12.2上进行了测试,这个答案实际上是更好的,原因如我在之前的评论中指出的那样。它避免了所有警告,甚至-Wcast-qual,而且它不会因为试图将整数转换为指针而搞乱指针来源。另一种选择是使用#pragma来消除更严格的警告,我想... - anonymous

-1
如果您查看free函数的签名,您会发现free函数总是将void* ptr作为参数,因此您需要将其转换为适当的类型,即free((void *)str)。 free不允许直接释放const指针,因此您需要将其转换为非const类型。

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