ChatGPT解决这个技术问题 Extra ChatGPT

我要转换 malloc 的结果吗?

this question 中,有人在 comment 中建议我应该转换 malloc 的结果。即,我应该这样做:

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

而不是:

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

为什么会这样?

演员是邪恶的。由于糟糕的编码实践,我看到了很多代码。每当您需要插入一个时,您应该问自己的第一件事是“这里出了什么问题”。一切都按原样宣布了吗?如果它不需要演员表,那么就宣布有些事情是错误的。如果你真的需要对 int 中的单个字节做一些低级的事情,或者考虑一个联合来访问它们。这将宣布他们就好了。根据经验,除非编译器抱怨,否则不要插入它们。然后避开它们。这个例子不会抱怨。 void 指针将提升为任何类型。
C++ 中的@HansLepoeter,这些对于 malloc 是必需的,这为我认为它有问题的观点提供了一些基础

k
klutt

TL;博士

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

有两个问题。强制转换并且您使用类型而不是变量作为 sizeof 的参数。相反,这样做:

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

长版

不;你不投射结果,因为:

这是不必要的,因为在这种情况下 void * 会自动且安全地提升为任何其他指针类型。

它增加了代码的混乱,强制转换不是很容易阅读(特别是如果指针类型很长)。

它会让你重复自己,这通常是不好的。

如果您忘记包含 ,它可以隐藏错误。这可能会导致崩溃(或者,更糟糕的是,直到稍后在代码的某些完全不同的部分中才会导致崩溃)。考虑如果指针和整数大小不同会发生什么;那么你通过强制转换隐藏了一个警告,并且可能会丢失你返回的地址。注意:从 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 数学来完成乘法。

比较:当 widthlength 的类型小于 size_t 时,malloc(sizeof *sieve * length * width)malloc(length * width * sizeof *sieve) 第二个可能会溢出 length * width


请考虑更新答案。演员表不再危险,重复自己不一定是坏事(冗余可以帮助捕捉错误)。
编译器发生了变化。最新的编译器会警告您缺少 malloc 声明。
@nm 好的。我认为假设在这里阅读的任何人都有特定的编译器是不好的。另外,自从 C11 以来,整个“隐式函数”概念都消失了,我不知道。不过,我看不出添加毫无意义的演员阵容有什么意义。您是否也这样做 int x = (int) 12; 只是为了让事情变得清晰?
@nm 如果显式转换 void 指针“帮助”解决了一个错误,您更有可能遇到未定义的行为,这意味着有问题的程序可能有一个更糟糕、尚未发现的错误,您还没有遇到过。有一天,在一个寒冷的冬夜,你下班回家发现你的 GitHub 页面上充斥着抱怨恶魔从用户的鼻子里飞出的问题报告
@unwind 即使我同意你的观点,(int)12 也没有可比性。 12 int,演员什么也不做。 malloc() 的 retval 是 void *,而不是转换为的指针类型。 (如果不是 void *。那么与 (int)12 的类比将是 (void*)malloc(…) 没有人在讨论。)
r
royhowie

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

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

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

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


@MAKZ 我认为 malloc(length * sizeof *sieve) 使它看起来像 sizeof 是一个变量 - 所以我认为 malloc(length * sizeof(*sieve)) 更具可读性。
malloc(length * (sizeof *sieve)) 更具可读性。恕我直言。
@Michael Anderson () 问题放在一边,请注意您建议的样式切换了顺序。考虑当元素计数像 length*width 一样计算时,在这种情况下保持 sizeof 优先确保乘法至少用 size_t 数学完成.比较 malloc(sizeof( *ptr) * length * width)malloc(length * width * sizeof (*ptr)) - 当 width,length 是比 size_t 更小的类型时,第二个可能会溢出 length*width
@chux 这不是很明显,但是答案已经过编辑,因此我的评论不太中肯-最初的建议是 malloc(sizeof *sieve * length)
C 不是 C++。假装他们是最终会导致混乱和悲伤。如果您使用的是 C++,那么 C 风格的转换也很糟糕(除非您使用的是非常旧的 C++ 编译器)。并且 static_cast>() (或 reinterpret_cast<>() )与 C 的任何方言都不兼容。
R
Ron Burk

你确实投了,因为:

它使您的代码在 C 和 C++ 之间更具可移植性,并且正如 SO 经验所表明的那样,许多程序员声称他们是在真正用 C++(或 C 加上本地编译器扩展)编写时用 C 编写的。

不这样做可能会隐藏一个错误:注意所有混淆何时编写类型 * 与类型 ** 的 SO 示例。

它使您不会注意到您未能#include 适当的头文件的想法错过了树木的森林。这就像说“不要担心你没有让编译器抱怨没有看到原型的事实——讨厌的 stdlib.h 是要记住的真正重要的事情!”

它强制进行额外的认知交叉检查。它将(所谓的)所需类型放在您为该变量的原始大小执行的算术旁边。我敢打赌,您可以进行一项 SO 研究,该研究表明 malloc() 错误在有演员表时被捕获得更快。与断言一样,揭示意图的注释可以减少错误。

以机器可以检查的方式重复自己通常是一个好主意。事实上,这就是断言,而这种使用 cast 就是断言。断言仍然是我们使代码正确的最通用技术,因为图灵在很多年前就提出了这个想法。


