铸造真的有什么作用吗?

klu*_*utt 5 c casting

考虑以下片段:

char x[100];
double *p = &x;
Run Code Online (Sandbox Code Playgroud)

正如预期的那样,这会产生以下警告:

f.c:3:15: warning: initialization of ‘double *’ from incompatible pointer type ‘char (*)[100]’ 
[-Wincompatible-pointer-types]
    3 |   double *p = &x;
      |               ^
Run Code Online (Sandbox Code Playgroud)

这很容易解决,只需更改为

double *p = (double*)&x;
Run Code Online (Sandbox Code Playgroud)

我的问题是,演员阵容真的有什么作用吗?如果没有演员表,代码会无效吗?或者这只是一种让编译器安静的方法?什么时候需要铸造?

我知道你可以对这样的片段产生一些影响:

int x = 666;
int y = (char)x;
Run Code Online (Sandbox Code Playgroud)

但这不和这个一样吗?

int x = 666;
char c = x;
int y = c;
Run Code Online (Sandbox Code Playgroud)

如果是相同的,那么演员表会一些事情,但没有必要。对?

请帮助我理解这一点。

Ste*_*mit 4

铸造可以做几种不同的事情。正如其他答案所提到的,它几乎总是会更改正在转换的值的类型(或者可能是该类型的属性,例如const)。它还可能以某种方式改变数值。但有很多种可能的解释:

  • 有时它只是静默警告,根本不执行“真正的”转换(如许多指针转换)。
  • 有时它会消除警告,只留下类型更改,但不更改值(如在其他指针转换中一样)。
  • 有时,类型更改虽然不涉及明显的值更改,但对于以后使用该值意味着非常不同的语义(同样,就像在许多指针转换中一样)。
  • 有时它要求进行毫无意义或不可能的转换。
  • 有时它会执行编译器本身会执行的转换(有或没有警告)。
  • 但有时它会强制执行编译器不会执行的转换。

另外,有时编译器试图发出的那些警告,即强制转换沉默,是无害的和/或令人讨厌的,但有时它们是非常真实的,并且代码可能会失败(也就是说,因为沉默的警告正在尝试)告诉你)。

对于一些更具体的例子:

改变类型但不改变值的指针转换:

char *p1 = ... ;
const char *p2 = (const char *)p;
Run Code Online (Sandbox Code Playgroud)

还有另一个:

unsigned char *p3 = (unsigned char *)p;
Run Code Online (Sandbox Code Playgroud)

以更重要的方式更改类型的指针转​​换,但这保证没问题(在某些体系结构上,这也可能会更改值):

int i;
int *ip = &i;
char *p = (char *)ip;
Run Code Online (Sandbox Code Playgroud)

一个类似的重要指针转换,但很可能不太

char c;
char *cp = &c;
int *ip = (int *)cp;
*ip = 5;                  /* likely to fail */
Run Code Online (Sandbox Code Playgroud)

指针转换毫无意义,以至于编译器拒绝执行它,即使使用显式转换也是如此:

float f = 3.14;
char *p = (char)f;        /* guaranteed to fail */
Run Code Online (Sandbox Code Playgroud)

进行转换的指针转换,但编译器无论如何都会进行转换:

int *p = (int *)malloc(sizeof(int));
Run Code Online (Sandbox Code Playgroud)

(这被认为是一个坏主意,因为在您忘记包含<stdlib.h>声明的情况下malloc(),强制转换可以消除可能提醒您该问题的警告。)

由于C 语言中非常具体的特殊情况,从整数到指针的三种转换实际上是明确定义的:

void *p1 = (void *)0;
char *p2 = (void *)0;
int *p3 = (int *)0;
Run Code Online (Sandbox Code Playgroud)

从整数到指针的两次强制转换不一定有效,尽管编译器通常会做一些明显的事情,并且强制转换将使其他警告静音:

int i = 123;
char *p1 = (char *)i;
char *p2 = (char *)124;
*p1 = 5;                  /* very likely to fail, except when */
*p2 = 7;                  /* doing embedded or OS programming */
Run Code Online (Sandbox Code Playgroud)

从指针返回到 的非常可疑的转换int

char *p = ... ;
int i = (int)p;
Run Code Online (Sandbox Code Playgroud)

从指针返回到应该足够大的整数的问题较少的转换:

char *p = ... ;
uintptr_t i = (uintptr_t)p;
Run Code Online (Sandbox Code Playgroud)

更改类型的强制转换,但“丢弃”而不是“转换”值,并且使警告静音:

(void)5;
Run Code Online (Sandbox Code Playgroud)

进行数字转换的强制转换,但编译器无论如何都会进行转换:

float f = (float)0;
Run Code Online (Sandbox Code Playgroud)

更改类型和解释值的强制转换,尽管它通常不会更改位模式:

short int si = -32760;
unsigned short us = (unsigned short)si;
Run Code Online (Sandbox Code Playgroud)

进行数字转换的强制转换,但编译器可能会发出警告:

int i = (int)1.5;
Run Code Online (Sandbox Code Playgroud)

进行编译器不会进行的转换的强制转换:

double third = (double)1 / 3;
Run Code Online (Sandbox Code Playgroud)

底线是强制转换肯定会做一些事情:有些有用,有些不必要但无害,有些危险。

如今,许多 C 程序员达成的共识是,大多数强制转换是或应该是不必要的,这意味着避免显式强制转换是一个不错的规则,除非您确定自己知道自己在做什么,并且对显式强制转换持怀疑态度是合理的。您在其他人的代码中发现的强制转换,因为它们可能是麻烦的迹象。


作为最后一个例子,在过去,这种情况确实让我在指针转换方面点亮了灯泡:

char *loc;
int val;
int size;

/* ... */

switch(size) {
    case 1: *loc += val; break;
    case 2: *(int16_t *)loc += val; break;
    case 4: *(int32_t *)loc += val; break;
}
Run Code Online (Sandbox Code Playgroud)

这三个实例loc += val执行三件截然不同的事情:一个更新字节,一个更新 16 位 int,一个更新 32 位 int。(有问题的代码是一个动态链接器,执行符号重定位。)