当初学c语言总感觉数组名和指针之间有种说不清道不明的关系,两者很像,但是又有着解释不清的区别。
比如定义:
char a[]=”hello”;
char *p=”hello”;
同:
都可以用名字输出字符串首地址
printf(“%p %p\n”,a,p);
都可以用名字输出字符串内容
printf(“%s %s\n”,a,p);
都可以用下标操作
a[1] 和 p[1]都对应字符串第1个字符
最大的不同:
p + 1 和 a+1 都对应第一个字符的地址
但是:
可以执行 p = p + 1 或 p = a + 1;
却不可以执行 a = a + 1;
即,赋值运算的左操作数不能是数组名
分析
为了方便,用8086汇编来说明一下:
数组名和变量名都可以看作汇编语言里的标号,如下:
只需要关注第9、10、20行即可
(1)对应的内存空间大小不一样
第9行定义了6个字节的大小(即数组),第10行定义了2个字节(即标号p对应的空间)。
char *p = “hello”;
虽然 a和p 指向的都是一个字符串,但是 a 对应的空间中直接存储了字符串“hello”( ‘$’是字符串结束符,相当于‘\0’), 而p 对应的空间中存储的是字符串的首地址。
p = “hello” 相当于汇编代码中的第20行,获取到”hello”的首地址存到p中。(间接指向字符串)
这也就说明了为什么 sizeof(a) 和 sizeof(p) 计算出的大小是不一样的。
(2)名称(标号)的区别
从汇编中可以看出,CPU 只能直接操作固定大小单元的数据(字节,字),因为数据的操作是要经过寄存器的,寄存器只能是8位寄存器(AL、BL…)、16位寄存器(AX、BX…)…
指针变量p 中存的是地址值,只需要分配 dw 大小(事实上在64位系统中并不是2个字节)。所以,指针变量可以被操作,加、减、赋值运算等。
比如:p = p + 1 (地址值加1)
add p,1
但是字符串是单个字符组成的,每个字符都占8个字节单元,CPU是无法直接对这么一大块内存进行操作的,比如“hello”+1 你觉得这个值该是什么呢。对于字符串是利用首地址进行操作的,根据标号a 可以获取到字符串的首地址,a+n 可以获取到相对首地址偏移n 的地址。使用offset 获取有效地址,如 mov p,offset a+3 ,等价于 p=a+3.
所以汇编中,p+3 和 offset a+3 是等价的,仅仅是a+3对应的是相对首地址偏移量为3的字符。
p = p+1
可以用add p,1 表示;
但是 a=a+1
若用add a,1 表示,则实际含义是:a 是首地址,对应第0个字符’h’, 加1 则表示第0个字符加1,字符串变成了”iello”。这其实是 a[0] = a[0]+1。
用汇编并不能实现 地址值+1 然后赋值到 a 中的效果,所以不能执行a = a+1 。
下标操作就是相对于标号偏移n对应的内存单元,a[2],p[2] 即a+2和p+2对应的单元(这里是一个字节)。
(3)用常量字符串初始化字符数组和指针变量
char a[]="hello";
char *p="hello";
这两条语句似乎没啥区别,都是初始化了一个字符串。但是,经过上面的分析可以看出这两条语句的区别是很明显的。
字符数组的初始化其实是定义了6个字节的内存空间,
然后把”hello”的每个字符依次放进去,a[0]=’h’,a[1]=’e’。。。
而指针变量的初始化是,先定义了比如2个字节的单元,然后把常字符串的首地址存进去。
所以,可以用下标操作,如 a[1] = ‘b’ 来更改字符串的内容。 但是,指针变量是间接的指向常字符串,他是通过常字符串的地址来操作字符串,在c语言中,常字符串是只读的,不允许修改,一般编译的时候把常字符串放进了文本段中。所以,不能通过指针变量来修改常字符串,比如 p[1] = ‘b’ 。他产生的结果是未定义的,未定义即编译器可能报错或产生了异常结果。
另外:
char *p = NULL;
p = “hello”;
这样也不可以修改常字符串,道理和上面是一样的。
以上是我在学习了汇编语言后的一些想法,如有疑问或错误还望指出。