@ulidtko 如果您不知道,可以编写可编译为 C 和 C++ 的代码。事实上大多数头文件都是这样的,而且它们通常包含代码(宏和内联函数)。将 .c/.cpp 文件作为两者进行编译通常不是很有用,但一种情况是在使用 C++ 编译器编译时添加 C++ throw 支持(但在使用 C 编译器编译时添加 return -1; 或其他)。
如果有人在标头中内联 malloc 调用,我不会留下深刻印象,#ifdef __cplusplus 和 extern "C" {} 用于这项工作,而不是添加额外的演员表。
好吧,第 1 点是无关紧要的,因为 C != C++,其他点也是微不足道的,如果您在 malloc 调用中使用 变量char **foo = malloc(3*sizeof(*foo)); 如果完全证明:3 指针到 char 指针。然后循环,执行foo[i] = calloc(101, sizeof(*(foo[i])));。分配 101 个字符的数组,整齐地初始化为零。不需要演员表。就此而言,将声明更改为 unsigned char 或任何其他类型,您仍然很好
当我坚持我得到它时,它来了!很棒的答案。这是我第一次在 StackOverflow 中 +1 两个相反的答案! +1 不,你不施放,+1 是的,你施放!哈哈。你们太棒了而对于我和我的学生,我下定了决心:我确实是演员。学生犯的那种错误在选角时更容易被发现。
@Leushenko:以无法通过机器或本地检查验证的方式重复自己是不好的。以可以通过这种方式验证的方式重复自己并不那么糟糕。给定 struct Zebra *p; ... p=malloc(sizeof struct Zebra);,malloc 无法避免重复有关 p 类型的信息,但是如果一种类型发生变化而另一种类型没有发生变化,编译器和本地代码检查都不会检测到任何问题。将代码更改为 p=(struct Zebra*)malloc(sizeof struct Zebra);,如果转换类型与 p 不匹配,编译器将发出警告,local 检查将显示...
c
chqrlie

正如其他人所说,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++ 的定义中使用 new 呢?
因为没有理由这样做。它主要用于使用 C++ 编译器编译的 C 程序。如果您要使用“新”,那么您唯一得到的就是问题。你还需要一个免费的宏。而且您需要一个宏来释放一个数组,这是 C 中不存在的差异化。
更不用说释放内存的不是你,而是你正在使用的 C 库等等。许多可能的问题没有任何收获。
@Hosam:是的,绝对是。如果使用 new,则必须使用 delete,如果使用 malloc(),则必须使用 free()。切勿混合它们。
如果要采用这种方法,调用宏 NEW 可能不是一个好主意,因为资源永远不会使用 delete(或 DELETE)返回,因此您正在混合词汇表。相反,将其命名为 MALLOC,或者在这种情况下命名为 CALLOC,会更有意义。
k
klutt

Wikipedia

强制转换的优点 包括强制转换可能允许 C 程序或函数编译为 C++。演员表允许最初返回 char * 的 malloc 的 1989 之前版本。如果目标指针类型发生变化,转换可以帮助开发人员识别类型大小的不一致,特别是当指针声明远离 malloc() 调用时(尽管现代编译器和静态分析器可以在不需要转换的情况下警告这种行为)。铸造的缺点 在 ANSI C 标准下,铸造是多余的。添加强制转换可能会掩盖包含标头 stdlib.h 的失败,在该标头中找到了 malloc 的原型。在没有 malloc 原型的情况下,标准要求 C 编译器假定 malloc 返回一个 int。如果没有强制转换,则在将此整数分配给指针时发出警告;但是,使用演员表时,不会产生此警告,从而隐藏了一个错误。在某些架构和数据模型上(例如 64 位系统上的 LP64,其中 long 和指针是 64 位而 int 是 32 位),此错误实际上可能导致未定义的行为,因为隐式声明的 malloc 返回一个 32-位值,而实际定义的函数返回一个 64 位值。根据调用约定和内存布局,这可能会导致堆栈粉碎。这个问题在现代编译器中不太可能被忽视,因为它们一致地产生警告,指出使用了未声明的函数,因此仍然会出现警告。例如,GCC 的默认行为是显示一条警告,内容为“内置函数的不兼容隐式声明”,无论是否存在强制转换。如果指针的类型在其声明时发生更改,则可能还需要更改调用 malloc 并强制转换的所有行。

尽管没有强制转换的 malloc 是首选方法,并且大多数有经验的程序员都会选择它,但您应该使用任何您喜欢的知道问题的方法。

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


如果目标指针类型发生变化,尤其是在声明指针远离 malloc() 调用时,转换可以帮助开发人员识别类型大小的不一致”是什么意思?你能举个例子吗?
@CoolGuy:See an earlier comment on another answer。但请注意,p = malloc(sizeof(*p) * count) 习语会自动获取类型的更改,因此您不必收到警告并进行任何更改。因此,与不铸造的最佳选择相比,这并不是真正的优势。
这是正确的答案:有利也有弊,归根结底是个人喜好问题(除非代码必须编译为 C++——然后强制转换)。
第 3 点没有实际意义,因为如果指针的类型在其声明时发生更改,则应检查涉及该类型的 malloc、realloc 和 free 的每个实例。铸造将迫使你这样做。
如果忘记包含 stdlib.h,并且程序编译,它如何在没有 malloc 定义的情况下链接?如果它仍然链接并运行,那么对于任何给定的 CPU,哪些指令实际上会在该行上运行?我想我应该检查一下godbolt...
c
chqrlie

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


