从编译器角度分析C语言中数组名和指针的区别--转

本文从编译器的角度出发,详细解释了C语言中数组名和指针之间的区别,包括声明方式、行为特性、引用方式、访问次数及应用场景等方面,旨在帮助开发者准确理解和使用这两个概念。
从编译器角度分析C语言中数组名和指针的区别

数组名和指针是两个往往很容易让人们混淆的概念,很多人以为数组名就是一个指针,也有很多人知道数组名不同于指针但是仅知道数组名的值不能像指针一样改变,例如你可以写出下面这样的代码:
int *p;
p++;
却不能写这样的代码:
int a[];
a++;
那么数组名跟指针之间到底有什么区别呢?

第一,在声明上,除了作为函数参数的数组名总是编译器转化成指针,其他情况下,数组名就是数组名,指针就是指针,二者不能混淆,你不能在一个文件中定义一个数组,而在另一个文件中把它声明成一个指针。
char a[]; //定义一个数组a
 extern char* a; //在另一个文件中将a声明成一个指针
在编译器中,符号表用来存放C语言中有关标识符的属性信息,这些信息集中反应了标识符的特征属性。等到词法分析到代码声称的各个阶段的时候,编译器需要根据源代码提出的要求,从表中获取不同标识符的不同属性。值得注意的是,数组标识符的属性和指针是完全不同的。因此,在一个文件中定义一个数组的时候,编译器会把它记录在符号表中,而在编译器分析另一个文件中的声明时,通过符号的语义检查发现不一致。也许这样的声明可以编译通过(在gcc下通过了),但是把一个数组名当成一个指针来使用,别指望它能运行起来。

第二,指针是一个变量,而数组名不是。数组名是数组的首地址,即它本身就是一个地址,对应到汇编语言级别就是一个常量,一个固定的数(地址)。因此数组名不能进行++,--等运算。
在大多数编译器中,对数组的引用a[i]总是被编译器改写成*(a+i)的格式。也就是说,编译器每遇到a[i],都会把它当作*(a+i)来处理。我们都知道,*addr表示内存中(addr)的位置存储的值,比如*0x8048000就表示地址为0x8048000的内存中所存储的值。所以a[i]就表示a的值加上i所得到的数作为一个内存地址里面所存储的值。
那么a的值是什么呢?编译器在做词法分析和语法分析的时候,遇到一个数组的定义,就会把数组的有关信息汇集在一个叫做“内情向量”或“信息向量”的表各种,其中的信息包括数组的类型,维数,各维的上、下边界,以及数组的首地址,然后将这个“内情向量”相关信息存储在符号表中。数组定义后位置就是固定的,因此其首地址可以在编译阶段得到。当编译器到达代码生成的各阶段时,每次遇到数组名这个标识符,编译器都会从符号表中取出这个数组的首地址,然后用这个地址来替代数组名,例如,假设数组a起始地址是0x8048000,则a[1]就会被编译器转化成*(0x8048000+1),因此在生成的汇编代码中,数组名已经完全被转化成一个常量,一个固定的数(地址)。
但是,对于指针p,它是一个变量,其值存储在地址&p中,这个值在编译时是不能得到的。因为是变量,所以指针可以作为表达式中的左值操作,如++或--,而被认为是常量的数组名却不可以,正如你可以骑走一辆自行车,但是不能骑走一棵树。另外,C语言把数组下标改写成指针偏移量的根本原因是指针和偏移量是底层硬件所使用的基本模型。

