一、指针与地址
学习指针就要知道指针到底是什么?我们知道内存是一系列连续的byte字节,每一个byte都有一个唯一的编号,叫内存地址(物理地址)。指针也是一个普通变量,只不过这个变量用于存放一个内存地址。下面我们来举例说明指针的本质:
int i = 7; int *p = &i;
假设变量i在内存中的地址为0x2000,变量p在内存中的地址为0x2004,它们在内存中的存储状态如下:
也就是说变量i的地址为0x2000,而指针变量p指向了这个地址,实际上就是p这个变量的值是0x2000这个数值。在32位架构下所有指针都占4个byte。
来看下面的例子:
#include <stdio.h> int main(int argc, char **args) { int i = 7; unsigned int addr = (unsigned int) &i; int *p = (int *) addr; printf("0x%x\n%d\n", p, *p); return 0; }
运行结果:
0xffc7f654 7
其实指针就是一个特殊的占用4字节的无符号整型变量,它与unsigned int唯一的区别就是指针的值是一个内存地址而已。我们可以通过&i来得到i所在的内存地址,这个地址就是一个无符号整数,再把这个整数做为指针类型赋值给p是合法的,没有任何问题(在32位架构下)。
二、类型转换
对于所有的指针都是用于存放内存地址,都占用4个byte。那么编译器是如何知道这些地址中存放的什么类型的变量呢?答案是通过指针的类型。假设一个指针变量的地址为0x2000,如果这是一个char型指针,那么编译器则认为这个地址中存放的是一个char型变量,占用1个byte;如果这是一个int型指针,那么编译器则认为这个地址中存放的是一个int型变量,占用4个byte;如果这是一个float型指针,那么编译器则认为这个地址中存放的是一个float型变量,占用4个byte;其它类型的指针变量也都是一样的道理。如下图:
下面我们来看一下关于指针的强制转换问题
#include <stdio.h> int main(int argc, char **args) { char *p1; short *p2; short *p3; int *p4; char i = 0x11; char j = 0x22; char k = 0x33; char l = 0x44; p1 = &i; printf("0x%x\n", *p1); p2 = (short *) &j; printf("0x%x\n", *p2); p3 = (short *) &k; printf("0x%x\n", *p3); p4 = (int *) &l; printf("0x%x\n", *p4); return 0; }
运行结果:
0x11 0x1122 0x2233 0x11223344
来看一下p1、p2、p3、p4、i、j、k、l这些变量在内存中的地址和内容:
可以看到当地址0x2002赋值给p2时,因为p2是一个short型指针,因此在对p2做解引用时,编译器认为在这个地址中存放的是一个short型变量,占2个byte,所以*p2的值为0x1122;同样的道理*p3的值为0x2233;而p4是一个int型指针,因此在对p4做解引用时,编译器认为在这个地址中存放的是一个int型变量,占4个byte,所以*p4的值为0x11223344。
再来看一个强制转换的例子:
#include <stdio.h> int main(int argc, char **args) { int i = -1024005767; printf("%d\n", i); float *p = (float *)&i; printf("%f\n", *p); return 0; }
运行结果:
-1024005767 -123.456001
是不是很奇妙?其实跟上面所说的原理没有任何区别,当指针p在解引用时编译器认为这是一个指向float浮点变量的地址,于是编译器就会按float的计算方式来解析这个地址的内容。-1024005767的16进制值为0xC2F6E979,这个数值用计算浮点数的公式来解析所得到的值就是-123.456001。所以,这个运行结果是正确的。
在浮点数章节中我们知道float和double类型的变量是不能做位运算的,但是我们可以通过指针强制转换的方式来间接的对其做位运算,虽然这个作法并不合理,我们也不提倡,但是我们要明白,在实际编程时可以这么做:
#include <stdio.h> int main(int argc, char **args) { float i =-0.123456; printf("%f\n", i); int *p = (int *) &i; *p <<= 1; printf("%f\n", i); return 0; }
运行结果:
-0.123456 2592781549273207636974872920896569344.000000
结果是一个非预期的浮点数,因为在做左位移运算时,浮点数的符号位、阶码和尾数都被修改了,这不是一个好的程序,这样的程序在实际中无法确定结果的正确性,所以对浮点数做位运算是很有风险的。
再来看一个例子:
#include <stdio.h> #include <string.h> int main(int argc, char **args) { //0x61 == 97 == 'a' int i = 0x61; char *p = (char *)&i; printf("%c\n", *p); printf("%d\n", strlen(p)); }
大尾机运行结果:
0
小尾机运行结果:
a 1
来看一下这个例子中变量i在内存中的结构:
字符型指针char *p的值为0x2000,编译器在对p解引用时认为0x2000处是一个char型变量占1个byte,在大尾机中0x2000处存放的是0x00,而在小尾机中存放的则是0x61。所以在大尾机中运行上面例子,结果为“空”,长度为0;而在小尾机中运行结果为‘a’,长度为1。
Copyright © 2015-2023 问渠网 辽ICP备15013245号