指针和数组永远是c语言中争论不休讨论不止的话题,多少菜鸟们在c语言的征途上栽倒在指针和数组上。其实只要理解计算机的工作机制,站在计算机的运行角度来分析C的指针和数组就会发现其实没有那么恐怖。这次我们就来探讨一下关于指针和数组的那点事儿。
一.何谓指针
所谓指针就是内存的地址编号,CPU靠地址来访问内存的数据或指令,因此如果在程序中能过直接获取或间接偏移得知一个数据的地址编号的话,将对数据的访问提供很大的便利。
下面通过一段程序演示指针的特性:
#include <stdio.h>
int value = 0x01020304;
int *p = &value;
int main(void)
{
printf("&value = %p\n", &value);
printf("&p = %p\n", &p);
return 0;
}
运行结果为:
接着我们使用objdump工具来分析该程序的数据存放特性:
运行objdump -s a.out
在图片显示的objdump打印结果中,每行最左侧是该行数据的起始地址(实际上是链接器链接后的逻辑地址,当程序被装载到内存中后会被MMU映射为真实的物理地址,而应用程序无需关心),地址自左向右依次递增。而后的四段则是每个地址所存放的数据:
804a00c 00000000 00000000 04030201 14a000408
起始地址 数据 数据 数据 数据
所有的地址和数据皆按照十六进制显示,所以数据中每两位则代表是一个字节(byte),由此可知04、03、02、01四个字节对应的地址风别是:
04: 804a014 (刚好是程序中打印输出的全局变量value的地址)
03: 804a015
02: 804a016
01: 804a017
而该区域对应的数据刚好是程序中全局变量value的值0x01020304 按照小端字节序存放的序列。
接下来的四个字节数据分别对应的数据和地址为:
14: 804a018 (刚好是程序中打印输出的指针变量p的地址)
a0: 804a019
04: 804a01a
08 804a01b
而该区域的数据刚好是程序中指针变量p的空间数据,由于X86架构是小端字节序(低位存放低地址),因此按照小端还原内存的数据为804a014,正是变量value的地址编号。
由此可见,指针变量也是存放在内存中的一个数据而已,只不过它存放的不是一般数据,而是一个内存地址编号。
二、指针的运算
指针中存放的是一个32位的整数(32位处理器下),因此指针也可以进行运算。但是由于指针变量的特殊性(地址编号)所以指针的运算就有了一些条件限制,下面列举出了指针的操作方法:
1.算术运算:
i. 相同类型的指针和指针:-
ii. 指针和整数:+ - (以当前指针为基址往高/低地址偏移)
iii. 单目指针: ++ -- (将当前指针向高/低地址偏移)
2.关系运算:
i. 相同类型的指针和指针: > >= < <= == != (比较地址高低)
3.逻辑运算:
i. 全部: && || ! (逻辑运算与类型无关,内存值非零为真)
4.赋值运算:
i. 相同类型的指针和指针: = (保存地址编号)
ii. 指针和整数: += -= (保存偏移后的地址编号)
5.特殊运算:
i. 访问数据: * [] (中括号也是一种表达式而非数组专用, 后期详细介绍)
ii. 取地址: & (取该指针变量所占的空间地址编号)
以上是常规的指针运算操作方法,站在编译器的角度去理解和分析指针运算就会发现不是那么难以理解,比如:
指针的运算无非就是通过偏移和跳转去访问数据,或者计算两个地址之间的相差数据个数,因此将两个指针相加、相乘、相除、取模等没有任何意义(好比拿中关村东路10号加上中关村东路20号),但是相同类型的两个指针相减就可以算出它们之间相差的元素个数。而不同类型的指针之间相减也无意义(比如10$ - 5¥, 不同单位之间无法直接运算)。