释放指向已释放内存的指针

4

我想知道将指针转换为另一种类型后,是否可以使用free()函数释放它。

例如,如果我这样做:

char *p = malloc (sizeof (int));
int *q = (int *)p;
free (q);

在gcc (-Wall)中我没有收到警告。

在linux上,free的man页面说在一个不是由malloc()、calloc()或realloc()返回的指针上调用free是非法的。但如果指针在之间被强制转换成另一种类型,会发生什么呢?

我提出这个问题是因为我读到C标准并不要求不同的指针类型(如int*和char*)具有相同的大小,但我不明白这是如何可能的,因为它们都需要转换为void*才能调用malloc/free函数。

上述代码是否合法?

4个回答

6

可能是安全的,但并不完全保证安全。

在大多数现代系统中,所有指针(至少所有对象指针)具有相同的表示形式,从一个指针类型转换为另一个只是重新解释构成表示的位。但C标准并不保证这一点。

char *p = malloc (sizeof (int));

这将给您一个指向sizeof(int)字节数据的char*指针(假设malloc()成功)。

int *q = (int *)p;

这将char*指针转换为int*指针。由于intchar更大,因此int*指针可能需要较少的信息来指示它所指向的内容。例如,在一个以字为单位的机器上,int*可能只需指向一个字,而char*必须包含一个字指针和一个指示它所指向的字节在该字中的偏移量。(我曾经在一个名为Cray T90的系统上工作过,它就是这样工作的。)因此,从char*int*的转换实际上可能会丢失信息。

free (q);

由于free()接受一个void*类型的参数,因此参数q会从int*隐式转换为void*。在语言标准中没有保证将char*指针转换为int*,然后将结果转换为void*会得到与直接将char*转换为void*相同的结果。

另一方面,由于malloc()总是返回一个指向任何类型的正确对齐的指针,即使在int*char*具有不同表示的系统上,它也不太可能在这种特殊情况下引起问题。

因此,您的代码实际上肯定可以在您可能正在使用的任何系统上正确工作,并且即使在您可能从未见过的奇异系统上也非常非常可能正常工作。

尽管如此,我建议编写可以轻松证明为正确的代码,方法是保存原始指针值(类型为char*)并将其传递给free()。如果需要几段文本来证明您的代码几乎肯定是安全的,则简化您的假设可能会为您节省长期的精力。如果程序中出现其他问题(相信我,会出问题),则最好少担心一个可能的错误源。

您的代码存在更大的潜在问题是您没有检查malloc()是否成功。如果您引用了分配的内存,则在它失败时您不会做任何失败的事情(转换和free()调用可以使用null指针),但您可能会遇到麻烦。

更新:

您问您的代码是否合法;您没有问它是否是实现您正在做的最好方法。

malloc()返回一个void*结果,可以通过赋值隐式地转换为任何对象类型的指针。 free()需要一个void*参数;您传递给它的任何对象类型的指针参数都将被隐式转换为void*。这个往返转换(void*something_else*void*)是安全的。除非您进行某种类型的强行转换(将相同的数据块解释为两种不同的类型),否则无需进行任何转换。

而不是:

char *p = malloc (sizeof (int));
int *q = (int *)p;
free (q);

你可以直接编写:

int *p = malloc(sizeof *p);
...
free(p);

请注意在调用malloc()函数时,参数中使用了sizeof *p。这样可以不必显式地引用指针变量p的类型,而直接得到它所指向的对象的大小。这避免了意外使用错误类型的问题。
double *oops = malloc(sizeof (int));

编译器可能不会对此发出警告。

感谢您的回复(我没有检查malloc()返回值,只是因为我想要一个小例子)。假设我有一个void指针,我将其分配为int或double(并且在实际解引用之前,我总是将其强制转换为正确的类型),当我完成后,我可以将其作为void指针释放,而无需将其强制转换为其实际类型吗? - Nicolas Grebille
@NicolasGrebille:这种方式:double *ptr = malloc(sizeof * ptr);…free(ptr);是安全且最好的方法。除非您在做一些非常奇怪的事情,否则您不应该需要将指针与malloc()free()一起使用进行强制转换; 隐式转换将恰好完成您所需的操作。 - Keith Thompson
是的,但如果我在结构体中有一个void指针,它可以被分配为double或int*,那么我需要自己进行强制类型转换。 - Nicolas Grebille
@NicolasGrebille:是的,有些情况下你确实需要声明一个对象(例如结构体成员)为void*,并且可以用它来指向不同类型。但是在void*与其他指针类型之间的转换并不需要强制类型转换;只需将值分配给指针变量即可进行隐式转换。 - Keith Thompson
所以如果我理解你的意思正确,任何指针类型到/从void的转换都保证不会丢失信息,但其他指针类型则不是这种情况?我的意思是,对于你的Cray T90示例,从char到int的转换与从char到void再到int的转换并不相同? - Nicolas Grebille

3

是的,指针没有改变,转换仅仅是编译器如何解释一堆位的方式。

编辑:malloc调用返回内存中的地址,即一个32(或64)位数字。 转换只告诉编译器如何解释存储在该地址处的值,它是浮点数、整数、字符串等等,当您对地址进行算术运算时,应该以多大的单位步进。


2
你确定吗?(void*)((int*)p)(void*)p对于任何p来说是相同的,有任何保证吗?据我所知,唯一的保证是你可以将T *转换为void *并返回相同的指针,但这并没有说明另一侧的 - Kerrek SB
@KerrekSB 为什么会不同呢?我们甚至没有进行指针算术运算。 - user529758
@H2CO3:为什么不可能不同呢?这正是我的问题。 - Kerrek SB
只要我不对指针进行解引用或递增操作,那么强制类型转换就可以被编译成无操作吗?我不明白的是为什么它可以对void合法,而对其他任何指针类型都不合法。如果我将一个int强制类型转换为void,然后转换为char,再转换为void,最后转换回int,这样做是否合法?我不理解如果所有指针类型在实现中都不相同,这怎么可能发生。 - Nicolas Grebille
malloc函数会在内存中分配一个地址,即一个32位的数字。你可以告诉编译器那里存储的值是浮点数/整数/字符串等等。但这并不会改变地址,只是改变了对地址进行算术运算的方式。 - Martin Beckett
1
是的,这就是我想的,但我在这里学到了这并不总是正确的:https://dev59.com/_XM_5IYBdhLWcg3ww2AQ。那么如果char*和int*没有相同的表示,怎么办? - Nicolas Grebille

3

是的,这是合法的。 free() 接受一个无类型指针 (void*),所以类型并不重要。只要传递给它的指针是由 malloc/realloc/calloc 返回的,它就是有效的。


0

这段代码是合法的,但并不是必要的。由于指针只指向数据存储的地址,因此无需分配空间,也无需随后释放。


2
我怀疑发帖者是在编写一个最小示例,而不是分配单个int的内存。 - Martin Beckett

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