本帖最后由 ccczzzwwwyy 于 2023-9-5 20:34 编辑
9月5日,何宾老师:
类型限定符 C251编译器支持两个类型限定符,包括const和volatile const类型限定符声明了一个在运行时未修改的对象 volatile类型限定符声明了一个对象,该对象的值可能会被出现 在其中的代码之外的某个对象修改 在嵌入式系统中,这通常是硬件
类型限定符 —const 在ANSIC中,const类型限定符用于定义和访问常数且不能更改的对象 用const声明的变量不能在程序中赋值 仅用const类型限定符声明的变量保存在与其定义相关联的存储器区域 (data, idata,xdata等)中 constunsigned short xdata a[10]={0,1,2,3,4,5,6,7,8,9}; 需要在ROM中定位的变量必须用code存储器类型声明 constunsigned short code a[10]={0,1,2,3,4,5,6,7,8,9);
类型限定符 ——const 常数对象通常在定义(在源文件中)时初始化 下面的变量定义显示了创建常数对象的不同方法: const inttable[2][2]={0, 2, 4, 8); // table保存在默认的存储器类中 const floatfar pi=3.1415926; // pi保存在HCONST存储器类中 printf("This is a string\n" ); // 字符串保存在HCONST存储器类中
类型限定符 --const 当使用指向const对象的指针时,可以在指针定义中排除const类型限定符,例如: constunsigned char mask[]={0x01, 0x02, 0x04, Ox08); constunsigned char *cp=mask; unsigned char*p=mask; //与cp效果相同 *p=' а'; // 这里没效果,不会导致警告或错误 *ср=' а'; //这会导致错误 从上面可知,可以将常数对象mask的地址分配给非常数的指针p,然后使用该指针更改常数对象。在这种情况下,编译器会生成代码写入常数对象。该代码的效果未定义,可能或无法按预期工作 不能使用const指针来更改它指向的常数对象。如果这样做,将导致编译器错误
类型限定符 —const const的一个有趣的用法是定义一个不可更改的指针 例如: chartext[]=" This is a string "; char *consttextp=text; *textp=' A'; // 这是正常的(它会改变test[0]) textp++; // 这会导致错误(textp为常数) textp[2]='B'; //这是正常的(它会改变test[2])
类型限定符 ——volatile 易失性类型限定符用于限制编译器对对象值的假设 例如: unsigned charreg1; //硬件寄存器#1 unsigned charreg2; // 硬件寄存器#2
void func(void)
{ while (reg1& 0x01) // 当设置位0时,重复
{ reg2 = 0x00; // 轮询位7 reg2 = 0x80;
类型限定符 --volatile 因此,引入volatile类型限定符来解决以上的问题。比如: volatileunsigned char reg1; // 硬件寄存器#1 volatileunsigned char reg2; // 硬件寄存器#2 void func(void) { while (reg1& Ox01) // 当设置位0时,重复 { reg2 = 0x00; // 轮询位7 reg2 = 0x80; } } 通过将reg1和reg2定义为volatile,编译器现在知道对这些变量的访问可能不会被优化。生成的代码按需要执行 类型限定符 -volatile 【例8—3】不同数据类型运算的C语言描述 void main() { volatile charedata a=100, b=-90,c; //edata空间放置双字变量 volatile intedata d=10000, e=-5000,f; //edata空间放置字符变量 edata空间放置字变量 volatile longint edata h=20000000, i=-10000000,j; volatile bitebdata x=1, y=1, z, w; // ebdata空间放置位变量 c=a+b; // 字符变量a和b相加 f=d+e; // 字变量d和e相加 j=h+i; // 双字变量h和i相加 z=x&y; // 位变量x和y逻辑“与” w=x^y; // 位变量x和y逻辑“异或” } 类型限定符 volatile 【例8—4】浮点数运算的实现(一) void main() { volatilefloata=100.0,b=200.0; volatilefloat edata c=a+b; } 类型限定符 ——volatile 【例8—6】数组元素存储和运算的C语言代码 void main() { const charcode table[]={1,2,4,8}; volatile chari=0,sum=0; for(i=0;i<4;i++) sum+=table; } 存储类别 C语言支持许多可以应用于程序变量的存储类 存储类用于定义变量和函数的范围和生存周期,这些存储类关键 字包括: auto register static extern
存储类别 auto auto存储类是本地变量默认的存储类 其格式如下: autodata-type name <[>=value<]> 其中 data—type为变量的数据类型 name是变量的名字 value是分配给变量的值 需要注意,存储类auto只能在函数定义内使用
存储类别 ——register register存储类定义了应该保存在一个或多个寄存器中而不是RAM中的局部变量 其格式如下: registerdata-type name <[>=value<]>; 其中 data—type为变量的数据类型 name为变量的名字 value为分配给变量的值 通常,C251编译器忽略register存储类。如果有可能,所有变量都保存在寄存器中 注:存储类register只能在函数定义内使用
存储类别 ——static static存储类限制变量的范围并修改局部变量的生存期 其格式如下: staticdata-type name <[>=value<]>; 其中 data—type为变量的数据类型 name为变量的名字 value是分配给变量的值 使用static在函数外部声明变量时,无法在声明变量的源文件外部访问该变量 当使用static在函数内部声明变量时,该变量在启动时就被初始化(与其他全局变量一样) ,并且在调用该函数时保留其值 在进入函数时,它不会被重新初始化
存储类别 -extern extern存储类声明在另一个源模块中定义的全局变量 其格式如下: externdata-name name; 其中 data—type为变量的数据类型 name为变量的名字 当使用extern声明变量时,无法初始化该变量,这是因为它已经在定义的位置初始化了
存储类别 【例8—7】存储类用法的C语言描述 在extern.c文件中定义变量 short inta=3000,b=-2000; 在main.c文件中调用外部变量 #include"math.h" extern shortint a,b; void main() { volatile longint c; c=a*b; } 存储类别 该C语言代码对应的反汇编代码 3: voidmain() 4: { 5: volatile long int c; 6: c=a*b; OxFF0042 7E17000A MOV WR2,b(0x000A) OxFF0046 1A02 MOVS WRO,R2 OxFF0048 1A00 MOVS WRO,RO 0xFF004A 7E370008 MOV WR6,a(0x0008) OxFF004E 1A26 MOVS WR4,R6 OxFF0050 1A24 MOVS WR4,R4 0xFF0052 12605A LCALL C?LMUL(C:0x005A) OxFF0055 7A1F000C MOV c(0x000C),DR4
绝对位置变量 变量可以使用_at_关键字定位在绝对存储器的位置 其格式为: type[memory_type]variable_name_at_const_expr; 其中 type为变量的类型 memory_type为变量的存储器类型。如果在声明中排除该项,则使用默认存储器类型 variable_name为变量的名字 const_expr为必须计算为绝对常数值的表达式。表达式的结果是定位变量的地址
绝对位置变量 随在_at_关键字后面的绝对地址必须符合变量存储空间的 物理边界 对于xdata和code存储器类型,该表达式解释为相对于存储器类 型的基地址的偏移量 C251编译器为xdata和code变量生成OFFS重定位类型 对于其他存储器类型,at_表达式表示STC32G系列单片机中的 绝对存储器位置 C251编译器检查地址范围是否有效
绝对位置变量 【例8—9】_at_关键字的C语言用法 Structum // 定义结构体num char a; // 定义结构体中的字符型变量a short int b; //定义结构体中的16位整数变量b long int c; // 定义结构体中的32位整数变量c }; // 结构变量x,位于edata存储类地址0x00200 struct numedata x_at_Ox0200; // 定义字符数组,位于code存储类地址0x0010 char codestr[20] _at_Ox0010 ="hello world\n";
绝对位置变量 void main() // 定义main主函数 { x.a=0xab; // 初始化x.a x.b=0x1234; // 初始化x.b x.c=0x5a5aa5a5; // 初始化x.c } 指针 C251编译器支持使用*字符声明变量指针 C251编译器可用于执行标准C中可用的所有操作
指针 指针声明中的存储器类型 可以在指针声明中使用存储器类型来定义指针的大小 没有显式存储器类型的指针称为通用指针 具有显式存储器类型的指针称为存储器特定指针 特定于存储器的指针为每个变量访问生成更高效的代码
指针 指针声明中的存储器类型 声明指针的规则 变量声明 指针大小(位) 指针声明 char c; 16/32 char *ptr; (指针大小取决于使用的存储器模型) int near nc; 16 int near *np; float datadf; 8 float data *dp;或float near *dp; char idataindex; 16 char idata *ip; 或 char near *ip; long xdata x; 16 long xdata *xp; const charcode c='A'; 16 char code *cp; /* 可以忽略const*/ const charnear n=5; 16 char near *np; /* 可以忽略const*/ unsigned longfar I; 32 long far *Ip; char huge hc; 32 char huge *hp_ptr; void nearfunc1(void); 16 void (near *fp1)(void); int farfunc2(void); 32 int (far *fp2)(void);
指针 指针声明中的存储器类型 指向data或idata对象的指针可以使用near存储器类型声明 因为near指针也可以访问data或idata存储器 在TINY和XTINY存储器模型中,默认指针大小为16位,指针的 默认存储器类型为near * 要访问0x010000以上的存储器区域,必须在指针声明中指定存储器类型 为far*或huge* 使用far或者huge指针,可以寻址near,data或idata对象。 不同之处在于 far或huge指针访问使用@DPk指令,然而near指针使用更高效的 @WRj指令 far或huge对象不能用near指针寻址
指针 指针声明中的存储器类型 下面给出一些使用例子: char near*px; 声明一个指针,该指针引用near存储器类型中char类型的对象。指针本身保存在默认存储器类型中(取决于使用的存储器模型)。大小为两个字节 char near*data pdx; 该声明与先前的声明相对应,只是指针本身被明确地放置到数据存储器(data)中,而与使用中地存储器模型无关 int (* xdata fp) (void); 该声明定义了函数指针,该函数指针被显式放置到xdata存储器类型中,而与使用地存储器模型无关。所寻址的函数的类型为void func (void)
指针 指针声明中的存储器类型 Struct time{ char hour; char min; char sec; struct timenear *pxtime; }; 除了其他元素之外,该结构还包含指向另一个结构的指针pxtime,该结构必须位于near存储器类型中。指针pxtime的大小为两个字节 指针声明中的存储器类型 struct timefar *ptime; 该声明定义了一个指针,该指针保存在默认的存储器类型中,并引用位于far存储器类型中的struct time。指针ptime需要四个字节 ptime->pxtime->hour=12; 使用前面的两个声明。指针pxtime是从结构间接加载的。它指向structtime,位于near存储器类型。将值12分配给该结构的成员hour
指针 指针声明中的存储器类型 struct timefar *ptime; 该声明定义了一个指针,该指针保存在默认的存储器类型中,并引用位于far存储器类型中的struct time。指针ptime需要四个字节 ptime->pxtime->hour=12; 使用前面的两个声明。指针pxtime是从结构间接加载的。它指向structtime,位于near存储器类型。将值12分配给该结构的成员hour
指针 指针转换 C251编译器可以转换指针的存储器类型 指针转换可以由使用类型强制转换的显式程序代码强制执行,也可以由编译器强制执行 通用指针与TINY或XTINY存储器模型的near*指针定义相同。对于所有 其他存储器模型,通用指针与far *指针定义相同
指针 指针转换 当存储器特定指针作为参数传递给需要通用指针的函数时,C251编译器将存储器特定指针转换为通用指针 C251运行时库函数(如printf, sprintf和gets)的情况就是如此,这些函数使用通用指针作为参数 指针 指针转换 extern intprintf (void *format, ...); extern intmyfunc (void near *p, int data *dq); int data *dp; const charnear *fmt = "value = %d | %04XH\n"; voiddebug_print (void) { printf (fmt,*dp); // 转换fmt myfunc (fmt,nx); // 没有转换
指针 指针转换 在对printf的调用中,表示2字节near指针的参数fmt被自动转换成强制为通用指针 这样做是因为printf的原型需要一个通用指针作为第一个参数。根据存储器模型,通用指针的大小为2个字节或4个字节 注:在TINY或XTINY存储器模型中,无法使用标准C251运行时库函数访问0x010000以上的存储器位置。因此,printf的格式字符串必须声明为constchar near,如果将格式字符串声明为char code (如C51程序中所做的那样)则printf仅适用于SMALL, XSMALL或LARGE存储器模型 由于将地址空间看作是线性实体,因此指针转换主要通过扩展或截断指针的高位部分完成 转换指针所涉及的过程 转换类型 功能描述 far *到code far *的低位字(2个字节)用作偏移量,丢弃高阶字 far *到data * far *的低位字用作偏移量 far *到idata* far *到near* far *的低位字(2个字节)用作偏移量 far *到xdata* far *的低位字(2个字节)用作偏移量 far *到pdata* far *的低位字用作偏移量 code *到far * far*的高位字设置code的基地址(默认0xFF)。使用code *的2字节偏移量 idata *到far* 对于idata/data, far *的高位字设置为0x00, Idata */data的1字节偏移量被 data *到far* 转换为无符号整数,并用作低位字 near *到far* 对于near, far*的高位字设置为0x00, near*的2字节偏移用作低位字 xdata *到far* far *的高位字设置为xdata的基地址(默认1) 。xdata *的2字节偏移 用作低位字 pdata *到far* far *的高位字设置为xdata的基地址(默认1)。偏移的高位字节设置pdata的页。pdata*的1字节偏移用作偏移的低位字节
near *到data* 只有near *指针的低位字节用作偏移 near *到idata* data *到near* idata */data *的1字节偏移转换为unsigned int,以及用作near *指针 idata *到near * 的偏移
注:在表中没有列出来的所有指针转换都是非法的,在编译时会产生警告信息
指针转换 转换指针所涉及的过程 转换类型 功能描述 far *到code* far*的低位字(2个字节)用作偏移量,丢弃高阶字 far *到data* far *的低位字用作偏移量 far *到idata* far *到near* far *的低位字(2个字节)用作偏移量 far *到xdata * far *的低位字(2个字节)用作偏移量 far *到pdata * far *的低位字用作偏移量 code *到far* far *的高位字设置code的基地址(默认OxFF)。使用code *的2字节偏移量
设计实例(一) 声明和使用指针的C语言描述 #include"stdio.h" // 包含头文件stdio.h #include"reg251s.h" // 包含头文件reg251s.h void main() // 定义main主函数 { volatile inta,b; //声明整型变量a和b volatile intedata *p2,*p3; //声明指针变量*p2和*p3,在edata区域 SCON= 0x52; // 初始化串口寄存器SCON TMOD = 0x20; // 初始化串口寄存器TMOD TCON = 0x69; // 初始化串口寄存器TCON TH1 = 0xF3; // 初始化串口寄存器TH1
scanf(“%d,%d”,&a,&b); //调用scanf函数,输入变量a和b的值 p2=&a; //通过取地址符号'&',使得指针p2指向变量a printf(“(*p2)=%d\n”,*p2); //调用printf函数,显示*p2的内容 printf(“&p2=%p\n”,&p2): //调用printf函数,显示指针对象*p2的地址 printf(“&a=%p\n”,&a); //调用printf函数,显示变量a的地址 p3=&b; // 通过取地址符号’&',使得指针p3指向变量b printf(“(*p3)=%d\n”,*p3); // 调用printf函数,显示*p3的内容 printf(“&p3=%p\n”,&p3); //调用printf函数,显示指针对象*p3的地址 printf(“&b=%p\n”,&b); //调用printf函数,显示变量b的地址 while(1); // 无限循环,用于设置断点的位置 } |