0w0

  •  例子引入
  •  一、不求甚解
  •  二、求甚解
    •   1、数组名和指针的区别
    •   2、数组名在哪里
    •   3、求甚解
    •   4、下标表达式 和 指针表达式

说是“深析”,我也不知道够不够深。
对于大佬来说,只能是献丑了。


 例子引入

首先看如下代码:

char arr[4] = { 1, 2,3, 4};
printf("arr:%p\n\n", arr);
printf("arr取址:%p\n\n", &arr);
printf("arr取址后寻址:%p\n\n", *(&arr));

  我们知道,数组名称 arr 是一个不可修改的变量,那么他的存放位置在哪呢?

这是我使用的教材上的描述:

  看起来数组名的存放位置应该是在非数组数据区的。

  现在假设,数组 arr 的首地址为 0x02,数组名的存放位置为 0x07
按照我们的正常思路,在内存中应有如下抽象表格:

地址 数据
0x01
0x02 1
0x03 2
0x04 3
0x05 4
0x06
0x07 0x02

  现在编译代码执行程序,来验证一下:
输出为:

arr:
0x02arr取址:
0x02arr取址后寻址:
0x02

  结果很奇怪,第二行输出表示 arr 并未存放在其他位置,而是就位于数组首地址处。
  这意味着,如果 arr 作为一个指针变量,那么他将指向自己。
  也就是说,我们无论对 arr 寻址多少次,输出的应该依然是它本身。
就像如下内存中的抽象表:

地址 数据
0x01
0x02 0x02
0x03 2
0x04 3
0x05 4
0x06
0x07

  然而这与我们的常识不符,1 去哪了?
  而且,arr 本应该是指向数组首成员的指针。

这里我们尝试对 arr 直接寻址试一下:

printf("arr寻址:%d\n", *arr);

输出为:

arr寻址:
1

  这样看来 arr 的地址并非是 0x02,那为什么刚才对 arr 取址后输出的却是 0x02 呢?

  这就出现了一个很神奇的矛盾,到底是为什么呢?


 一、不求甚解

  实际上,在上述例子中,我们使用 & 和 * 对arr的运算并非取址和寻址。

  先给出一个不求甚解的解释:
对数组名的 & 和 * 运算会使得该数组名对维度的引用范围“升维”或“降维”。

  怎么个升维降维?
  我们知道,数组名其实“相当于”一个行指针,或者说具有行指针的性质,而行指针可以定义一个多维数组的“框架”,然后通过指向一个数组来将定义的框架“套上去”。(“相当于”的说法是不严谨的,下一节我们详细讨论)

  在一个定义好的行指针框架中,声明了其维度和每个维度的下界。
  而对于直接声明的数组,它的数组名具有行指针的性质。

例如:

char arr[2][3][4] = { 0 }; //24个元素

  重点来了!
  这时候,对于数组名 arr ,其类型为 ( char (*arr)[2][3][4] ) ,arr + 1 意味着地址增加 3 * 4 * sizeof(char)
arr 等价于 ( char (*arr)[2][3][4] ) arr

  另外地,还有如下对应关系:
(我们刚才提到,*对数组名 arr 的运算意味着 “降维”)

*arr,其类型为 ( char (*arr)[3][4] ) ,*arr + 1 意味着地址增加 4 * sizeof(char)
*arr 等价于 ( char (*arr)[3][4] ) arr


**arr,其类型为 ( char (*arr)[4] ),**arr + 1 意味着地址增加 1 * sizeof(char)(此时已经降维至最低维)
**arr 等价于 ( char (*arr)[4] ) arr


***arr,其类型为 ( char arr ),已经引用到了第一个数组元素的实际数据

为了方便理解,我们看一下代码和输出:

#include<stdio.h>
void main()
{char arr[2][3][4] = { 0 };printf("arr = "); printf("%d\n", arr);//arrprintf("sizeof(arr) = "); printf("%d\n", sizeof(arr));//sizeof arrprintf("arr + 1= "); printf("%d\n\n", arr + 1);//arr + 1printf("*arr = "); printf("%d\n", *arr);printf("sizeof(*arr) = "); printf("%d\n", sizeof(*arr));printf("*arr + 1 = "); printf("%d\n\n", *arr + 1);printf("**arr= "); printf("%d\n", **arr);printf("sizeof(**arr) = "); printf("%d\n", sizeof(**arr));printf("**arr + 1 = "); printf("%d\n\n", **arr + 1);printf("***arr= "); printf("%d\n", ***arr);printf("sizeof(***arr) = "); printf("%d\n", sizeof(***arr));printf("***arr + 1 = "); printf("%d\n\n", ***arr + 1);
}

