那年声明理解不了定义与初始化

  • MD建档:2/5/2016 7:36:43 PM by Jimbowhy
  • CSDN系列发布:
  • http://blog.csdn.net/WinsenJiansbomber/article/details/50641687
  • http://blog.csdn.net/WinsenJiansbomber/article/details/50646981

穷则独善其身,达则兼善天下 —— 《孟子》

第一部分内容

  • 编程之外
  • 追逐简单美
  • 编程之内
  • 回顾微机原理-浮点数

第二部分内容

  • 神秘角色-机器码
  • 神秘角色-机器码基础
  • 神秘角色-反汇编

第三部分内容

  • 当数组遇上指针
  • 引用的本质
  • 引用与类实例
  • new/malloc 选美之争

当数组遇上指针

当数组遇上指针,指针数组和数组指针就如同梦魇!曾经多少英雄就因为几个字的颠倒,搞得天地乾坤易位。有人用英文的明了的逻辑来解说两都的差别,其实也枉然,去掉英文的解析,再回来看“指针数组数组指针”,方向错乱依然如故。按我说,中文语境真够折磨的,老外要是用中文还学习编程,不进行青山疗养才怪。

要说破这两者的差别,也是挺明显的。平时听到乳黄、米白这类词时,一下就可以反应这是在讲颜色。而倒过来,白米,就是另一样东西了吧。所以按照中文这种“修饰·中心”语法结构来解读指针数组,首先是一种数组,还是用来保存指针的数组。另一个数组指针,也同样分解,首先是指针的一种,是指向数组的指针。

当然这个中文语境也不是总是这样好使的,例如来理解常量指针与指针常量,逻辑上可能更明确一点。常量指针,首先是个指针,指针就有指向,你说它是一个指向常量的指针还是指针本身是常?所以当内容变得丰富的时候,中文语境就有巨大的想象空间了,这也就是中文的最大魅力所在。然后是指针常量,首先它是个常量这点没问题,修饰词说它是指针呢,还是指针呢?好吧指针常量就是指针本身为常量的指针,指针本身是不能改变的了,但它指向的内容是变量时,这个变量还是可以被修改的啊。再来讨论一下“常量指针”的问题,按从简原则,使用“修饰·中心”这个中文语境时,不应该引入过多的影响因素。因此“常量”应该修饰“指针”,而不应该修饰“指针的指向”,这样就会发现指针常量和常量指针竟然是在说同一个东西!好神奇的中文!因为这个,我特意翻了压箱底的谭老那本黄色封面的《C语言程序设计》,打开目录,看到指针部分:

  • 9.2 变量的指针和指向变量的指针变量
  • 9.3 数组的指针和指向数组的指针变量
  • 9.4 字符串的指针和指向字符串的指针变量
  • 9.5 函数的指针和指向函数的指针变量
  • 9.7 指针数组和指向指针的指针

看看,这目录还是很清晰的吧,为什么当时就是有种“法海不懂爱”的感觉,是不是专家不懂学生的哀?

当数组和指针相遇,指针与变量这些高级语言概念混杂在一起时,脱清关系最有力的解释还是反汇编,从机器的角度,从编译器生成的机器码的视角去解读才是最浅显的。看以下代码,定义了数组和数组指针,当使用下标访问数组元素时,当指针用来运算时,究竟程序内容发生了什么:

volatile int j[2][2] = {{1,2},{3,4}};
volatile int k = 1;
j[1][1] = 2;
j[1][k] = 3;volatile int (*p)[2][2] = &j;
*(p+1)[0][0] = 5;
*(p+1)[1][0] = 6;
*(*p + 1)[0] = 7;
*(*p + 1)[1] = 8;

先瞄一下源代码,不急着分析,这里重点不在于语法结构,而在于,程序是如何定位变量在内存地址的。先看第一行代码反汇编机器码,数据初始化使用了 movs 数据串拷贝指令,立即数 $0x6a5000 是初始化数据的地址,movs 指令会将4个初始值拷贝到数组所在的内存中 -0x48(%ebp):

23  volatile int j[2][2] = {{1,2},{3,4}};
0x401468    lea    -0x48(%ebp),%edx
0x40146b    mov    $0x6a5000,%ebx
0x401470    mov    $0x4,%eax
0x401475    mov    %edx,%edi
0x401477    mov    %ebx,%esi
0x401479    mov    %eax,%ecx
0x40147b    rep movsl %ds:(%esi),%es:(%edi)

