01.int main()
02.{
int arr[5] = {10, 20, 30, 40, 50};
int* ptr=arr;
int i = 2;
printf("%d\n", arr[i]); // 输出:30
printf("%d\n", *(arr + i)); // 输出:30
printf("%d\n", *(ptr + i)); // 输出:30
10.}
由上面的例子我们可以看出:
- arr 和 &arr[0]都是表示数组首元素的内存地址
- arr[i] 等价于 *(arr + i) 等价于 *(ptr + i),这里 arr 退化为指向首元素的指针
- ptr是指针,在代码的第09行,它的使用方式几乎和数组名字是一样的(08行)
很多教材也在讲:数组名在大多数情况下可以当作指针使用 ,
那么我们到底可以认为数组名就是地址么?可以把数组名字作为指针使用么?
让我们来看一些更详细的例子来做区分:
1. 数组名是地址,但不是指针类型的变量
char str_array[] = "Hello"; // str_array是数组名
char *str_ptr = "Hello"; // str_ptr是指针变量
2. 数组名 vs 指针的实际表现
#include <stdio.h>
int main() {
char str_array[] = "Hello"; // 数组名str_array
char *str_ptr = "Hello"; // 指针 指向内存的.rodata段(存放的是字面量"Hello")只读
char *p = str_array; // 指针
//地址比较
printf("%p\n", str_array); // 地址 0x7ffeed8a42d2
printf("%p\n", &str_array[0]); // 地址 0x7ffeed8a42d2
printf("%p\n", str_ptr); // str_ptr 指向内存的只读.rodata段存放的字面量,
//其首地址是 0x5897ba5b4004
printf("%p\n", p); // 地址 0x7ffeed8a42d2
//sizeof比较
printf("%zu\n", sizeof(str_array)); // 6(数组大小)
printf("%zu\n", sizeof(str_ptr)); // 8(指针大小)
printf("%zu\n", sizeof(p)); // 8(指针大小)
//运算符比较
printf("%p\n", &str_array); // 数组的地址 0x7ffeed8a42d2 但类型不同
printf("%p\n", &str_ptr); // str_ptr指针变量本身的地址 0x7fff0f99b890
//(栈上的某个位置,程序每次运行都会有变化)
printf("%p\n", &p); // p指针变量本身的地址 0xffcee57d058
//指针运算
printf("%p\n", str_array + 1); // 0x7ffeed8a42d3
printf("%p\n", str_ptr + 1); // 只读.rodata段,指针前进1字节 地址是 0x5897ba5b4005
printf("%p\n", p + 1); // 0x7ffeed8a42d3
//注意
// str_array = str_ptr; // 错误!数组名不能出现在赋值左边
return 0;
}
一个常见误解的澄清// 很多人以为:
int arr[5];
int *p = arr;
// 之后 arr 和 p 完全等价,其实不是!
// 关键区别:
// 1. sizeof(arr) != sizeof(p)
// 2. &arr != &p
// 3. arr 不能赋值,p 可以
3. 数组名的本质
数组名在大多数情况下会退化为指向第一个元素的指针,但这不是指针变量:
情况1:作为函数参数
void func(char arr[]) { // 即使写成char arr[] ,但实际上编译器也要把它看成:char *arr
// 这里arr是指针,不是数组
printf("%zu\n", sizeof(arr)); // 8(指针大小)
}
int main() {
char str[] = "Hello";
printf("%zu\n", sizeof(str)); // 6(数组大小)
func(str); // str退化为指针
return 0;
}
情况2:数组名不是左值
char str1[] = "Hello";
char str2[] = "World";
// str1 = str2; // 编译错误!数组名不能赋值
// str1++; // 编译错误!数组名不能自增
st1++ 等价于 str1 = str1 + 1
但 str1 不是左值,不能出现在赋值左边
所以 str1++ 是语法错误
比较指针:
char str_array[] = "Hello"; // 数组
char *str_ptr = "Hello"; // 指针
char *p = str_array;
char *p1 =p; //可以赋值
p1++; //可以做自增运算
printf("%s\n",p); //"Hello"
printf("%s\n",p1); //"ello"
4.内存空间比较:
数组的内存布局:
栈内存 :
地址 内容 变量名
0x1000 'H' str_array(数组从这里开始)
0x1001 'e'
0x1002 'l'
0x1003 'l'
0x1004 'o'
0x1005 '\0'
str_array的值就是0x1000,但这个"值"不存在于一个单独的变量中
简单解释一下这句话 :
编译器处理数组名 str_array 时:
- 没有一个叫做 str_array 的变量来存储地址 0x1000
- 当你在代码中写 str_array 时,编译器直接使用地址 0x1000
- 可以理解为:str_array 是一个编译时常量,它的值在编译时就确定了
指针的内存布局:
栈内存: 只读段:
地址 内容 变量名 地址 内容
0x2000 0x4000 str_ptr 0x4000 'H'
0x4001 'e'
0x4002 'l'
0x4003 'l'
0x4004 'o'
0x4005 '\0'
str_ptr变量存在栈上,存着地址0x4000
5. 一个更深入的例子
#include <stdio.h>
int main() {
char arr[] = "Hello";
char *ptr = "Hello";
//地址对比
printf("%p\n", arr);
printf("%p\n", &arr); // 地址和arr相同!但类型不同(后面有详细的解释)
printf("%p\n", &arr[0]); // 地址和arr相同 类型是指针 char*
printf("%p\n", ptr); // 和arr首元素地址不同,它是指向字面量,.rodata的一个地址
printf("%p\n", &ptr); // 和ptr不同!这是指针变量的地址
printf("%c\n", *ptr); // 'H'
//类型不同
// arr的类型是 char[6](数组类型)
// 在表达式使用时,会退化成char* 指向首元素 即只在需要时会变成指针
// &arr的类型是 char(*)[6](指向整个数组char[6]的指针)
//不能赋值
char arr2[] = "World";
// arr = arr2; // 错误:数组名不是左值
char *ptr2 = "World";
ptr = ptr2; // 正确:指针可以重新赋值
return 0;
}
6. 为什么 &arr 和 arr 的地址相同但类型不同?
看一个例子:
char arr[6] = "Hello";
arr 和 &arr 在数值上是同一个地址,但类型不同,所以指针运算的单位不同。
假设内存从地址 0x1000 开始:
地址 内容 说明
0x1000 'H' ← arr[0]
0x1001 'e' ← arr[1]
0x1002 'l' ← arr[2]
0x1003 'l' ← arr[3]
0x1004 'o' ← arr[4]
0x1005 '\0' ← arr[5]
0x1006 未知 ← 数组后面的内存
0x1007 未知
...
arr(指向单个char的指针)// arr 退化为 char* 类型
// 它"认为"自己指向的是一个char
printf("arr = %p\n", arr); // 输出: 0x1000
printf("arr + 1 = %p\n", arr + 1); // 输出: 0x1001 (前进1字节)
// 因为char大小是1字节
&arr(指向整个数组的指针)// &arr 是 char(*)[6] 类型
// 它"认为"自己指向的是一个"char[6]"的数组
printf("&arr = %p\n", &arr); // 输出: 0x1000
printf("&arr + 1 = %p\n", &arr + 1); // 输出: 0x1006 (前进6字节)
// 因为整个数组大小是6字节
类型的不同,导致操作的不同
表达式 类型 可以进行的操作arr 编译时:char[6] 使用时:char*(退化的) arr[i], *arr, *(arr+i)
&arr char(*)[6] 不能直接[]索引,需要先转为char*
所以请牢记:
- arr 在表达式中退化为 char* 类型
- 但 arr 本身的类型是数组类型 char[6]
- &arr 的类型是 char(*)[6](指向整个数组的指针)
- 数值上相同,但语义不同:
- arr + 1 → 指向 arr[1]
- &arr + 1 → 指向 arr后面的下一个"char[6]"数组
类比:
char book[100]; // 一本100页的书
book → 第1页的地址
&book → 整本书的地址
book + 1 → 第2页的地址(前进1页)
&book + 1 → 下一本书的地址(前进100页)
7. 总结---数组名和指针
特性 数组名 char arr[6] 指针 char *ptr
内存位置 数组元素所在的位置 栈/堆上的一个变量
存储内容 不单独存储(是地址常量) 存储一个地址值
sizeof 数组总大小 指针大小
&运算符 得到相同地址 得到指针变量的地址
能否赋值 不能(不是左值) 能(是变量)
能否++ 不能 能
作为参数传递 退化为char* 直接传递
类型 char[6](数组类型) char*(指针类型)
编译时确定 大小和类型编译时确定 运行时动态
作为右值 退化为 char* 直接是 char*
指针运算 arr + n 前进 n 个元素 ptr + n 前进 n 个元素
8. 最准确的表述
"数组名在大多数表达式中会计算为指向其第一个元素的地址,但它本身不是一个指针变量,而是表示整个数组对象的标识符。"
"数组名在大多数表达式中会退化为 指向其首元素的指针(右值),但它本身是数组类型的标识符,不是指针变量。编译器在编译时知道它是数组,所以 sizeof 返回数组大小,且它不能出现在赋值左边。"
换句话说:
数组名是地址常量:它的值不能改变
指针是地址变量:它的值可以改变