尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

数组名不是指针?一文讲透C语言数组名与指针的区别

数组名不是指针?一文讲透C语言数组名与指针的区别 有一次我帮一位准备校招的学弟看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)); // 864位平台 printf(sizeof(a) %zu\n, sizeof(a)); // 8类型是 int(*)[10] printf(sizeof(a0) %zu\n, sizeof(a0)); // 8a0 已经退化成 int* return 0; }第一个输出是10 * sizeof(int) 40。第二个输出是 8因为指针变量本身占 8 个字节。第三个输出也是 8但注意a的类型是int(*)[10]它是一个指针所以大小同样是 8。第四个输出最容易出错a0中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得到的一定是指针大小。拿到一个数组形参第一反应应该想到它已经退化成指针了。如果项目里反复传递
返回列表