指针和数组是两个不同概念,比较容易产生混淆的原因是数组表示法其实是在变相的使用指针。
一个T数组类型的对象如果出现在表达式中会退化为一个指向数组第一个元素的指针(有3种例外情况),指针的类型是指向T的指针。
一旦数组出现在表达式中,编译器会隐式地生成一个指向数组第一个元素的指针,就像是&a[0]一样,当数组作为sizeof或&操作符的操作数,或者作为字符数组的字符串初始值的时候例外。
/* a.c */
const char version[] = "V22030122";
/* b.c */
extern const char *version;
int main(int argc, char *argv[])
{
printf("%s\r\n", version);
return 0;
}
程序执行结果是:
A 输出:V22030122
B 输出:V
C 输出乱码
D 程序异常退出
答案是D。
C语言没有专门用于存储字符串的变量类型,字符串常量不可修改,被保存在.rodata
中,其起始地址可用于保存字符指针类型的变量中。
在b.c
中引入version
时,申明的是一个字符指针,相比于数组,指针在访问相同数据时,会多一次内存寻址,如果是32-bit环境,前4个字节“V220”会被当做内存地址进行第2次寻址;如果是64-bit环境,“V2203012”会被当做内存地址。“V220”对应ASCII码0x56 0x32 0x32 0x30
,若是小端环境,则会读取内存0x30303256
,此处内存恰好能输出乱码可能性极小,大概率会发生core dump,段访问错误。
对于a和p是数组和指针,a[0`和p[0]这样的引用生成的代码差别很大。如下图:
当编译器看到表达式a[0]时,它生成的代码从a的位置开始跳过0个,然后取出哪个字符(对应下图汇编代码第二行)。当它看到p[0],生成代码会先找到p的位置取出其中的指针值,在指针值加0然后取出指向的字符(对应下图汇编第一三行)。
由此可见,a[0]是名为a的对象(的起始位置)之后第0个位置的值,而p[0]是p指向的对象的第0个位置的值。