如果在声明器中没有显式注释类型,则转换通常更容易产生误导,因为很容易混淆分配对象的意图(对初始值不感兴趣)和仅分配一些(未初始化的)内存。
V
Vishnu CS

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

人们选择 malloc 结果的最常见原因是他们不确定 C 语言的工作原理。这是一个警告信号:如果您不知道特定语言机制是如何工作的,那么不要猜测。在 Stack Overflow 上查找或询问。

一些评论:

void 指针可以转换为/从任何其他指针类型转换,无需显式转换(C11 6.3.2.3 和 6.5.16.1)。

然而,C++ 不允许在 void* 和另一个指针类型之间进行隐式转换。所以在 C++ 中,强制转换是正确的。但是如果你用 C++ 编程,你应该使用 new 而不是 malloc()。而且你永远不应该使用 C++ 编译器来编译 C 代码。如果您需要使用相同的源代码同时支持 C 和 C++,请使用编译器开关来标记差异。不要试图用相同的代码来满足两种语言标准,因为它们不兼容。

如果 C 编译器因为您忘记包含标头而找不到函数,您将收到一个编译器/链接器错误。因此,如果您忘记包含 这没什么大不了的,您将无法构建您的程序。

在遵循超过 25 年历史的标准版本的古代编译器上,忘记包含 会导致危险行为。因为在那个古老的标准中,没有可见原型的函数会隐式地将返回类型转换为 int。然后显式地从 malloc 转换结果会隐藏这个错误。但这真的不是问题。您没有使用 25 年的计算机,那么为什么要使用 25 年的编译器呢?


“毫无意义的混乱”是不屑一顾的夸张,它往往会破坏任何说服任何不同意你的人的可能性。演员表当然不是毫无意义的。 Ron Burk 和 Kaz 的回答提出了支持选角的论点,我非常同意。这些担忧是否比您提到的担忧更重要,这是一个合理的问题。对我来说,与他们相比,您的担忧相对较小。
6.3.2.3 不支持“空指针可以在没有显式转换的情况下转换为任何其他指针类型/从任何其他指针类型转换”。也许您正在考虑“指向任何对象类型的指针”? “空指针”和“指向函数的指针”不是那么容易转换的。
事实上,参考文献并不完整。 “隐含”的相关部分是简单赋值规则 6.5.16.1。 “一个操作数是指向对象类型的指针,另一个是指向 void 的限定或非限定版本的指针”。为了完整起见,我已将此参考添加到答案中。
正如我对 another answer 所评论的那样,原因是通过滥用 C 语言规则来防止一些混乱。如果没有完整的 C 对象创建规则(包括 有效类型 的工作原理),您也无法解决混淆,因此推理非常不完整。 OTOH,源代码中的类似效果可以在 C++ 中通过使用成员 template<T> operator T*() const {...} 替换此处的 void* 的类类型来实现,但是由于更严格的规则,C++ 仍然不会引起这种混淆。从这个意义上说,答案也是不完整的。
@FrankHB 你是什么意思?有效类型不受演员表影响。返回的对象在您写入之前不会获得有效类型(“左值访问”)。
c
chqrlie

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


@Jens:好的,也许更合适的措辞是“隐式转换”。就像在浮点表达式中使用整数变量一样。
@EFraim这实际上会导致演员阵容,并且是隐式演员。
Y
Yu Hao

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

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

参考:C FAQ


关闭编译器警告是个坏主意。
@AlbertvanderHorst 如果您通过解决警告的确切问题来警告您,则不会。
@丹。如果通过解决确切的问题意味着重写子程序以返回现代 ANSI C 类型而不是 char *,我同意。我不会称之为关闭编译器。不要屈服于坚持不存在编译器警告的管理人员,而是在每次重新编译时使用它们来发现可能的问题。格罗杰斯·阿尔伯特
u
user3079666

只是添加我的经验,学习计算机工程,我看到我看到用 C 编写的两三个教授总是使用 malloc,但是我问的那个(具有巨大的 CV 和对 C 的理解)告诉我这是绝对没有必要的,但是以前只是绝对具体,让学生进入绝对具体的心态。本质上,强制转换不会改变它的工作方式,它完全按照它说的去做,分配内存,并且强制转换不会影响它,你得到相同的内存,即使你错误地将它强制转换为其他东西(并且以某种方式逃避编译器错误)C 将以相同的方式访问它。

编辑:铸造有一定的意义。当您使用数组表示法时,生成的代码必须知道它必须提前多少内存位置才能到达下一个元素的开头,这是通过强制转换来实现的。这样,您就知道对于 double,您会提前 8 个字节,而对于 int,您会提前 4 个字节,依此类推。因此,如果您使用指针表示法,它没有效果,在数组表示法中它变得必要。