再接下来一组语句,主要是试用变量的方式来指定数组的元素。通过常数来指定数组元素,编译器在编译阶段就可以确定地址,这个没什么特别的。通过变量的方式,在 0x401491 处看到了寄存器在寻址功能的不同应用,这是基址+变址+偏移的寻址方式,基址是 %ebp,变址是 %eax*4,偏移是 -0x40,有效地址就是 %ebp+%eax*4-0x40。这里变址的比例值选择4而不选2或8,主要是因为整形占4个字节,这样通过给 %eax 设定不同的数就可以找到数组的对应序号的元素,也就是前面的指令要把 k 拷贝到 %eax 的原因。这种变址寻址也是数组通过下标来定位的对应元素的基本实现方法:

24  volatile int k = 1;
0x40147d    movl   $0x1,-0x4c(%ebp)
25  j[1][1] = 2;
0x401484    movl   $0x2,-0x3c(%ebp)
26  j[1][k] = 3;
0x40148b    mov    -0x4c(%ebp),%eax
0x40148e    add    $0x2,%eax
0x401491    movl   $0x3,-0x48(%ebp,%eax,4)

再来看数组与指针运算的部分,这段汇编代码里,先将数组的首地址 -0x48(%ebp) 拷贝到指针p的内存中。在进行数组的指针运算之前,需要了解数组每一个维数对应的元素数量,这个值是直接作为指针运算时的参考值。一个规则就是,指针指向整个数组时,指针每加1表示地址移动一个数组的字节数;指向数组某一维时,指针每加1表示地址移动这个维数里的所有元素所占的内存字节数。所以,第29行代码中,p直接加1,表示地址偏移了整整一个数组j所占的字节数,即 4x4=16字节,所以汇编指令 0x4014a9 处要加上偏移值 0x10。而这里的内存实际上并不属于数组的,所以这里真实的情况就是数组越界!第30行源代码也可以知道存在数组越界的情况,但对于两个方括号的值再分析清楚。方括号的使用,地址的计算就是按上面的规则进行的,注意方括号优先于星号,所以这里实际的地址偏移为 4x(4x2)。到31行代码,对指针每个星号运算表示向维度减小的方向降一个维度,所以这里偏移值为 4x(2x1)=8。第32行,方括号的数字之前,指针只进行了一个星号的运算,所以方括号的数和圆括号的数具有等价的功能,实际偏移为 4x(2x(1+1)),结果和29行代码做了一样的事情,越界访问:

28  volatile int (*p)[2][2] = &j;
0x4014a0    lea    -0x48(%ebp),%eax
0x4014a3    mov    %eax,-0x1c(%ebp)
29  *(p+1)[0][0] = 5;
0x4014a6    mov    -0x1c(%ebp),%eax
0x4014a9    add    $0x10,%eax
0x4014ac    movl   $0x5,(%eax)
30  *(p+1)[1][0] = 6;
0x4014b2    mov    -0x1c(%ebp),%eax
0x4014b5    add    $0x20,%eax
0x4014b8    movl   $0x6,(%eax)
31  *(*p + 1)[0] = 7;
0x4014be    mov    -0x1c(%ebp),%eax
0x4014c1    add    $0x8,%eax
0x4014c4    movl   $0x7,(%eax)
32  *(*p + 1)[1] = 8;
0x4014ca    mov    -0x1c(%ebp),%eax
0x4014cd    add    $0x10,%eax
0x4014d0    movl   $0x8,(%eax)

引用的本质

引用 Reference 这东西是非常容易引人误解的东西,因为指针存在一个叫解引用 Dereference 的过程,通过指针解引用就可以直达变量的内容。来看这组C语言语句,定义了变量 j、引用 k 及数组a, 还有指向它们的指针或引用:

volatile int j=1;
volatile int &k = j;
volatile int *p1 = &j;
volatile int *p2 = &k;
volatile int a[2] = {1,2};
volatile int (&r)[2] = a;

如果学过C语言理解上面的代码是没有问题的,但会以汇编的角度来看代码,脉络会更清晰。反汇编后得到相对应的指令显示,编译器将内存地址 -0x44(%ebp) 分配给了变量 j,内存地址 -0x1c(%ebp) 则分配给了引用 k,并且将 j 的地址转存到 k 的内存中。再来看指针 p1,发现除了编译器给 p1 分配的内存地址 -0x20(%ebp) 不同外ÿ

