C语言中指针的类型转换

21

我知道一个类型的指针可以转换为另一个类型的指针。 我有三个问题:

  1. 类型转换指针时应该注意什么?
  2. 可能会出现哪些异常/错误的指针?
  3. 有哪些最佳实践可以避免异常/错误?
3个回答

17

通常情况下,良好编写的程序不会使用过多的指针类型转换。例如,在malloc中可能需要使用ptr类型转换(声明为(void *)malloc(...)),但在C语言中甚至没有必要(尽管有些编译器可能会发出警告)。

  int *p = malloc(sizeof(int)); // no need of (int *)malloc(...)

然而,在系统应用程序中,有时您希望使用技巧执行二进制或特定操作 - 而C语言,作为一种接近于机器结构的语言,非常方便。例如,假设您想要分析遵循IEEE 754实现的double的二进制结构,并且使用二进制元素更简单,那么您可以声明:

  typedef unsigned char byte;
  double d = 0.9;
  byte *p = (byte *)&d;
  int i;
  for (i=0 ; i<sizeof(double) ; i++) { ... work with b ... }

您还可以使用一个union,这是一个例子。

更复杂的用法可能是模拟C++多态性,这需要在某个地方存储"类"(结构体)的层次结构以记住每个类别,然后执行指针类型转换,例如,父级"类"指针变量在某些时候指向派生类(请参见C ++链接)。

  CRectangle rect;
  CPolygon *p = (CPolygon *)&rect;
  p->whatami = POLY_RECTANGLE; // a way to simulate polymorphism ...
  process_poly ( p );

但在这种情况下,也许直接使用C++更好!

指针类型转换应该谨慎使用,在程序分析的确定情况下使用——在开发开始之前。

指针类型转换的潜在危险

  • 不必要时避免使用——容易出错且会使程序复杂化
  • 指向不同大小的对象可能会导致访问溢出、错误结果等问题
  • 指向两个不同结构体的指针,如 s1 *p = (s1 *)&s2;:依赖它们的大小和对齐方式可能会导致错误

(不过,有经验的C程序员不会犯上述错误...)

最佳实践

  • 仅在需要时使用,并注释解释为何需要使用
  • 了解自己在做什么——有经验的程序员可能会使用大量指针类型转换而不会失败,即不要试图去尝试,这可能在某些系统/版本/操作系统上有效,但在另一个系统上可能无效

3
在普通的C语言中,您可以将任何指针类型转换为任何其他指针类型。如果您将指针强制转换为或从不兼容的类型,并且错误地写入内存,则可能会导致应用程序出现分段错误或意外结果。以下是一个结构体指针强制转换的示例代码:
struct Entity { 
  int type;
}

struct DetailedEntity1 {
  int type;
  short val1;
}

struct DetailedEntity2 {
  int type;
  long val;
  long val2;
}

// random code:
struct Entity* ent = (struct Entity*)ptr;

//bad:
struct DetailedEntity1* ent1 = (struct DetailedEntity1*)ent;
int a = ent->val; // may be an error here, invalid read
ent->val = 117; // possible invali write

//OK:
if (ent->type == DETAILED_ENTITY_1) {
  ((struct DetailedEntity1*)ent)->val1;
} else if (ent->type == DETAILED_ENTITY_2) {
  ((struct DetailedEntity2*)ent)->val2;
} 

关于函数指针 - 你应该始终使用与声明完全匹配的函数。否则,您可能会得到意想不到的结果或段错误。
当从指针转换为指针(结构体或非结构体)时,您必须确保内存以完全相同的方式对齐。当转换整个结构时,最好的方法是在开始时使用相同变量的相同顺序,并仅在“公共头”之后区分结构。还要记住,内存对齐可能因机器而异,因此您不能只将结构体指针作为字节数组发送并将其作为字节数组接收。您可能会遇到意外行为甚至段错误。
在将较小的变量指针转换为较大的变量指针时,您必须非常小心。考虑以下代码:
char* ptr = malloc (16);
ptr++;
uint64_t* uintPtr = ptr; // may cause an error, memory is not properly aligned

而且,你还应该遵循严格别名规则。

我认为这有点过于简单化了......例如,您不能自由地在函数指针和数据对象指针之间进行转换。此外,并不保证会出现段错误。 - unwind
我同意@unwind的观点。在C语言中,指针转换最糟糕的事情之一就是其本质思想;你想把某些东西当作它不是的东西来处理。除了通过void *(又名qsort()类型工作)进行不透明隐藏之外,在一般情况下最好完全避免使用它。最后,一个更微妙的问题(也是头痛的根源),就是数据对齐,但这一点根本没有被提到。 - WhozCraig
@WhozCraig 感谢您提醒数据对齐的问题。虽然在指针转换中这并不是一个问题,但提到它可能会有用。 - Dariusz
@DariuszWawer 如果存储在“转换为”指针中的地址,其类型要求比现在所持有的数据地址更严格的对齐方式,则这是一个巨大的问题(即将奇数内存地址上的unsigned char *强制转换为float *将在许多平台上在您解引用浮点指针时导致总线错误),因此我们必须就此问题保持不同意见。 - WhozCraig
@WhozCraig 嘿,我感受到你评论中的一些沮丧或愤怒情绪。我在尽力做好这件事。我已经将你提供的信息添加到答案中。非常感谢:) - Dariusz
@DariuszWawer 先生,完全不是这样的。我提及对齐方式只是因为在过去转换类型时它毫无疑问是最让我头痛的地方。这是一个难以回答并覆盖所有情况的问题。我向您保证,如果我认为答案完全偏离了题目,我会进行反对投票的。您很好,而且没有发现任何愤怒或沮丧=P - WhozCraig

1

你可能需要查看由Steve Summit维护的C-faq(曾经发布在新闻组中,这意味着当时很多最好的程序员阅读和更新了它,有时甚至是语言本身的创造者)。

还有一个简明版,可能更易于理解,但仍然非常非常非常有用。我认为,如果你使用C语言,阅读整个简明版是必须的。


我确实想念一个中心化的地方来查看/更新事物...现在知识分散在几个网站上,有些答案也非常错误...有人知道是否有一个好的(即“每个人都应该去的”)FAQ维基网站吗? - Olivier Dulac

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接