除非已经提到,强制转换可能会隐藏错误并使编译器或静态分析器更难分析代码。
“基本上铸造不会改变它的工作方式”。转换为匹配的类型不应该改变任何东西,但是如果 var 的类型改变并且转换不再匹配,会出现问题吗? IWOs,cast 和 var 类型应该保持同步——维护工作的两倍。
我明白为什么教授更喜欢铸造。从教育的角度来看,铸造可能很有用,它向教师传达信息并且不需要维护学生代码 - 它的一次性代码。然而,从编码、同行评审和维护的角度来看,p = malloc(sizeof *p * n); 是如此简单和更好。
W
WedaPashi

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


void* 不能指向任何启用此功能的东西,这并不是事实。事实上,void* 可以隐式转换 为任何其他指针类型。为了澄清区别,在 C++ 中 void* 仍然可以指向任何东西,但是隐式转换被删除了,所以必须强制转换。
S
Slothworks

这就是 The GNU C Library Reference 手册所说的:

您可以将 malloc 的结果存储到任何指针变量中而无需强制转换,因为 ISO C 在必要时会自动将类型 void * 转换为另一种类型的指针。但是在赋值运算符以外的上下文中,或者如果您可能希望您的代码在传统 C 中运行,则强制转换是必要的。

确实 ISO C11 standard (p347) 是这么说的:

如果分配成功,则返回的指针经过适当对齐,以便可以将其分配给具有基本对齐要求的任何类型对象的指针,然后用于访问分配的空间中的此类对象或此类对象的数组(直到空间被显式释放)


E
Endeavour

void 指针是通用对象指针,C 支持从 void 指针类型到其他类型的隐式转换,因此不需要显式类型转换。

但是,如果您希望相同的代码在不支持隐式转换的 C++ 平台上完美兼容,则需要进行类型转换,因此这完全取决于可用性。


将单个源代码同时编译为 C 和 C++(与使用包含声明的头文件将 C 和 C++ 代码链接在一起)相反,这不是一个正常的用例。在 C++ 中使用 malloc 和朋友是一个很好的警告信号,它值得特别注意(或用 C 重写)。
P
Peter Mortensen

返回的类型是 void*,可以将其强制转换为所需的数据指针类型以便可解引用。


void* 可以转换为所需的类型,但无需这样做,因为它会自动转换。所以演员阵容是没有必要的,实际上由于高分答案中提到的原因是不可取的。
但仅当您需要“即时”取消引用它时,如果您创建一个变量,它将被安全地自动转换为变量的有效类型,而不需要强制转换(在 C 中)。
N
NAND

这取决于编程语言和编译器。如果您在 C 中使用 malloc,则无需进行类型转换,因为它会自动进行类型转换。但是,如果您使用的是 C++,那么您应该键入 cast,因为 malloc 将返回一个 void* 类型。


函数 malloc 在 C 中也返回一个 void 指针,但该语言的规则与 C++ 不同。
A
August Karlstrom

在 C 语言中,可以将 void 指针分配给任何指针,这就是不应该使用类型转换的原因。如果你想要“类型安全”的分配,我可以推荐以下宏函数,我总是在我的 C 项目中使用它们:

#include <stdlib.h>
#define NEW_ARRAY(ptr, n) (ptr) = malloc((n) * sizeof *(ptr))
#define NEW(ptr) NEW_ARRAY((ptr), 1)

有了这些,你可以简单地说

NEW_ARRAY(sieve, length);

对于非动态数组,第三个必备函数宏是

#define LEN(arr) (sizeof (arr) / sizeof (arr)[0])

这使得数组循环更安全、更方便:

int i, a[100];

for (i = 0; i < LEN(a); i++) {
   ...
}

“可以将 void 指针分配给任何 object 指针”函数指针是另一个问题,尽管不是 malloc()
void* 分配给/从函数指针可能会丢失信息,因此“可以将 void 指针分配给任何指针”在这些情况下是一个问题。不过,将 void*,从 malloc() 分配给任何 object 指针都不是问题。
S
StephenG - Help Ukraine

习惯 GCC 和 Clang 的人都被宠坏了。那里并不是那么好。

多年来,我被要求使用的年代久远的编译器吓坏了。公司和管理人员通常会采用极其保守的方法来更改编译器,甚至不会测试新的编译器(具有更好的标准合规性和代码优化)是否可以在他们的系统中工作。工作开发人员的实际情况是,当您编写代码时,您需要覆盖您的基础,不幸的是,如果您无法控制可以将哪些编译器应用于您的代码,那么强制转换 malloc 是一个好习惯。

我还建议许多组织应用他们自己的编码标准,如果它被定义,那应该是人们遵循的方法。在没有明确指导的情况下,我倾向于最有可能在任何地方编译,而不是盲目地遵守标准。

在当前标准下没有必要的论点是非常有效的。但这个论点忽略了现实世界的实用性。我们不是在一个完全由当时的标准统治的世界中编码,而是由我喜欢称之为“本地管理的现实领域”的实用性来编码。这比时空更弯曲和扭曲。 :-)

YMMV。

我倾向于将 malloc 转换为防御性操作。不漂亮,不完美,但通常是安全的。 (老实说,如果你没有包含 stdlib.h 那么你比强制转换 malloc 有更多的问题!)。


R
RobertS supports Monica Cellio

这个问题是基于意见的滥用的主题。

有时我会注意到这样的评论:

不要转换 malloc 的结果

