1. 原理与细节讲解
C语言中的数组和指针虽然在某些场景下表现相似(如数组名常可作为指针使用),但它们本质是不同的数据类型,具有不同的内存语义和生命周期:
- 数组:一段连续内存空间,大小在编译时期已确定。
- 指针:保存一个地址,可以指向任意类型的内存空间。
典型混淆点
- 数组名退化为指针:函数参数传递时,
int arr[]
和int *arr
等价,实际上传递的是指针。 - sizeof运算:数组和指针的
sizeof
结果完全不同。 - 内存分配和释放:静态数组自动分配和释放,指针需要手动管理。
- 数组不能重新赋值,指针可以改变所指内容。
2. 典型陷阱与成因剖析
陷阱1:数组大小误判
void foo(int arr[]) {
printf("%zu\n", sizeof(arr)); // 实际上是指针的大小,不是数组
}
int main() {
int a[10];
foo(a); // 结果通常为4或8
}
成因:数组名在函数参数中退化为指针,sizeof(arr)
得到的是指针大小。
陷阱2:指针越界访问
int *p = malloc(10 * sizeof(int));
p[10] = 0; // 越界,未分配到第11个元素
陷阱3:错误的数组赋值
int a[10], b[10];
a = b; // 错误,数组名不能作为左值
成因:数组不是指针,不能整体赋值。
陷阱4:函数返回局部数组指针
int* foo() {
int arr[10];
return arr; // 警告!返回局部数组地址,未定义行为
}
成因:局部数组在函数结束后被释放,指针失效。
3. 规避方法与最佳实践
- 明确区分数组和指针的语义和生命周期
- 传递数组长度:函数参数传递数组时,始终额外传递长度,避免越界和错误推断
- 使用
sizeof
时注意作用域:只在声明原始数组的作用域内用sizeof
求元素个数 - 不要将数组名当作可赋值的指针:数组名是常量,不能被赋值
- 返回数组需用动态内存分配:如需返回数组,使用
malloc
并说明释放责任
4. 典型错误代码与优化对比
错误代码
#include <stdio.h>
void print_arr(int arr[]) {
printf("size: %zu\n", sizeof(arr)); // 实际为指针大小
}
int main() {
int a[10];
print_arr(a);
}
正确代码
#include <stdio.h>
void print_arr(int arr[], size_t n) {
printf("size: %zu\n", n * sizeof(arr[0])); // 正确的数组大小
}
int main() {
int a[10];
print_arr(a, sizeof(a)/sizeof(a[0]));
}
机制差异:数组作为参数时退化为指针,必须手动传递长度。
5. 底层原理补充
- 数组名退化:在表达式中,数组名大多退化为指向首元素的指针(除了
sizeof
、&
、字符串常量初始化等情形)。 - 指针与数组内存布局:数组是固定大小的连续内存块,指针仅保存地址。
6. SVG辅助图示
<svg width="410" height="70">
<rect x="10" y="20" width="40" height="30" fill="#bdf" stroke="#000"/>
<rect x="50" y="20" width="40" height="30" fill="#bdf" stroke="#000"/>
<rect x="90" y="20" width="40" height="30" fill="#bdf" stroke="#000"/>
<rect x="130" y="20" width="40" height="30" fill="#bdf" stroke="#000"/>
<rect x="170" y="20" width="40" height="30" fill="#bdf" stroke="#000"/>
<text x="20" y="40" font-size="12">a[0]</text>
<text x="60" y="40" font-size="12">a[1]</text>
<text x="100" y="40" font-size="12">a[2]</text>
<text x="140" y="40" font-size="12">a[3]</text>
<text x="180" y="40" font-size="12">a[4]</text>
<text x="10" y="15" font-size="12">数组:a[5]</text>
<line x1="210" y1="35" x2="270" y2="35" stroke="#000" marker-end="url(#arrow)"/>
<rect x="270" y="20" width="40" height="30" fill="#fdc" stroke="#000"/>
<text x="275" y="40" font-size="12">指针p</text>
<text x="320" y="40" font-size="14" fill="#c00">仅保存地址</text>
<defs>
<marker id="arrow" markerWidth="8" markerHeight="8" refX="4" refY="4"
orient="auto" markerUnits="strokeWidth">
<path d="M0,0 L8,4 L0,8 L2,4 L0,0" fill="#000" />
</marker>
</defs>
</svg>
7. 总结与建议
- 数组与指针语法相似却本质不同,易混淆导致隐蔽错误
- 函数传数组时一定要传长度,避免退化误区
- 不要用数组名赋值或返回局部数组指针
- 用
sizeof
判断数组大小时只在数组作用域内
实际建议:写C时始终牢记:数组是集合,指针是地址。理解二者差异,是编写健壮C代码的基础。
公众号 | FunIO
微信搜一搜 “funio”,发现更多精彩内容。
个人博客 | blog.boringhex.top