我要转换 malloc 的结果吗?

这个问题 ,有人建议意见 ,我应该投的结果malloc ,即

int *sieve = malloc(sizeof(int) * length);

而不是:

int *sieve = (int *) malloc(sizeof(int) * length);

为什么会这样呢?

答案

没有投放结果,因为:

  • 这是不必要的,因为在这种情况下, void *会自动安全地提升为任何其他指针类型。
  • 它给代码增加了混乱,强制转换不是很容易阅读(特别是如果指针类型很长的话)。
  • 它使您重复自己,这通常是不好的。
  • 如果您忘记包含<stdlib.h>它可能会隐藏错误。这可能会导致崩溃(或更糟,而不是在代码中的一些完全不同的部分会导致崩溃,直到后来的方式)。考虑一下如果指针和整数的大小不同会怎样?那么您将通过投射隐藏警告,并且可能会丢失返回地址的一部分。注意:从 C99 开始,隐式函数不再使用 C,这一点不再相关,因为没有自动假设未声明的函数返回int

为了澄清起见,请注意我说的是 “您不要投”,而不是 “您不需要投”。我认为,即使您没做错,也不能包括演员表。这样做没有任何好处,但是有很多潜在风险,包括强制转换说明您不知道这些风险。

还要注意,正如评论员指出的那样,以上内容是关于直接 C 而不是 C ++ 的。我非常坚信 C 和 C ++ 是独立的语言。

要进一步添加,您的代码不必要地重复可能导致错误的类型信息( int )。最好取消引用用于存储返回值的指针,以将两者 “锁定” 在一起:

int *sieve = malloc(length * sizeof *sieve);

这也将length移到前面以增加可见度,并用sizeof删除多余的括号; 当参数为类型名称时才需要使用它们。许多人似乎不知道(或忽略)这一点,这会使他们的代码更加冗长。请记住: sizeof不是函数! :)


虽然在某些极少数情况下将length移到最前面可能会增加可见性,但也应注意,在一般情况下,将表达式写为:

int *sieve = malloc(sizeof *sieve * length);

在这种情况下,由于先保留sizeof ,因此可以确保至少用size_t数学进行乘法。

比较: malloc(sizeof *sieve * length * width) vs. malloc(length * width * sizeof *sieve)widthlength是小于size_t类型时,秒可能会溢出length * width

在 C 语言中,您无需malloc的返回值。 malloc返回的指向 void 的指针自动转换为正确的类型。但是,如果希望代码使用 C ++ 编译器进行编译,则需要进行强制转换。社区中首选的替代方法是使用以下方法:

int *sieve = malloc(sizeof *sieve * length);

如果您更改了sieve的类型,这还使您不必担心更改表达式的右侧。

正如人们指出的那样,演员表很糟糕。特别是指针转换。

进行强制转换是因为:

  • 它使您的代码在 C 和 C ++ 之间更易于移植 ,并且正如 SO 经验所表明的那样,许多程序员声称他们实际上是在用 C ++(或 C 加本地编译器扩展)编写时才用 C 编写。
  • 否则, 可能会隐藏一个错误 :请注意所有 SO 示例,它们混淆了何时编写type *type **
  • 它使您无法注意到您未能#include适当的头文件的想法错过了树木的森林 。就像说 “不必担心您没有要求编译器抱怨没有看到原型的事实 - 讨厌的 stdlib.h 是要记住的真正重要的事情!”
  • 它迫使进行额外的认知交叉检查 。它将(指定的)所需的类型放在要对该变量的原始大小执行的算术旁边。我敢打赌,您可以进行 SO 研究,该研究表明在进行强制转换时, malloc()错误的捕获速度要快得多。与断言一样,揭示意图的注释会减少错误。
  • 以机器可以检查的方式重复自己通常是个主意。实际上,这就是一个断言,而对 cast 的这种使用就是一个断言。断言仍然是我们使代码正确的最通用技术,因为 Turing 早在多年前就提出了这个想法。

如前所述,C 不需要,但 C ++ 不需要。如果您出于某种原因打算使用 C ++ 编译器来编译 C 代码,则可以改用宏,例如:

#ifdef __cplusplus
# define NEW(type, count) ((type *)calloc(count, sizeof(type)))
#else
# define NEW(type, count) (calloc(count, sizeof(type)))
#endif

