当前位置:首页 > 学习资源 > 讲师博文 > C语言开发最容易混淆的知识点 ——数组和指针到底有什么区别?

C语言开发最容易混淆的知识点 ——数组和指针到底有什么区别? 时间:2026-10-09      来源:华清远见

在C语言的学习和面试中,有一个问题几乎人人都会被问到:“数组和指针到底是不是一回事?”。很多人会脱口而出:“数组名就是指针嘛!”这句话一半对,一半错。对的部分是:在很多表达式场景下,数组名会“退化”成指向首元素的指针。错的部分是:数组是数组,指针是指针,它们是两种完全不同的类型。把两者混为一谈,是C语言开发中最容易踩坑的知识点之一。

下面,本文就用几段简单的示例代码和图解,把这件事讲清楚。 

一、先看一段“看起来能用”的代码

#include <stdio.h>

 

void func(int arr[]) {

    printf("sizeof(arr) = %zu\n", sizeof(arr));  // 输出的是指针大小

}

 

int main(void) {

    int a[5] = {1, 2, 3, 4, 5};

    printf("sizeof(a) = %zu\n", sizeof(a));      // 输出整个数组大小

    func(a);

    return 0;

}

在64位机器上,典型的输出是:

sizeof(a) = 20

sizeof(arr) = 8

为什么同一个“数组”,在 main 里是 20 字节,传到函数里就变成 8 字节了?因为 函数参数中的 int arr[] 其实等价于 int *arr,它已经不是数组了,而是一个指针。这就是“数组退化成指针”的经典表现。

二、数组和指针的本质区别

我们从三个角度来对比。

(一). 类型不同

int a[5];

int *p = a;

•a 的类型是 int[5],即“含5个int的数组”。

•p 的类型是 int *,即“指向int的指针”。

它们是不同的类型。sizeof 的结果不同就是最直接的证据:

sizeof(a) == 5 * sizeof(int) == 20;

sizeof(p) == 8   // 64位系统

(二). 内存布局不同

数组 a 本身占据一块连续的内存,用来存放5个int:

a:

+------+------+------+------+------+

| a[0] | a[1] | a[2] | a[3] | a[4] |

+------+------+------+------+------+

 0x100  0x104  0x108  0x10C  0x110

而指针 p 只是一个变量,它里面存的是地址:

p:

+----------+

|  0x100   |  ---> 指向 a[0]

+----------+

 0x200

图1:数组是“一排房子”,指针是“写着地址的纸条”。

也就是说,数组名 a 在大多数表达式中会被转换成“首元素地址”,但它本身并不存储这个地址,它只是代表那块内存。

(三). 可修改性不同

int a[5];

int *p = a;

 

p = p + 1;   // 合法,p 是变量

a = a + 1; // 非法!a 不是可修改的左值

数组名 a 不能被赋值,因为它不是一个变量,而是代表数组这块内存的名字。指针 p 是一个变量,可以被修改。 

三、什么时候“数组名退化”?

数组名在以下场景中会退化为指向首元素的指针:

1.作为函数参数传递

2.参与算术运算,如 a + 1

3.用于比较、赋值给指针变量

4.在 sizeof 之外的大多数表达式里

而在以下场景中,数组名不会退化:

1.sizeof(a) —— 得到整个数组大小

2.&a —— 得到“指向整个数组的指针”,类型是 int (*)[5]

3._Alignof(a) 等

看一个容易混淆的例子:

int a[5];

 

printf("%p\n", (void*)a);       // a[0] 的地址

printf("%p\n", (void*)&a);      // 整个数组的地址

printf("%p\n", (void*)(a+1));   // a[0] 地址 + 4

printf("%p\n", (void*)(&a+1));  // 整个数组地址 + 20

虽然 a 和 &a 的数值相同,但类型不同,+1 的步长也不同:

a      -> int*          , a+1   跳过 1 个 int   (4字节)

&a     -> int(*)[5]     , &a+1  跳过整个数组     (20字节)

图2:a+1 与 &a+1 的区别

低地址                                              高地址

+------+------+------+------+------+

| a[0] | a[1] | a[2] | a[3] | a[4] |

+------+------+------+------+------+

^      ^                           ^

a      a+1                         &a+1

&a 

四、二维数组:更容易混淆的地方

int b[3][4];

b 的类型是 int[3][4],它的首元素是 b[0],类型是 int[4]。

所以:

b        -> int (*)[4]   // 指向“含4个int的数组”的指针

b[0]     -> int[4]       // 一个数组

b[0][0]  -> int

当 b 作为函数参数时,会退化成 int (*)[4],而不是 int **。这也是很多人写二维数组传参时出错的原因:

void f(int (*p)[4], int rows) { ... }  // 正确

void f(int **p, int rows) { ... }      // 错误,类型不匹配

图3:二维数组的内存布局

b:

+---------+---------+---------+---------+

| b[0][0] | b[0][1] | b[0][2] | b[0][3] |

+---------+---------+---------+---------+

| b[1][0] | b[1][1] | b[1][2] | b[1][3] |

+---------+---------+---------+---------+

| b[2][0] | b[2][1] | b[2][2] | b[2][3] |

+---------+---------+---------+---------+

它是一整块连续内存,b[i] 是其中第 i 行的数组。 

五、常见误区总结

六、一句话记住区别

数组是“一块连续内存”,指针是“存放地址的变量”。数组名在大多数场合会退化成指针,但它本身不是指针。

理解这一点,你就能避开C语言里最常见的一类陷阱:函数传参时 sizeof 失效、二维数组传参类型不匹配、a+1 和 &a+1 步长不同等等。

写代码时多问自己一句:

我现在操作的是“那块内存”,还是“存地址的变量”?

它退化了吗?退化成了什么类型?

把这两个问题想清楚,数组和指针就不再是容易混淆的概念了。

上一篇:栈、队列、链表分别适合什么场景?嵌入式数据结构选型指南

下一篇:没有了

戳我查看嵌入式每月就业风云榜

点我了解华清远见高校学霸学习秘籍

猜你关心企业是如何评价华清学员的

干货分享
相关新闻
前台专线:010-82525158 企业培训洽谈专线:010-82525379 院校合作洽谈专线:010-82525379 Copyright © 2004-2026 北京华清远见科技发展有限公司 版权所有 ,京ICP备16055225号-5,京公海网安备11010802025203号

回到顶部