C语言 关于指针的格式化
写这篇文章时由于没有参考过什么权威资料教材,所以有些观点可能是错误的, 不过我本人都是经过大量调试后才写出来的啦..
1. 内存由多个单位为8bit = 1byte的单元组成,每1个单元都配有1个编号,这个就是内存地址。
计算机的主存储器是内存,而不是硬盘,因为cpu只能直接访问内存... 而cpu访问内存是根据内存的编号来访问的,这个编号就是内存地址,cpu获得内存地址就能直接寻址到对应的内存单位。
2.指针就是内存地址..它们是同一概念
很多人都讲指针就是指向内存地址的东西, 但是我认为指针就是内存地址..
3.指针变量的概念
首先,指针变量是1个变量, 也就是它的值是可以变的。 其次,指针变量是1个特殊的变量, 它存放的值是1个指针(也就是内存地址啦,所以我认为它们是同一样野..
4.指针变量所占字节
在32位的操作系统中, 系统会使用32个2进制位来表示内存地址, 就是从0000...(32个0)....00 到1111...(32个1)..111, 在这里面的每1个2进制数的都可以表示1个地址, 那么这里面有多少个地址呢? 就是2的32次方 = 4G 个啊。
而每1个地址对于1个大小为1byte=8bit(1字节)的内存单元, 所以32位系统最多只支持4GB内存的原因就是这个了,因为地址不够用啊.
而每1个地址都要用32个bit(位来表示), 那么如果要存放这个地址在内存中, 就必须要有32个bit的位置的内存,前面讲到了,每个内存单位的容量是8bit, 所以需要4个内存单元来存放1个内存地址(指针), 也就是说1个指针变量需要 4个地址的内存来存放. 占4个字节。
即sizeof(p) = 4 了 //p是1个指针
即对于32位系统来讲, 1个指针变量的长度是4字节
通常我们会讲2进制的地址换算成十六进制来表示,
就是从0x 00000000 到0x FFFFFFFF
上面的0x 只不过是十六进制的标志.
在64位的操作系统中,相应地系统会用64个2进制位来表示1个地址, 理论上会有17亿多G个地址, 支持17亿GB内存...当然只是理论上了啊。
所以64位操作系统中, 1个地址需要64bit内存来存放, 也就是1个指针变量长度是8byte(字节)啦。
由于本屌的系统是64位的, 所以下面的内容和例子都是基于64位系统来说明的。
下面是一张内存的大概结构图
上面都是一些基本概念, 下面开始才是真正想要说的内容.
5.为什么定义指针变量时要同时指定它的类型。
通常我们定义1个指针时要同时指定指针的类型:
例如
其实,指针的类型决定了指针获取对应地址内存单元的后续单元个数, 怎么理解呢?
参考上图,下面的指针就是1个char 类型的指针,指向了上面的char类型的变量, 地址是00000000FFFF0001, 而char类型只占1个字节, 所以cpu找到这个地址后就直接获得char类型的值了( 01100001 的值是97 ascii换算后就是字符'a')
5.1 int类型变量占4字节, 占用4个地址的内存
但是如果下面的指针要指向上图蓝色的int类型变量呢?
可以由图中得知,int 类型占4个字节啊, 也就是占住4个地址, 分别是00000000FFFF0002, 00000000FFFF0003,
00000000FFFF0004, 00000000FFFF0005, 但是下面char类型指针只能存放1个地址啊?
如果我们强制把其中1个int类型的赋给char 类型指针, 就很能报错了, 因为char 类型指针只能访问1个地址内存啊...
例如我们写个代码如下:
5.2 要指向int类型的变量或常量, 则必须要用int 类型的指针
如果要用指针指向那个Int 类型的变量, 就必须定义1个int 类型的指针
那是不是指 int 类型指针的长度是char 类型指针的4倍, 能存放4个地址呢?
当然不是, 其实所有类型的指针长度都一样的, 都只能放1个地址!
所有当执行 p =&i; 时, 系统实际上是把变量i的头1个字节的地址 赋值给指针p.
而p去访问他的存放地址时, 首先就会指向int类型i的头1个字节, 而因为它是1个int类型的指针, 所以它会从头1个字节开始, 共访问 int类型的长度个数的字节.
而int 类型的长度是4嘛. 所以p指需要知道 int类型的变量的头1个地址, 然后从头1个字节逐个访问4个字节的内存, 就能完全取得int类型的值了.
如下图:
6.指针可以和整数执行加减运算
我们经常在代码看到 p++; p--; 这种语句. 其中p是1个指针啦.
那么p+1 到底代表什么呢?
其实假如 n 是 1个整数
那么 p+ n 其实就是另1个指针, 它 指向P的地址再加上 p指向的数据类型(也就是p的指针类型)的长度 乘于 n.
也就是假如p 是1个char 类型的指针, 它指向的地址是 0000FFFF00000001 , 那么p + 1 就指向 0000FFFF00000002
假如p是1个 Int 类型的指针 它的指向的地址是FFFFFFFF00000001, 那么p+1 就指向 FFFFFFFF00000005
也就是
p + n指向的地址 = p指向的地址 + sizeof(p的类型) * n
画个图:
假如上面的内存存放的是1个int 类型数组的数据P. 那么P指向的就是头1个数组元素的地址了.
而数组的变量名字本身是1个指向第1个数组元素的指针
也就是 *p = p[0]
而p + 1 指向的是下1个元素的地址 即 *(p+1) = p[1]
所以就得出经典公式 *(p+n) = p[n] 啦
8.不同的类型的指针可以指向同1个地址.
大家留意上图, 貌似int 类型 p 和 char 类型的指针q 都指向同1个地址啊..
写几行代码做个例子.
那么正确的写法是什么呢?
因为i 是1个 int类型变量, &i就是 这个变量的首个字节地址. 如果要将这个地址传给1个 char类型的变量.
就要对其进行格式化
正确写法:
这个就是指针的格式化了( 内存地址格式化) // 这句话只是我自己的理解, 有可能是错的!
那么这样做有什么效果呢?
char类型指针 q指向 int类型 i后, *q 是什么?
几下图:
因为q指向的地址内容是 01100001 ,就是 97的二进制表示嘛, 而q是1个char 类型的指针. 所以 *q 就是 97的 ascii对应字符 就是小写字母'a'啊.
下面写个例子程序:
执行结果:
其实 *q 的真实地址内容是 01100001 这个8个bit 的二进制数. 作为char类型的值就是'a'了,上面程序第个printf 函数只是强制输出为十进制数.
附上gdb的调试信息, 可以看到本例子 p 和 q的指向地址都是 0x7fffffffdebc 啦
9.内存插入数据的规律.
我们来研究下数据是怎样插入内存的.
我们知道1个int 类型是占4字节啦 (讲过好多次了)
而当定义1个i 变量时. 系统就会给它分配 4字节内存.
而当给i 赋值时, 系统就忘对应内存写数据了
例如 当执行 i = 97; 时, 系统会将97 换成二进制 1100001, 但这只占7个位. 还有前面32 -7 = 25个位就是0了. 所以
97 = 00000000 00000000 00000000 01100001
问题是这些数据怎么插入内存呢, 见上图debug信息: 内存里是
01100001
00000000
00000000
00000000
点解貌似反了啊. 不是应该如下吗:
00000000
00000000
00000000
01100001
内存每1个位(bit) 都有顺序. 分高低位.
到底怎么分呢,
首先,地址小是地位. 地址大的是高位.
相同地址的8个bit中, 按照常识, 左边是高位.
而插入数据时, 是从低位开始写入地位的内存, 一直到高位.
例如97 这个整数,
00000000 00000000 00000000 01100001
从左到右是高到低, 插入内存时就下图所示了:
假如我定义1个int类型变量
接下来定义1个char 类型指针指向它.
步骤还是先把24930 这个化为二进制数 110000101100010, 然后补齐到32bit (4字节)
就是 00000000 00000000 01100001 01100010
那么p 就是指向最低位(头部地址)的字节 01100010 ,化成10进制数是98 啊, ascii码就是小写字母'b'了
而p+1 指向p的地址加上 1乘于 char类型的长度. 就是下1个地址啊. 就是指向 01100001 ascii码就是小写字母'a'了
下面就是这个例子程序
结果:
10.内存如何存入int类型的负数
继续用上面那个例子, 假如我定义1个int 类型的负数
int i = -24930;
那么内存里的数值是什么呢?
我们可以分析 24930的二进制是
00000000 00000000 01100001
01100010
本文第1张插图已经提过int 类型最高位是用来代表正负的. 所以加上正负位:
10000000 00000000 01100001 01100010
那么内存里的数据就是上面那个二进制数吗?
不是的因为实际上负数是按补码形式来存储的, 而负数的补码是取反再+1
10000000 00000000 01100001 01100010
11111111 11111111 10011110 10011101 先取反(符号位忽略)
11111111 11111111 10011110 10011110 再+1
上面第3行的就是-24930 在内存中的数据了
gdb的信息
1. 内存由多个单位为8bit = 1byte的单元组成,每1个单元都配有1个编号,这个就是内存地址。
计算机的主存储器是内存,而不是硬盘,因为cpu只能直接访问内存... 而cpu访问内存是根据内存的编号来访问的,这个编号就是内存地址,cpu获得内存地址就能直接寻址到对应的内存单位。
2.指针就是内存地址..它们是同一概念
很多人都讲指针就是指向内存地址的东西, 但是我认为指针就是内存地址..
3.指针变量的概念
首先,指针变量是1个变量, 也就是它的值是可以变的。 其次,指针变量是1个特殊的变量, 它存放的值是1个指针(也就是内存地址啦,所以我认为它们是同一样野..
4.指针变量所占字节
在32位的操作系统中, 系统会使用32个2进制位来表示内存地址, 就是从0000...(32个0)....00 到1111...(32个1)..111, 在这里面的每1个2进制数的都可以表示1个地址, 那么这里面有多少个地址呢? 就是2的32次方 = 4G 个啊。
而每1个地址对于1个大小为1byte=8bit(1字节)的内存单元, 所以32位系统最多只支持4GB内存的原因就是这个了,因为地址不够用啊.
而每1个地址都要用32个bit(位来表示), 那么如果要存放这个地址在内存中, 就必须要有32个bit的位置的内存,前面讲到了,每个内存单位的容量是8bit, 所以需要4个内存单元来存放1个内存地址(指针), 也就是说1个指针变量需要 4个地址的内存来存放. 占4个字节。
即sizeof(p) = 4 了 //p是1个指针
即对于32位系统来讲, 1个指针变量的长度是4字节
通常我们会讲2进制的地址换算成十六进制来表示,
就是从0x 00000000 到0x FFFFFFFF
上面的0x 只不过是十六进制的标志.
在64位的操作系统中,相应地系统会用64个2进制位来表示1个地址, 理论上会有17亿多G个地址, 支持17亿GB内存...当然只是理论上了啊。
所以64位操作系统中, 1个地址需要64bit内存来存放, 也就是1个指针变量长度是8byte(字节)啦。
由于本屌的系统是64位的, 所以下面的内容和例子都是基于64位系统来说明的。
下面是一张内存的大概结构图
上面都是一些基本概念, 下面开始才是真正想要说的内容.
5.为什么定义指针变量时要同时指定它的类型。
通常我们定义1个指针时要同时指定指针的类型:
例如
char * p; //定义1个char类型的指针 int * p; //定义1个整性指针
其实,指针的类型决定了指针获取对应地址内存单元的后续单元个数, 怎么理解呢?
参考上图,下面的指针就是1个char 类型的指针,指向了上面的char类型的变量, 地址是00000000FFFF0001, 而char类型只占1个字节, 所以cpu找到这个地址后就直接获得char类型的值了( 01100001 的值是97 ascii换算后就是字符'a')
5.1 int类型变量占4字节, 占用4个地址的内存
但是如果下面的指针要指向上图蓝色的int类型变量呢?
可以由图中得知,int 类型占4个字节啊, 也就是占住4个地址, 分别是00000000FFFF0002, 00000000FFFF0003,
00000000FFFF0004, 00000000FFFF0005, 但是下面char类型指针只能存放1个地址啊?
如果我们强制把其中1个int类型的赋给char 类型指针, 就很能报错了, 因为char 类型指针只能访问1个地址内存啊...
例如我们写个代码如下:
int i = 123; char * p; p = &i; // error char类型指针不能指向int类型的变量
5.2 要指向int类型的变量或常量, 则必须要用int 类型的指针
如果要用指针指向那个Int 类型的变量, 就必须定义1个int 类型的指针
int i = 123; int * p; p = &i; //正确
那是不是指 int 类型指针的长度是char 类型指针的4倍, 能存放4个地址呢?
当然不是, 其实所有类型的指针长度都一样的, 都只能放1个地址!
所有当执行 p =&i; 时, 系统实际上是把变量i的头1个字节的地址 赋值给指针p.
而p去访问他的存放地址时, 首先就会指向int类型i的头1个字节, 而因为它是1个int类型的指针, 所以它会从头1个字节开始, 共访问 int类型的长度个数的字节.
而int 类型的长度是4嘛. 所以p指需要知道 int类型的变量的头1个地址, 然后从头1个字节逐个访问4个字节的内存, 就能完全取得int类型的值了.
如下图:
6.指针可以和整数执行加减运算
我们经常在代码看到 p++; p--; 这种语句. 其中p是1个指针啦.
那么p+1 到底代表什么呢?
其实假如 n 是 1个整数
那么 p+ n 其实就是另1个指针, 它 指向P的地址再加上 p指向的数据类型(也就是p的指针类型)的长度 乘于 n.
也就是假如p 是1个char 类型的指针, 它指向的地址是 0000FFFF00000001 , 那么p + 1 就指向 0000FFFF00000002
假如p是1个 Int 类型的指针 它的指向的地址是FFFFFFFF00000001, 那么p+1 就指向 FFFFFFFF00000005
也就是
p + n指向的地址 = p指向的地址 + sizeof(p的类型) * n
画个图:
假如上面的内存存放的是1个int 类型数组的数据P. 那么P指向的就是头1个数组元素的地址了.
而数组的变量名字本身是1个指向第1个数组元素的指针
也就是 *p = p[0]
而p + 1 指向的是下1个元素的地址 即 *(p+1) = p[1]
所以就得出经典公式 *(p+n) = p[n] 啦
8.不同的类型的指针可以指向同1个地址.
大家留意上图, 貌似int 类型 p 和 char 类型的指针q 都指向同1个地址啊..
写几行代码做个例子.
int i = 97; int * p = &i; // 定义1个int 类型指针 指向i的地址 int * p2 = &i; //在定义1个指向i的地址的int类型指针 p2, 完全无问题的,只不过浪费8字节去存放这个指针变量了. 现在我想定义1个指向i地址的char 类型指针? char * q = &i; //定义1个指向i的地址的 char 类型指针, // 在linux gcc里是可以通过编译的, 但是有warning 信息, 不推荐
那么正确的写法是什么呢?
因为i 是1个 int类型变量, &i就是 这个变量的首个字节地址. 如果要将这个地址传给1个 char类型的变量.
就要对其进行格式化
正确写法:
char * q = (char *)&i; //将int类型变量i的头部地址格式花成 char类型的地址.
这个就是指针的格式化了( 内存地址格式化) // 这句话只是我自己的理解, 有可能是错的!
那么这样做有什么效果呢?
char类型指针 q指向 int类型 i后, *q 是什么?
几下图:
因为q指向的地址内容是 01100001 ,就是 97的二进制表示嘛, 而q是1个char 类型的指针. 所以 *q 就是 97的 ascii对应字符 就是小写字母'a'啊.
下面写个例子程序:
执行结果:
其实 *q 的真实地址内容是 01100001 这个8个bit 的二进制数. 作为char类型的值就是'a'了,上面程序第个printf 函数只是强制输出为十进制数.
附上gdb的调试信息, 可以看到本例子 p 和 q的指向地址都是 0x7fffffffdebc 啦
9.内存插入数据的规律.
我们来研究下数据是怎样插入内存的.
我们知道1个int 类型是占4字节啦 (讲过好多次了)
而当定义1个i 变量时. 系统就会给它分配 4字节内存.
而当给i 赋值时, 系统就忘对应内存写数据了
例如 当执行 i = 97; 时, 系统会将97 换成二进制 1100001, 但这只占7个位. 还有前面32 -7 = 25个位就是0了. 所以
97 = 00000000 00000000 00000000 01100001
问题是这些数据怎么插入内存呢, 见上图debug信息: 内存里是
01100001
00000000
00000000
00000000
点解貌似反了啊. 不是应该如下吗:
00000000
00000000
00000000
01100001
内存每1个位(bit) 都有顺序. 分高低位.
到底怎么分呢,
首先,地址小是地位. 地址大的是高位.
相同地址的8个bit中, 按照常识, 左边是高位.
而插入数据时, 是从低位开始写入地位的内存, 一直到高位.
例如97 这个整数,
00000000 00000000 00000000 01100001
从左到右是高到低, 插入内存时就下图所示了:
假如我定义1个int类型变量
int i = 24930;
接下来定义1个char 类型指针指向它.
char *q = &i printf("*q is %c\n", *q) //*q是多少呢? printf("*(q+1) is %c\n", *(q+1)) //*(q+1) 又是多少呢?
步骤还是先把24930 这个化为二进制数 110000101100010, 然后补齐到32bit (4字节)
就是 00000000 00000000 01100001 01100010
那么p 就是指向最低位(头部地址)的字节 01100010 ,化成10进制数是98 啊, ascii码就是小写字母'b'了
而p+1 指向p的地址加上 1乘于 char类型的长度. 就是下1个地址啊. 就是指向 01100001 ascii码就是小写字母'a'了
下面就是这个例子程序
结果:
10.内存如何存入int类型的负数
继续用上面那个例子, 假如我定义1个int 类型的负数
int i = -24930;
那么内存里的数值是什么呢?
我们可以分析 24930的二进制是
本文第1张插图已经提过int 类型最高位是用来代表正负的. 所以加上正负位:
10000000 00000000 01100001 01100010
那么内存里的数据就是上面那个二进制数吗?
不是的因为实际上负数是按补码形式来存储的, 而负数的补码是取反再+1
10000000 00000000 01100001 01100010
11111111 11111111 10011110 10011101 先取反(符号位忽略)
11111111 11111111 10011110 10011110 再+1
上面第3行的就是-24930 在内存中的数据了
gdb的信息