数组名为何不可变?

99ANYc3cd6
预计阅读时长 11 分钟
位置: 首页 C语言 正文

您这个说法非常准确,可以说是C语言中一个核心且重要的概念,我们可以从几个层面来深入理解“数组名是一个不可变的左值(const lvalue)”这句话。

c语言中 数组名是一个不可变的
(图片来源网络,侵删)

数组名的本质:指向首元素的“常量”指针

在大多数情况下,数组名会被“衰变”(decay)为其首元素的地址,这个地址值被存储在一个指针常量中。

让我们看一个例子:

int arr[5] = {10, 20, 30, 40, 50};

在这个例子中:

  • arr 是一个包含5个整数的数组。
  • 当我们使用 arr 时(在 printf("%p", arr); 中),它会被编译器视为一个指向第一个元素 arr[0](值为10)的指针。
  • 这个指针的值是固定的,因为它代表了数组在内存中的起始位置,你不能改变这个起始位置。

为什么说它是“不可变的”?

“不可变”主要体现在你不能对数组名本身进行赋值运算,这会导致编译错误。

c语言中 数组名是一个不可变的
(图片来源网络,侵删)

1 尝试修改数组名的地址(直接赋值)

这是最直接的错误尝试,你无法让数组名指向一个不同的内存地址。

int arr[5] = {1, 2, 3, 4, 5};
int another_arr[5] = {6, 7, 8, 9, 10};
// 以下代码是错误的,会导致编译错误
arr = another_arr; // 错误: assignment to expression with array type 'int[5]'

编译错误信息:GCC/Clang 会报错 error: assignment to expression with array type 'int[5]',这清晰地表明,你不能给一个数组类型的表达式(即数组名)赋值。

2 尝试修改数组名的值(通过指针算术)

即使你将数组名赋给一个指针,你也不能通过这个指针来修改原始数组名的“地址”值。

int arr[5] = {1, 2, 3, 4, 5};
int *p = arr; // p 指向 arr 的首元素,p == arr
// 以下代码是错误的,但错误可能不那么直观
p++; // p 现在指向 arr[1],这是合法的指针操作
// 这并没有改变 arr 本身!
// arr 仍然指向 arr[0] 的原始地址
printf("p = %p\n", (void*)p);      // 输出: p = (地址+4)
printf("arr = %p\n", (void*)arr);   // 输出: arr = (原始地址)

在这个例子中,p 是一个普通指针,可以被修改,但 arr 本身没有被修改,它依然牢牢地“钉”在数组的起始位置上。p = arr 只是复制了地址值,parr 是两个不同的变量,p 是可变的,而 arr 是不可变的。


数组名与指针的关键区别

尽管数组名在很多情况下会衰变成指针,但它们在C语言中不是一回事,理解它们的区别是掌握C语言的关键。

特性 数组名 指针
本质 代表整个数组,是一个符号常量,其值是数组首地址。 一个变量,存储一个内存地址。
sizeof sizeof(arr) 返回整个数组占用的字节大小int arr[5];sizeof(arr)5 * sizeof(int) sizeof(ptr) 返回指针变量本身占用的字节大小(在32位系统上是4字节,64位上是8字节)。
地址 &arrarr 的值(地址)可能相同,但类型不同。&arr 的类型是 "指向数组 Nint 的指针" (int (*)[N])。 &ptr 是取指针变量的地址,其类型是 "指向指针的指针" (int **)
可变性 不可变,不能被重新赋值指向别处。 可变,可以指向任何兼容类型的内存地址(ptr++, ptr = another_ptr 都是合法的)。

示例:sizeof 的区别

int arr[5] = {0};
int *ptr = arr;
printf("sizeof(arr) = %zu\n", sizeof(arr));  // 输出: sizeof(arr) = 20 (假设int为4字节)
printf("sizeof(ptr) = %zu\n", sizeof(ptr));  // 输出: sizeof(ptr) = 8 (在64位系统上)

这个例子最能体现数组名和指针的根本不同。


哪些情况下数组名不会“衰变”为数组指针?

有两种主要情况数组名会保持其“数组”身份,不会衰变:

  1. sizeof 操作符:如上所述,sizeof(arr) 计算的是整个数组的大小。
  2. & (取地址) 操作符&arr 返回的是指向整个数组的指针,而不是指向首元素的指针。
int arr[5] = {10, 20, 30, 40, 50};
// 情况1: sizeof
size_t arr_size = sizeof(arr); // arr_size 是 5 * sizeof(int)
// 情况2: &
int (*p_arr)[5] = &arr; // p_arr 是一个指向包含5个int的数组的指针
// 注意:这里的类型是 int (*)[5],而不是 int*
// 解引用 &arr 和 arr 的结果是一样的
printf("%d\n", *arr);      // 输出 10 (arr[0])
printf("%d\n", **(&arr));  // 输出 10 (先通过 &arr 找到整个数组,再解引用得到首元素)

尽管 *arr**(&arr) 的结果一样,但它们的操作过程和类型是不同的。


  • 核心观点数组名是一个不可变的左值,它的值(即数组的首地址)是固定的,在程序的整个生命周期中都不能被改变。
  • 衰变现象:在大多数表达式(如作为函数参数、进行指针算术等)中,数组名会“衰变”为一个指向其首元素的指针常量
  • 根本区别:数组名和指针是两种不同的类型。sizeof 操作符是区分它们最简单的方法,数组名代表整个数组,而指针只是一个地址变量。
  • 例外情况:当数组名作为 sizeof& 操作符的操作数时,它不会衰变,而是保持其完整的数组身份。

理解了这一点,你就能明白为什么不能 arr = ptr,也能更好地理解C语言中数组与指针之间复杂而微妙的关系。

-- 展开阅读全文 --
头像
织梦CMS二次安装如何覆盖原数据?
« 上一篇 01-08
织梦后台密码忘了怎么办
下一篇 » 01-08

相关文章

取消
微信二维码
支付宝二维码

目录[+]