我刚刚读到了关于铸造返回值的不良做法malloc
.如果我理解正确的话,离开演员是绝对合法的,因为它是隐含的(并且应该留下,因为它可能产生其他问题).那么我的问题是,我什么时候应该施展我的价值观呢?有一些一般规则或什么?例如,此代码编译时没有任何错误gcc -W -Wall
(除了未使用bar
,但这不是重点):
float foo(void) {
double bar = 4.2;
return bar;
}
int main(void) {
double bar = foo();
return 0;
}
Run Code Online (Sandbox Code Playgroud)
我现在很困惑.有关铸造的良好做法和规则是什么?
谢谢.
有几种情况需要在C中进行完全有效的铸造.要注意像"铸造总是糟糕的设计"这样的清晰断言,因为它们明显而且显然是虚假的.
严重依赖演员表的一大群情境是算术运算.当您需要强制编译器解释与"默认"类型不同的类型中的算术表达式时,需要进行转换.如在
unsigned i = ...;
unsigned long s = (unsigned long) i * i;
Run Code Online (Sandbox Code Playgroud)
避免溢出 或者在
double d = (double) i / 5;
Run Code Online (Sandbox Code Playgroud)
为了使编译器切换到浮点除法.或者在
s = (unsigned) d * 3 + i;
Run Code Online (Sandbox Code Playgroud)
为了取整个浮点值的一部分.等等(例子是无穷无尽的).
另一组有效用途是成语,即完善的编码实践.例如,当一个函数将一个const指针作为一个输入并返回一个非const指针指向同一个(可能是常量的)数据时,例如标准的经典C语言strstr
.实现这个习惯用法通常需要使用强制转换来抛弃输入的常量.有人可能称之为糟糕的设计,但实际上在C中没有更好的设计选择.否则,它不会是一个成熟的成语:)
另外值得一提的是,作为一个例子,标准printf
函数的迂腐正确使用可能需要在一般情况下对参数进行强制转换.(就像%p
格式说明符期望void *
指针作为参数一样,这意味着int *
必须以void *
某种方式将参数转换为a .显式转换是执行转换的最合理方式.).
当然,在需要强制转换时,还有其他众多完全有效的情况.
当人们不加思索地使用它们时,通常会出现演员表的问题,即使在不需要它们的情况下也是如此(例如,投票的回报malloc
,其原因多于一个).或者当人们使用强制转换来强制编译器接受他们的错误代码时.毋庸置疑,从一个糟糕的演员表中讲出一个有效的演员情况需要一定的专业水平.
在某些情况下,强制转换用于使编译器停止发出一些恼人的和不必要的警告消息.这些演员阵容属于好人和坏人之间的灰色区域.一方面,不必要的演员阵容很糟糕.另一方面,用户可能无法控制编译设置,因此使转换成为处理警告的唯一方法.
如果你需要演员,我总是建议你明确地这样做,以向别人展示,或者将来你自己也可以表明你想要这种行为.
顺便说一句,gcc警告是这样的-Wconversion
.不幸的是-Wall和-Wextra仍然留下了很多好的警告.
下面是我希望gcc非常像lint时使用的标志
-pedantic -std=c99 -ggdb3 -O0 -Wall -Wextra -Wformat=2 -Wmissing-include-dirs -Winit-self -Wswitch-default -Wswitch-enum -Wunused-parameter -Wfloat-equal -Wundef -Wshadow -Wlarger-than-1000 -Wunsafe-loop-optimizations -Wbad-function-cast -Wcast-qual -Wcast-align -Wconversion -Wlogical-op -Waggregate-return -Wstrict-prototypes -Wold-style-definition -Wmissing-prototypes -Wmissing-declarations -Wpacked -Wpadded -Wredundant-decls -Wnested-externs -Wunreachable-code -Winline -Winvalid-pch -Wvolatile-register-var -Wstrict-aliasing=2 -Wstrict-overflow=2 -Wtraditional-conversion -Wwrite-strings
我还首先使用cppcheck检查我的代码,cppcheck是一个用于C和C++的免费静态代码分析器.强烈推荐.
您不能在不了解转换涵盖不止一种类型的操作的情况下询问“C”中的转换。本质上有两种,类型转换和类型强制。在 C++ 中,因为它有更多的类型信息,所以它创建 4 种类型的强制转换,并使用独占符号对其进行编码。 reinterpret_cast<>
,const_cast<>
,dynamic_cast<>
和static_cast<>
。C 中没有这些,因为所有强制转换都有语法(ctype)
,但它们的原因仍然存在,并且它有助于理解为什么需要强制转换,即使您的问题具体是关于“C”的。
静态转换的“需要”就是您在示例中所显示的内容。即使您没有指定,编译器也会为您执行这些操作 - 但是,将警告级别调高到足够高,编译器会警告您,如果从双精度到浮点(您的return bar;
陈述)。添加强制转换告诉编译器有意损失精度。
第二个最不危险的强制转换是 const 强制转换 <>。它用于从类型中删除 const 或 volatile。这通常发生在结构具有内部“缓存”的地方。因此,调用者可能拥有结构的 const 版本,但“内部函数”需要更新缓存,因此必须从指向 const 结构的指针转换为常规结构以更新内部字段。
最危险的类型是重新解释演员阵容以及为什么人们会不断地谈论演员阵容有多么糟糕。这就是您不转换任何内容,而是告诉编译器将值重新解释为完全不同的类型的地方。下面的内容可能是由天真的程序员添加的,试图消除编译器错误。
char **ptostr = (char **p) "this is not a good idea";
Run Code Online (Sandbox Code Playgroud)
正确的解决办法可能是使用“&”,这就是强制转换获得坏名声的原因。像这样的演员可以用于善行或邪恶。我在回答另一个关于如何找到 2 的最小幂以利用 CPU 中 FPU 的能力的问题时使用了它。一个更好的用于善意的例子是实现链表。如果链接位于对象本身中,则必须从链接指针强制转换回包含的对象(如果链接不能位于结构的顶部,则可以很好地使用 offsetof 宏)。
动态转换在 C 中没有语言支持,但这种情况仍然会发生。如果您有异构列表,那么您可以使用列表链接标头中的字段来验证对象是否属于给定类型。手动实现时,您将验证类型是否兼容,如果不兼容则返回 NULL。这是重新诠释演员阵容的特别版本。
有许多复杂的编程模式需要强制转换,因此我不会说需要避免强制转换或表明存在问题。“C”的问题在于如何以与安全代码相同的方式编写不安全代码。保持它的包含和限制是一个很好的做法,这样你就可以确保你的做法是正确的(例如,如果可以的话,使用库例程、强类型和断言)。