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;
}
它编译,但我的print语句吐出垃圾变量(每次调用程序时它们都不同)。有任何想法吗?
答案 0 :(得分:111)
在考虑指针时,有助于绘制图表。指针是指向内存中地址的箭头,带有指示值类型的标签。地址表示要查看的位置,类型表示要采取的操作。投射指针会更改箭头上的标签,但不会更改箭头指向的位置。
d
中的 main
是c
的指针,其类型为char
。 char
是内存的一个字节,因此当取消引用d
时,您将获得该内存的一个字节中的值。在下图中,每个单元代表一个字节。
-+----+----+----+----+----+----+-
| | c | | | | |
-+----+----+----+----+----+----+-
^~~~
| char
d
当您将d
投射到int*
时,您说d
确实指向int
值。在今天的大多数系统中,int
占用4个字节。
-+----+----+----+----+----+----+-
| | c | ?₁ | ?₂ | ?₃ | |
-+----+----+----+----+----+----+-
^~~~~~~~~~~~~~~~~~~
| int
(int*)d
取消引用(int*)d
时,会得到一个由这四个字节的内存确定的值。您获得的值取决于标记为?
的这些单元格中的内容,以及int
在内存中的表示方式。
PC是little-endian,这意味着int
的值以这种方式计算(假设它跨越4个字节):
* ((int*)d) == c + ?₁ * 2⁸ + ?₂ * 2¹⁶ + ?₃ * 2²⁴
。因此,您会看到虽然值是垃圾,但如果以十六进制(printf("%x\n", *n)
)打印,则最后两位数将始终为35
(这是字符'5'
的值)。
其他一些系统是big-endian,并将字节排列在另一个方向:* ((int*)d) == c * 2²⁴ + ?₁ * 2¹⁶ + ?₂ * 2⁸ + ?₃
。在这些系统上,当以十六进制打印时,您会发现以#{1}}开始值。某些系统的大小为35
,与4个字节不同。少数几个系统以不同的方式排列int
,但您极不可能遇到它们。
根据您的编译器和操作系统的不同,您可能会发现每次运行程序时该值都不同,或者它始终相同,但在对源代码进行微调时会发生变化。
在某些系统上,int
值必须存储在4的倍数(或2或8)的地址中。这称为alignment要求。根据{{1}}的地址是否恰好正确对齐,程序可能会崩溃。
与您的程序相比,这是当您有一个int
值并指向它时会发生什么。
c
int
指针int x = 42;
int *p = &x;
指向-+----+----+----+----+----+----+-
| | x | |
-+----+----+----+----+----+----+-
^~~~~~~~~~~~~~~~~~~
| int
p
值。箭头上的标签正确描述了内存单元格中的内容,因此在解除引用时没有任何意外。
答案 1 :(得分:34)
char c = '5'
在地址char
的堆栈上分配0x12345678
(1个字节)。
char *d = &c;
您获取c
的地址并将其存储在d
中,因此d = 0x12345678
。
int *e = (int*)d;
您强制编译器假定0x12345678
指向int
,但int不只是一个字节(sizeof(char) != sizeof(int)
)。根据架构甚至其他值,它可能是4或8个字节。
因此,当您打印指针的值时,通过取第一个字节(即c
)和堆栈中的其他连续字节来考虑整数,这对于您的意图来说只是垃圾。
答案 2 :(得分:12)
C中的转换指针通常无效。原因有多种:
对齐。由于对齐考虑,目标指针类型可能无法表示源指针类型的值。例如,如果int *
固有地为4字节对齐,则将char *
转换为int *
会丢失较低位。
混叠。通常,除了通过对象的正确类型的左值之外,禁止访问对象。有一些例外,但除非你非常了解它们,否则你不想这样做。请注意,如果您实际取消引用指针(将*
或->
运算符应用于它),或将其传递给将取消引用它的函数,则别名只是一个问题。
铸造指针的主要值得注意的案例是:
当目标指针类型指向字符类型时。保证字符类型的指针能够表示任何类型的指针,并且如果需要,可以成功地将其往返返回到原始类型。 void(void *
)指针与指向字符类型的指针完全相同,只是不允许取消引用它或对其进行算术运算,它会自动转换为其他指针类型,而不需要为了这个目的,所以指向void的指针通常优于指向字符类型的指针。
当目标指针类型是指向其成员与原始指向结构类型的初始成员完全匹配的结构类型的指针时。这对于C语言中的各种面向对象编程技术很有用。
其他一些模糊的案例在语言要求方面在技术上是可以接受的,但是有问题且最好避免。
答案 3 :(得分:2)
你有一个指向char
的指针。因此,正如您的系统所知,在该内存地址上,char
空间上存在sizeof(char)
值。当你把它强加到int*
时,你将使用sizeof(int)
的数据,所以你将打印你的char和一些内存垃圾作为整数。
答案 4 :(得分:2)
我怀疑你需要一个更一般的答案:
C中没有关于转换指针的规则! 该语言允许您将任何指针转换为任何其他指针而不进行注释。
但问题是: 没有数据转换或其他任何操作! 它完全是你自己的责任,系统不会在演员表之后误解数据 - 这通常是这种情况,导致运行时错误。
因此,当完全向您推荐时,请注意如果从类似指针使用数据,则数据是兼容的!
C针对性能进行了优化,因此缺少指针/引用的运行时反射性。 但这是有代价的 - 作为程序员,你必须更好地照顾你正在做的事情。 你必须知道自己想做什么是“合法的”
答案 5 :(得分:1)
垃圾值实际上是由于在声明之前调用了函数bleh()。
对于c ++,你会得到编译错误,但是c中的,编译器认为函数的返回类型是 int ,而你的函数返回一个指向整数的指针。
有关详情,请参阅此处: http://www.geeksforgeeks.org/g-fact-95/