那年声明理解不了定义与初始化(三)相关推荐

  1. c语言常数-ox6a是什么意思,那年声明理解不了定义与初始化(三)

    那年声明理解不了定义与初始化 穷则独善其身,达则兼善天下 -- <孟子> 编程之外 追逐简单美 编程之内 回顾微机原理-浮点数 神秘角色-机器码 神秘角色-机器码基础 神秘角色-反汇编 当 ...

  2. 那年声明理解不了定义与初始化(二)

    那年声明理解不了定义与初始化 MD建档:2/5/2016 7:36:43 PM by Jimbowhy CSDN系列发布: http://blog.csdn.net/WinsenJiansbomber ...

  3. java 成员变量声明顺序_C++核心准则讨论:按照成员声明的顺序定义和初始化成员变量...

    Discussion: Define and initialize member variables in the order of member declaration 讨论:按照成员声明的顺序定义 ...

  4. 【Struct(结构体)杂谈之二】名不正则言不顺---Struct(结构体)的声明、定义及初始化

    Struct(结构体)的声明.定义及初始化 上一篇里我们讲了为什么我们要引入Struct这个数据类型,我们了解到Struct是一种聚合数据类型,是为了用户描述和解释一些事物的方便而提出的,Struct ...

  5. 变量的声明、变量的定义、变量的初始化

    变量的声明:用于向程序表明变量的类型和名字. 变量的定义:用于为变量分配存储空间,还可以为变量指定初始值. 变量的初始化:为变量指定初始值. 广义上来说,变量的声明有两种情况: 1.需要建立存储空间的 ...

  6. ARM64系统中兼容系统调用表compat_sys_call_table的定义和初始化

    在我使用的某国产ARM64笔记本上(安装的统信UOS系统), Linux内核有2个系统调用表sys_call_table和compat_sys_call_table 其中sys_call_table为 ...

  7. golang 变量定义和初始化

    变量命名原则 go语言的变量名有字母数字和下划线组成,首字母不能为数字,但是字母不仅仅只限于英文字母,所有的UTF-8字符都是可以的. 变量声明和初始化方式 使用var关键字 var a int = ...

  8. java 定义类变量初始化吗_Java的变量有哪些类型?变量如何定义?如何初始化?请说明理由并举例_学小易找答案...

    [单选题]根据金杯萃取理论,咖啡萃取比较适合的参数是: (2.9分) [判断题]高分化的鳞状细胞癌恶性程度高,低分化的鳞状细胞癌恶性程度低. ( ) [简答题]第 3-4 节之间的关系是什么?在全诗结 ...

  9. C语言-定义与初始化总结

    目录 1 实型 2 标识符 3 变量 4 一维数组 4.1 注意点 4.2 初始化 5 二维数组 5.1 初始化 6 字符数组 6.1 初始化 6.2 与字符指针的比较 7 结构体 7.1 声明定义 ...

最新文章

  1. RTEMS文件系统(4):系统调用开发信息(上)
  2. 迭代器、生成器、装饰器
  3. 重磅风控干货:如何用数据分析监测交易欺诈
  4. element-ui table点击某行,修改背景颜色(即高亮)
  5. SQL按照年月员工状态统计出勤情况
  6. Tomcat servlet工作原理
  7. 10个加速Table Views开发的Tips
  8. Tensorflow 相关概念
  9. 边学边写,琐碎记载oracle
  10. 数据库文件导入SQL数据库
  11. 戴尔(Dell)笔记本电脑开机后插上耳机没反应怎么办
  12. 用anacnda创建虚拟环境用不用指定python版本
  13. 基于C#+Mysql实现(WinForm)企业的设备管理系统【100010018】
  14. 迪士尼正在寻找一个区块链实习生
  15. C语言学习笔记09-数组、字符数组、字符串数组、二维数组(单字符输入输出putchar、getchar,字符串输入输出的scanf、gets、puts)
  16. new FormData()
  17. 2021数据库课程设计培训笔记:【navicat】部分
  18. Android安全框架:Verfied boot -- Secure Boot
  19. SNS是什么?有哪些类型的SNS网站?
  20. 以太网接口 数据采集 matlab,基于以太网的远程数据采集系统

热门文章

  1. websphere服务五:导出ear包的几种方法
  2. javaFX学习之DatePicker日期控件
  3. 一文帮你搞懂GBK码协议,让你真正理解和搞定它!
  4. 懂得智慧生活 方能不负时光
  5. 电弧无线发送机与泰坦尼克号的悲剧
  6. java调用g726_AACEncode: Android G711(PCMA/PCMU)、G726、PCM音频转码到AAC
  7. OpenCV中读取、显示、保存图像及获取图像属性操作讲解及演示(附源码)
  8. 8.MySQL替换回车、换行符、空格
  9. 收藏!第十届全球互联网架构大会全日程及参会指南公布
  10. 高速公路超速处罚 (15分)