输出:

arr = 9698400
sizeof(arr) = 24   //作为第一维度的行指针,其数组长度 2 * 3 * 4 * sizeof(char) == 24
arr + 1= 9698412  //增加了 3 * 4 * sizeof(char) == 12//降维
*arr = 9698400
sizeof(*arr) = 12  //作为第二维度的行指针,其数组长度 3 * 4 * sizeof(char) == 12
*arr + 1 = 9698404    //增加了 4 * sizeof(char) == 4//降维
**arr= 9698400
sizeof(**arr) = 4  //作为第三维度的行指针,其数组长度  4 * sizeof(char) == 4
**arr + 1 = 9698401   //增加了 1 * sizeof(char) == 1//降维
***arr= 0  //取到了首元素数据 0
sizeof(***arr) = 1
***arr + 1 = 1


  而对于 & 运算符,即为 * 的逆运算:

简单例子:将第二维的行指针 (*arr) 进行 & 运算即 &(*arr)

 printf("&(*arr) = "); printf("%d\n", &(*arr));printf("sizeof(&(*arr)) = "); printf("%d\n", sizeof(&(*arr)));printf("&(*arr) + 1 = "); printf("%d\n\n", &(*arr) + 1);

输出:

&(*arr) = 9698400
sizeof(&(*arr) ) = 4   //这里存疑,尽管其拥有三维的行指针的性质,但其长度后却是一个普通指针的长度(对于任意行指针取址后皆如此)
&(*arr)  + 1 = 9698412    //增加了 3 * 4 * sizeof(char) == 12

 二、求甚解

  问题算是基本解决了,但是我仍然有很多疑惑,比如 arr 作为指针常量时,其储存位置到底在哪?或者到底该如何理解数组名和指针?…

  我尝试从图书馆借到大名鼎鼎的《C和指针》,却发现根本找不到,传说中的《C专家编程》,《C陷阱与缺陷》更是没有影子。(吐槽一下图书馆。。。之前想借那本绿皮的 Python 也没找到,只能自掏腰包买来看)
  只能费劲从网上找了pdf版本拿来参考,翻起来真的很费劲…
  另外,我本以为《C和指针》读起来应该晦涩难懂,却没想到读着非常顺畅,并没有过于高深的难以理解的东西。
