C:何时在指针类型之间进行转换而不是未定义的行为?

作为C的新手,我很困惑何时投射指针实际上是可以的。

据我所知,你几乎可以将任何指针类型转换为任何其他类型,编译器将允许你这样做。 例如:

int a = 5; int* intPtr = &a; char* charPtr = (char*) intPtr; 

但是,通常这会调用未定义的行为(尽管它恰好在许多平台上工作)。 这说,似乎有一些例外:

  • 你可以自由地void* (?)
  • 你可以自由地转换为char* (?)

(至少我在代码中看过它…)。

那么指针类型之间的转换在C中是不是未定义的行为?

编辑:

我试着查看C标准(“6.3.2.3指针”一节,见http://c0x.coding-guidelines.com/6.3.2.3.html ),但除了有点void*之外,我并没有真正理解它void*

EDIT2:

只是为了澄清:我明确地只询问“正常”指针,即不是关于函数指针。 我意识到,转换函数指针的规则是非常严格的。 事实上,我已经问过:-): 如果我转换函数指针,改变参数的数量会发生什么

基本上:

  • 一个T *可以自由地转换为void *并再次返回(其中T *不是函数指针),你将获得原始指针。
  • 一个T *可以自由转换为U *并再次返回(其中T *U *不是函数指针),如果对齐要求相同,您将获得原始指针。 如果不是,则行为未定义。
  • 函数指针可以自由转换为任何其他函数指针类型,然后再返回,您将获得原始指针。

注意: T * (对于非函数指针)始终满足char *的对齐要求。

重要提示:这些规则都没有说明如果将T *转换为U *然后尝试取消引用它会发生什么。 这是标准的一个完全不同的领域。

Oli Charlesworth的优秀答案列出了将指针指向不同类型的指针的所有情况都给出了明确定义的结果。

此外,有四种情况下,转换指针会给出实现定义的结果:

  • 您可以将指针强制转换为足够大的(!)整数类型。 为此,C99具有可选类型intptr_tuintptr_t 。 结果是实现定义的。 在将内存作为连续字节流(即大多数现代平台使用的“线性内存模型”)处理的平台上,它通常返回指针指向的内存地址的数值,因此只是字节数。 但是,并非所有平台都使用线性内存模型,这就是为什么这是实现定义的原因:-)。
  • 相反,您可以将整数强制转换为指针。 如果整数的类型足够大于intptr_tuintptr_t并且是通过强制转换指针创建的,那么将它转换回相同的指针类型将返回该指针(但可能不再有效)。 否则结果是实现定义的。 请注意,实际取消引用指针(而不是仅读取其值)可能仍然是UB。
  • 您可以将指向任何对象的指针强制转换为char* 。 然后,结果指向对象的最低寻址字节,您可以通过递增指针来读取对象的剩余字节,直到对象的大小。 当然,您实际获得的值又是实现定义的……
  • 你可以自由地转换空指针,无论指针类型如何,它们都会保持空指针:-)。

来源:C99标准,第6.3.2.3节“指针”和7.18.1.4“能够保存对象指针的整数类型”。

据我所知,指向不同类型指针的指针的所有其他转换都是未定义的行为。 特别是,如果您没有转换为char或足够大的整数类型,则可能总是 UB将指针转换为不同的指针类型 – 即使没有取消引用它。

这是因为类型可能具有不同的对齐方式,并且没有通用的可移植方法来确保不同类型具有兼容的对齐(除了某些特殊情况,例如有符号/无符号整数类型对)。

通常,如果像现在一样,指针本身具有相同的对齐属性,问题不在于转换本身,而在于您是否可以通过指针访问数据。

对任何对象类型T保证将任何类型的T*void*并返回:这保证为您提供完全相同的指针。 void*是catch所有对象指针类型。

对于对象类型之间的其他转换,无法保证,通过这样的指针访问对象可能会导致各种问题,例如对齐(总线错误),整数的陷阱表示。 不同的指针类型甚至不能保证具有相同的宽度,因此从理论上讲,您甚至可能会丢失信息。

但是,应该始终有效的一个演员是(unsigned char*) 。 通过这样的指针,您可以调查对象的各个字节。

当您转换为具有不同大小的类型时,它是未定义的行为。 例如,从char转换为int。 char是1个字节长。 整数长度为4个字节(在32位Linux系统上)。 因此,如果您有一个指向char的指针,并将其转换为指向int的指针,则会导致未定义的行为 。 希望这可以帮助。

类似下面的内容会导致未定义的行为:

 #include  #include  int main() { char *str = "my str"; int *val; val = calloc(1, sizeof(int)); if (val == NULL) { exit(-1); } *val = 1; str = (char) val; return 0; } 

编辑:顺便说一下,Oli所说的关于void *指针的内容是正确的。 您可以在任何void指针和另一个指针之间进行转换。