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

时间:2011-01-26 21:48:59

标签: c casting undefined-behavior

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

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

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

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

  • 你可以自由地({)分别来回void*
  • 你可以自由地({)分别来回char*

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

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

编辑:

我尝试查看C标准(“{3}}”中的“6.3.2.3指针”部分,但除了void*之外,我并没有真正理解它。

EDIT2:

只是为了澄清:我明确地只询问“正常”指针,即关于函数指针。我意识到强制转换函数指针的规则是非常严格的。事实上,我已经问过:-):http://c0x.coding-guidelines.com/6.3.2.3.html

5 个答案:

答案 0 :(得分:27)

基本上:

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

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

重要提示:如果您将{{1}}转换为{{1}},然后尝试将 转换为{{1}}取消引用它。这是标准的一个完全不同的领域。

答案 1 :(得分:9)

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

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

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

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

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

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

答案 2 :(得分:5)

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

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

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

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

答案 3 :(得分:0)

该标准的作者未尝试权衡平台上大多数此类指针类型组合中支持转换的成本和收益,因为:

  1. 大多数此类转换昂贵的平台可能对标准的作者所不了解的平台而言是晦涩的。

  2. 使用此类平台的人们在这种支持的成本和收益方面要比标准的作者更好。

如果某个特定平台对int*double*使用不同的表示形式,那么我认为该标准将刻意允许这种可能性。从double*int*再回到double*的滴灌转换将一直有效,但是从int*double*再回到int*的转换可能会一直有效失败。

我不认为该标准的作者打算在这种转换不花费任何费用的平台上可能会失败。他们在章程和基本原理文档中描述了C的精神,包括“不要阻止或不必要地阻碍程序员完成需要做的事情”的原则。根据这一原则,该标准就不需要强制实施以某种方式帮助程序员完成在不需付出任何代价的情况下完成操作的方式,因为这种实施需要做出真诚的努力来坚持这种做法。无论有没有授权,C的精神都会以这种方式表现。

答案 4 :(得分:-2)

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

如下所示,会导致未定义的行为:

#include <stdio.h>
#include <stdlib.h>

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指针和另一个指针之间进行转换。