在C中转换指针的规则是什么?

The*_*nic 60 c pointers casting

K&R不会过去,但他们会使用它.我尝试通过编写示例程序来了解它是如何工作的,但它并没有那么顺利:

#include <stdio.h> 
int bleh (int *); 

int main(){
    char c = '5'; 
    char *d = &c;

    bleh((int *)d); 
    return 0;  
}

int bleh(int *n){
    printf("%d bleh\n", *n); 
    return *n; 
}
Run Code Online (Sandbox Code Playgroud)

它编译,但我的print语句吐出垃圾变量(每次调用程序时它们都不同).有任何想法吗?

Gil*_*il' 117

在考虑指针时,绘制图表很有帮助.指针是指向内存中地址的箭头,带有指示值类型的标签.地址表示要查看的位置,类型表示要采取的操作.转换指针会更改箭头上的标签,但不会更改箭头指向的位置.

din main是指向c其类型的指针char.A char是内存的一个字节,因此当d取消引用时,您将获得该内存的一个字节中的值.在下图中,每个单元代表一个字节.

-+----+----+----+----+----+----+-
 |    | c  |    |    |    |    | 
-+----+----+----+----+----+----+-
       ^~~~
       | char
       d
Run Code Online (Sandbox Code Playgroud)

当你施展dint*,你说这d真的指向一个int价值.在今天的大多数系统中,int占用4个字节.

-+----+----+----+----+----+----+-
 |    | c  | ?? | ?? | ?? |    | 
-+----+----+----+----+----+----+-
       ^~~~~~~~~~~~~~~~~~~
       | int
       (int*)d
Run Code Online (Sandbox Code Playgroud)

取消引用时(int*)d,您将获得一个由这四个字节的内存确定的值.您获得的值取决于标记的这些单元格中的内容?以及int在内存中如何表示.

甲PC是小端,这意味着,一个值int被计算这种方式(假设它跨越4个字节): * ((int*)d) == c + ?? * 2? + ?? * 2¹? + ?? * 2²?.所以你会看到,虽然值是垃圾,但如果你用十六进制(printf("%x\n", *n))打印,最后两位数将始终是35(这是字符的值'5').

其他一些系统是big-endian,并在另一个方向排列字节:* ((int*)d) == c * 2²? + ?? * 2¹? + ?? * 2? + ??.在这些系统中,你会发现,价值总是开始35时十六进制打印.某些系统的大小int与4个字节不同.罕见的少数系统int以不同的方式排列,但您极不可能遇到它们.

根据您的编译器和操作系统的不同,您可能会发现每次运行程序时该值都不同,或者它始终相同,但在对源代码进行微调时会发生更改.

在某些系统上,int值必须存储在4的倍数(或2或8)的地址中.这称为对齐要求.根据地址是否c正确对齐,程序可能会崩溃.

与您的程序相比,这是当您有一个int值并指向它时会发生什么.

int x = 42;
int *p = &x;
Run Code Online (Sandbox Code Playgroud)
-+----+----+----+----+----+----+-
 |    |         x         |    | 
-+----+----+----+----+----+----+-
       ^~~~~~~~~~~~~~~~~~~
       | int
       p
Run Code Online (Sandbox Code Playgroud)

指针p指向一个int值.箭头上的标签正确描述了存储器单元中的内容,因此在解除引用时没有任何意外.

  • 很好的描述。我想指出/讨论的是,在大多数计算机上,int 是 32 位值可能是真的,但对于嵌入式工程师来说,int *通常* 是 16 位,它显示了多么有用,而且可能很重要它是使用 uint16_t、uint32_t、int32_t 等。不要试图成为一个聪明的屁股,请不要冒犯。:) (4认同)
  • @马尔科姆这是未定义的行为。取消引用强制转换的结果是 UB(例如,它可能没有正确对齐),即使只是构造一个指针,如果取消引用它也会是 UB,通常也是 UB(我认为唯一的例外是函数指针和指向结尾的指针)数组)。有一种情况定义了行为,即指针最初是一个“int*”指针;任何数据指针都可以转换为“unsigned char*”并返回,并且我认为“unsigned char *”可以转换为“char *”并返回。 (3认同)
  • “根据‘c’的地址是否正确对齐,程序可能会崩溃。” 我认为情况可能比这更糟糕。如果编译器设法向自己证明该程序违反了“int”的对齐规则,它可能会决定删除整个代码块,基于诸如“没有符合规范的程序可以执行此操作,并且不符合规范的程序可以执行此操作”之类的推理。做任何事情。” 这是您实际上会看到编译器在实践中所做的事情,具体取决于您的优化设置。 (2认同)

