需要对C中的铸造做一些澄清

时间:2009-12-21 19:39:01

标签: c casting

我刚刚读到了关于转换malloc的返回值的不良做法。如果我理解正确的话,离开演员是绝对合法的,因为它是隐含的(并且应该留下,因为它可能产生其他问题)。那么我的问题是,我什么时候应该施展我的价值观呢?有一些一般规则或什么?例如,此代码使用gcc -W -Wall编译时没有任何错误(除了未使用的bar,但这不是重点):

float foo(void) {
    double bar = 4.2;
    return bar;
}

int main(void) {
    double bar = foo();
    return 0;
}
我现在很困惑。有关铸造的良好做法和规则是什么?

感谢。

13 个答案:

答案 0 :(得分:9)

有几种情况需要在C中进行完全有效的铸造。要注意像“铸造总是糟糕的设计”这样的清晰断言,因为它们显然是明显的假冒伪劣。

严重依赖演员表的一大群情况是算术运算。在需要强制编译器解释与“默认”类型不同的类型中的算术表达式时,需要进行转换。如在

unsigned i = ...;
unsigned long s = (unsigned long) i * i;

避免溢出。或者在

double d = (double) i / 5;

为了使编译器切换到浮点除法。或者在

s = (unsigned) d * 3 + i;

以获取浮点值的整个部分。等等(例子是无穷无尽的)。

另一组有效用途是成语,即完善的编码实践。例如,当一个函数将一个const指针作为输入并返回一个非const指针指向相同(可能是常量)的数据时,例如标准strstr,这是经典的C语言。实现这个习惯用法通常需要使用强制转换来抛弃输入的常量。有人可能称之为糟糕的设计,但实际上在C中没有更好的设计选择。否则,它不会是一个成熟的习语:)

另外值得一提的是,作为一个例子,标准printf函数的迂腐正确使用可能需要在一般情况下对参数进行强制转换。 (与%p格式说明符一样,期望void *指针作为参数,这意味着必须以某种方式将int *参数转换为void *。 cast是执行转换的最合理方式。)。

当然,在需要演员表时还有其他众多完全有效的情况。

当人们不加思索地使用它们时,通常会出现强制转换的问题,即使它们不需要它们(比如投射malloc的返回,这种情况比一个原因更糟糕)。或者当人们使用强制转换来强制编译器接受他们的错误代码时。毋庸置疑,需要一定程度的专业知识才能从糟糕的演员表中讲出有效的演员情况。

在某些情况下,强制转换用于使编译器停止发出一些烦人且不必要的警告消息。这些演员阵容属于好人和坏人之间的灰色区域。一方面,不必要的演员阵容很糟糕。另一方面,用户可能无法控制编译设置,因此使转换成为处理警告的唯一方法。

答案 1 :(得分:8)

如果你需要施展,我总是建议你明确地这样做,以向其他人展示,或者将来可能是你自己想要这种行为。

顺便说一句,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检查我的代码,{{3}}是C和C ++的免费静态代码分析器。强烈推荐。

答案 2 :(得分:4)

我的简单指南 - 如果它需要演员,那可能是错的。如果您不需要演员表,请不要使用它们。

答案 3 :(得分:4)

你没有强制转换malloc的原因是因为你总是将返回值赋给指针类型,而C标准允许void *隐式地转换为任何指针类型其他指针类型。明确地施放它是多余的,因此是不必要的。

答案 4 :(得分:4)

你不能在'C'中询问铸造是否理解铸件涵盖多种类型的操作。基本上有两种类型转换和类型强制。在C ++中,因为它有更多的类型信息,它创建了4种类型的强制转换,并使用独有的符号对其进行编码。 reinterpret_cast<>const_cast<>dynamic_cast<>static_cast<>。你没有在C中使用这些,因为所有的强制转换都有(ctype)的语法,但它们的原因仍然存在,并且有助于理解为什么需要强制转换,即使你的问题是关于'C'的具体。

静态演员的“需要”就是你在例子中展示的内容。编译器会为你做这些,即使你没有指定它 - 但是,将警告级别提高到足够高,编译器会警告你是否有精度损失,因为从double到float(你的return bar;声明)。添加转换会告诉编译器预期会出现精度损失。

第二个最不危险的演员是const cast&lt;&gt;。它用于从类型中删除const或volatile。这通常发生在结构具有内部“缓存”的地方。因此调用者可能具有结构的const版本,但是“内部函数”需要更新缓存,因此必须从指向const结构的指针转换为常规结构以更新内部字段。

最危险的类型是重新诠释,以及为什么人们会继续谈论施展的糟糕程度。这就是你没有转换任何内容的地方,而是告诉编译器将值重新解释为完全不同的类型。试图摆脱编译器错误的天真程序员可能会添加以下内容。

    char **ptostr = (char **p) "this is not a good idea"; 

可能正确的解决方法是使用'&amp;'这就是铸造声名狼借的原因。像这样的演员可以用于善恶。我在answer to another question about how to find the smallest power of 2中使用它以便在CPU中利用FPU的功能。实现链接列表是一个更好的用例。如果链接存在于对象本身中,则必须从链接指针转换回封闭的对象(如果链接不能位于结构的顶部,则可以很好地使用offsetof宏。)