这样,您仍然可以以非常紧凑的方式编写它:

int *sieve = NEW(int, 1);

它将针对 C 和 C ++ 进行编译。

维基百科

铸造的优势

  • 包括强制转换可能允许 C 程序或函数编译为 C ++。

  • 强制转换允许 1989 年前的 malloc 版本最初返回 char *。

  • 如果目标指针类型发生更改,则强制转换可以帮助开发人员识别类型大小不一致的地方,尤其是如果声明的指针远离 malloc()调用时(尽管现代编译器和静态分析器可以在不需要强制强制转换的情况下警告此类行为)。

铸造的缺点

  • 在 ANSI C 标准下,强制转换是多余的。

  • 添加强制类型转换可能会掩盖未能包含标头stdlib.h 的失败,在标头中找到了 malloc 的原型。在没有 malloc 原型的情况下,该标准要求 C 编译器假定 malloc 返回一个 int。如果没有强制转换,则在将此整数分配给指针时会发出警告;否则,将发出警告。但是,使用强制转换时,不会产生此警告,从而隐藏了错误。在某些体系结构和数据模型上(例如 64 位系统上的 LP64,其中 long 和指针是 64 位,而 int 是 32 位),此错误实际上可能导致未定义的行为,因为隐式声明的 malloc 返回 32 - 位值,而实际定义的函数返回 64 位值。根据调用约定和内存布局,这可能会导致堆栈崩溃。在现代编译器中,此问题不太可能被忽略,因为它们会统一生成警告,提示已使用未声明的函数,因此仍会出现警告。例如,GCC 的默认行为是显示一条警告,显示 “内置函数的不兼容隐式声明”,而不管是否存在强制转换。

  • 如果指针的类型在其声明处更改,则可能还需要更改调用 malloc 并强制转换的所有行。

尽管不进行强制转换的 malloc 是首选方法,并且大多数有经验的程序员都选择它 ,但是您应该使用任何了解问题的方法。

即:如果需要将 C 程序编译为 C ++(尽管它是一种独立的语言),则必须强制转换 use malloc的结果。

在 C 中,您可以将 void 指针隐式转换为任何其他类型的指针,因此不需要强制转换。使用一个可能会向不经意的观察者暗示需要某个原因的某些原因,这可能会引起误解。

您不会转换 malloc 的结果,因为这样做会给您的代码增加毫无意义的混乱。

人们使用 malloc 结果的最常见原因是因为他们不确定 C 语言的工作方式。这是一个警告信号:如果您不知道特定语言机制的工作原理, 请不要猜测。查找它或询问堆栈溢出。

一些评论:

  • 无需显式强制转换即可将空指针转换为任何其他指针类型或从任何其他指针类型转换为空指针(C11 6.3.2.3 和 6.5.16.1)。

  • 但是,C ++ 不允许在void*和其他指针类型之间进行隐式转换。因此,在 C ++ 中,强制转换将是正确的。但是,如果您使用 C ++ 编程,则应使用new而不是 malloc()。而且,您永远不要使用 C ++ 编译器来编译 C 代码。

    如果需要使用相同的源代码同时支持 C 和 C ++,请使用编译器开关来标记差异。不要试图用相同的代码来区分这两种语言标准,因为它们不兼容。

  • 如果 C 编译器由于忘记了包含标头而无法找到函数,则会出现有关此的编译器 / 链接器错误。因此,如果您忘记包含<stdlib.h> ,那不是什么大问题,那么您将无法构建程序。

  • 在遵循 25 年以上标准版本的古老编译器上,忘记包含<stdlib.h>可能会导致危险行为。因为在那个古老的标准中,没有可见原型的函数将返回类型隐式转换为int 。显式地从 malloc 转换结果将隐藏此错误。

    但这确实不是问题。您没有使用 25 年历史的计算机,那么为什么要使用 25 年历史的编译器呢?

在 C 语言中,您将获得从void*到任何其他(数据)指针的隐式转换。

现在不需要malloc()返回的值,但是我想补充一点,似乎没有人指出:

在古代,也就是在ANSI C提供void *作为指针的通用类型之前, char *是此类用法的类型。在这种情况下,强制转换可以关闭编译器警告。

参考: C 常见问题解答

强制转换malloc的结果不是强制性的,因为它返回void* ,并且void*可以指向任何数据类型。