或者

为什么你不投射 malloc 的结果

关于 OP 使用强制转换的问题。评论本身包含指向此问题的超链接。

这在任何情况下都是不恰当和不正确的。当这真的是一个人自己的编码风格问题时,没有对错之分。

为什么会这样?

它基于两个原因:

这个问题确实是基于意见的。从技术上讲,这个问题应该在几年前以基于意见的方式结束。一个“我要”或“我不要”或等效的“我应该”或“我不应该”的问题,如果没有自己观点的态度,你就无法专注地回答。关闭问题的原因之一是因为它“可能会导致基于意见的答案”,正如这里所展示的那样。许多答案(包括@unwind 最明显和公认的答案)要么完全或几乎完全基于意见(如果你自己铸造或重复自己会被添加到你的代码中的神秘“混乱”会很糟糕)并显示省略演员表的明确而集中的倾向。他们一方面争论演员阵容的冗余,但更糟糕的是,他们争论解决由编程本身的错误/失败引起的错误——如果想使用 malloc() 就不要#include

我想对所讨论的一些观点提出真实的看法,而不是我的个人意见。有几点需要特别注意:

这样一个非常容易陷入自己观点的问题需要一个中性利弊的答案。不仅有缺点也有优点。此答案中列出了优缺点的一个很好的概述:https://stackoverflow.com/a/33047365/12139179 (由于这个原因,我个人认为这是迄今为止最好的答案。)

最多遇到的一个原因是省略演员表的原因是演员表可能隐藏了一个错误。如果有人使用返回 int 的隐式声明 malloc()(隐式函数自 C99 以来已从标准中消失)和 sizeof(int) != sizeof(int*),如本问题所示Why does this code segfault on 64-bit架构但在 32 位上工作正常?演员会隐藏一个错误。虽然这是真的,但它只显示了故事的一半,因为演员的遗漏只会是一个更大的错误的前瞻性解决方案 - 不包括使用 malloc() 时的 stdlib.h。这永远不会是一个严重的问题,如果你,使用符合 C99 或更高版本的编译器(推荐并且应该是强制性的),并且当你想使用 malloc( ) 在您的代码中,这本身就是一个巨大的错误。

有些人争论 C 代码的 C++ 合规性,因为强制转换在 C++ 中是必需的。首先要笼统地说:用 C++ 编译器编译 C 代码并不是一个好习惯。 C 和 C++ 实际上是两种完全不同的语言,具有不同的语义。但是,如果您真的想要/需要使 C 代码与 C++ 兼容,反之亦然,请使用编译器开关而不是任何强制转换。由于演员表倾向于被宣布为多余甚至有害,我想重点关注这些问题,这很好地说明了演员表有用甚至是必要的原因:https://stackoverflow.com/a/34094068/12139179 https ://stackoverflow.com/a/36297486/12139179 https://stackoverflow.com/a/33044300/12139179

当您的代码,分别分配指针的类型(以及强制转换的类型)发生变化时,强制转换可能是无益的,尽管这在大多数情况下不太可能。然后您还需要维护/更改所有强制转换,如果您的代码中有几千次调用内存管理函数,这真的可以总结并降低维护效率。

概括:

事实是,如果分配的指针指向具有基本对齐要求的对象(包括所有对象中的大多数),则根据 C 标准(从 ANSI-C (C89/C90) 开始),强制转换是多余的。

您不需要进行强制转换,因为在这种情况下指针会自动对齐:

“连续调用aligned_alloc、calloc、malloc和realloc函数分配的存储顺序和连续性是未指定的。如果分配成功返回的指针被适当对齐,以便它可以分配给指向任何类型对象的指针一个基本的对齐要求,然后用于访问分配的空间中的此类对象或此类对象的数组(直到空间被显式释放)。资料来源:C18,§7.22.3/1

“基本对齐是小于或等于 _Alignof (max_align_t) 的有效对齐。所有存储持续时间的对象的实现都应支持基本对齐。以下类型的对齐要求应为基本对齐: — 所有原子的、合格的, 或不合格的基本类型; — 所有原子的、合格的或不合格的枚举类型; — 所有原子的、合格的或不合格的指针类型; — 其元素类型具有基本对齐要求的所有数组类型;57) — 第 7 条中规定的所有类型作为完整的对象类型;——所有结构或联合类型,其所有元素都具有具有基本对齐要求的类型,并且没有一个元素具有指定非基本对齐的对齐方式的对齐说明符。如 6.2.1 中所述,后面的声明可能会隐藏先前的声明。”资料来源:C18,§6.2.8/2

但是,如果您为扩展对齐要求的实现定义的对象分配内存,则需要进行强制转换。

扩展对齐由大于 _Alignof (max_align_t) 的对齐表示。是否支持任何扩展对齐以及支持它们的存储持续时间是实现定义的。具有扩展对齐要求的类型是过度对齐类型。58) 源。 C18,§6.2.8/3

其他一切都取决于具体的用例和自己的意见。

请注意你如何教育自己。

我建议您首先仔细阅读所有的答案(以及他们可能指出失败的评论),然后如果您或如果您不投malloc() 在特定情况下。

请注意:

这个问题没有正确和错误的答案。这是风格问题,您自己决定选择哪种方式(当然,如果您没有受到教育或工作的强迫)。请注意这一点,不要让欺骗你。