动态演员在C中没有语言支持,但情况仍然存在。如果您有异构列表,则可以使用列表链接标题中的字段验证对象是否为给定类型。手动实现,您将验证类型是否兼容,如果不是,则返回NULL。这是重新解释演员的特殊版本。

有许多复杂的编程模式需要进行投射,因此我不会说需要避免投射或表明存在问题。 'C'的问题在于你如何以与安全的方式相同的方式编写不安全的问题。保持它包含和限制是一个很好的做法,所以你可以确保你正确(例如,如果可以的话,使用库例程,强类型和断言)。

答案 5 :(得分:2)

只有在绝对必要时才能施放结果;如果您使用的是从两个不同的人(例如静态库或动态库)开发的代码,并且两个函数不使用兼容值,那么转换是唯一的解决方案(只要您不尝试转换字符串到一个整数)。

在使用转换之前,最好验证使用的数据类型是否正确。在示例代码中(其目的是提供示例),当函数返回double时,将返回值声明为float值是没有意义的。

答案 6 :(得分:1)

double bar = foo();

这里发生的事情称为促销转化,其中转换后保留了转换变量的值。反之则不然,即float -> double。唯一的答案是只在真正需要时才进行投射。施展很多是设计糟糕的表现。

答案 7 :(得分:1)

在你的例子中,精度有所下降,但是演员是隐含的。有时候必须进行强制转换,例如当您从字节流中读取数据时,或者当您拥有的是通过void*指针进入的数据时,您知道它代表什么数据。但在大多数情况下,应该避免铸造并保留用于这些极端情况。

答案 8 :(得分:1)

您正在关注的是隐式类型转换。如果你的类型范围比你最终的范围更有限,那么这被认为是安全的,即对int的缩写是正常的,因为浮动为double。

我很惊讶gcc在将double转换为float时没有生成警告;我相信微软的编译器可以。

答案 9 :(得分:0)

答案 10 :(得分:0)

基本上你需要将参数转换为期望与原型声明不同的参数的函数。

例如,isalpha()的原型带有int参数,但实际上需要unsigned char

char *p;
if ((*p != EOF) && isalpha((unsigned char)*p) /* cast needed */
{
    /* ... */
}

而且,您需要特别注意接受可变数量参数的函数,例如:

long big;
printf("%d\n", (int)big);

修改

编译器无法将可变参数函数的参数转换为正确的类型,因为原型本身没有可用的类型信息。考虑一个类似printf()的函数

int my_printf(const char *fmt, ...);

就编译器而言,您可以在“...”参数中传递各种类型的值,必须确保参数与函数所期望的相匹配。例如,假设my_printf()函数接受格式字符串中具有相应“%t”说明符的类型time_t的值。

my_printf("UNIX Epoch: %t.\n", 0);         /* here, 0 is an int value */
my_printf("UNIX Epoch: %t.\n", (time_t)0); /* and here it is a time_t */

我的编译器不想让它失败!显然它(and the one at codepad too)为“...”

中的每个参数传递8个字节

使用没有“...”(int my_printf(const char *fmt, time_t data);)的原型,编译器会自动将前0个转换为正确的类型。

注意:如果格式字符串是文字字符串

,某些编译器(包括gcc)将根据printf()的格式字符串验证参数

答案 11 :(得分:0)

由于C隐式地将以前未声明的函数的结果键入malloc()(从C99开始不允许IINM),因此反对强制转换int结果的警告是一种特殊情况。

通常,您希望尽可能限制显式强制转换的使用;你需要使用一个的唯一时间是你试图将一种类型的值分配给不兼容类型的变量(例如,将指针值赋给int变量,反之亦然) 。由于void *与其他所有指针类型兼容,因此不需要显式转换。但是,如果您尝试将类型int *的值分配给类型为struct foo *的变量,则需要显式强制转换 。如果您发现自己分配了不兼容类型的值 lot ,那么您可能需要重新访问您的设计。

答案 12 :(得分:0)

至少有两种演员阵容:

  • 转换导致更改代表的数值。 (这是一个艺术术语,你会发现在哈比森和斯蒂尔非常有帮助C Reference Manual。)这些演员大多是无害的;唯一可以造成伤害的方法是,例如,将较宽的类型转换为较窄的类型,在这种情况下,你故意扔掉一些东西。只有你,程序员,知道扔掉那些位是否安全。

    C还有一个潜在的功能,即当您分配,返回或传递其类型与关联左值,结果或参数的类型不完全匹配的数值表达式时,它可能会代表您执行更改表示。我认为这个功能是有害的,但它坚定地嵌入在C的做事方式中。 gcc选项-Wconversion将让您知道编译器代表您更改代表的位置,而不会被询问。

  • 不涉及更改表示的转换,只是要求编译器以某种方式查看位。这些包括相同大小的有符号和无符号类型之间的转换,以及指向不同类型数据的指针类型之间的转换。类型void *在C中具有特殊状态,因为编译器将在void *和其他数据指针类型之间自由转换,而不需要转换。请注意,两个不同类型的数据从不的指针之间的强制转换涉及表示的更改。这是void *约定可以起作用的一个原因。

C程序员试图避免无偿演员的一个原因是当你编写演员表时,编译器完全信任你。如果你犯了错误,编译器就不会为你捕获它。出于这个原因,我建议我的学生从不投射指针类型,除非他们确切知道他们在做什么。


P.S。我认为可以为这个问题写一个简短有用的答案。错!