C语言类型转换:取地址符&与不同类型指针强转的差异问题
C语言指针类型转换问题解答
两段代码的核心差异
所有指针存储的本质都是内存地址,指针的强制类型转换不会修改指针存储的地址数值,只会改变编译器对该地址指向内存的解析规则,两段代码的核心差异体现在转换的语义和后续访问的合法性上:
- 第一段
long *l_ptr = (long *)&f_var;:&f_var是取float变量f_var的内存首地址,这个地址原本的类型是float*,指向栈上已经分配好的、大小为sizeof(float)(绝大多数平台为4字节)的合法内存。强转为long*后,编译器会将从该地址起始、长度为sizeof(long)的内存块当做长整型数据解析。如果是32位平台long占4字节,此时访问*l_ptr不会越界,只是把浮点数的IEEE754二进制位直接按整数规则读取;如果是64位类Unix平台long占8字节,访问时就已经会越界读取f_var之外的内存。 - 第二段
double *d_ptr = (double *)l_ptr;:是直接将存储了f_var地址的long*类型指针,转为double*类型。转换后指针存的地址值和l_ptr完全一致,还是f_var的首地址,但编译器会将从该地址起始、长度为sizeof(double)(固定为8字节)的内存块当做双精度浮点数解析。由于f_var本身只占4字节,此时访问*d_ptr必然越界读取不属于f_var的栈内存,属于标准未定义行为,运行结果不可预期,甚至可能触发程序崩溃。
&取地址符的作用
你疑惑的&,本质是获取变量绑定的真实内存地址的入口:你定义float f_var = 1.5F;的时候,编译器会在栈上给这个变量分配合法的内存空间,把1.5的浮点数二进制存进去,只有通过&f_var你才能拿到这块内存的真实地址,后续所有指针转换都是基于这个真实地址做的「标签替换」,不会改变地址本身,也不会动内存里存的原始数据。
举个生活化的例子:你有个4格的小抽屉(对应f_var占4字节内存),里面放了4张写了二进制位的纸条,抽屉外面贴的标签是「单精度浮点数:1.5」。
&f_var就是把这个抽屉的位置编号给你,这个编号自带标签:「此处是一个4格的浮点数抽屉」- 把
&f_var强转成long*,相当于你给同一个位置编号换了个标签:「此处是一个N格的长整数抽屉」(N是long的长度),如果N是4,你拉开抽屉拿4张纸条读,读的还是原来抽屉里的东西,只是按整数规则翻译内容;如果N是8,你伸手就会掏到抽屉外面的东西。 - 再把指针转成
double*,相当于换标签成「此处是一个8格的双精度浮点数抽屉」,位置编号还是原来那个4格小抽屉的位置,你要拿8张纸条,必然会拿到抽屉外面不属于你的未知纸条。
验证示例
你可以跑下面这段代码直观验证逻辑,注意越界访问的结果每次运行可能都不一样:
#include <stdio.h> int main() { float f_var = 1.5F; printf("f_var 本身的内存地址:%p\n", (void*)&f_var); printf("按float类型读取f_var的值:%f\n", f_var); // 第一段转换逻辑 long *l_ptr = (long *)&f_var; printf("\nl_ptr 存储的地址值:%p\n", (void*)l_ptr); printf("按long类型读取l_ptr指向内存的值:%ld\n", *l_ptr); // 上面输出的是1.5F对应的IEEE754单精度浮点数二进制的整数表示,值为1069547520 // 第二段转换逻辑 double *d_ptr = (double *)l_ptr; printf("\nd_ptr 存储的地址值:%p\n", (void*)d_ptr); // 你会看到这里的地址和l_ptr、&f_var完全一致 printf("按double类型读取d_ptr指向内存的值:%lf\n", *d_ptr); // 上面这行是未定义行为,会读取f_var后面4字节的未知栈内存,结果无参考意义 return 0; }
补充提醒:不要把指针类型转换和数值类型转换搞混。如果你是想把1.5这个浮点数的值转成long类型的数值1,应该写
long l_val = (long)f_var;,这时候编译器会做真正的数值转换,把转换后的值存在新分配的l_val内存里,和指针转换的逻辑完全不同。
内容的提问来源于stack exchange,提问作者RamaKrishna
相关产品推荐
相关产品推荐