最后一点:我最近投票决定以基于意见的方式结束这个问题,多年来确实需要。如果您获得关闭/重新打开特权,我也想邀请您这样做。


这与这个旧答案中所说的几乎相同:stackoverflow.com/a/22538350/584518
@Lundin您一定粘贴了错误的链接,这个答案与这个afaics完全无关
@RobertSsupportsMonicaCellio 你会怎么称呼这个表达式中的演员:int x, y; x = (int)y; 那么?主观编码风格的问题?
这是一个糟糕的答案,因为它依赖于隐含的主张,即这场辩论中的所有论点都具有同等价值,而事实显然并非如此。支持强制转换的论点——除了一个利基例外(遵守外部代码风格要求)——只是糟糕的论点,原因有多种(从主观到事实错误)。仅仅因为双方有名义上的“争论”,就得出这样的结论是错误的,因此该决定是一个折腾或基于意见的决定。同样,你会支持关于生物进化或全球变暖的非科学辩论。
@KonradRudolph我不明白支持省略演员表的基于意见的论点比关于它被允许并且可以使用的论点更有价值,我也不明白为什么所有给定的演员表论点是“坏”的论点。分类为“坏”也是主观的和基于意见的,我想用这个答案来防止什么,只是简单的事实。
佚名

不,您不会强制转换 malloc() 的结果。

一般来说,您不会向 void * 投射或从 void * 投射。

不这样做的一个典型原因是,#include <stdlib.h> 失败可能会被忽视。由于 C99 使 隐式函数声明 非法,这在很长一段时间内都不再是问题,因此如果您的编译器至少符合 C99,您将收到一条诊断消息。

但是有一个更强有力的理由不引入不必要的指针转换:

在 C 中,指针转换几乎总是错误的。这是因为以下规则(N1570 中的§6.5 p7,C11 的最新草案):

对象的存储值只能由具有以下类型之一的左值表达式访问: — 与对象的有效类型兼容的类型, — 与对象的有效类型兼容的类型的限定版本, —对应于对象有效类型的有符号或无符号类型, — 对应于对象有效类型的限定版本的有符号或无符号类型, — 聚合或联合类型,包括一个其成员中的上述类型(递归地,包括子聚合或包含联合的成员),或者 - 字符类型。

这也称为严格别名规则。所以下面的代码是未定义的行为:

long x = 5;
double *p = (double *)&x;
double y = *p;

而且,有时令人惊讶的是,以下内容也是如此:

struct foo { int x; };
struct bar { int x; int y; };
struct bar b = { 1, 2};
struct foo *p = (struct foo *)&b;
int z = p->x;

有时,您确实需要转换指针,但鉴于严格的别名规则,您必须非常小心。因此,代码中任何出现的指针转换都是您必须仔细检查其有效性的地方。因此,您永远不会编写不必要的指针转换。

tl;博士

简而言之:因为在 C 语言中,任何指针转换的出现都应该为需要特别注意的代码提出一个危险信号,所以您永远不应该编写不必要的指针转换。

旁注:

在某些情况下,您实际上需要强制转换为 void *,例如,如果您想打印一个指针: int x = 5; printf("%p\n", (void *)&x);强制转换在这里是必要的,因为 printf() 是一个可变参数函数,所以隐式转换不起作用。

在 C++ 中,情况有所不同。在处理派生类的对象时,转换指针类型有些常见(并且正确)。因此,在 C++ 中,与 void * 之间的转换不是隐式的,这是有道理的。 C++ 有一整套不同风格的转换。


在您的示例中,您避免使用 void *.从 double * 转换为 int * 之间存在差异,反之亦然。 malloc 返回与最大标准类型对齐的pointel,因此即使有人将此对齐的指针转换为其他类型,也不会破坏别名规则。
别名与对齐和您的其余评论无关 - 您显然没有明白这一点。
@PeterJ:以防万一,关键是要避免不必要的指针转换,因此它看起来不像是您必须特别注意的一段代码。
严格的别名问题实际上与 void 指针没有任何关系。为了获得由严格的别名违规引起的错误,您必须取消引用指向的数据。而且由于您无法取消引用 void 指针,因此此类错误根据定义与 void 指针无关,而是与其他内容相关。
相反,您必须制定规则来禁止所有指针强制转换。但是,您将如何编写诸如序列化例程和与硬件相关的编程之类的东西呢? C的强项。如果你知道你在做什么,这样的演员就很好。
K
Kaz

我加入演员表只是为了表明不赞成类型系统中的丑陋漏洞,它允许以下代码片段之类的代码在没有诊断的情况下编译,即使没有使用演员表来导致错误的转换:

double d;
void *p = &d;
int *q = p;

我希望那不存在(并且它在 C++ 中不存在),所以我投了。它代表了我的品味和我的编程政治。我不仅投了一个指针,而且还有效地投了一张选票,然后casting out demons of stupidity。如果我不能实际上 cast out stupidity,那么至少让我以抗议的姿态表达希望这样做。

事实上,一个好的做法是用返回 unsigned char * 的函数包装 malloc(和朋友),并且基本上永远不要在代码中使用 void *。如果您需要指向任何对象的通用指针,请使用 char *unsigned char *,并进行双向转换。也许可以放纵的一种放松方式是使用 memsetmemcpy 等不带强制转换的函数。

