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)
当你施展d时int*,你说这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值.箭头上的标签正确描述了存储器单元中的内容,因此在解除引用时没有任何意外.
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堆栈中的第一个字节(即)和其他连续字节来考虑整数,这对于您的意图而言只是垃圾.
R..*_*R.. 12
C中的转换指针通常是无效的.有几个原因:
对准.由于对齐考虑,目标指针类型可能无法表示源指针类型的值.例如,如果int *是有内在的4字节对齐,铸造char *到int *会失去低位.
别名.通常,除了通过对象的正确类型的左值之外,禁止访问对象.有一些例外,但除非你非常了解它们,否则你不想这样做.请注意,如果您实际取消引用指针(将*或->运算符应用于它,或将其传递给将取消引用它的函数),则别名只是一个问题.
铸造指针可行的主要值得注意的案例是:
目标指针类型指向字符类型时.保证字符类型的指针能够表示任何类型的指针,并且如果需要,可以成功地将其往返返回到原始类型.指向void(void *)的指针与指向字符类型的指针完全相同,只是你不允许取消引用它或对它进行算术运算,它会自动转换为其他指针类型,而不需要转换,所以指向对于此目的,void通常优于指向字符类型的指针.
目标指针类型是指向结构类型的指针,该结构类型的成员与原始指向的结构类型的初始成员完全匹配.这对C中的各种面向对象编程技术很有用.
其他一些不起眼的案例在语言要求方面在技术上是可以接受的,但是有问题且最好避免.
我怀疑你需要一个更普遍的答案:
C 中没有关于强制转换指针的规则!该语言允许您将任何指针强制转换为任何其他指针,而无需注释。
但问题是:没有进行任何数据转换或任何操作!您自己有责任确保系统在转换后不会误解数据 - 通常情况下会出现这种情况,从而导致运行时错误。
因此,在强制转换时,完全取决于您,请注意,如果从强制转换的指针使用数据,则数据是兼容的!
C 针对性能进行了优化,因此它缺乏指针/引用的运行时自反性。但这是有代价的——作为一名程序员,你必须更好地照顾你正在做的事情。您必须自己了解自己想做的事情是否“合法”
小智 5
你有一个指向 a 的指针char。正如您的系统所知,在该内存地址上有一个空间char值sizeof(char)。当您将其转换为 时int*,您将使用 的数据sizeof(int),因此您将打印您的 char 和其后的一些内存垃圾作为整数。