[学习小结]数组名与数组首元素地址解析

2024-01-28 19:58

本文主要是介绍[学习小结]数组名与数组首元素地址解析,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

2012.04.12



学C++的日子里经常被莫名其妙的问题(没意义但是很纠结的问题)给缠住,然后想了半天,大概是处于这么一个状态:知道怎么用,但是却不知道为什么是这样,而且还带有一点疑问。

今天被 变量名 这个问题给缠住了。因为我有个概念,CPU只认识地址,那么声明个变量到底是怎么回事,这个变量不是地址啊。


然后得出了这个结论。


编译器都帮我们弄好了,机器码内是没有变量符号地址的,只是为了帮助我们阅读而已。全局变量之类的,应该都在运行开始的时候就分配好了,而在函数体内的局部变量,即声明个变量,都是借由编译器改变esp的地址来分配堆栈段空间,而我们的变量的地址,则由编译器编译时候的符号表来替代成地址了。


一个变量,依我理解,在符号表内至少有2个属性,引用的内存地址,类型

int a=0;

符号表就会有a这个符号,同时会有分配好的内存的地址。这个内存地址每次运行不一定是相同的,受到进入函数体时候ebp的影响。则定义一个变量,只是把esp移相应字节数。而访问这个a变量,则替换成引用符号表内的地址的内存,是根据ebp偏移相应字节来访问的。


即高级语言的a=1的赋值语句,将符号表中a地址解引用访问到那个栈内存块,然后赋值。



而上述讨论的指针和数组名,似乎也能这么理解。


数组名只是存在于编译器编译过程中的一个符号而已,这个符号记录的信息,肯定有类型,类似于int[5]之类的,然后就是数组名所指向的地址了。所以数组名是不分配空间的。


对数组元素的访问,可以这样的分析。


int a[5];

int b=a[0];


这种访问方式,则编译后分配数组a空间的时候esp移动了4*5字节,同时符号表内a对应着起始地址。访问时a的符号表直接替换a,用地址+0 的偏移访问元素。也可以理解为什么用a的符号能得到数组的长度。


char *szA="hello";

int c=szA[2];


这种方式,则首先会为szA分配空间,即符号表存在着szA的符号,同时有着szA引用的内存,szA引用的内存存放着字符串常量"hello"的地址。

访问时,szA被*pszA替代,pszA就是符号表记录的szA引用的内存地址,同时用该内存地址的值+偏移量来访问字符串的值。


所以,个人理解是在编译过程中,定义一个变量的时候,会增加一个符号表的内容(变量名,数组名,函数名等),然后在堆栈区分配空间,记录它引用的内存的地址(变量),或者代表的地址(数组名函数名等),定义一个变量最终是转化为机器认得的地址,接下来所有引用到这个变量的东西均由符号表内的内容替换成地址。



一段简单的代码

int _tmain(int argc, _TCHAR* argv[])
{int a;
	int *b=&a;
	return 0;
}

int _tmain(int argc, _TCHAR* argv[])
{
00411A00  push        ebp               //保存进入main函数前的ebp堆栈段基址
00411A01  mov         ebp,esp 		//更新ebp基址,用于寻址main函数下的局部变量
00411A03  sub         esp,0CCh 		//减去相应字节数来增加局部变量
00411A09  push        ebx  
00411A0A  push        esi  
00411A0B  push        edi  		//保存寄存器数值
00411C9C  lea         edi,[ebp+FFFFFF28h] 
00411CA2  mov         ecx,36h 
00411CA7  mov         eax,0CCCCCCCCh 
00411CAC  rep stos    dword ptr [edi] 
	int a;
	int *b=&a;
00411CAE  lea         eax,[ebp-8] 	//最终a的符号被[ebp-8]替代来访问引用的内存
00411CB1  mov         dword ptr [ebp-14h],eax 
	return 0;
00411CB4  xor         eax,eax 
}









在课堂上老师经常把这两者等价,可是是否两者是一样的呢?

 

我们来看一个例子:

int a[5];

cout<<sizeof(a);

int* p=a;

cout<<sizeof(a);

 

若两者是等价的,那么两者的输出应该是一样的,可事实是第一个输出了数组的总长度sizeof(int)*5,而第二个输出了32位机器上的地址长度,也就是记录一个地址需要4字节。由此可见数组名绝对不是简简单单的一个数组首地址而已。

 

由此我们可以推论,实际上还存在着某种特殊的数据结构,似乎与int float等内置基本类型相同的类型。按命名法则,可以推断a的类型是int[5]!

存在int[5]这种类型么?

请在编译器中输入以下代码:

    int a[5];
    int *p=0;
    a=p;

这说明了两个问题:

1.存在int[5]类型,详情请见编译器错误提示。

2.a是一个int[5]类型,而且这种类型是个常量,即不可修改。

所以说,C++中是存在这个类型的,我们也可以写如下的代码:

int a[5];

int (*p)[5];

p=&a;

 

