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

sle*_*ske 32 c casting undefined-behavior

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

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

int a = 5;
int* intPtr = &a;
char* charPtr = (char*) intPtr; 
Run Code Online (Sandbox Code Playgroud)

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

  • 你可以void*自由地投射(?)
  • 你可以char*自由地投射(?)

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

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

编辑:

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

EDIT2:

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

Oli*_*rth 30

基本上:

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

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

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

  • @sleske:`T*`(对于任何非函数`T`)在设计上保证正确对齐`char*`.更重要的是,该标准保证允许一个人取消引用`char*`并访问底层数据(对于任何其他目标类型都不是这样). (4认同)
  • @sleske:由于数组和对象大小的交互方式(类型的大小等于数组中该类型的间距),类型的必需对齐必须均匀地划分类型的大小.由于`char`的大小固定为1,因此`char`的对齐也不能是1. (4认同)
  • 除了第二点,如果对齐要求相同,从U*到T*的回送将与原始T*进行比较. (3认同)
  • @Oli Charlesworth:关于"如果对齐要求是相同的":什么是"对齐要求",我应该如何知道给定类型?标准是否说了些什么.是否有一些像`alignedof`这样的运算符? (2认同)
  • @malfy:没有要求对齐与对象的大小相同.对象的大小将影响它的对齐要求,对象的对齐将影响对象的大小(因为大小必须是对齐的倍数).但没有任何东西可以说对象的大小和对齐需要相同.甚至有些平台根本不需要对齐. (2认同)
  • @Oli:嗯,这是你的答案:-).我很感激提到T* - > char* - > T*总是有效的.从逻辑上讲它是从第二点开始的,但我觉得这远非显而易见,但很重要,因为`char*`经常被用作"通用指针".答案应该尽可能简洁,但不能超过;-)(向爱因斯坦道歉).也许你可以添加一些注意事项? (2认同)
  • @OliverCharlesworth:如果我不取消引用它,那么强制转换有什么意义?我还以为你可以施放任何东西来使任何东西无效,但是这里的第二颗子弹,不是这样说的吗?http://spin.atomicobject.com/2014/05/19/c-undefined-behaviors/ (2认同)

sle*_*ske 9

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

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

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

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

据我所知,指向不同类型指针的指针的所有其他转换都是未定义的行为.特别是,如果你没有强制转换为char足够大的整数类型,那么将指针指向不同的指针类型也可能总是 UB - 即使没有取消引用它.

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


Jen*_*edt 5

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

铸造任何类型T*void*和背部,保证对任何对象类型T:这是保证给你完全一样的指针返回.void*是捕获所有对象指针类型.

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

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

  • @sleske:我没说'char`我说'unsigned char`.对于`signed char`(如果签名则为`char`),这是特殊的,因为该类型的陷阱表示可能存在问题.但是根据标准`unsigned char`的定义,永远不会有陷阱表示或填充位,所以始终很好地定义了作为`unsigned char`的单个字节,并且没有解释的余地​​.在标准的几个地方可以预见到通过`unsigned char`访问对象的组件. (2认同)