第三,对数组的引用,如a[i],或*(a+1),需要访存一次;而对指针的引用,如*(p+1),需要访存两次。
如果理解了第二条的解释,这个应该就不难理解。因为a被认为是常数,所以取*(a+1)的值只需将a所表示的常数加1,然后从得到的地址里访存取一次即可。而对于指针,需要先从&p这个地址里把p的值取出来,然后加1,再从得到的地址里访存取一次,一共需要两次访存。
第四,假设a是一个数组名,而p是一个指针,当你使用 a 和 &a 时,得到值是一样的,都是数组的起始地址。而使用 p 和 &p 时,得到的值是不一样的, p 表示指针 p 所指向的地址,而 &p 表示 p 这个变量的地址。再假设
p = a;
则 p 就表示数组a的起始地址,而&p是存储数组a的起始地址的那个地址。
这是因为编译器把a当成数组首地址,而&a当作数组第一个元素的地址,因此得到的值是一样的。
另外,sizeof(a)得到的是a所表示的数组的大小,而sizeof(p)得到的是指针的大小。
<think> 嗯,用户这次的问题是关于C语言数组指针的联系。从对话历史看,用户之前询问过函数返回的相关知识,现在向C语言的底层概念,可能是想深入理解内存操作机制。 用户引用的三篇资料很有价值:第一篇强调数组名不等同指针,第二篇展示指针数组的用法,第三篇讨论数组指针的声明方式。这说明用户已经做过基础调研,现在需要系统性的串联解释。 需要特别注意几个技术细节:数组名的常量特性、指针运算的实质、编译器如何处理下标访问。用户可能被"arr[i]""*(ptr+i)"的等价性迷惑,需要解释底层机制而非表面语法。 考虑到用户正在学习C语言内存模型,应该强调两个关键点:1)类型系统如何影响指针运算 2)数组退化为指针的例外情况。示例代码要体现数组在sizeof&操作时的特殊性。 上次回答函数返回时提到过栈内存陷阱,这次可以关联到"返回局部数组指针"的场景。用户似乎对底层原理感兴趣,可以补充内存布局示意图,但需避免过度展开影响主线逻辑。 用户没有明确具体应用场景,但引用[2]涉及字符串数组,可能实际需要处理多级指针。将在"多维数组"部分重点说明char*[]与char[][]的区别,这是常见易错点。 </think> 在C语言中,数组指针的联系紧密而微妙,主要体现在以下核心层面: --- ### 一、本质联系:数组名作为常量指针 1. **隐式类型换** 数组名在表达式中**自动换为指向首元素的指针常量**(除`sizeof`、`&`操作): ```c int arr[3] = {10, 20, 30}; int *p = arr; // 等价于 int *p = &arr[0]; ``` 此时`arr`作为右值时等价于`&arr[0]`,类型为`int*`常量[^1]。 2. **下标操作的等价性** 指针数组共享相同的下标访问机制: ```c arr[i] ≡ *(arr + i) // 编译器数组下标换为指针算术运算 p[i] ≡ *(p + i) // 指针同样支持下标语法 ``` --- ### 二、内存访问的底层一致性 1. **指针算术运算** 指针加减整数时,步长由指向类型决定: ```c int *ptr = arr; ptr++; // 实际地址增加 sizeof(int) 字节 ``` 此特性使得指针可遍历数组: ```c for(int *iter = arr; iter < arr+3; iter++) { printf("%d ", *iter); // 输出 10 20 30 } ``` 2. **多维数组指针表示** 二维数组本质是"数组数组",可通过指针数组模拟: ```c int matrix[2][3] = {{1,2,3}, {4,5,6}}; int *row_ptr[2] = {matrix[0], matrix[1]}; // 指针数组[^2] printf("%d", row_ptr[1][2]); // 输出6 ``` --- ### 三、关键区别:类型系统与内存分配 | **特性** | **数组** | **指针** | |------------------|---------------------------|---------------------------| | 内存分配 | 静态连续内存块 | 动态/静态分配地址值 | | `sizeof`行为 | 返回整个数组字节大小 | 返回指针自身大小 | | 可修改性 | 数组名不可重新赋值 | 指针变量可指向新地址 | | 取地址操作 | `&arr`返回整个数组的地址 | `&p`返回指针变量的地址 | > 示例说明: > ```c > int arr[5]; > int *p = arr; > > sizeof(arr); // 20 (假设int为4字节) > sizeof(p); // 4或8(指针大小) > > &arr + 1; // 地址增加 sizeof(arr) 字节 > p + 1; // 地址增加 sizeof(int) 字节 > ``` --- ### 四、函数参数传递的退化机制 数组作为函数参数时**退化为指针**,失去数组长度信息: ```c // 以下声明完全等价 void func(int *param); void func(int param[]); void func(int param[10]); // 10被忽略 void func(int param[][3]); // 二维数组需指定第二维 ``` --- ### 五、特殊结构:数组指针 vs 指针数组 1. **数组指针**:指向数组指针 ```c int (*arr_ptr)[3]; // 指向含3个int元素的数组 int matrix[2][3]; arr_ptr = matrix; // 指向第一行 printf("%d", (*arr_ptr)[2]); // 访问matrix[0][2] ``` 2. **指针数组**:元素为指针数组 ```c char *str_arr[3] = {"Hello", "World", "!"}; // 字符串指针数组[^2] printf("%c", str_arr[1][0]); // 输出 'W' ``` --- ### 六、典型注意事项 1. **禁止返回局部数组地址** ```c int* dangerous_func() { int local_arr[3] = {1,2,3}; return local_arr; // 错误!栈内存释放后指针失效 } ``` 2. **数组边界安全** 指针运算需显式控制边界,避免越界: ```c int arr[5] = {0}; int *p = arr + 5; // 指向末尾后位置 // *(p) 访问越界!(允许指向末尾后,但解引用非法) ``` --- ### 结论 数组指针的联系本质上是C语言**直接操作内存**特性的体现: 1. 数组名为编译期确定的**地址常量**,支持指针算术 2. 指针提供动态访问数组元素的灵活机制 3. 多维数组依赖指针进行高效访问 4. 函数参数传递通过退化机制实现统一处理 理解二者的区别与联系是掌握C语言内存模型的核心,对编写高效安全的底层代码至关重要[^3]。 --- ### 相关问题 1. 为什么`sizeof`操作在数组指针上表现不同? 2. 如何通过指针安全地遍历动态分配的二维数组? 3. 数组指针指针数组在内存布局上有何差异? 4. 函数参数传递时数组退化为指针会带来哪些潜在风险? 5. C语言中如何实现类似C++的数组引用传递?
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值