关于转换和 C++ 兼容性的主题,如果您编写代码以使其同时编译为 C 和 C++(在这种情况下,您必须在将 malloc 的返回值分配给某物时对其进行转换除了 void *),您可以为自己做一件非常有帮助的事情:您可以使用宏进行转换,在编译为 C++ 时转换为 C++ 样式转换,但在编译为 C 时减少为 C 转换:

/* In a header somewhere */
#ifdef __cplusplus
#define strip_qual(TYPE, EXPR) (const_cast<TYPE>(EXPR))
#define convert(TYPE, EXPR) (static_cast<TYPE>(EXPR))
#define coerce(TYPE, EXPR) (reinterpret_cast<TYPE>(EXPR))
#else
#define strip_qual(TYPE, EXPR) ((TYPE) (EXPR))
#define convert(TYPE, EXPR) ((TYPE) (EXPR))
#define coerce(TYPE, EXPR) ((TYPE) (EXPR))
#endif

如果您坚持使用这些宏,那么在您的代码库中搜索这些标识符的简单 grep 将显示您的所有演员表在哪里,因此您可以查看其中是否有任何不正确。

然后,如果您定期使用 C++ 编译代码,它将强制使用适当的强制转换。例如,如果您使用 strip_qual 只是为了删除 constvolatile,但程序发生了变化,现在涉及到类型转换,您将获得诊断,并且您必须使用强制转换组合以获得所需的转换。

为了帮助您遵守这些宏,GNU C++(不是 C!)编译器有一个漂亮的特性:为所有出现的 C 样式转换生成一个可选诊断。

-Wold-style-cast (C++ and Objective-C++ only)
         Warn if an old-style (C-style) cast to a non-void type is used
         within a C++ program.  The new-style casts (dynamic_cast,
         static_cast, reinterpret_cast, and const_cast) are less vulnerable
         to unintended effects and much easier to search for.

如果您的 C 代码编译为 C++,您可以使用此 -Wold-style-cast 选项找出所有可能出现在代码中的 (type) 强制转换语法,并通过将其替换为以下选项来跟进这些诊断上述宏(或组合,如有必要)。

这种转换处理是在“干净的 C”中工作的最大的独立技术理由:组合 C 和 C++ 方言,这反过来在技术上证明了转换 malloc 的返回值是合理的。


正如其他人指出的那样,我通常建议不要混合使用 C 和 C++ 代码。但是,如果您有充分的理由这样做,那么宏可能会很有用。
@Phil1970 这一切都是用一种连贯的方言编写的,恰好可以移植到 C 和 C++ 编译器,并利用了 C++ 的某些功能。它必须全部编译为 C++,否则全部编译为 C。
即我在之前的评论中想说的是,没有混合 C 和 C++。目的是代码全部编译为 C 或全部编译为 C++。
u
user877329

尽可能在 C 中编程时最好的做法是:

使您的程序通过打开所有警告的 C 编译器编译 -Wall 并修复所有错误和警告 确保没有变量声明为 auto 然后使用带有 -Wall 和 -std=c++11 的 C++ 编译器编译它。修复所有错误和警告。现在再次使用 C 编译器进行编译。您的程序现在应该可以在没有任何警告的情况下编译,并且包含更少的错误。

此过程允许您利用 C++ 严格类型检查,从而减少错误数量。特别是,此过程会强制您包含stdlib.h,否则您将获得

malloc 未在此范围内声明

并且还强制您转换 malloc 的结果,否则您将得到

从 void* 到 T* 的无效转换

或者你的目标类型是什么。

我能找到用 C 而不是 C++ 编写的唯一好处是

具有明确指定的 ABI C++ 可能会生成更多代码 [异常、RTTI、模板、运行时多态性]

请注意,在理想情况下,当将 C 的公共子集与静态多态特性一起使用时,第二个缺点应该消失。

对于那些觉得 C++ 严格规则不方便的人,我们可以使用带有推断类型的 C++11 特性

auto memblock=static_cast<T*>(malloc(n*sizeof(T))); //Mult may overflow...

为 C 代码使用 C 编译器。对 C++ 代码使用 C++ 编译器。没有如果,没有但是。用 C++ 重写你的 C 代码完全是另一回事,可能——也可能不——值得花时间和冒险。
我想补充@TobySpeight 的建议:如果您需要在 C++ 项目中使用 C 代码,通常可以将 C 代码编译为 C(例如 gcc -c c_code.c),将 C++ 代码编译为 C++(例如 g++ -c cpp_code.cpp) , 然后将它们链接在一起(例如 gcc c_code.o cpp_code.o 或反之亦然,具体取决于项目依赖项)。现在应该没有理由剥夺自己任何一种语言的任何优点......
@user877329 这是一个更明智的选择,而不是煞费苦心地向代码添加强制转换以降低代码的易读性,只是为了“与 C++ 兼容”。
相对于 C++ 的其他优势: C99 可变长度数组,用于非常有效地分配/释放暂存空间。您不会在无意中编写非常量初始化程序(例如,如果您认为将 static const __m128 ones = _mm_set1_ps(1.0f); 放在全局范围内很聪明,以便多个函数可以共享一个常量,那么构造函数并非如此) C 中的一个东西阻止你生成更糟糕的代码。(这真的是找到 C 限制的一线希望......))
在这种情况下,主要优势可能是 C 允许您编写 p = malloc(sizeof(*p));,如果 p 更改为不同的类型名称,则不需要更改。建议的强制转换“优点”是如果 p 是错误的类型,则会出现编译错误,但如果它只是 Works 会更好。
N
Nae