(尽管已经对着舍友发誓过 期末之前不碰代码


  1、数组名和指针的区别

  先聊一聊数组名和指针

  我大致翻阅了《C和指针》第八章(数组)的内容。

  首先要提到,所谓“行指针”,貌似只是我们中国人(或是部分教材)对多维数组的认知,实际上C标准并未对所谓“行指针”有过要求和定义。

  书中作者并未通过我们认知的“行指针”来理解多维数组,而是直接指出了指针与数组的区别。
  一般认为,数组名是指针常量(但其仍然是变量,只不过其内容不允许更改),指针是变量(内容随意更改)。

  而作者认为,指针是一个标量值,而数组名则包含很多属性(就像结构体),只不过这些属性是存在于 编译器层面的底层逻辑 里。数组名在表达式中表现出的指针常量是众多属性其中之一,还有其作为高维指针时表现出的“+ 1 跳行”也是属性之一。

我们来看这两段话:

数组具有一些和指针完全不同的特征。
例如,数组具有确定数量的元素,而指针只是一个标量值。
编译器用数组名来记住这些属性。
只有当数组名在表达式中使用时,编译器才会为他产生一个指针常量。

  也就是说,作者认为数组名并非指针常量,指针常量仅仅作为数组名的一个属性,将会在某些情况下从数组名当中体现出来。

只有在两种场合下,数组名并不用指针常量来表示——就是当数组名作为 sizeof 操作符或单目操作符&的操作数时。
sizeof 返回整个数组的长度,而不是指向数组的指针长度。
取一个数组名的地址所产生的是一个指向数组的指针,而不是一个指向某个指针常量值的指针。

  令我疑惑的是,作者在这一章并未总结对数组名进行取址寻址运算时会发生的事情,尽管他提到了&运算会返回一个指向数组的指针,但仍然没有说明白这个指针实际上是一个高维指针。


  2、数组名在哪里

  尽管我们知道了对数组名使用 * & 代表的含义,却还是无法知晓数组首元素地址到底储存在哪里?储存在数组名 arr 中?那么 arr 本身又储存在哪?
  清早起来跑步的时候,我突然想起在vs的调试器中曾经看到过什么“反汇编”这样的选项,回到宿舍之后我马上尝试去看一下这个汇编代码。
  
  虽然我根本没学过汇编,但是对照着内存中的数据,大概可以猜到:
  
  然而在汇编代码前后仔细翻了好几遍,把所有可能的地址全部在内存中找了一遍,没有发现哪个地方存有数组首元素的地址。
  后来包括昨天我也查了很多资料,毫无头绪,好像大家都并不关心数组首元素地址到底存在哪里了...
  
  ------------------------------------------------12/20更新------------------------------------------------
  
  问题已经解决。
  实际上,程序并未对该地址分配空间,这个问题我们马上讨论。


  3、求甚解

  我们一般这样理解数组,(数组 = 指针 + 数据) 他的数组名是一个指针,其后分配了数个连续的内存空间来存放同类型数据。然而事实并非如此,这种理解是错误的。
  那么凭什么对数组名的 * 和 & 运算就不是寻址取址,而是另有意义?
  
  既然这一节叫“求甚解”,那么在这里让我们更进一步:数组和指针完全不同
  
  前边提到了“数组名和指针的区别”,说到了《C和指针》作者对数组和指针的理解,我们即将再往深了讨论。
  
  从本质上来说,数组和指针是完全不同的派生类型,这种不同不仅仅体现在数组等于指针加数据。实际上数组里面根本没有指针,我们常说的数组名是指针变量或常量的说法是错误的,数组就是数组,数组名就是数组的名称,仅此而已。只不过数组名恰好表现出了类似指针的性质,一些教科书为了方便读者理解,对这里的内容没有深究。
  数组名所代表的是那一整块数组的内存空间,我们使用数组名就是针对这个数组,而不是针对一个地址甚至指针。之前我们讨论过 “数组名包含很多属性”,实际上这些属性就是数组这个数据类型所具有的属性。
  
  数组和指针的关系,就像 int 和 char 的关系一样,指针是指针类型,数组是数组类型,它们是两种数据类型。之所以我们可以将一个指针当做数组来用(甚至可以使用下标表达式 arr[3]),那是因为 C 恰好允许你这样做,而不是意味着我们就要将两者等同起来。(下一节将详细剖析其中的缘由)
  
  有了这些铺垫,我们回到最初的问题上,一切迎刃而解。
  我们之所以对 &arr == arr 疑惑不解,就是因为我们没有搞清楚数组和指针到底有什么区别。
  数组名代表着一段连续的内存空间,所以 对数组名取址 &arr 的含义是对数组取址,而非对指针取址,那么返回数组的首元素地址是很正常且自然的,就像我们对 int 取址也是返回首地址一样。
  
  正因为这种不同,* 和 & 作用于数组会体现出完全不同的意义也很好理解。


  4、下标表达式 和 指针表达式

  对于引用数组元素,下面我将介绍一种有些怪异的方法,我将通过它来揭示一个令人惊讶的事实。

我们知道,引用数组元素有两个途径,

char arr[4] = { 0 };

1.通过数组下标表达式:

arr[2];

2.通过指针表达式:

*(arr + 2);

  实际上,所谓“数组下标表达式”是一种伪装的写法,我们马上来说明他。
现在我们来看这个古怪的例子:

2[arr];

  注意,这样写是完全可以编译通过的!你可以现在就试一试。

  为什么这样古怪的写法并不会报错?
  这其实就是 C 编译器理解下标表达式的方法。

  很显然,上面的例子表明 2[arr]arr[2] 是等价的。

  我将 2[arr] 转换一下形态(等价的指针表达式):*( 2 + (arr) )
  不难猜到,arr[2] 等价的指针表达式为:*( (arr) + 2 )

往下拓展,对于多维数组:

char arr[2][3];

  arr[1][2] 的等价指针表达式为: *(*((arr) + 1) + 2)

  结合先前我们学到的知识,
  内层的 arr 为高纬指针:arr + 1
  对其寻址后就“降维”了:*(arr + 1) + 2
  最后我们再寻址取到数据: *(*(arr + 1) + 2)

  更多维数组以此类推。

【数组】深析 “数组名称”相关推荐

  1. 【C 语言】数组 ( 多维数组本质 | n 维数组名称本质 是 n-1 级数组指针 )

    文章目录 总结 一.多维数组本质 二.完整代码示例 总结 n 维数组名称本质 是 n-1 级数组指针 一.多维数组本质 给定多维数组 : // 声明一个多维数组int array[2][3]; 打印二 ...

  2. Linux C编程---指针数组简析(二维数组、多级指针)

    讲到指针和数组,先给大家看一道例题: 题目:填空练习(指向指针的指针) 1.程序分析: 2.程序源代码: main() { char *s[]={"man","woman ...

  3. php 数组去重_数组去重(JavaScript 为例)

    数组去重,就是在数组中查找相同的元素,保留其中一个,去除其他元素的程. 从这句话揭示了数组去重的两个关键因素: 找到重复项 去除重复项 本文告诉你在遇到去重问题时该如何思考,并以 JavaScript ...

  4. java数组解析_Java - 数组解析

    一.什么是数组 数组?什么是数组?在我印象中的数组是应该这样的:通过new关键字创建并组装他们,通过使用整形索引值访问它的元素,并且它的尺寸是不可变的! 但是这只是数组的最表面的东西!深一点?就是这样 ...

  5. 【C 语言】指针 与 数组 ( 指针 | 数组 | 指针运算 | 数组访问方式 | 字符串 | 指针数组 | 数组指针 | 多维数组 | 多维指针 | 数组参数 | 函数指针 | 复杂指针解读)

    相关文章链接 : 1.[嵌入式开发]C语言 指针数组 多维数组 2.[嵌入式开发]C语言 命令行参数 函数指针 gdb调试 3.[嵌入式开发]C语言 结构体相关 的 函数 指针 数组 4.[嵌入式开发 ...

  6. 指针数组、数组指针、数组的区别与联系

    指针数组.数组指针.数组的区别与联系! 一:基本定义 1.指针数组 char *arr[4] = {"hello", "world", "shannx ...

  7. 不确定大小的数组_原来数组是容器喔

    和我一起学C吧  努力是可以提升智力的5.1.2    数组的使用:如何定义和使用数组,数组的下标和下标的范围 前面有数组的简单介绍,可以去回顾一下其相关定义    变量名称[元素数量]: 例:    ...

  8. java 二维数组倒置_Java数组反转及二维数组

    数组的反转 反转就是把最远端的元素和最近端的元素位置互换(反转前数据:1,2,3,4,5,6反转后:6,5,4,3,2,1) //准备一个需要反转的数组 int[] arr = {1,2,3,4,5, ...

  9. java定义数组_java中数组的三种定义方式_java中数组的定义及使用方法(推荐)...

    java中数组的三种定义方式 java中,数组是一种很常用的工具,今天我们来说说数组怎么定义 [java] view plain copy /** * 数组的三种定义方法 * 1.数组类型[] 数组名 ...

最新文章

  1. Linux系统搭建FTP服务器
  2. DL之DNN:利用MultiLayerNet模型【6*100+ReLU+SGD,weight_decay】对Mnist数据集训练来抑制过拟合
  3. 测试Spring的“会话”范围
  4. Mybatis的缓存机制Cache
  5. 吉林建筑大学电气与计算机学院院长,吉林建筑大学电气与计算机学院研究生导师:张玉红...
  6. 无可用源 没有为任何调用堆栈加载任何符号_看完这篇后,别再说你不懂JVM类加载机制了~...
  7. Linux网络协议栈【转载】
  8. radius服务器认证系统,TekRadius(RADIUS服务器)
  9. 项目使用微信公众平台图片显示此图片来自微信公众平台 解决方法
  10. 我的wow血精灵圣骑士,晒晒
  11. 浩辰3D软件中应用程序菜单如何使用?
  12. html基础之好看的header
  13. 手把手教你webpack3(14)HMR模块热加载
  14. P 问题、NP 问题、NPC 问题(NP 完全问题)、NPH 问题和多项式时间复杂度
  15. sd和sem啥区别_SD与SEM有区别吗
  16. GPS纠偏 WGS84转GCJ02 Java版本
  17. 工厂用计算机自动控制技术,一种基于计算机技术的工厂车间灯光控制系统的制作方法...
  18. spring相关资料
  19. 锁相环技术,单边带信号,信号的调制
  20. 《人民的名义》---简单的文本分析

热门文章

  1. jQuary中delegate()函数的作用
  2. STM32F4主板硬件设计与接口
  3. 2020 校招,我是如何拿到小米、京东、字节大厂前端offer
  4. 最新浴血凤凰2020年DNF自动化辅助开发教程
  5. 浅谈DDS IP核之频率控制字与相位控制字
  6. 【转】VC++的窗口句柄和窗口ID
  7. 《你好,数智新世界》系列访谈 对话数睿数据总裁刘超|企业级无代码赋能软件产业变革...
  8. Flash 第一章 课堂笔记
  9. 联想小新一键恢复小孔_联想小新一键恢复小孔 联想小新笔记本怎么一键恢复...
  10. 好久不见,我回来了!