上述代码是什么意思呢?我们首先定义了一个数组,然后声明了一个p指针,这个指针是指向了int[5]类型的,即a,故需要给a取地址,这也侧面说明了为什么声明一个指针需要说明数组的维数,因为int[5]和int[6]的类型是不同的!

而又由于int[5]可以隐式转换为int*,故上述的p指针其实也是一个int**,但其实是上述定义的int(*)[5],为什么呢,读者可以再次对p进行sizeof(*p)操作,可发现依旧能得出数组的大小,而普通的非数组名的指针,或者是由数组名隐式转换的指针是不会知道数组大小的。

所以可以得出以下结论:

1.数组名不是指针,仅仅是可以隐式转换为指针

2.数组名是一种特殊的数据结构,可以得出数组的大小

3.当数组名作为形参进行值传递时,将失去这种特殊的数据结构

4.数组名是一个常量





这篇关于[学习小结]数组名与数组首元素地址解析的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/654644

相关文章

python进行while遍历的常见错误解析

《python进行while遍历的常见错误解析》在Python中选择合适的遍历方式需要综合考虑可读性、性能和具体需求,本文就来和大家讲解一下python中while遍历常见错误以及所有遍历方法的优缺点... 目录一、超出数组范围问题分析错误复现解决方法关键区别二、continue使用问题分析正确写法关键点三

CSS 样式表的四种应用方式及css注释的应用小结

《CSS样式表的四种应用方式及css注释的应用小结》:本文主要介绍了CSS样式表的四种应用方式及css注释的应用小结,本文通过实例代码给大家介绍的非常详细,详细内容请阅读本文,希望能对你有所帮助... 一、外部 css(推荐方式)定义:将 CSS 代码保存为独立的 .css 文件,通过 <link> 标签

使用Java实现Navicat密码的加密与解密的代码解析

《使用Java实现Navicat密码的加密与解密的代码解析》:本文主要介绍使用Java实现Navicat密码的加密与解密,通过本文,我们了解了如何利用Java语言实现对Navicat保存的数据库密... 目录一、背景介绍二、环境准备三、代码解析四、核心代码展示五、总结在日常开发过程中,我们有时需要处理各种软

JAVA数组中五种常见排序方法整理汇总

《JAVA数组中五种常见排序方法整理汇总》本文给大家分享五种常用的Java数组排序方法整理,每种方法结合示例代码给大家介绍的非常详细,感兴趣的朋友跟随小编一起看看吧... 目录前言:法一:Arrays.sort()法二:冒泡排序法三:选择排序法四:反转排序法五:直接插入排序前言:几种常用的Java数组排序

Python多进程、多线程、协程典型示例解析(最新推荐)

《Python多进程、多线程、协程典型示例解析(最新推荐)》:本文主要介绍Python多进程、多线程、协程典型示例解析(最新推荐),本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定... 目录一、multiprocessing(多进程)1. 模块简介2. 案例详解:并行计算平方和3. 实现逻

Spring Boot拦截器Interceptor与过滤器Filter深度解析(区别、实现与实战指南)

《SpringBoot拦截器Interceptor与过滤器Filter深度解析(区别、实现与实战指南)》:本文主要介绍SpringBoot拦截器Interceptor与过滤器Filter深度解析... 目录Spring Boot拦截器(Interceptor)与过滤器(Filter)深度解析:区别、实现与实

MyBatis分页插件PageHelper深度解析与实践指南

《MyBatis分页插件PageHelper深度解析与实践指南》在数据库操作中,分页查询是最常见的需求之一,传统的分页方式通常有两种内存分页和SQL分页,MyBatis作为优秀的ORM框架,本身并未提... 目录1. 为什么需要分页插件?2. PageHelper简介3. PageHelper集成与配置3.

SQL 外键Foreign Key全解析

《SQL外键ForeignKey全解析》外键是数据库表中的一列(或一组列),用于​​建立两个表之间的关联关系​​,外键的值必须匹配另一个表的主键(PrimaryKey)或唯一约束(UniqueCo... 目录1. 什么是外键?​​ ​​​​2. 外键的语法​​​​3. 外键的约束行为​​​​4. 多列外键​

CSS引入方式和选择符的讲解和运用小结

《CSS引入方式和选择符的讲解和运用小结》CSS即层叠样式表,是一种用于描述网页文档(如HTML或XML)外观和格式的样式表语言,它主要用于将网页内容的呈现(外观)和结构(内容)分离,从而实现... 目录一、前言二、css 是什么三、CSS 引入方式1、行内样式2、内部样式表3、链入外部样式表四、CSS 选

Java进行日期解析与格式化的实现代码

《Java进行日期解析与格式化的实现代码》使用Java搭配ApacheCommonsLang3和Natty库,可以实现灵活高效的日期解析与格式化,本文将通过相关示例为大家讲讲具体的实践操作,需要的可以... 目录一、背景二、依赖介绍1. Apache Commons Lang32. Natty三、核心实现代