C语言fread/fwrite传入数组时加&与不加&为何效果一致
C语言fread/fwrite传数组名加&与不加&运行结果一致的原因
核心前提:数组名与&数组名的数值相同,类型不同
首先要纠正一个普遍误区:数组名本身不是指针。C语言标准规定,除了作为sizeof、&运算符的操作数,以及用来初始化字符数组的字符串字面量场景外,数组名在表达式中会自动发生「数组退化」,转换为指向数组首元素的指针,值为数组第一个元素的内存地址。
以示例代码中的uint8_t header[HEADER_SIZE];为例:
- 直接传
header时,触发数组退化,得到的是uint8_t*类型的指针,指向数组首元素header[0],值为数组首元素的内存地址 - 传
&header时,因为是&运算符的操作数,不会触发数组退化,得到的是指向整个数组的指针,类型为uint8_t (*)[HEADER_SIZE],值为整个数组的内存起始地址
数组在内存中是连续存储的整块空间,整个数组的起始地址和第一个元素的起始地址在数值上完全相等,这是两种写法能得到相同结果的基础。
为什么fread/fwrite感知不到两者的差异
fread和fwrite的第一个参数类型是通用无类型指针void*,函数原型为:
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream); size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);
这两个函数的内部逻辑根本不关心传入指针的原始类型,只会拿到ptr指向的起始内存地址,按照单个元素大小 * 元素个数计算需要操作的总字节长度,从起始地址开始连续读写对应长度的内存。
不管你传入的是退化成uint8_t*的数组名,还是uint8_t (*)[HEADER_SIZE]类型的数组指针,转成void*后指向的内存起始位置完全一致,读写的内存范围也完全重合,运行结果自然没有区别。
加&的写法属于不推荐的冗余写法
虽然在这个场景下两种写法功能一致,但给数组名加&传参的写法并不规范:
- 类型匹配存在问题:如果开启编译器严格告警(比如gcc加
-Wall -Wextra参数),会提示指针类型不兼容的警告,只是因为最终都会隐式转成void*,不会影响实际运行 - 容易误导代码阅读者:这种写法和普通变量取地址的形式一致,很容易让人误以为传入的是二级指针,混淆数组和普通变量的传参逻辑
- 换场景就会出错:不要认为数组名和&数组名在所有场景下等价——做指针运算时两者步长完全不同:
header + 1会向后偏移1个uint8_t的长度(1字节),指向header[1];而&header + 1会向后偏移整个数组的长度(HEADER_SIZE字节),直接指向数组末尾之后的内存,这时候两种写法的结果会天差地别,只是fread/fwrite内部不会对传入的ptr做指针偏移操作,才没有暴露问题。
日常写代码直接传数组名即可,也就是示例中的第二种写法,是C语言的标准、规范用法。
内容的提问来源于stack exchange,提问作者Dharmapala
相关产品推荐
相关产品推荐

