☰
数组名不是指针?一文讲透C语言数组名与指针的区别
2026/10/18 17:23:19 网站建设 项目流程

有一次我帮一位准备校招的学弟看C语言笔试题,其中有一道关于数组名和指针的题目,他做错了。他问我:为什么arr和&arr打印出来的地址一样,但题目答案却说“数组名不是指针”?这个问题在很多C语言学习者脑子里盘旋很久,也是笔试和面试反复出现的坑。

其实,这个问题的答案并不复杂,但需要从C语言的类型系统去理解。先给一个明确结论:数组名和指针在“值”上经常相同,但在“类型”和“性质”上完全不同。数组名本质上是一个对象的名字,而不是一个保存地址的变量;只是在大多数表达式中,数组名会“退化”成一个指向首元素的指针。搞清楚了“退化”的规则,再回头看那些笔试坑题,会发现它们其实是在同一个知识点上反复挖坑。

下面从四个角度把这个问题讲透:数组名的本质是什么,sizeof和&为什么是关键分水岭,笔试高频坑题怎么判断,以及实际开发中应该注意哪些边界。

1. 先搞清楚数组名和指针到底哪里一样、哪里不一样

1.1 数组名的本质是“不可修改的左值”

C语言中,数组是一组同类型对象的连续存储组合。int a[10];这句代码创建了一个名为a的数组对象,它占用连续的内存空间,可以存放 10 个 int 值。数组名a是这个数组对象的标识符,它代表整个数组对象,而不是一个保存着地址的指针变量。

但它有一个特别的性质:数组名是一个左值,却不是一个可修改的左值。所谓左值,可以简单理解成“能出现在赋值号左边的东西”,但数组名不能出现在赋值号左边。比如下面的代码无法通过编译:

int a[10]; int b[10]; a = NULL; // 错误:数组类型不可赋值 a = b; // 错误:不能修改数组名

而指针变量是真正的可修改左值:

int *p = NULL; p = a; // 合法:p 是变量,可以重新指向

这个区别是很多人第一层误解的来源:因为数组名在使用时能当成地址用,所以下意识认为它就是一个指针变量。其实它更像是“一个固定地址的别名”,你能读取它、把它传给函数、用它访问元素,但不能让它指向别处。

1.2 值相同时,类型决定了后续行为

几乎每个初学者都写过这样的代码:

int a[5] = {1, 2, 3, 4, 5}; int *p = a; printf("%p\n", (void*)p); printf("%p\n", (void*)a);

两个输出确实一样。原因是在绝大多数表达式中,数组名a会退化成指向首元素的指针&a[0],而p被赋的也正是这个地址。

但“值一样”不代表“类型一样”,更不代表两者等价。这里的a表达式的类型是int*,p的类型也是int*,所以在这段代码里它们确实可以互换。可一旦把a放到sizeof、&或者函数参数里,退化行为就会发生变化,结果也会完全不同。

1.3 数组名在什么情况下退化成指针

C标准规定,除少数情况外,当数组名出现在表达式中时,会自动转换为指向首元素的指针。这个转换通常叫“数组衰减”或“退化(decay)”。

少数例外情况包括:

  • sizeof(a):操作数是数组名时,返回整个数组体积,不退化。
  • &a:取数组的地址,得到指向整个数组的指针,不退化。
  • 字符串字面量用于初始化字符数组时,不退化。

打个比方:数组名像一台机器,指针只是这台机器的出厂地址。绝大多数场景下,你会把地址给别人用;但当你问“这台机器有多重”(sizeof),或者要“给机器整体编号”(&)时,它就不再退化成地址了。

这一节是理解后面所有坑的基础。先记住一句话:数组名默认会退化成指针,但在sizeof和&面前不会退。

2. sizeof 是第一个分水岭:一个测类型,一个测指针大小

2.1 sizeof(数组名) 返回整个数组的大小

直接看代码:

#include <stdio.h> int main(void) { int a[10]; int *p = a; printf("sizeof(a) = %zu\n", sizeof(a)); // 40(假设 int 为 4 字节) printf("sizeof(p) = %zu\n", sizeof(p)); // 8(64位平台) printf("sizeof(&a) = %zu\n", sizeof(&a)); // 8,类型是 int(*)[10] printf("sizeof(a+0) = %zu\n", sizeof(a+0)); // 8,a+0 已经退化成 int* return 0; }

第一个输出是10 * sizeof(int) = 40。第二个输出是 8,因为指针变量本身占 8 个字节。第三个输出也是 8,但注意&a的类型是int(*)[10],它是一个指针,所以大小同样是 8。第四个输出最容易出错:a+0中a先退化成int*,再做加法,所以结果也只是指针大小。

如果笔试只考到这里,其实是送分题。真正容易错的是下面这个问题:数组名作为函数参数传进去之后,函数内部用sizeof会得到什么?

2.2 函数参数里的“数组”其实是指针

很多项目里能见到这样的代码:

int sum(int a[], int n) { printf("sizeof(a) = %zu\n", sizeof(a)); // 8,不是数组长度 int s = 0; for (int i = 0; i < n; i++) { s += a[i]; } return s; }

问题出在:C语言函数参数并不能真正接收一个数组。编译器看到函数形参写成int a[],会把它解释成int *a。这是C语言里最经典的退化场景之一。

所以上面的sizeof(a)得到的是指针大小。很多人在函数内部试图用sizeof(a) / sizeof(a[0])来计算数组长度,结果在 64 位系统上得到的是8 / 4 = 2,与预期完全不符。这也是 C 语言新手最常踩的坑之一。

2.3 实际开发里怎么正确获取数组长度

正确的做法是:在数组名还没有退化的地方,也就是定义数组的那个作用域里,用sizeof计算长度,然后把长度作为参数传给函数。

void print_array(int a[], size_t len) { for (size_t i = 0; i < len; i++) { printf("%d ", a[i]); } printf("\n"); } int main(void) { int a[] = {1, 2, 3, 4, 5}; print_array(a, sizeof(a) / sizeof(a[0])); return 0; }

这里必须重点关注:sizeof(a) / sizeof(a[0])必须写在main里,因为在main里a还是数组名;一旦传入print_array,它就已经退化成指针了。

注意:在函数内部对数组形参使用sizeof,得到的一定是指针大小。拿到一个数组形参,第一反应应该想到它已经退化成指针了。

如果项目里反复传递

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询