关于堆栈和指针(指针例子解释很好)
- 格式:doc
- 大小:38.00 KB
- 文档页数:13
第一章汇编语言简介先说一点和实际编程关系不太大的东西。
当然,如果你迫切的想看到更实质的内容,完全可以先跳过这一章。
那么,我想可能有一个问题对于初学汇编的人来说非常重要,那就是:汇编语言到底是什么?汇编语言是一种最接近计算机核心的编码语言。
不同于任何高级语言,汇编语言几乎可以完全和机器语言一一对应。
不错,我们可以用机器语言写程序,但现在除了没有汇编程序的那些电脑之外,直接用机器语言写超过1000条以上指令的人大概只能算作那些被我们成为“圣人”的牺牲者一类了。
毕竟,记忆一些短小的助记符、由机器去考虑那些琐碎的配位过程和检查错误,比记忆大量的随计算机而改变的十六进制代码、可能弄错而没有任何提示要强的多。
熟练的汇编语言编码员甚至可以直接从十六进制代码中读出汇编语言的大致意思。
当然,我们有更好的工具——汇编器和反汇编器。
简单地说,汇编语言就是机器语言的一种可以被人读懂的形式,只不过它更容易记忆。
至于宏汇编,则是包含了宏支持的汇编语言,这可以让你编程的时候更专注于程序本身,而不是忙于计算和重写代码。
汇编语言除了机器语言之外最接近计算机硬件的编程语言。
由于它如此的接近计算机硬件,因此,它可以最大限度地发挥计算机硬件的性能。
用汇编语言编写的程序的速度通常要比高级语言和C/C++快很多--几倍,几十倍,甚至成百上千倍。
当然,解释语言,如解释型LISP,没有采用JIT技术的Java虚机中运行的Java 等等,其程序速度更无法与汇编语言程序同日而语。
永远不要忽视汇编语言的高速。
实际的应用系统中,我们往往会用汇编彻底重写某些经常调用的部分以期获得更高的性能。
应用汇编也许不能提高你的程序的稳定性,但至少,如果你非常小心的话,它也不会降低稳定性;与此同时,它可以大大地提高程序的运行速度。
我强烈建议所有的软件产品在最后Release之前对整个代码进行Profile,并适当地用汇编取代部分高级语言代码。
至少,汇编语言的知识可以告诉你一些有用的东西,比如,你有多少个寄存器可以用。
简答题什么是嵌入式操作系统?答:嵌入式系统是以应用为中心,以计算机技术为基础,软/硬件可裁减,功能。
可靠性,成本,体积,功耗要求严格的专用计算机系统。
与通用计算机相比,嵌入式系统有哪些特点?答:(1).嵌入式系统通常是面向特定应用的;(2).嵌入式系统是将计算机技术,半导体技术和电子技术与各行各业的具体应用相结合的后的产物,是一门综合技术学科;(3).嵌入式系统和具体应用有机的结合在一起,它的升级换代也是和具体产品同步进行的,因此嵌入式产品一旦进入市场,就有较长的生命周期;(4).为了提高执行速度和可靠性,嵌入式系统中的软件一般都固化在存储器芯片或单片机本身中,而不是存储于磁盘等载体中;(5).嵌入式系统本身不具有自主开发能力,即使设计完成以后用户通常也不能对其中的程序功能进行修改,必须有一套开发工具和环境才能进行开发。
ARM处理器有几种寻址方式,说明各种寻址的方式。
答:立即寻址:操作数直接放在指令中。
例如:ADD R0,R0,#0x3f ;R0←R0+0x3f寄存器寻址:操作数放在寄存器中。
例如:ADD R0,R1,R2 ;R0←R1+R2寄存器间接寻址:操作数在内存,以寄存器中的值作为操作数的地址。
例如:LDR R0,[R1] ;R0←[R1]基址加偏移量寻址(基址变址寻址):基址寄存器的内容与指令中的偏移量相加形成操作数的有效地址例如:LDR R0,[R1,#4] ;R0←[R1+4]LDR R0,[R1,R2] ;R0←[R1+R2]多寄存器寻址:一条指令可以完成多个寄存器值的传送。
例如:LDMIA R0,{R1,R2,R3,R4} ;R1←[R0];R2←[R0+4];R3←[R0+8];R4←[R0+12]堆栈寻址:堆栈是一种数据结构,按先进后出(First In Last Out,FILO)的方式工作,使用一个称作堆栈指针的专用寄存器指示当前的操作位置,堆栈指针总是指向栈顶。
例如:STMFD R13!,{R0,R4-R12,LR}LDMFD R13!,{R0,R4-R12,PC}举例介绍嵌入式处理器有哪几类?答:1.嵌入式微处理器(Embedded Microprocessor Unit,EMPU);2.嵌入式微控制器;(Embedded Microcontroller Unit,EMCU)3.嵌入式DSP处理器(Embedded Digital Signal Processor,EDSP);4.嵌入式片上系统(Embedded System on Chip,EsoC);什么是立即数?请简要描述立即数在使用时有什么注意要点。
微机原理习题解答:4习题四1.8086语言指令的寻址方式有哪几类?用哪一种寻址方式的指令执行速度最快?答:数据操作数的寻址方式有七种,分别为:立即寻址,寄存器寻址,直接寻址,寄存器间接寻址,寄存器相对基址变址和相对基址变址寻址。
其中寄存器寻址的指令执行速度最快。
2.若DS=6000H,SS=5000H,ES=4000H,SI=0100H,BX=0300H,BP=0400H,D=1200H,数据段中变量名NUM的偏移地址为0050H,试指出下列源操作数的寻址方式和物理地址是多少?(1)MOV AX,[64H]答:寻址方式为直接寻址;PA=60064H(2)MOV AX,NUM 答:寻址方式为直接寻址;PA=60005H (3)MOV AX,[SI]答:寻址方式为寄存器间接寻址;PA=60100H (4)MOV AX,[BX]答:寻址方式为寄存器间接寻址;PA=60300H (5)MOV AX,[BP]答:寻址方式为寄存器间接寻址;PA=50400H (6)MOV AL,[DI]答:寻址方式为寄存器间接寻址;PA=61200H (7)MOV AL,[BX+1110H]答:寻址方式为寄存器相对寻址;PA=61410H (8)MOV AX,NUM[BX]答:寻址方式为寄存器相对寻址;PA=60305H (9)MOV AX,[BX+SI]答:寻址方式为基址变址寻址;PA=60400H(10)MOV AX,NUM[BX][DI]答:寻址方式为相对基址变址寻址;PA=61505H3.设BX=637DH,SI=2A9BH,位移量为C237H,试确定由这些寄存器和下列寻址方式产生的有效地址。
(1)直接寻址答:有效地址为EA=C237H(2)用BX的寄存器间接寻址答:有效地址为EA=637DH(3)用BX的相对寄存器间接寻址答:有效地址为EA=125B4H (4)基址加变址寻址答:有效地址为EA=8E18H(5)相对基址变址寻址答:有效地址为EA=1504FH其中,(3)和(5)中产生进位,要把最高位1舍去。
第 1 章思考题及习题 1 参考答案一、填空1. 除了单片机这一名称之外,单片机还可称为或。
答:微控制器,嵌入式控制器.2. 单片机与普通微型计算机的不同之处在于其将、、和三部分,通过内部连接在一起,集成于一块芯片上。
答:CPU、存储器、I/O 口、总线3. AT89S52 单片机工作频率上限为MHz 。
答:33 MHz 。
4. 专用单片机已使系统结构最简化、软硬件资源利用最优化,从而大大降低和提高。
答:成本,可靠性。
二、单选1. 单片机内部数据之所以用二进制形式表示,主要是A.为了编程方便 B .受器件的物理性能限制C.为了通用性 D .为了提高运算速度答:B2. 在家用电器中使用单片机应属于微计算机的。
A .辅助设计应用B.测量、控制应用C.数值计算应用 D .数据处理应用答:B3. 下面的哪一项应用,不属于单片机的应用范围。
A .工业控制B.家用电器的控制C.数据库管理D.汽车电子设备答:C三、判断对错1. STC系列单片机是8051 内核的单片机。
对2. AT89S52 与AT89S51 相比,片内多出了4KB 的Flash 程序存储器、128B 的RAM 、1个中断源、 1 个定时器(且具有捕捉功能)。
对3. 单片机是一种CPU。
错4. AT89S52 单片机是微处理器。
错5. AT89C52 片内的Flash程序存储器可在线写入,而AT89S52 则不能。
错6. 为AT89C51 单片机设计的应用系统板,可将芯片AT89C51 直接用芯片AT89S51 替换。
对7. 为AT89S51 单片机设计的应用系统板,可将芯片AT89S51 直接用芯片AT89S52 替换。
对8. 单片机的功能侧重于测量和控制,而复杂的数字信号处理运算及高速的测控功能则是DSP的长处。
对四、简答1. 微处理器、微计算机、微处理机、CPU 、单片机、嵌入式处理器它们之间有何区别?答:微处理器、微处理机和CPU 它们都是中央处理器的不同称谓,微处理器芯片本身不是计算机。
尽管在.NET framework下我们并不需要担心内存管理和垃圾回收(Garbage Collection),但是我们还是应该了解它们,以优化我们的应用程序。
同时,还需要具备一些基础的内存管理工作机制的知识,这样能够有助于解释我们日常程序编写中的变量的行为。
在本文中我将讲解栈和堆的基本知识,变量类型以及为什么一些变量能够按照它们自己的方式工作。
在.NET framework环境下,当我们的代码执行时,内存中尽管在.NET framework下我们并不需要担心内存管理和垃圾回收(Garbage Collection),但是我们还是应该了解它们,以优化我们的应用程序。
同时,还需要具备一些基础的内存管理工作机制的知识,这样能够有助于解释我们日常程序编写中的变量的行为。
在本文中我将讲解栈和堆的基本知识,变量类型以及为什么一些变量能够按照它们自己的方式工作。
在.NET framework环境下,当我们的代码执行时,内存中有两个地方用来存储这些代码。
假如你不曾了解,那就让我来给你介绍栈(Stack)和堆(Heap)。
栈和堆都用来帮助我们运行代码的,它们驻留在机器内存中,且包含所有代码执行所需要的信息。
* 栈vs堆:有什么不同?栈负责保存我们的代码执行(或调用)路径,而堆则负责保存对象(或者说数据,接下来将谈到很多关于堆的问题)的路径。
可以将栈想象成一堆从顶向下堆叠的盒子。
当每调用一次方法时,我们将应用程序中所要发生的事情记录在栈顶的一个盒子中,而我们每次只能够使用栈顶的那个盒子。
当我们栈顶的盒子被使用完之后,或者说方法执行完毕之后,我们将抛开这个盒子然后继续使用栈顶上的新盒子。
堆的工作原理比较相似,但大多数时候堆用作保存信息而非保存执行路径,因此堆能够在任意时间被访问。
与栈相比堆没有任何访问限制,堆就像床上的旧衣服,我们并没有花时间去整理,那是因为可以随时找到一件我们需要的衣服,而栈就像储物柜里堆叠的鞋盒,我们只能从最顶层的盒子开始取,直到发现那只合适的。
DSP应用程序中C代码和汇编代码的结合◆ CEVA公司高级编译器项目经理Eran Balaish随着DSP处理器的功能日益强大,加上编译器的优化技术不断提高,以往只利用汇编语言编写DSP应用程序的普遍做法已逐渐被淘汰。
今天,几乎所有的DSP应用程序都是由C代码和汇编代码共同构成。
对于性能是核心的关键性功能,DSP工程师继续使用高度优化的汇编代码,而其它功能则采用C语言编写,以便于维护和移植。
C代码和汇编代码的结合需要DSP工程师在工具箱中备有专用工具和方法。
众所周知,汇编代码编程的性能更好,而C代码编程的优势在于编写更为方便、快捷。
为了解释清楚原因,让我们仔细对比一下汇编代码和C代码编程的优缺点。
汇编代码编程的优势● 汇编代码能够利用处理器独有的指令和各种专用硬件资源。
另一方面,C代码则是通用性的,必须支持不同的硬件平台,因此,对C代码来说,支持专用平台代码十分困难。
● 汇编代码的编程人员通常对应用非常熟悉,并可能做出编译器难以企及的设想。
● 汇编代码编程人员可以发挥人们的创造力;而编译器再先进也只是一个自动程序而已。
汇编代码编程的缺点:● 汇编代码编程人员不得不处理耗时的机器级问题,比如寄存器分配和指令调度。
而对C代码,这些问题都可交给编译器去做。
● 汇编代码编程需要拥有关于DSP架构及其指令集的专业知识,而C编码只需要掌握广为流行的C语言即可。
● 采用汇编代码,平台之间的应用移植极其困难和耗时。
而C应用程序的移植要简单得多。
图1显示了如何利用专用硬件机制来高度优化汇编代码。
左边的C代码实现方案利用模数运算创建了循环缓冲器。
右边的是高度优化的汇编代码,利用CEVA-TeakLite-III DSP核的模数机制来创建相同的缓冲器。
只要缓冲器指针(这里是r0)更新,该模数机制就自动执行模数算法。
这种运算和指针更新发生在同一个周期内,故汇编代码比C代码有效得多,其可为模数运算产生单独的指令。
在DSP应用中选择适当的C和汇编代码混合问题在于C 代码和汇编代码之间的界限究竟在哪里,答案由分析器提供的性能分析给出。
关于可重入函数(可再入函数)和模拟堆栈(仿真堆栈)(2012-04-21 14:53:58)转载▼分类:51单片机标签:杂谈作者:xzp21st 邮箱:*****************撰文辛苦,转载请注明作者及出处关键字:keilc51,模拟堆栈,可重入函数调用,参数传递,C?XBP,C?ADDXBP摘要:本文较详细的介绍了keilc51可再入函数和模拟堆栈的一些概念和实现原理,通过一个简单的程序来剖析keilc51在大存储模式下可重入函数的调用过程,希望能为keilc51和在51系列单片机上移植嵌入式实时操作系统的初学者提供一些帮助。
1、关于可重入函数(可再入函数)和模拟堆栈(仿真堆栈)“可重入函数可以被一个以上的任务调用,而不必担心数据被破坏。
可重入函数任何时候都可以被中断,一段时间以后又可以运行,而相应的数据不会丢失。
”(摘自嵌入式实时操作系统uC/OS-II)在理解上述概念之前,必须先说一下keilc51的“覆盖技术”。
(采用该技术的原因请看附录中一网友的解释)(1)局部变量存储在全局RAM空间(不考虑扩展外部存储器的情况);(2)在编译链接时,即已经完成局部变量的定位;(3)如果各函数之间没有直接或间接的调用关系,则其局部变量空间便可覆盖。
正是由于以上的原因,在Keil C51环境下,纯粹的函数如果不加处理(如增加一个模拟栈),是无法重入的。
举个例子:void TaskA(void* pd){int a;//其他一些变量定义do{//实际的用户任务处理代码}while(1);}void TaskB(void* pd){int b;//其他一些变量定义do{func();//其他实际的用户任务处理代码}while(1);}void func(){int c;//其他变量的定义//函数的处理代码}在上面的代码中,TaskA与TaskB并不存在直接或间接的调用关系,因而它们的局部变量a与b便是可以被互相覆盖的,即它们可能都被定位于某一个相同的RAM空间。
《微机原理与接口技术》期末复习题《微机原理与接口技术》期末复习题一、选择题1. 在微机中,用来表示信息的最小单位是()。
A. 位B. 字节C. 字D. 双字2. 机器字长为8位,十进制数125转换成十六进制数是()。
A. 125HB. 7DHC. 0FFHD. 0D7H3. 8253的三个计数器中每一个都有三条信号线,其中CLK是指()。
A.定时计数脉冲输入B.输出信号C.选通输入D.门控制输入4. 高速I/O设备或成组交换数据时,可以采用()方式传送数据。
A. 查询B. 中断C. DMAD. 同步5. 堆栈操作时,段基址由()寄存器指出,段内偏移量由SP寄存器指出。
A. CSB. DSC. SSD. ES6. 8255A的“端口C按位置1/置0控制字”应写入()。
A. A口B. B口C. C口D. 控制口7. 采用条件传送时,必须要有()。
A. 中断逻辑B. 类型号请求信号C. 状态端口D. 请求信号8. 中断服务程序入口地址是()A 中断向量表的指针B 中断向量C 中断向量表D 中断号9. 8086/8088CPU在执行IN AL,DX指令时,DX寄存器的内容输出到()上。
A. 地址总线B. 数据总线C. 存储器D. 寄存器10. 一般地,将计算机指令的集合称为()。
A. 指令系统B. 汇编语言C. 模拟语言D. 仿真语言11. 8086处理器最小工作方式和最大工作方式的主要差别是()。
A. 内存容量不同B. I/O端口数不同C. 数据总线位数不同D. 单处理器和多处理器的不同12. 将寄存器AX的内容求反的正确操作是()A. NEG AXB. XOR AX,0FFFFHC. OR AXD. CMP AX,AX13. 用DEBUG调试汇编语言程序时,显示某指令的地址是2F80:F400,存放该指令的存储单元的物理地址是()A. 3EC00HB. 2F80HC. 12380HD. 2F800H14.汇编程序是一种()A. 汇编语言程序B. 编辑程序C. 翻译程序D. 将高级语言程序转换成汇编语言程序的程序15. 在查询方式下输入/输出时,在I/O接口中设有(),通过它来确定I/O设备是否准备好。
CheatEngine(CE)AA教程Cheat Engine(CE)AA教程适合CE初学者观看目录1. 介绍2. 寄存器2.a 32位寄存器2.b 16位寄存器3. 指令3.a JMP3.b MOV3.c Push/Pop + The Stack3.dalloc/label/registersymbol3.e Call and Ret3.f 其他4. Array of Bytes5. 结尾6. 人员名单/致谢词额外内容: 写一个脚本1. 介绍'哟,我猜你在读这个是因为下面两件事中的一个。
1) 你正在试图学习自动汇编(我不会叫你菜鸟,因为每个人都是从哪里开始的,对吧=) ) or2) 你想测试你的自动汇编知识(作为扩展)。
那么,如果你是前者,那就慢慢的体会好每个部分,并且在继续进行前确认自己已经明白了这章。
Dark Byte wrote:大多数人都认为AA很难,其实它可容易了。
来自CE作者自己的话如果是后者,那我不会给你提任何的建议,即使我想帮忙。
如果你发现有什么错了,或者含糊或者认为我可以做的更好,请告诉我。
我一直处于自我学习的状态! 等等!别问,我知道你在想什么。
You wrote:为什么我非要听一个还在学习的人的话?好,我来告诉你,我的朋友。
即使我仍然在学习,我了解AA,并且我认为与你分享知识是一件很棒的事。
=)Edit: 这个是很久以前写的,但是现在我学到了很多,并且已经重新检查了。
现在,让我们投身入奇幻的电脑世界2. 寄存器这些也许你已经在一些脚本中看到过,它们被非常广泛的使用。
有两种寄存器被使用,接下来来进行讲解。
---------------2.a 32 Bit---------------首先,我将解释每个寄存器是如何得到它们的名字的,这会帮助你记住它们哪个是哪个。
首先,以E开头(如果你注意了下面,你会发现所有的寄存器都是以E开头的) 它告诉你这个寄存器是32位寄存器。
而A,B,C,D的含义你看完描述就能明显得体会到了。
第一课基本概念我们先假设您已知道了如何使用MASM。
如果您还不知道的话,请下载win32asm.exe ,并请仔细研读其中所附带的文档资料。
好,如果您已准备就绪,我们这就开始吧!理论:WIN32 程序运行在保护模式下的,保护模式的历史可以追溯到 80286。
而今80286 已成为了历史。
所以我们将只把精力集中于 80386 及后续的X86 系列CPU。
Windows 把每一个 Win32 应用程序放到分开的虚拟地址空间中去运行,也就是说每一个应用程序都拥有其相互独立的 4GB 地址空间,当然这倒不是说它们都拥有 4GB 的物理地址空间,而只是说能够在 4GB 的范围内寻址。
操作系统将会在应用程序运行时完成 4GB 的虚拟地址和物理内存地址间的转换。
这就要求编写应用程序时必须格守 Windows 的规范,否则极易引起内存的保护模式错误。
而过去的 Win16 内存模式下,所有的应用程序都运行于同一个 4GB 地址空间,它们可以彼此"看"到别的程序的内容,这极易导致一个应用程序破坏另一个应用程序甚至是操作系统的数据或代码。
和 16 位 Windows 下的把代码分成 DATA,CODE 等段的内存模式不同,WIN32 只有一种内存模式,即 FLAT 模式,意思是"平坦"的内存模式,再没有 64K 的段大小限制,所有的 WIN32 的应用程序运行在一个连续、平坦、巨大的 4GB 的空间中。
这同时也意味着您无须和段寄存器打交道,您可以用任意的段寄存器寻址任意的地址空间,这对于程序员来说是非常方便的,这也使得用32位汇编语言和用C语言一样方便。
在Win32下编程,有许多重要的规则需要遵守。
有一条很重要的是:Windows 在内部频繁使用 ESI,EDI,EBP,EBX 寄存器,而且并不去检测这些寄存器的值是否被更改,这样当您要使用这些寄存器时必须先保存它们的值,待用完后再恢复它们,一个最显著的应用例子就是 Windows 的CallBack 函数中。
C++内存回收3.1 C++内存对象⼤会战 如果⼀个⼈⾃称为程序⾼⼿,却对内存⼀⽆所知,那么我可以告诉你,他⼀定在吹⽜。
⽤C或C++写程序,需要更多地关注内存,这不仅仅是因为内存的分配是否合理直接影响着程序的效率和性能,更为主要的是,当我们操作内存的时候⼀不⼩⼼就会出现问题,⽽且很多时候,这些问题都是不易发觉的,⽐如内存泄漏,⽐如悬挂指针。
笔者今天在这⾥并不是要讨论如何避免这些问题,⽽是想从另外⼀个⾓度来认识C++内存对象。
我们知道,C++将内存划分为三个逻辑区域:堆、栈和静态存储区。
既然如此,我称位于它们之中的对象分别为堆对象,栈对象以及静态对象。
那么这些不同的内存对象有什么区别了?堆对象和栈对象各有什么优劣了?如何禁⽌创建堆对象或栈对象了?这些便是今天的主题。
3.1.1 基本概念 先来看看栈。
栈,⼀般⽤于存放局部变量或对象,如我们在函数定义中⽤类似下⾯语句声明的对象:Type stack_object ; stack_object便是⼀个栈对象,它的⽣命期是从定义点开始,当所在函数返回时,⽣命结束。
另外,⼏乎所有的临时对象都是栈对象。
⽐如,下⾯的函数定义:Type fun(Type object); 这个函数⾄少产⽣两个临时对象,⾸先,参数是按值传递的,所以会调⽤拷贝构造函数⽣成⼀个临时对象object_copy1 ,在函数内部使⽤的使⽤的不是object,⽽是object_copy1,⾃然,object_copy1是⼀个栈对象,它在函数返回时被释放;还有这个函数是值返回的,在函数返回时,如果我们不考虑返回值优化(NRV),那么也会产⽣⼀个临时对象object_copy2,这个临时对象会在函数返回后⼀段时间内被释放。
⽐如某个函数中有如下代码:Type tt ,result ; //⽣成两个栈对象tt = fun(tt); //函数返回时,⽣成的是⼀个临时对象object_copy2 上⾯的第⼆个语句的执⾏情况是这样的,⾸先函数fun返回时⽣成⼀个临时对象object_copy2 ,然后再调⽤赋值运算符执⾏tt = object_copy2 ; //调⽤赋值运算符 看到了吗?编译器在我们毫⽆知觉的情况下,为我们⽣成了这么多临时对象,⽽⽣成这些临时对象的时间和空间的开销可能是很⼤的,所以,你也许明⽩了,为什么对于“⼤”对象最好⽤const引⽤传递代替按值进⾏函数参数传递了。
c语言阅读心得(优秀8篇)c语言阅读心得篇1看了一段时间的《C和指针》,这样看的效果不是很好,看着书本当时是懂了,有点恍然大悟的感觉,但是发现并不能真正的把理解的内容加入到自己程序当中,不能很好的去运用它,所以对于C语言我认为理论加实践,是学习C语言的最好方法,看书并不能完全理解书中的内容,也较容易遗忘,学习效率比较低,所以现在学习《数据结构》,在看书的同时,会在Microsoft Visual C++ 6.0软件上实践,严蔚敏写的《数据结构》这本书,比较经典但是这本书是伪算法,并不能直接在计算机上运行,对于初学者上机实践比较困难,另外说一下,我认为学习应该是在模仿中理解,在模仿中创新,所以我选择结合高一凡写的书《数据结构算法实现及解析》,这本书严蔚敏写的伪算法全部用程序实现了,给我上机实践提供了很大的帮助,貌似说到数据结构了,呵呵,回到正题,下面继续说说C语言。
指针是C语言的精华,也是C语言的难点,它就像一把双刃剑,锋利无比但运用的不好也会给自己带来危害,后果比较严重,所以重点来说说指针。
很多初学者弄不清指针和数组到底有什么样的关系,为避免混淆不清,下面总结一下指针和数组的特性。
指针是保存数据的地址,任何存入指针变量的数据都会被当作地址来处理,指针变量本身的地址由编译器另外存储,存储在哪里,我们并不知道,间接访问数据,首先取得指针变量的内容,把它作为地址,然后从这个地址读或写入数据。
指针可以用间接访问操作符(_)访问,也可以用以下标的形式访问,指针一般用于动态数据结构。
数组是用来保存数据的,数组名代表的是数组首元素的地址而不是数组的首地址,所以数组p与p是有区别的,虽然内容相同,但意义却不同,p才是整个数组的首地址,数组名是整个数组的名字,数组内每个元素并没有名字,不能把数组当一个整体来进行读写操作。
当然数组在初始化时也有例外,如int p[]=“12345”是合法的。
数组可以以指针的形式访问如_(p+i);也可以以下标的形式访问p[i],但其本质都是p所代表的数组首元素的地址加上i_sizeof(类型)个字节作为数据的真正地址来进行访问的。
《KEIL C51可重入函数及模拟栈浅析》一文的理解此片文章主要是关于前辈文章的理解,没有超越前辈的地方,前辈文章链接如下:/lyb1900/item/404f12f49176f2b730c1995c一下是我对于这篇文章的理解,如果有错希望大家能够指出并改正,鄙人QQ:384710930在进入代码阅读之前,本人想说说我的见解:本人觉得先读C?ADDXBP和C?XBPOFF函数对于理解该文有比较好效果,但是鉴于前辈的顺序不是如此,所以我就不加更改!关键在于下面两个函数!文字功底不行,请大家见谅,一下附录原文和前辈文章里没有写全的代码!附录(原文):KEIL C51可重入函数及模拟栈浅析(转)原文:/shuqianyan/blog/static/74740421200911594540643/摘要:本文较详细的介绍了keilc51可再入函数和模拟堆栈的一些概念和实现原理,通过一个简单的程序来剖析keilc51在大存储模式下可重入函数的调用过程,希望能为keilc51和在51系列单片机上移植嵌入式实时操作系统的初学者提供一些帮助。
1、关于可重入函数(可再入函数)和模拟堆栈(仿真堆栈)“可重入函数可以被一个以上的任务调用,而不必担心数据被破坏。
可重入函数任何时候都可以被中断,一段时间以后又可以运行,而相应的数据不会丢失。
”(摘自嵌入式实时操作系统uC/OS-II)在理解上述概念之前,必须先说一下keilc51的“覆盖技术”。
(采用该技术的原因请看附录中一网友的解释)(1)局部变量存储在全局RAM空间(不考虑扩展外部存储器的情况);(2)在编译链接时,即已经完成局部变量的定位;(3)如果各函数之间没有直接或间接的调用关系,则其局部变量空间便可覆盖。
正是由于以上的原因,在Keil C51环境下,纯粹的函数如果不加处理(如增加一个模拟栈),是无法重入的。
举个例子:在上面的代码中,TaskA与TaskB并不存在直接或间接的调用关系,因而它们的局部变量a与b便是可以被互相覆盖的,即它们可能都被定位于某一个相同的RAM空间。
Cortex-M3体系结构学习笔记-寄存器知识要想了解Cortex-M3体系结构的知识,必须学习一下几个部分:CM3微处理器内核结构,处理器的工作模式及状态,寄存器,总线结构,存储器的组织与映射,指令集,流水线,异常和中断,存储器保护单元MPU。
这篇文章主要是我学习CM3机构寄存器的知识笔记。
Cortex‐M3 处理器拥有R0‐R15 的寄存器组。
其中 R13 作为堆栈指针SP。
SP 有两个,但在同一时刻只能有一个可以看到,这也就是所谓的“banked”寄存器。
CM3寄存器全局图如下所示:1. 通用寄存器通用寄存器包括R0-R12,R0-R7也被称为低组寄存器。
它们的字长全是32位的。
所有指令(包括 16位的和32位的)都能访问他们。
复位后的初始值是随机的。
R8-R12也被称为高组寄存器。
它们的字长也是32位的。
16位的Thumb指令不能访问他们,32位的Thumb-2指令则不受限制。
复位后的初始值也是随机的。
2.堆栈指针R13Cortex‐M3 拥有两个堆栈指针,然而它们是banked,因此任一时刻只能使用其中的一个。
•主堆栈指针(MSP):复位后缺省使用的堆栈指针,用于操作系统内核以及异常处理例程(包括中断服务例程)•进程堆栈指针(PSP):由用户的应用程序代码使用。
堆栈指针的最低两位永远是0,这意味着堆栈总是4 字节对齐的。
(在ARM 编程领域中,凡是打断程序顺序执行的事件,都被称为异常(exception)。
除了外部中断外,当有指令执行了“非法操作”,或者访问被禁的内存区间,因各种错误产生的fault,以及不可屏蔽中断发生时,都会打断程序的执行,这些情况统称为异常。
在不严格的上下文中,异常与中断也可以混用。
另外,程序代码也可以主动请求进入异常状态的(常用于系统调用).)在处理模式和线程模式下,都可以使用MSP,但只能在线程模式下使用PSP。
堆栈与微处理器模式的对应关系如下图所示。
使用两个堆栈的目的是为了防止用户堆栈的溢出影响系统核心代码(如操作系统内核)的运行。
从hs_strcpy谈安全——缓冲区溢出前不久,看到了几篇关于缓冲溢出的文章,发现写的不错,然后自己也研究、动手尝试了一下,发现真的很有意思。
再后来突然想起恒生他有自己的api函数,发现有一个hs_strcpy,然后尝试了几次搜索,还让我真的找到了这个hs_strcpy定义的源码,最后发现了一个惊奇的事情,具体什么,请看下文。
专有名字解释:在看下文之前,我们还是先看一下专有的名词解释吧,这样可以更好的带大家全面的了解本文的核心内容。
1.缓存溢出(Buffer overflow),是指在存在缓存溢出安全漏洞的计算机中,攻击者可以用超出常规长度的字符数来填满一个域,通常是内存区地址。
在某些情况下,这些过量的字符能够作为“可执行”代码来运行。
从而使得攻击者可以不受安全措施的约束来控制被攻击的计算机。
[1]2.栈(操作系统):由编译器自动分配释放,存放函数的参数值,局部变量的值等。
其操作方式类似于数据结构中的栈。
[2]3. ESP(Extended stack pointer)是指针寄存器的一种(另一种为EBP)。
用于堆栈指针。
[3]开门见山:随着IDE工具的越来越发达,越来越多的程序员开始疏忽缓冲区溢出,甚至一些开发程序员从未听说过此专有名词。
当然如果真的发生了缓冲溢出,那还是非常可怕的,虽然光大的乌龙指并非缓冲溢出所为,但是缓冲区可以再次创造光大的乌龙指。
所以我们开发人员不可以忽视我们程序的缓冲区溢出问题。
那到底什么是缓冲区溢出呢?顾名思义,缓冲区溢出的含义是为缓冲区提供了多于其存储容量的数据,打个比方,我有一个100ml的水杯,但是我倒入了120ml的水,那么20ml 的水就会溢出杯子,造成一些想不到的后果。
strcpy和hs_strcpy我们先来看一下微软为我们开发人员提供的strcpy这个api函数的实现源码(参考百度百科):/*********************** C语言标准库函数strcpy的一种典型的工业级的最简实现* 返回值:目标串的地址。
关于堆栈和指针堆栈是一种执行“后进先出”算法的数据结构。
设想有一个直径不大、一端开口一端封闭的竹筒。
有若干个写有编号的小球,小球的直径比竹筒的直径略小。
现在把不同编号的小球放到竹筒里面,可以发现一种规律:先放进去的小球只能后拿出来,反之,后放进去的小球能够先拿出来。
所以“先进后出”就是这种结构的特点。
堆栈就是这样一种数据结构。
它是在内存中开辟一个存储区域,数据一个一个顺序地存入(也就是“压入——push”)这个区域之中。
有一个地址指针总指向最后一个压入堆栈的数据所在的数据单元,存放这个地址指针的寄存器就叫做堆栈指示器。
开始放入数据的单元叫做“栈底”。
数据一个一个地存入,这个过程叫做“压栈”。
在压栈的过程中,每有一个数据压入堆栈,就放在和前一个单元相连的后面一个单元中,堆栈指示器中的地址自动加1。
读取这些数据时,按照堆栈指示器中的地址读取数据,堆栈指示器中的地址数自动减1。
这个过程叫做“弹出pop”。
如此就实现了后进先出的原则。
堆栈是计算机中最常用的一种数据结构,比如函数的调用在计算机中是用堆栈实现的。
堆栈可以用数组存储,也可以用以后会介绍的链表存储。
下面是一个堆栈的结构体定义,包括一个栈顶指针,一个数据项数组。
栈顶指针最开始指向-1,然后存入数据时,栈顶指针加1,取出数据后,栈顶指针减1。
#define MAX_SIZE 100typedef int DATA_TYPE;struct stack{DATA_TYPE data[MAX_SIZE];int top;};堆栈是系统使用是临时存储区域。
它是后进先出的数据结构。
C++主要将堆栈用于函数调用。
当函数调用时,各种数据被推入堆栈顶部;函数终止后的返回地址、传递给函数的参数、函数返回的结果以及函数中声明的局部变量等等。
因此当函数A调用函数B调用函数C,堆栈是增长了,但调用完成后,堆栈又缩小了。
堆是一种长期的存储区域。
程序用C++的new操作符分配堆。
对new的调用分配所需的内存并返回指向内存的指针。
与堆栈不同,你必须通过调用new明确的分配堆内存。
你也必须通过调用C++的delete 操作符明确的释放内存,堆不会自动释放内存。
如果C++中的一个类是定义在堆栈上的,就使用"."开访问它的成员。
如果是定义在堆上的,就使用"->"指针来开访问。
但在,"->"操作符也可以用在堆栈上的类。
什么是指针?和其它变量一样,指针是基本的变量,所不同的是指针包含一个实际的数据,该数据代表一个可以找到实际信息的内存地址。
这是一个非常重要的概念。
许多程序和思想依靠指针作为他们设计的基础。
开始怎样定义一个指针呢?除了你需要在变量的名称前面加一个星号外,其它的和别的变量定义一样。
举个例子,以下代码定义了两个指针变量,它们都指向一个整数。
int* pNumberOne;int* pNumberTwo;注意到两个变量名称前的前缀‟p‟了么?这是一个惯例,用来表示这个变量是个指针。
现在,让我们将这些指针实际的指向某些东西:pNumberOne = &some_number;pNumberTwo = &some_other_number;…&‟符号应该读作”什么什么的地址”,它返回一个变量在内存中的地址,设置到左侧的变量中。
因此,在这个例子中,pNumberOne设置和some_number的地址相同,因此pNumberOne现在指向some_number。
现在,如果我们想访问some_number的地址,可以使用pNumberOne。
如果我们想通过pNumberOne 访问some_number的值,那么应该用*pNumberOne。
这个星号表示解除指针的参照,应该读作“什么什么指向的内存区域”。
到现在我们学到了什么?举个例子哟,有许多东西需要理解。
我的建议是,如果你有哪个概念没有弄清楚的话,那么,不妨再看一遍。
指针是个复杂的对象,可能需要花费一段时间来掌握它。
这儿有一个例子示范上面所将的概念。
这是用C写的,没有C++扩展。
#include <stdio.h>void main(){// 申明变量int nNumber;int *pPointer;//赋值nNumber = 15;pPointer = &nNumber;// 输出nNumber的值printf("nNumber is equal to : %d\n", nNumber);// 通过pPointer修改nNumber的值*pPointer = 25;// 证明nNumber已经被改变了// 再次打印nNumber的值printf("nNumber is equal to : %d\n", nNumber);}通读一遍,并且编译样例代码,确信你理解了它为什么这样工作。
如果你准备好了,那么继续。
一个陷阱!看看你能否发现下面这段程序的毛病:#include <stdio.h>int *pPointer;void SomeFunction();{int nNumber;nNumber = 25;//将pPointer指向nNumberpPointer = &nNumber;}void main(){SomeFunction(); //用pPointer做些事情// 为什么会失败?printf("Value of *pPointer: %d\n", *pPointer);}这段程序先调用SomeFunction函数,该函数创建一个叫做nNumber的变量,并将pPointer指向它。
那么,问题是,当函数退出时,nNumber被删除了,因为它是一个局部变量。
当程序执行到局部变量定义的程序块以外时,局部变量总是被删除了。
这就意味着,当SomeFunction函数返回到main函数时,局部变量将被删除,因此pPointer将指向原先nNumber的地址,但这个地址已经不再属于这段程序了。
如果你不理解这些,那么重新阅读一遍关于局部变量和全局变量的作用范围是明智的选择。
这个概念也是非常重要的。
那么,我们如何解决这个问题呢?答案是使用大家都知道的一个方法:动态分配。
请明白C和C++的动态分配是不同的。
既然现在大多数程序员都使用C++,那么下面这段代码就是常用的了。
动态分配动态分配可以说是指针的关键所在。
不需要通过定义变量,就可以将指针指向分配的内存。
也许这个概念看起来比较模糊,但是确实比较简单。
下面的代码示范如何为一个整数分配内存:int *pNumber;pNumber = new int;第一行申明了一个指针pNumber,第二行分配一个整数内存,并且将pNumber指向这个新内存。
下面是另一个例子,这次用一个浮点数:double *pDouble;pDouble = new double;动态分配有什么不同的呢?当函数返回或者程序运行到当前块以外时,你动态分配的内存将不会被删除。
因此,如果我们用动态分配重写上面的例子,可以看到现在能够正常工作了。
#include <stdio.h>int *pPointer;void SomeFunction(){// make pPointer point to a new integerpPointer = new int;*pPointer = 25;}void main()SomeFunction(); // make pPointer point to somethingprintf("Value of *pPointer: %d\n", *pPointer);}通读一遍,编译上面的代码,确信你已经理解它是如何工作的。
当调用SomeFunction时,分配了一些内存,并且用pPointer指向它。
这次,当函数返回时,新内存就完整无缺了。
因此pPointer仍旧指向有用的东西。
这是因为使用了动态分配。
确信你已经理解它了。
那么继续向下看,了解为什么上面的程序还会有一系列的错误。
内存分配和内存释放这里有一个问题,可能会变得十分严重,虽然它很容易补救。
这个问题就是,虽然你用动态分配可以方便的让内存完整无缺,确实不会自动删除,除非你告诉计算机,你不再需要这块内存了,否则内存将一直被分配着。
因此结果就是,如果你不告诉计算机你已经使用完这块内存,那么它将成为被浪费的空间,因为其它程序或者你的应用程序的其它部分不能使用这块内存。
最终将导致系统因为内存耗尽而崩溃。
因此这个问题相当重要。
内存使用完后释放非常容易:delete pPointer;需要做的就是这些。
但是你必须确定,你删除的是一个指向你实际分配的内存的指针,而不是其它任何垃圾。
尝试用delete已经释放的内存是危险的,并且可能导致程序崩溃。
这里再次举个例子,这次修改以后就不会有内存浪费了。
#include <stdio.h>int *pPointer;void SomeFunction(){// make pPointer point to a new integerpPointer = new int;*pPointer = 25;}void main(){SomeFunction(); // make pPointer point to somethingprintf("Value of *pPointer: %d\n", *pPointer);delete pPointer;}只有一行不同,但这行是要点。
如果你不删除内存,就会导致“内存泄漏”,内存将逐渐减少,除非应用程序重新启动,否则将不能再生。
向函数传递指针传递指针给函数非常有用,但不容易掌握。
如果我们写一个程序,传递一个数值并且给它加上5,我们也许会写出如下的程序:#include <stdio.h>void AddFive(int Number){Number = Number + 5;}void main()int nMyNumber = 18;printf("My original number is %d\n", nMyNumber);AddFive(nMyNumber);printf("My new number is %d\n", nMyNumber);}但是,程序中函数AddFive的参数Number只是变量nMyNumber的一个拷贝,而不是变量本身,因此,Number = Number + 5只是为变量的拷贝增加了5,而不是最初的在main()函数中的变量。