Jac*_*ack 37

char c = '5'
Run Code Online (Sandbox Code Playgroud)

char(1个字节)是在地址上堆分配0x12345678.

char *d = &c;
Run Code Online (Sandbox Code Playgroud)

您获取地址c并将其存储d,因此d = 0x12345678.

int *e = (int*)d;
Run Code Online (Sandbox Code Playgroud)

您强制编译器假定0x12345678指向a int,但int不只是一个字节(sizeof(char) != sizeof(int)).根据体系结构或甚至其他值,它可以是4或8个字节.

因此,当您打印指针的值时,通过获取c堆栈中的第一个字节(即)和其他连续字节来考虑整数,这对于您的意图而言只是垃圾.

  • 其他连续字节不是垃圾,而是"d"的值,即示例中的"0x12345678". (2认同)

R..*_*R.. 12

C中的转换指针通常是无效的.有几个原因:

  1. 对准.由于对齐考虑,目标指针类型可能无法表示源指针类型的值.例如,如果int *是有内在的4字节对齐,铸造char *int *会失去低位.

  2. 别名.通常,除了通过对象的正确类型的左值之外,禁止访问对象.有一些例外,但除非你非常了解它们,否则你不想这样做.请注意,如果您实际取消引用指针(将*->运算符应用于它,或将其传递给将取消引用它的函数),则别名只是一个问题.

铸造指针可行的主要值得注意的案例是:

  1. 目标指针类型指向字符类型时.保证字符类型的指针能够表示任何类型的指针,并且如果需要,可以成功地将其往返返回到原始类型.指向void(void *)的指针与指向字符类型的指针完全相同,只是你不允许取消引用它或对它进行算术运算,它会自动转换为其他指针类型,而不需要转换,所以指向对于此目的,void通常优于指向字符类型的指针.

  2. 目标指针类型是指向结构类型的指针,该结构类型的成员与原始指向的结构类型的初始成员完全匹配.这对C中的各种面向对象编程技术很有用.

其他一些不起眼的案例在语言要求方面在技术上是可以接受的,但是有问题且最好避免.

  • 你能链接到这些不起眼的案件的官方文件吗? (4认同)
  • @EvanBenn:有可能。如果缓冲区是通过“malloc”获得的,并且通过“fread”或类似方式将数据按字节存储到其中,那么只要偏移量适当对齐(通常这可能很难确定,但如果它们“ (类型大小的倍数)它应该符合转换为适当的指针类型并以该类型访问数据。但是,如果您正在使用实际类型为“char[N]”或其他类型的缓冲区,则它是无效的。 (2认同)

Ole*_*ann 6

我怀疑你需要一个更普遍的答案:

C 中没有关于强制转换指针的规则!该语言允许您将任何指针强制转换为任何其他指针,而无需注释。

但问题是:没有进行任何数据转换或任何操作!您自己有责任确保系统在转换后不会误解数据 - 通常情况下会出现这种情况,从而导致运行时错误。

因此,在强制转换时,完全取决于您,请注意,如果从强制转换的指针使用数据,则数据是兼容的!

C 针对性能进行了优化,因此它缺乏指针/引用的运行时自反性。但这是有代价的——作为一名程序员,你必须更好地照顾你正在做的事情。您必须自己了解自己想做的事情是否“合法”

  • 有一些关于强制转换指针的规则,其中一些规则位于 C 2011 标准的第 6.3.2.3 条中。除此之外,指向对象的指针可以转换为其他指向对象的指针,并且如果转换回来,将与原始指针进行比较。指向函数的指针可以转换为其他指向函数的指针,如果转换回来,将比较相等。将函数指针转换为对象指针会导致未定义的行为。指向对象的指针可以转换为指向字符的指针并用于访问对象的字节。 (15认同)
  • @aqjune 您引用了 C 的_流行扩展_,根据定义,它不是标准 C。它仅供参考。 (4认同)

小智 5

你有一个指向 a 的指针char。正如您的系统所知,在该内存地址上有一个空间charsizeof(char)。当您将其转换为 时int*,您将使用 的数据sizeof(int),因此您将打印您的 char 和其后的一些内存垃圾作为整数。