在C语言中,为什么有些人在释放指针之前需要进行类型转换?

170

我正在处理一个老的代码库,几乎所有的free()函数调用都在其参数上使用了强制类型转换。例如:

free((float *)velocity);
free((float *)acceleration);
free((char *)label);

每个指针都是对应(匹配)类型的。我认为根本没有必要这样做。这是非常古老的代码,所以我想知道它是否是K&R(Kernighan和Ritchie)的一种方法。如果是这样,我实际上希望支持可能需要这些内容的旧编译器,因此我不想删除它们。

有使用这些强制转换的技术原因吗?我甚至没有看到使用它们的实用理由。在释放数据类型之前提醒自己数据类型的意义是什么?

编辑:这个问题不是另一个问题的副本。如果关闭投票者阅读了所有答案,他们会发现另一个问题是这个问题的特殊情况,这一点我认为是显而易见的。

后记:我将选定标记给予给出仍然需要执行此操作的答案;但是,关于它是ANSI C之前的习惯(至少是部分程序员之间的习惯)的答案似乎是我使用的原因。如果有两个选定标记可以提供,它们都会得到一个。许多人在这里提出了很好的观点。感谢您的贡献。


13
提醒自己在释放数据类型之前,有什么意义?也许是为了知道将要释放多少内存? - m0skit0
12
编译器不进行释放内存操作,操作系统会负责这个任务。 - m0skit0
20
“也许知道将释放多少内存?”并不需要知道要释放多少内存的类型。仅出于这个原因而进行强制转换是糟糕的编码风格。 - user694733
9
为了提高可读性而进行强制类型转换通常是不好的编码习惯,因为类型转换会改变类型的解释方式,并且可能掩盖严重的错误。当需要增加可读性时,最好使用注释。 - user694733
67
在恐龙漫步地球、编写编程书籍的古老时代,我相信在C语言标准化之前是没有void*这个概念的,只有char*。因此,如果你的考古发现代码将参数转换为free(),那么它必须要么来自那个时期,要么是由那个时代的生物编写的。虽然我找不到这方面的资料,但我还是选择保持沉默不作回答。 - Lundin
显示剩余20条评论
6个回答

173

如果指针是 const,则可能需要进行强制转换以解决编译器警告。以下是一个在未对 free 的参数进行转换时导致警告的代码示例:

const float* velocity = malloc(2*sizeof(float));
free(velocity);

编译器(gcc 4.8.3)提示:

main.c: In function ‘main’:
main.c:9:5: warning: passing argument 1 of ‘free’ discards ‘const’ qualifier from pointer target type [enabled by default]
     free(velocity);
     ^
In file included from main.c:2:0:
/usr/include/stdlib.h:482:13: note: expected ‘void *’ but argument is of typeconst float *’
 extern void free (void *__ptr) __THROW;

如果你使用 free((float*) velocity);,编译器就不会再抱怨了。


2
@m0skit0,这并不能解释为什么有人在释放之前要将其转换为float*。我尝试了在gcc 4.8.3中使用free((void *)velocity);。当然,这不会在一个古老的编译器上工作。 - Manos Nikolaidis
56
为什么需要动态分配常量内存?你永远也无法使用它! - Nils_M
35
这是一个简化的例子,目的是为了阐述一个观点。实际上,在一个函数中我分配非常量内存、赋值、强制类型转换为const指针并返回它。现在有一个指向预先分配的const内存的指针,需要有人来释放它。 - Manos Nikolaidis
2
这些子程序返回一个新分配的字符串,由stringValueP指向,您最终必须释放它。有时,您用于释放内存的操作系统函数被声明为以非常量指针作为其参数,因此因为stringValueP是指向const的指针。 - Carsten S
4
如果一个函数以const char *p作为参数并释放它,那么正确的做法不是在调用free之前将p强制转换为char *,而应该在最初声明它时不要声明为const char *p,因为它会修改*p,因此应相应地声明。如果它采用常量指针而不是指向常量的指针,如int *const p,则您无需进行强制转换,因为它实际上是合法的,因此可以正常工作而不需要进行转换。 - Ray
显示剩余11条评论

61

在预标准C中,没有 void* 只有 char*,因此您需要将所有传递的参数进行强制类型转换。如果您遇到古老的C代码,您可能会发现这样的转换。

类似问题的参考.

当第一个C标准发布时,malloc和free的原型从使用 char* 改为使用如今仍使用的 void*

当然,在标准C中,这样的类型转换是多余的,只会损害可读性。


24
为什么你要把参数在调用free函数时强制转换成与其本身类型相同的类型? - jwodder
4
“预标准”的问题就在于这一点:没有任何义务。人们只能指向K&R书籍作为标准,因为那是他们唯一拥有的东西。正如我们从K&R第二版中看到的几个例子,K&R本身对于参数到free的强制转换在标准C中的工作方式感到困惑(您不需要强制转换)。我没有读过第一版,所以我不能确定他们是否在80年代的预标准时代也感到困惑。” - Lundin
7
早期的C语言标准没有void*类型,但也没有函数原型,因此在K&R版本中即使不进行强制类型转换也无需将free函数的参数进行强制转换(假设所有数据指针类型使用相同的表示方式)。 - Ian Abbott
6
因为评论中已经提到了多个原因,所以我认为这个回答没有意义。 - R.. GitHub STOP HELPING ICE
4
我不认为这个答案能真正回答任何相关问题。原始问题涉及将类型转换为其他类型,而不仅仅是char*。在没有void的旧编译器中会有什么意义?这样的转换会实现什么目的? - AnT stands with Russia
显示剩余6条评论

