stc15中,flash烧写程序编写flash与数据flash的工作特征


一 C51内存结构深度剖析
三, 浅淡變量类型及其作用域
四 C51常用头文件
六, C51编译器的限制
一C51内存结构深度剖析
在编写应用flash烧写程序编写时,定义一个变量一个数组,或昰说一个固定表格到底存储在什么地方;
当定义变量大小超过MCU的内存范围时怎么办;
如何控制变量定义不超过存储范围;
以及如何定义變量才能使得变量访问速度最快,写出的flash烧写程序编写运行效率最高以下将一一解答。
1 类关键字(六类存储类型)
code: code memory (flash烧写程序编写存储器也即只读存储器)用来保存常量或是flash烧写程序编写code memory 采用16位地址线编码,可以是在片内或是片外,大小被限制在64KB
作用:定义常量如八段数码表或是编程使用的常,在定义时加上code 或明确指明定义的常量保存到code memory(只读)
此关键字的使用方法等同于const
data data memory (数据存储区)只能鼡于声明变量不能用来声明函数,该区域位于片内采用8位地址线编码,具有最快的存储速度但是数量被限制在128byte或更少。
idata idata memory(数据存储區)只能用于声明变量不能用来声明函数. 该区域位于片内,采用8位地址线编码,内存大小被限制在256byte或更少该区域的低地址区与data memory地址一致;高地址区域是52系列在51系列基础上扩展的并与特殊功能寄存器具有相同地址编码的区域。即:data memory是idata memory的一个子集
外部,采用16位地址线进行编碼存储大小被限制在64KB以内。
pdata pdata memory 只能用于声明变量不能用来声明函数,该区域位于MCU外部采用8位地址线进行编码。存储大小限制在256byte. 是xdata memory的低256byte为其子集。
bdata bdata memory 只能用于声明变量不能用来声明函数。该区域位于8051内部位数据地址定义的量保存在内部位地址空间,可用位指令直接读寫
注:有些资料讲,定义字符型变量时在缺省unsigned 时,字符型变量默认为无符号,与标准C不同但我在Keil uVision3中测试的时候发现并非如此。在缺省的情况下默认为有符号或许在以前的编译器是默认为无符号。所以看到有的资料上面这样讲的时候要注意一下,不同的编译器或許不同所以我们在写flash烧写程序编写的时候,还是乖乖的把unsigned signed 加上咱也别偷这个懒。
2函数的参数和局部变量的存储模式
C51 编译器允许采用三種存储器模式:SMALLCOMPACT 和LARGE。一个函数的存储器模式确定了函数的参数的局部变量在内存中的地址空间处于SMALL模式下的函数参数和局部变量位于8051單片机内部RAM中,处于COMPACT和LARGE模式下的函数参数和局部变量则使用单片机外部RAM在定义一个函数时可以明确指定该函数的存储器模式。方法是在形参表列的后面加上一存储模式
上面例子在第一行用了一个预编译命令#pragma 它的意思是告诉c51编译器在对flash烧写程序编写进行编译时,按该预编譯命令后面给出的编译控制指令LARGE进行编译即本例flash烧写程序编写编译时的默认存储模式为LARGE.随后定义了三个函数,第一个定义为SMALL存储模式苐二个函数定义为LARGE第三个函数未指定,在用C51进行编译时只有最后一个函数按LARGE存储器模式处理,其它则分别按它们各自指定的存储器模式處理
本例说明,C51编译器允许采用所谓的存储器混合模式即允许在一个flash烧写程序编写中将一些函数使用一种存储模式,而其它一些则按叧一种存储器模式采用存储器混合模式编程,可以充分利用8051系列单片机中有限的存储器空间同时还可以加快flash烧写程序编写的执行速度。
3绝对地址访问 absacc.h(相当重要)
例: 如下指令在对外部存储器区域访问地址0x1000
功能:与前面的一个宏相似只是它们指定的数据类型为unsigned int .。
通过靈活运用不同的数据类型所有的8051地址空间都是可以进行访问。
注:用以上八个函数可以完成对单片机内部任意ROMRAM进行访问,非常方便还有一种方法,那就是用指钟后面会对C51的指针有详细的介绍。
为了提高flash烧写程序编写的执行效率C语言允许将一些频率最高的那些变量,定义为能够直接使用硬件寄存器的所谓的寄存器变量定义一个变量时,在变量类型名前冠以“register” 即将该变量定义成为了寄存器变量寄存器变量可以认为是一自动变量的一种。有效作用范围也自动变量相同由于计算机寄存器中寄存器是有限的。不能将所有变量都定義成为寄存器变量通常在flash烧写程序编写中定义寄存器变量时,只是给编译器一个建议该变量是否真正成为寄存器变量,要由编译器根據实际情况来确定另一方面,C51编译器能够识别flash烧写程序编写中使用频率最高的变量在可能的情况下,即使flash烧写程序编写中并未将该变量定义为寄存器变量编译器也会自动将其作为寄存器变量处理。被定义的变量是否真正能成为寄存器变量最终是由编译器决定的。
指鍾本身是一个变量其中存放的内容是变量的地址,也即特定的数据8051的地址是16位的,所以指针变量本身占用两个存储单元指针的说明與变量的说明类似,仅在指针名前加上“*”即可
利用指针可以间接存取变量。实现这一点要用到两个特殊运算符
* 取指针指向单元的数据
指针可以进行运算它可以与整数进行加减运算(移动指针)。但要注意移动指针后,其地址的增减量是随指针类型而异的如,浮点指针进行自增后其内部将在原有的基础上加4,而字符指针当进生自增的时候其内容将加1。原因是浮点数占4个内存单元,而字符占一個字节
宏晶科技最新一代STC12C5A360S2系列,每一个单片机出厂时都有全球唯一身份证号码(ID号)用户可以在单片机上电后读取内部RAM单元F1H~F7H的数值,來获取此单片机的唯一身份证号码使用MOV @Ri 指令来读取。下面介绍C51 获取方法:
(此处只是对指针做一个小的介绍达到访问内部任何空间的方式,后述有对指针使用的详细介绍)
C51提供了一组可以直接对其操作的扩展函数
若源flash烧写程序编写中#include包含头文件,io51.h 后就可以在扩展函数中使用特殊功能寄存器的地址名,以增强flash烧写程序编写的可读性:
此方法对SFR,RAM,ROM的直接存取不建议使用.因为,io51.h这个头文件在KEIL中无法打开可用指针,或是采用absacc.h头文件
PWM:(Pulse Width Modulation)脉宽调制,是一种使用flash烧写程序编写来控制波形占空比周期,相位波形的技术
PCA:(Programmable Counter Array)可编程计數阵列,它比通常的定时/计数器的定时能力强需要CPU的干预少。其优势一是软件简单二是精度大有提高。
我们平时写单片机应用flash烧写程序编写的时候所使用的头文件大多都是用的的reg51.h或是用reg52.h。会写C51的人都会用但对其头文件内部的定义有所了解的人确并不多。
下面对其内蔀做详细解释方便读者作进一步的了解,并能运用各类型号的单片机因为增强型号的单片机的增强功能都是通过特殊功能寄存器控制。
打开 reg52.h 头文件会发现是由大量的 sfr ,sbit的声明组成,甚至于还有sfr16.其实这样的声明都是与单片机内部功能寄存器(特殊功能寄存器)联系起来的,丅面对其做出详细解释
SFR 声明一个变量它的声明与其它的C变量声明基本相同,唯一的区别SFR在声明的同时为其指定特殊功能寄存器作为存儲地址,而不同于C变量声明的整型字符型等等由编译器自动分配存储空间。
此处声明一个变量P0并指定其存储地址为特殊功能寄存器0x80;,在加入reg52.h头文件后。编写应用flash烧写程序编写时P0就可以直接使用而无需定义对P0的操作就是,对内部特殊功能寄存器(0x80对应用MCU的P0口)的操作可進行读写操作。
如果将第一条声明改为sfr K0 = 0x80; 那么如果要把单片机的P0口全部拉低,则不能写P0=0x00;而应保存后再在应用flash烧写程序编写中写成K0=0x00;否则编译器会提示“P0为未定义标识符”
1 等号右边只能是十进制,十六进制整型的数据常量,不允许带操作符的表达式
2 SFR不能声明于任何函数内部包括main函数。只能声明于函数外
3 用SFR声明一个变量后,不能用取地址运算符&获取其地址 编译无法通过,编译器会提示非法操作
4 有一点須特别注意,51内核0x80~0xff,为特殊功能寄存器地址区间,但并不是所有的地址都有定义如果说你所用的MCU芯片上对于某个地址没有定义,那么用sfr在定義变量的时候不要把变量的地址分配到未定义的特殊功能寄存器上,虽然编译时能通过用KEIL仿真时貌似是没有问题,但下载到芯片里运荇时是会出问题的。比如说向一个未定义的特殊功能寄存器执行读操作,读出来的就是一个未知的数(读者可自行测试,先把串口通信调通然后做一个简单的人机交互。读出一个数后再发给计算机,用串口调试助手或是串口监控查看这用方法在仿真的时候很有鼡。)所以具体那些特殊功能寄存器能够用就要查看你使用的芯片手册。
5 若遇到增强性的单片机只要知道其扩展的特殊功能寄存器的哋址,用SFR定
就可以很方便进行编程
sbit 同样是声明一个变量,和SFR 使用方法类似但是SBIT是用来声明一个位变量,因为在51系列的应用中,非常囿必要对SFR的单个位进行存取而通过bit 数据类型,使其具备位寻址功能
如,在reg52.h中有如下声明
所以对EA的操作即是对IE最高位的操作。
但如果想让 SP DPL DPH PCON TMOC TL0 TL1 TH0 TH1 SBUF这些特殊功能寄存器具备位寻址采用上述如IE类似的定义,是不行的虽然修改后,在编译的时候不会出现错误但只要用到你定义嘚位变量名时就会出错。原因是只有特殊功能寄存器的地址是8的倍数(十六进制以0或8结尾)才能进行位寻址。
打开reg52.h头文件可以看到所囿用sbit声明了的特殊功能寄存器的地址均是以0或8结尾
如硬要达到上述要求,可用带参的宏定义来完成此处不做详细说明(意义并不大)。
丅面对sbit的使用做详细介绍:
随着8051的应用非常有必要对特殊功能寄存器的单个bit位进行存取,C51编译器通过sbit 数据类型提供了对特殊功能寄存器的位操作。
以下是sbit的三种应用形式:
现对上述三种形式的声明做必要的说明
OV的地址计算方式是OV所在的寄存器地址加上OV的bit-position
不是所有的SFR都鈳位寻址。只有特殊功能寄存器的地址是8的倍数(十六进制以0或8结尾)才能进行位寻址,并且sbit声明的变量名虽可以是任意取,但是最好不偠以下划线开头因为以下划线开头的都保留给了C51的头文件做保留字。
许多8051的派生型单片机用两个连续地址的特殊功能寄存器,来存储┅个16bit的值例如,8052就用了0xCC和0xCD来保存定时/计数寄存器2的高字节和低字节编译器提供sfr16这种数据类型,来保存两个字节的数据虚拟出一个16bit的寄存器。
存储方面为小端存储方式低字节在前,高字节在后定义时,只写低字节地址如上,则定义T2为一个16位的特殊功能寄存器 T2L= 0CCh, T2H= 0CDh
1 等號右边,只写两个特殊功能寄存器的低地址且只能是十进制,十六进制的整型数据常量不允许带操作符的表达式
2 SFR不能声明于任何函数內部,包括main函数只能声明于函数外。
3 用SFR声明一个变量后不能用取地址运算符&获取其地址, 编译无法通过编译器会提示非法操作。
4 当伱向一个sfr16写入数据的时候KEIL CX51 编译器生成的代码,是先写高字节后写低字节,(可通过返汇编窗口查看)在有些情况下这并非我们所想偠的操作顺序。使用时须注意。
5 当你所要写入sfr16的数据当是高字节先写还是低字节先写非常重要的时候,就只能用sfr 这个关键字来定义並且任意时刻只保存一个字节,这样操作才能保证写入正确
三, 浅淡变量类型及其作用域
变量可分为 1.局部变量
(按变量的有效作用范围划汾)
是指函数内部(包括main函数)定义的变量,仅在定义它的那个函数范围内有效不同函数可使用相同的局部变量名,函数的形式参数也屬于局部变量在一个函数的内部复合语句中也可以定义局部变量,该局部变量只在该复合语合中有效
是指函数外部定义的变量,以称外部变量可为多个函数共同使用,其有效作用范围是从它定义开始到整个flash烧写程序编写文件结束如果全局变量,定义在一个flash烧写程序編写文件的开始处则在整个flash烧写程序编写文件范围都可以使用它,如果一个全局变量不是在flash烧写程序编写文件的开始处定义但又希望茬它定义之前的函数中引用该变量,这时应在引用该变量的函数中用关键字extern将其声明为“外部变量”另个,如果在一个flash烧写程序编写模塊文件中引用另一个flash烧写程序编写模块文件中定义的变量时也必须用extern进行说明。
外部变量的说明与外部变量的定义是不同的外部变量萣义只能有一次,定义的位置在所有函数之外而同一个flash烧写程序编写文件中(不是指模块文件)的外部变量声明可以有多次,声明的置茬需要引用该变量的函数之内外部变量的声明的作用只是声明该变量是一个已经在外部定义过了的变量而已。
如在同一个flash烧写程序编写攵件中全局变量与局部变量同名,则在局部变量的有效作用范围之内全局变量不起作用,也就是说局部变量的优先级比全局变量高。
在编写C语言flash烧写程序编写时不是特别必要的地方一般不要使用全局变量,而应当尽可能的使用局部变量因为局部变量只在使用它的時候,才为其分配内存单元而全局变量在整个flash烧写程序编写的执行过程中都要占用内存单元,且当全局变量使用过多时会降低flash烧写程序编写的可读性。
1自动变量(auto
定义变量时在变量类型名前加上 “auto” ,自动变量是C语言中使用最为广泛的一类变量在函数体内部或是複合语句内部定义的变量,如果省略了存储种类说明则该变量默认为自动变量。
自动变量的作用范围在定义它的函数体或是复合语句内蔀只有在定义它的函数内被调用,或是定义它的复合语句被执行时编译器才会为其分配内存空间,开始其生存期当函数调用结束返囙,或复合语句执行结束自动变量所占用的内存空间就被释放,变量的值当然也就不复存在其生存期结束。当函数再次调用或是复匼语句被再次执行时,编译器又会为其内部的自动变量重新分配内存空间但不会保留上一次运行的值。而必须被重新分配因此自动变量始终是相对于函数或复合语句的局部变量。
用说明符“extern”定义的变量称为外部变量按缺省规则,凡是在所有函数之前在函数外部定義的变量都是外部变量,定义时可以不写extern说明符但是一个函数体内说明一个已在该函数体外或别的flash烧写程序编写模块文件中定义过的外蔀变量时,刚必须要使用extern说明符外部变量定义后,它就被分配了固定的内存空间外部变量的生存期为flash烧写程序编写的整个执行时间。 外部变量的存储不会随函数或复合语句执行完毕而释放因此外部变量属于全局变量。
C语言允许将大型flash烧写程序编写分解为若干个独立的flash燒写程序编写模块文件各个模块可分别进行编译,然后再将它们连接在一起如果某个变量需要在所有flash烧写程序编写模块文件中使用,呮要在一个flash烧写程序编写模块文件中将该变量定义成全局变量而在其它flash烧写程序编写模块文件中用extern声明该变量是已被定义过的外部变量僦可以了。
函数是可以相互调用的定义函数时,如果冠以关键字extern 即将其明确定义为一个外部函数例如 extern int func2(char a,b) 。如果在定义函数时省略关键字extern则隐含为外部函数。如果在调用一个在本flash烧写程序编写模块文件以外的其它模块文件所定义的函数则必须要用关键字extern说明被调用的函數是一个外部函数。对于具有外部函数相互调用的多模块flash烧写程序编写可用C51编译器分别对各个模块文件进行编译,最后再用L51连接定位器將它们连接成为一个完整的flash烧写程序编写
flash烧写程序编写模块1,文件名为file1.c
flash烧写程序编写模块2文件名为file2.c
static int a=5; //静态变量只在第一次调用函数时赋徝,退出函数时
//会保留上次的值下次调用不再重新赋值。
C语言不允许在一个函数内嵌套定义另一个函数为了能够访问不同文件中各个函数的变量,除了可以采用参数传递的方法外还可以采用外部变量的方法,上面的例子就说了这一点不过,尽管使用外部变量在不同函数之间传递数据有时比使用函数参数传递更为方便不过当外部变量过多时,会增加flash烧写程序编写的调试排错的困难使得flash烧写程序编寫不便于维护。别外不通过参数传递直接在函数中改变全局变量的值有时还会发生一些意想不到的副作用。因些最好还是使用函数参数來传递数据
为了提高flash烧写程序编写的执行效率,C语言允许将一些频率最高的那些变量定义为能够直接使用硬件寄存器的所谓的寄存器變量。定义一个变量时在变量类型名前冠以“register” 即将该变量定义成为了寄存器变量。寄存器变量可以认为是一自动变量的一种有效作鼡范围也自动变量相同。由于计算机寄存器中寄存器是有限的不能将所有变量都定义成为寄存器变量,通常在flash烧写程序编写中定义寄存器变量时只是给编译器一个建议,该变量是否真正成为寄存器变量要由编译器根据实际情况来确定。另一方面C51编译器能够识别flash烧写程序编写中使用频率最高的变量,在可能的情况下即使flash烧写程序编写中并未将该变量定义为寄存器变量,编译器也会自动将其作为寄存器变量处理被定义的变量是否真正能成为寄存器变量,最终是由编译器决定的
使用存储种类说明符“static”定义的变量为静态变量,在上媔模块2flash烧写程序编写文件中使用了一个静态变量:static int a =5 ;由于这个变量是在函数fun1( )内部定义因此称为内部静态变量或局部静态变量。局部静态變量始终都是存在的但只有在定义它的函数内部进行访问,退出函数之后变量的值仍然保持,但不能进行访问
还有一种全局静态变量,它是在函数外部被定义的作用范围从它的定义点开始,一直到flash烧写程序编写结束当一个C语言flash烧写程序编写由若干个模块文件所组荿时,全局静态变量始终存在但它只能在被定义的模块文件中访问,其数据值可为该模块文件内的所有函数共享退出该文件后,虽然變量的值仍然保持着但不能被其它模块文件访问。在一个较大的flash烧写程序编写中这就方便了多人设计时,各自写的flash烧写程序编写模块鈈会被别的模块文件所引用
全局静态变量和单纯的全局变量,在编译时就已经为期分配了固定的内存空间只是他们的作用范围不同而巳。
局部静态变量是一种在两次函数调用之间仍能保持其值的局部变量
如下,局部变量的使用——计算度输出1~5的阶乘值
在这个flash烧写程序编写中一共调用了5次计算阶乘的函数fac(i),每次调用后输出一个阶乘值i!,同时保留了这个i!值,以便下次再乘(i+1).由此可见如果要保留函数上一佽调用结束时的值,或是在初始化之后变量只被引用而不改变其值则这时使用局部静态变量;较为方便,以免在每调用时都要重新进行賦值但是,使用局部静态变量需要占用较多的内存空间而且降低了flash烧写程序编写的可读性,因此并不建议多用局部静态变量
对于函數也可以定义成为具为静态存储种类的属性,定义函数时在函数名前冠以关键字static即将其定义为一个静态函数例如static int func1(char x, y)函数是外部型的,使用靜态函数可以使该函数只局限于当前定义它的模块文件中其它模块文件是不能调用它的。换名话说就是在其它模块文件中可以定义与靜态函数完全同名的另一个函数。不会因为flash烧写程序编写中存在相同的函数名而发生函数调用时的混乱 这一点对于进行模块化flash烧写程序編写设计是很有用的。
, C51常用头文件
在KEIL 中对于单片机所使用的头文件,除了reg51 reg52以外还有一些从各芯片制商的官网下载与reg51,reg52功能类似的头文件,需了解透外还要对各类型单片机均可通用且相当有用的的头文件,做相应的了解因为,内部所包含的函数与宏定义可以及大的方便我们编写应用flash烧写程序编写。
功能:检查参数字符是否为英文字母是则返回1
功能:检查字符是否为英文字母或数字字符,是则返回1
功能:检查参数值是否在0x00~0x1f 之间或等于0x7f是则返回1
功能: 检查参数是否为数字字符,是则返回1
功能: 检查参数值是否为可打印字符是则返囙1,可打印字符为0x21~0x7e
功能:除了与isgraph相同之外还接受空格符0x20
功能:检查参数字符的值是否为小写英文字母,是则返回1
功能:检查参数字符的徝是否为大写英文字母是则返回1
功能:检查字符是否为下列之一,空格制表符,回车换行,垂直制表符和送纸如果为真则返回1
功能:检查参数字符是否为16进制数字字符,是则返回1
功能:将ASCII字符0~9 a~f(大小写无关)转换成对应的16进制数字
功能:将大写字符转换成小写形式,洳字符变量不在A~Z之间则不作转换而直接返回该字符
功能:将小写字符转换成大写形式,如字符变量不在a~z之间则不作转换而直接返回该芓符
功能:该宏将任何整形数值缩小到有效的ASCII范围之内,它将变量和0x7f相与从而去掉第7位以上的所有数位
功能:该宏将字符与常数0x20 逐位相或
功能:该宏将字符与常数0xdf 逐位相与
功能:返回绝对值上面四个函数,除了形参和返回值不一样之外
功能: 返回val的正平方根
功能: rand返回┅个0到32767之间的伪随机数,srand用来将随机数发生器初始化成一个已知的(期望)值
功能: asin 返回val的反正弦值。acos 返回val的反余弦值
功能:cosh返回var的雙曲余弦值,sinh返回var的双曲正弦值
tanh返回var的双曲正切值。
功能: 向上取整返回一个大于val的最小整数。
功能: 向下取整返回一个小于val的最夶整数。
功能: 计算计算xy的值当(x=0,y<=0)或(x<0.y不是整数)时会发生错误。
功能:fpsave 保存浮点了flash烧写程序编写的状态fprestore恢复浮点子flash烧写程序编写嘚原始状态,当中断flash烧写程序编写中需要执行浮点运算时这两个函数是很有用的。
例: 如下指令在对外部存储器区域访问地址0x1000
功能:与湔面的一个宏相似只是它们指定的数据类型为unsigned int .。
通过灵活运用不同的数据类型所有的8051地址空间都是可以进行访问。
功能:将变量var 循环祐移 n 位
上三个函数的区别在于,参数及返回值的类型不同
功能:将变量var 循环左移 n 位
上三个函数的区别在于,参数及返回值的类型不同
功能:_nop_产生一个8051单片机的NOP指令C51编译器在flash烧写程序编写调用_nop_ 函数的地方,直接产生一条NOP指令

C51中断flash烧写程序编写编写要求:

5         如果中断函数Φ用到了浮点运算,必须保存浮点寄存器的状态当没有其它的flash烧写程序编写执行浮点运算时(即只有中断中用到浮点运算),可以不用保存

6         如果中断函数中调用了其它函数,则被调用的函数所使用的寄存器组必须与中断函数相同用户必须保证按要求使用相同的寄存器組,否则会产生不正确的结果这一点必须引起足够的注意,如果定义中断函数时没有使用using选项则由编译器选择一个寄存器组作绝对寄存器访问。另外不断的产生不可预测,中断函数对其它函数的调用可能形成递规调用需要时,可将被中断调用的其它函数定义为再入函数

函数的递规调用与再入函数:

      再入函数可被递归调用,无论何时包括中断服务函数在内的任何函数都可调用再入函数。与非再入函数的参数传递和局部就是的存储分配方法不同C51编译器为每个再入函数都生成一个模拟栈。模拟栈所在的存储器空间根据再入函数的存儲模式的不同可以分配到DATA,PDATA 或XDATA

对再入函数有如下规定:

1.  再入函数不能传送bit类型的参数。也不能定义一个局部位变量再入函数不能包括位操作以及8051系列单片机的可位寻址区。

3.  编译时在存储器模式的基础上,为再入函数在内部或外部存储中建立一个模拟堆栈区称為再入栈,再入函数的局部变量及参数被放在再入栈中从而使得再入函数可以进行递规调用,再非再入函数的局部变量被放在再入栈の外的暂存区内,如果对非再入函数进行递规调用则上次调用时使用的局部变量数据将被覆盖。

4.  在同一个flash烧写程序编写中可以定义和使用不同存储器模式的再入函数任意模式的再入函数不能调用不同模式的再入函数,但可以任意调用非再入函数

5.  在参数的传递上,實际参数可以传递给间接调用的再入函数,无再入属性的间接调用函数不能包含调用参数但是可以使用定义的全局变量来进行参数传遞。

1  名字最长为255个字符但只有前32个字符有效,尽管C语言对大小写敏感但由于历史原因,目标文件中的名字是否大小无关紧要

  指针是C語言中的一个重要概念,使用也十分普遍正确使用指针类型数据可以有效的表示复杂的数据结构,直接处理内存地址而且可以更为有效的使用数组

在C语言中,为了能够实现直接对内存单元的操作引入了指针类型的数据,指针类型数据是专门用来确定其它数据类型的地址的因此一个变量的地址就被称为该变量的指针如: 一个整形变量i  存放在内存单元40H中,则该内存单元地址40H就是变量i  的指针如果有一个變量专门用来存放另一个变量的地址,则称之为“指针变量”

 变量指针与指针变量

   变量的指针:  是指某个变量的地址而一个指针变量里媔存放的是另一个变量在内存中的地址。拥有这个地址的变量则称为该指针变量所指向的变量 所以每个变量都有它自己的指针(地址),而每一个指针变量都是指向另一个变量的C语言中用符号*来表示“指向”

 如果 指针ip这个指针变量指向i那么,两个赋值表达或同义苐二个表达式可以解释为“给指针变量ip所指向的变量赋值50

指针变量的定义与一般变量的定义类似,其一般形式如下:

数据类型  说明了該指针变量所指向的变量类型

存储器类型,是可选的它是C51编译器的一种扩展,如果带有此选项指针被定义为基于存储器的指针,无此選项时被定义为一般指针,这两种指针的区别在于它们的存储字节不同

      一般指针:   占用三个字节,第一个字节存放该指针存储器类型嘚编码第二和第三个字节分别存放该指针的高位和低位地址的偏移量

基于存储器指针:则该指针长度可为一个字节,也可为两字节

注:茬定义指针变量时最好指定其为基于存储器的指针这个生成的汇编代码长精       练一些,而且也节省空间(读者可自行到C51中写一个flash烧写程序編写查看其反汇编flash烧写程序编写)但在一些函数调用的参数中指针需要采用一般指针,为此C51编译器允许这两种指针相互转换转换规则洳下:

 一般指针转换成基于存储器指针,采取截断基于存储器类型指针转换成一般指针采用扩展的

  指针变量是含有一个数据对象地址的特殊变量,指针变量中只能存放地址

3.指针变量作为函数的参数

      函数的参数不仅可以是整型字符型等数据,还可以是指针类型指针变量作为函数的参数的作用是将一个变量的地址传到另一个函数中去,地址传递是双向的即主调用函数不仅可以向被调用函数传递参数,洏且还可以从被调用函数返回其结果

下面通过一个简单的示例来进行说明

上flash烧写程序编写上定义了一个swap(  )函数,两个形参为指针变量在調用函数时,所用的实参也是指针变量在调用开始,实参变量将它的值传递给形参变量采取的仍然是“值传递”方式,但这时传递的昰指针的值(地址)传递后,形参pi的值为&apj的值为&b,即指针变量*pi 和*pa都指向了a, *pj和*pb指向了b。接着使*pj与*pi的值互换从而达到了实现了a,和b值的互换。虽然函数返回时pi  pj被释放而不存在,但main函数中a 与b的值已经交换

       在C语言中,指针与数组有着十分密切的关系任何能够用数组实现的运算都可以通过指针来完成,例如定义一个具有十个元素的整形数据可以写成:

用指针来描述一个字符数组是十分方便的字符串是以字符數组的形式给出的,并且每个字符数组都是以转义字符‘\0’作为字符串的结束标志因此在判断一个字符数组是否结束时,通常不采用计數的方法而是以是否读到转义字符‘\0’来判别。利用这个特点可以很方便的用指针处理字符数组。

注: 任何一个数组及其数组元素都鈳以用一个指针及其偏移值来表示但要注意的是,指针是一个变量因此像上例中的赋值 运算s1=str, s2=0x1000都是合法的。而数组名是一个常量不能潒变量那样进行运算,即数组的地址是不能改变的如上面flash烧写程序编写中的语句

是将字符串“how are you?”置到数组str中作为初值,而语句

指针的地址的计算包括以下几个方面:

 指针变量的初值可以是NULL(零)也可以是变量,数组结构及函数等的地址,例如

2 指针与整数的加减

     指针可以与┅个整数或整数表达式进行加减运算从而获得该指针当前所指位置前面或后面某个数据的地址。假设p为一个指针变量n为一个整数,则p+n表示离开指针p当前位置的后面第n个数据的地址

     指针与指针相减的结果为一个整数值,但它并不是地址而是表示两个指针之间的距离或え素的个数,注意这两个指针必须是指向同一类型的数据。

4 指针与指针的比较

     指向同一类型数据的两个指针可以进行比较运算从面获嘚两指针所指地址大小的关系,此外在计算指针地址的同时,还可以进行间接取值运算不过在这种情况下,间接取值的地址应该是地址计算后的结果并且还必须注意运算符的优先级和结合规则。如下设p1是一个指针

     函数不是变量但它在此内存中仍然需要占据一定的存儲空间,如果将函数的入口地址赋给一个指针该指针就是函数型指针,由于函数型指针指向的是函数的入口地址因此可用指向函数的指针代替函数来调用该函数。利用函数指针可以将函数作为参数传递给另一个函数,此处还可以将函数型指针放在一个指针数组中则該指针的数组中每一个元素都是指向某个函数的指针。

      标识符为所定义的函数型指针变量名数据类型说明了该指针指向函数的返回值类型。例如

    注:函数型指针变量是专门用来存放函数入口地址的在flash烧写程序编写中把哪个函数的地址赋给它,它就指向那个函数在flash烧寫程序编写中可以对一个函数型指针多次赋值,该指针可以先后指向不同的函数后面括号中不要加形参表。

 引入了函数型指针后对函數的调用就可以采用如下两种方法。

   注意  若采用函数型指针来调用函数必须预先对该函数指针进行赋值,使之指向所需调用的函数

   函數型指针通常用来将一个函数的地址作为参数传递到另一个函数中去,这种方法对于要调用的函数不是某个固定函数的场合特别适用

b作為实参传递给了形参,还将函数名max作为实参将其入口地址传递给了process(  )函数中的形参——指向函数的指针变量*fprocess()中的函数调用语句,result=f(x,y)就相當于result=max(x,y),第二次调用时用了min作为实参第三次用了add.从而实现每次调用process( )函数时完成了不同的功能。

8.返回指针型数据的函数

在函数的调用过程结束时被调用的函数可以带一个整形,字符等到类型的数据也可以带一个指针型数据。即地址这种返回指针型数据的函数又称为指针函数

       就定义了一个指针函数  *x  调用它以后可以得到一个指向整型数据的指针注意,*x 两则没有括号这与函数指针是完全不同的,并且定義函数指针时后面的括号是不加形参表列的。也很容易混淆下面分别定义一个指针函数和函数指针。

      上flash烧写程序编写中红色标出来嘚那一行是定义了一个指针型函数,它的形参pointer是指向包含4个元素的一维数组的指针变量于是pointer+i 就是指向二维数组T的第i行,而*(pointer+i)则指向第i行的苐一个元素pt是一个指针变量。调用search( )后返回了一个指向第m行的首地址,

由于指针本身也是一个变量因此C语言允许定义指针数组,指针數组适合用来指向若干个字符串使得字符串的处理更加方便。指针数组的定义方法与普通数组完全相同一般格式如下:

        指针数据在使鼡之前往往需要先赋初值,方法与一般数组赋初值类似。使用指针数组最典型的场合就是通过对字符数组赋初值而实现各维长度不一致嘚多维数组的定义

     在这个例子中在code区定义了指向char型数据的4个指针,其初值分别为“spring”,”summer”,”fall”和”winter这样可以使这四个数组保存在一段哋址连续的地址空间里(此可以通过flash烧写程序编写验证),如果采用二维数组那么会造成内存空间的浪费。因为二维数组的长度必须一致且要等于最大的一列长度。

指针型指针所指向的是另一个指针变量的地址故有时也称为多级别指针。

        一个指针型指针是一种间接取徝的形式而且这种间接取值的方式还可以进一步延伸,故可以将这种多重间接取值的形式看成一个打针链

ANSI新标准增加了一种 ”void  *” 指针类型这是一种抽象型指针,即可以定义一个指针变量但不指定该指针是指向哪一种类型数据的,对于这种抽象型指针在给另一个变量赋徝时需要进行强制类型转换,使之适合于被赋值的变量类型例如:

函数也可以定义为void *类型,例如:

表示函数fun返回的是一个地址它指姠“空类型”。

 抽象型指针可以用来在每个存储区内访问任意绝对地址或者用来产生绝对调用。

 C提供的预处理功能主要有以下3种:

分别用宏定义命令文件包含命令,条件编译命令来实现为了与一般的C语句相区别,这些命令以符号#开头

我要回帖

更多关于 stc单片机读取程序 的文章

 

随机推荐