C语言中数组与指针

331 阅读4分钟

C语言中数组与指针

C编程中, 指针是最难的点. 而数组与指针的关系也是很多人都容易混淆的地方.

C语言的数组与指针相同, 这个错误的观念非常可怕!!!

更加准确的说法是, 数组在很多场景下会退化成指针!!!

最近在做国密的相关需求, 接触了大量的C代码, 借着机会把脑子里面的浆糊疏通一下.

C语言中对象的声明与定义

常见的对于数组与指针的申明过程中:

// 声明一个指针, 声明非常清晰, 真正的指针定义在别的其他地方
extern int *a;

// 声明一个数组, 长度不确定, 真正的数组定义在其他地方
extern int b[];

C语言中的声明与定义可以在不同的地方. 通常来说, 我们很容在头文件.h中声明一个变量, 但是在源文件.c中去定义这个变量, 数组与指针也不例外.

并且一个变量可以多次声明, 但是只能定义一次.

extern int a;
extern int b[];

例如以上代码表示, extern对象声明会告诉编译器对象的类型名字, 但是它具体的内存地址在别的地方.

因此对于数组声明来说, 由于对象的声明并不涉及内存分配, 因此无需提供数组的具体长度!!!

定义一个指针, 只需要让指针指向一个地址即可, 但是, 如果定义一个数组, 就需要提供数组的长度信息, 或者隐士提供长度信息:

char x = '0';
char *a = &x; // 定义指针, 直接指向一个地址
char a[3] = {0}; // char数组在定义时直接给出长度为3
char b[] = "12"; // char数组隐士提供了长度信息, "12" C语言字符串隐含'\0'结尾字符
char b[3] = {'0', '1', '2'}; // char数组隐士提供了长度信息

C语言中的数组与指针是如何工作的

实际上在编译器层面上来看指针和数组会非常清晰.

char *a = "123";
char b[] = "123";
...

int i = 1;

c = a[i]; // 编译器在使用指针符号a时, 需要分三步, 第一步, 获取a指向的地址(间接地址), 第二步, 取i的值(步长) ... 
d = b[i]; // 编译器在使用数组符号b时, 需要分两步, 第一步, 取i的值(步长), 将它与b符号的地址相加, 第二步, 获取内存地址为 (b + i) 存储的内容

对于数组符号b, 编译器如果需要一个地址(可能还需要一定的偏移量)来执行一些操作, 它就能直接进行操作, 并不需要增加额外的指令首先取得具体的地址. 相反, 对于指针的符号a, 编译器必须首先在运行时取得它的当前值, 然后才能对它进行解引用操作.

因此, 以下数组的声明是等价的, 声明只需要告知编译器有一个符号是一个数组, 在使用它时, 直接获取值, 不用像指针一样间接寻址:

extern char a[];
extern char a[100];

一句话总结, 一个符号被声明成数组, 就是告诉编译器, 直接用我的地址就好!!!

一个声明为指针, 但是用下标访问:

char *p = "abcdefgh";
...
c = p[i];

当我们声明一个指针时, 也可以对它使用下标访问, 这里的逻辑与数组下标访问是类似的, 只是多了第一步获取p指向的地址.

数组什么时候会退化成指针

我们会考虑两种情况 -- 声明情况使用情况.

针对声明情况, 可以进一步拆分成3种情况:

  1. 外部数组的声明: extern 场景!!! 数组不能退化成指针
  2. 数组的定义(定义是声明的一种特殊情况, 编译器会帮助分配内存空间, 并可能在内存空间中提供初始值) - 数组不能退化成指针(数组会直接分配内存空间)
  3. 函数参数的声明 - 可以退化!!!! 也就是函数参数场景, 数组可以退化成指针

以上函数参数声明, 以下代码是等价的:

void func(int *p) { ... }
void func(int p[]) { ... }
void func(int ip[10]) { ... }

针对使用情况, 例如c = a[i], 数组和指针的互换!!!

小结: 什么时候数组与指针是相同的?

1. 表达式中的数组名就是指针!!!

int a[10], *p, i = 2;
p = a;
p = a + i;
p[i];
*p;
*(p + i);

以上代码使用数组名a时, 都表示是指向数组第一个元素的指针

2. c语言把数组下表作为指针的偏移量

a[i]这种形式对数组进行访问, 总是被编译器解释为类似*(a + i)这样的指针行为!!!

3. 作为函数参数的数组名就是指针!!!

但是, 指针就是指针, 它绝对不是数组, 虽然数组名作为函数参数时候会退化成指针, 并且在函数的内部, 实际上获取的是一个指针.