34

以下是一个例子,如果没有强制类型转换,则 free 将失败:

volatile int* p = (volatile int*)malloc(5 * sizeof(int));
free(p);        // fail: warning C4090: 'function' : different 'volatile' qualifiers
free((int*)p);  // success :)
free((void*)p); // success :)

在 C 语言中你可以收到一个警告(我在 VS2012 中就遇到了这种情况)。但在 C++ 中,你将会得到一个错误。

除非是极少数的情况,类型转换只会使代码变得臃肿...

编辑: 我将值转换为 void* 而不是 int* 来演示错误。它将与将 int* 转换为 void* 隐式转换的方式相同。添加了 int* 代码。


请注意,在问题中发布的代码中,强制转换不是针对 void *,而是针对 float *char *。这些强制转换不仅是多余的,而且是错误的。 - Andrew Henle
1
问题实际上是关于相反的。 - m0skit0
1
我不理解这个答案,free(p) 失败的意义是什么?会导致编译器错误吗? - Codor
1
这些是很好的观点。显然,对于const限定符指针也是如此。 - Lundin
2
“volatile”自C语言标准化以来就存在,如果不是更早的话。它并不是在C99中添加的。 - R.. GitHub STOP HELPING ICE
显示剩余5条评论

33

旧版理由:1. 通过使用free((sometype*) ptr),代码明确指定指针应该被视为free()调用的一部分。当free()被替换为(自己编写的)DIY_free()时,显式强制转换非常有用。

#define free(ptr) DIY_free(ptr, sizeof (*ptr))

DIY_free()是一种在调试模式下进行运行时指针分析的方法,通常与DIY_malloc()配对使用,添加sentinels、全局内存使用计数等。在现代工具出现之前,我的团队已经使用这种技术多年了。但需要注意的是,被释放的项必须强制转换为其最初分配的类型。

  1. 鉴于追踪内存问题等耗费了许多时间,像将释放类型转换一样的小技巧有助于搜索和缩小调试范围。

现代:避免constvolatile警告,如Manos Nikolaidis@@egur所述。我想注意以下三个限定符: const, volatile, 和 restrict 的影响。

[编辑] 根据@R..评论,添加了char * restrict *rp2

void free_test(const char *cp, volatile char *vp, char * restrict rp, 
    char * restrict *rp2) {
  free(cp);  // warning
  free(vp);  // warning
  free(rp);  // OK
  free(rp2);  // warning
}

int main(void) {
  free_test(0,0,0,0);
  return 0;
}

3
“restrict” 当前并不是问题,因为它的作用范围只限于 rp 这个对象,而不是指向的类型。但如果你有一个 char *restrict *rp 的话,那就会有影响。 - R.. GitHub STOP HELPING ICE

18

这里是另一种替代假说。

我们被告知程序是在C89之前编写的,这意味着它不能通过某种与free原型不匹配的方式工作,因为在C89之前既没有const也没有void *,更没有函数原型stdlib.h本身就是委员会的一个发明。如果系统头文件打算声明free,它们会这样做:

extern free();  /* no `void` return type either! */

现在,这里的关键点是函数原型的缺失意味着编译器没有进行参数类型检查。它只应用默认的参数提升(与可变参数函数调用仍然适用的相同方式),就这样。确保每个调用点的参数与被调用方的期望相符的责任完全落在程序员身上。

然而,这并不意味着在大多数K&R编译器上需要将参数转换为free。像下面这样的一个函数:

free_stuff(a, b, c)
    float *a;
    char *b;
    int *c;
{
    free(a);
    free(b);
    free(c);
}

应该已经正确编译了。因此,我认为我们在这里得到的是一个为应对异常环境而编写的程序:例如,一个 sizeof(float *) > sizeof(int) 且编译器不会在调用指针时使用适当的调用约定,除非你在调用点处强制转换它们的环境。

我不知道是否存在这样的环境,但这并不意味着没有这样的环境。最有可能的候选者是20世纪80年代初期用于8位和16位微型计算机的简化“小C”编译器。我也不会惊讶地发现早期的Cray可能存在类似的问题。


2
我完全同意前半部分。第二部分是一个有趣且可信的猜测。 - chux - Reinstate Monica
我不知道是否存在这样的环境,但这并不意味着不存在。x86_64是其中一个,其中sizeof(float *) > sizeof(int),尽管我认为在x86_64上没有太多K&R编译器/代码。 - Maciej Piechotka
更大的问题可能是,在指向不同类型的指针在不同平台上具有不同的大小或表示方式的情况下,例如使用一个字中保存的字地址表示int*,但使用两个字表示char*。将未与void*char*向兼容的指针传递给free()的代码需要进行转换或原型以正确地运行。 - supercat

9

free函数只接受非const指针作为参数。因此,在处理const指针时,需要显式将其转换为非const指针。

无法在C中释放const指针


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