You need to enable JavaScript to run this app. C语言中指向unsigned char数组的指针为何c与*c地址相同且无法直接用c[i]访问
优惠活动
大模型
产品
解决方案
定价
更多

C语言中指向unsigned char数组的指针为何c与*c地址相同且无法直接用c[i]访问

核心问题现象

给定如下测试代码:

typedef unsigned char mac_address_t[6];
mac_address_t* c = malloc(sizeof(mac_address_t)); 
printf("%p \n", c); 
printf("%p \n", (*c));

实际运行时两次printf输出的内存地址完全一致;已知&(*c)[0]的打印结果和上述两个值也完全相同。这里存在两个核心疑惑:

  • 为什么指针c和解引用得到的*c打印出的地址值完全一样
  • 既然二者地址相同,为什么不能直接用c[i]的形式访问数组内的单字节元素
为什么c和*c打印的地址值完全相同

这个现象的本质是两个表达式的类型不同,但存储的地址数值一致,拆解底层类型逻辑如下:

  1. 首先明确自定义类型的本质:mac_address_t是长度为6的unsigned char数组类型,本身不是指针。因此mac_address_t* c的实际类型是unsigned char (*)[6]——即指向「一整个长度为6的char数组块」的指针,它存储的内存值就是这个数组块的起始地址。
  2. 对c执行解引用*c,得到的是位于该地址处、类型为unsigned char[6]的数组左值。根据C语言的数组退化规则:当数组类型的表达式出现在大多数场景(比如作为可变参数传入printf这类参数类型不固定的函数)时,会自动转换为指向该数组首元素的指针,类型为unsigned char*,转换后的指针数值同样是数组块的起始地址。

也就是说,虽然两个表达式最终打印出来的地址数值完全重合,但二者的指针类型有本质区别:

  • c是指向整个数组块的指针,做指针运算时的步长是6个字节
  • 退化后的*c是指向单字节char的指针,做指针运算时的步长是1个字节

这也是为什么&(*c)[0]的打印值和二者完全一致:它本身就是直接取数组首元素的地址,和前两个表达式指向的物理内存位置完全相同。

为什么不能直接用c[i]访问数组元素

C语言中的下标运算a[b]本质等价于*(a + b),地址偏移量的计算完全由操作数的指针类型决定,规则是:偏移量 = 下标值 × 指针类型的步长。

如果直接对c使用下标运算c[i]:
因为c的类型是unsigned char (*)[6],步长为6字节,计算地址偏移时会按照“每个步长对应一整个6字节mac数组”的规则计算:

  • c[0]等价于*(c + 0),本质是拿到c指向的那个6字节数组本身(退化为char*的首地址)
  • c[1]等价于*(c + 1),会直接从当前数组的起始位置向后偏移6个字节,完全跨过当前申请的整个数组块,访问到相邻的未知内存,完全不符合逐字节访问数组元素的预期。

正确的数组元素访问方式

如果要访问指向的mac地址数组的第k个元素,常见的合法写法包括:

  • 先解引用拿到数组本身,再对数组取下标:(*c)[k]
  • 利用二维下标的等价规则:c[0][k](本质和上一种写法的执行逻辑完全等价)
  • 强制转换为逐字节的字符指针后再做偏移:*((unsigned char*)c + k)
常见认知误区
  • 只要打印的地址值相同,两个指针的行为就可以完全互换:实际上指针类型决定了运算规则、步长,哪怕两个指针存储的数值完全一样,类型不同时的行为可能天差地别
  • 地址重合就可以直接用下标访问元素:跨类型的指针如果步长不一致,直接用下标访问会出现非预期的内存越界

内容的提问来源于stack exchange,提问作者damooo

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.11.09 14:54:03