我更喜欢做演员,但不是手动的。我最喜欢使用 glib 中的 g_newg_new0 宏。如果不使用 glib,我会添加类似的宏。这些宏在不影响类型安全的情况下减少了代码重复。如果类型错误,您将在非 void 指针之间获得隐式转换,这将导致警告(C++ 中的错误)。如果您忘记包含定义 g_newg_new0 的标头,则会收到错误消息。 g_newg_new0 都采用相同的参数,而 malloc 采用的参数少于 calloc。只需添加 0 即可获得零初始化内存。代码可以用 C++ 编译器编译而无需更改。


佚名

强制转换仅适用于 C++ 而不是 C。如果您使用的是 C++ 编译器,则最好将其更改为 C 编译器。


A
Aashish Kumar

malloc 的强制转换在 C 中是不必要的,但在 C++ 中是强制性的。

在 C 中不需要强制转换,因为:

在 C 的情况下,void * 会自动且安全地提升为任何其他指针类型。

如果您忘记包含 ,它可以隐藏错误。这可能会导致崩溃。

如果指针和整数的大小不同,那么您正在通过强制转换隐藏警告,并且可能会丢失返回地址的位。

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

另一方面,强制转换可能会增加程序的可移植性。即,它允许将 C 程序或函数编译为 C++。


i
iec2011007

void 指针背后的概念是它可以转换为任何数据类型,这就是 malloc 返回 void 的原因。此外,您必须注意自动类型转换。因此,尽管您必须这样做,但并不强制强制转换指针。它有助于保持代码清洁并有助于调试


“这不是强制性的——尽管你必须这样做”——我认为那里有矛盾!
我认为你应该把这篇文章读给别人听,看看他们是否明白你想说什么。然后重写它,明确你想说什么。我真的无法理解你的答案是什么。
d
dhana govindarajan

void 指针是泛型指针,C 支持从 void 指针类型到其他类型的隐式转换,因此不需要显式类型转换。

但是,如果您希望相同的代码在不支持隐式转换的 C++ 平台上完美兼容,则需要进行类型转换,因此这完全取决于可用性。


M
Mohit

如前所述,C 不需要它,但 C++ 需要它。包括强制转换可能允许 C 程序或函数编译为 C++。在 C 中这是不必要的,因为 void * 会自动且安全地提升为任何其他指针类型。但是如果你在那时进行转换,如果你忘记包含 stdlib.h,它可能会隐藏一个错误。这可能会导致崩溃(或者,更糟糕的是,直到稍后在代码的某些完全不同的部分中才会导致崩溃)。因为 stdlib.h 包含了 malloc 的原型,所以找到了。在没有 malloc 原型的情况下,标准要求 C 编译器假定 malloc 返回一个 int。如果没有强制转换,则在将此整数分配给指针时发出警告;但是,使用演员表时,不会产生此警告,从而隐藏了一个错误。


t
tstanisl

malloc 的主要问题是获得正确的尺寸

malloc() 返回的内存是无类型,并且由于简单的强制转换,它不会神奇地获得有效类型

我想这两种方法都很好,选择应该取决于程序员的意图。

如果为类型分配内存,则使用强制转换。

ptr = (T*)malloc(sizeof(T));

如果为给定指针分配内存,则不要使用强制转换。

ptr = malloc(sizeof *ptr);

广告 1

第一种方法通过为给定类型分配内存来确保正确的大小,然后对其进行强制转换以确保将其分配给正确的指针。如果使用了不正确的 ptr 类型,则编译器将发出警告/错误。如果 ptr 的类型发生变化,那么编译器将指向代码需要重构的地方。

而且,第一种方法可以组合成一个类似于C++中new运算符的宏。

#define NEW(T) ((T*)malloc(sizeof(T)))
...
ptr = NEW(T);

此外,如果 ptrvoid*,则此方法有效。

广告 2

第二种方法不关心类型,它通过从指针的类型中获取正确的大小来确保正确的大小。这种方法的主要优点是,只要 ptr 的类型发生变化,就会自动调整存储大小。它可以在重构时节省一些时间(或错误)。

缺点是如果 ptrvoid* 该方法不起作用,但它可能被认为是一件好事。并且它不适用于 C++,因此不应在 C++ 程序将使用的头文件中的内联函数中使用它。

就个人而言,我更喜欢第二种选择。


p
pasignature

对我来说,这里的带回家和结论是完全没有必要在 C 中强制转换 malloc,但是如果你强制转换,它不会影响 malloc,因为 malloc 仍会为你分配你请求的祝福内存空间。另一个带回家的是人们进行强制转换的原因或原因之一,这是为了使他们能够用 C 或 C++ 编译相同的程序。

可能还有其他原因,但几乎可以肯定,其他原因迟早会让你陷入严重的麻烦。