资源描述
C++中mutable关键字的作用
在C++语言中,是命令语言——如果一个函数被const 修饰,那么它将无法修改其成员变量的,但是如果这个成员变量是被mutable修饰的话,则可以修改。
mutable 可以用来指出,即使结构或者类变量为const,其某个成员也可以被修改。 在c++的类中, 如果一个函数被const 修饰,那么它将无法修改其成员变量的,但是如果这个成员变量是被mutable修饰的话,则可以修改。
例如
struct mydata
{
char name[30];
mutable int accesses;
…………
};
const mydata veep = {"david";,0,};
strcpy(veep.name,"Jimmy");// not allowed
veep.accesses++; // allowed
veep 的const限定符禁止程序修改veep的成员,但access成员的mutable说明符表示access不受这种限制。
ARM编程中关键字的使用
_irq
为了方便使用高级语言编写异常处理函数,ARM编译器对异常处理函数做了特定扩展,只要使用关键字_irq,这样编译出来的函数就满足异常响应对现场保护和恢复的要求。
关于编写中断服务程序的一些基本原则:
ü 避免在中断服务程序中做浮点运算
好的中断服务程序应该遵循短而有效这一原则,但在中断服务程序中做浮点运算却大大地违背这一原则。同时有些处理器/编译器就是不允许在中断服务程序中做浮点运算。
ü 中断服务程序不能有返回值
所以中断服务程序都定义为返回类型为void,如:
void _irq Eint4567isr(void)
ü 中断服务程序不能传递参数
所以中断服务程序的参数表列为void,如:
void _irq Eint4567isr(void)
__REG2, __REG(), __REGP() 宏的解析
在前面看到的几个宏,如 GPCON(x), GPDAT(x)等,最终要涉及到 __REG(), REGP()更底层的宏。这里对这些宏进行解析(属于个人理解,不一定正确,如果有误,高手看到请斧正,不胜感激)。像 GPCON(x) 宏的作用就是从物理地址到虚拟地址的一个转换过程。
以 GPCON(x) 宏的展开,顺藤摸瓜。GPCON(x) 的定义为:
#define GPCON(x) __REG2(0x56000000, (x) * 0x10)
上面,0x56000000 是寄存器 GPACON 的物理地址,在 8 组 GPIO 寄存器中,相对而言,它就是一个基地址。x 表示是寄存器组的偏移,比如 x 无偏移时(x=0),对应的地址就是 0x56000000, x 偏移 1 时 (x=1),对应的地址就是 0x56000010 ,这个地址也就是 GPBCON 寄存器的地址。
__REG2() 的定义是:
# define __REG2(x,y) ( __builtin_constant_p(y) ? (__REG((x) + (y))) : (*(volatile u32 *)((u32)&__REG(x) + (y))) )
上面,__builtin_constant_p() 是 GCC 编译器的一个内建函数(详见:
__REG2(x,y) 有两个参数,第一个参数 x 是一个“相对基地址”,之所以起这个名称,是因为它是在一定范围内(比如GPIO控制寄存器组里)作为一个基地址; y 参数是一个偏移量。用 GPCON(2) 来举例,就是__REG(0x56000000,2 * 0x10) 。
那么 __builtin_constant_p(0x20) ,这个 0x20 并不是一个事先定义好的宏,所以在整个 __REG2(x,y) 展开的表达式中,取后者即:
(*(volatile u32 *)((u32)&__REG(x) + (y)))
从上面可见,__REG2() 宏,已经演变成了对 __REG(x) 的应用。从这个宏的形式可以知道,它实际上就是要取得 0x56000020 这个寄存器里的值罢了,只是这个定义形式上看有点让人眼花。只不过这里,先是将物理地址转化为虚拟地址后,再取值,但本质却都是拿同样的东西。
__REG(x) 的定义为:
#define __REG(x) __REGP(io_p2v(x))
io_p2v(x) 的定义为:
#define io_p2v(x) ((x) | 0xa0000000)
从 io_p2v(x) 的名字也可以望文生义,p2v 即 physics to virtual 。这里,物理地址到虚拟地址的转换只是将物理地址加上一个 0xa0000000 的偏移量,从hardware.h 文件的一个注释中可看到这一点:
*
* S3C2410 internal I/O mappings
*
* We have the following mapping:
* phys virt
* 48000000 e8000000
*/
#define VIO_BASE 0xe8000000 /* virtual start of IO space */
#define PIO_START 0x48000000 /* physical start of IO space */
上面,0xe8000000 - 0x48000000 = 0xa0000000 .
在 io_p2v() 里或上一个 0xa0000000 也就相当于加上一个 0xa0000000 。
为了继续展开 _REGP() 宏,使用一个更普通点的地址,如 0x56000004 (这是GPADAT 寄存器的地址),REGP() 的定义为:
#define __REGP(x) ((__regbase *)((x)&~4095))->offset[((x)&4095)>>2]
__regbase 是一个有着 4096 个整型值的数组。假设这里的 x 是 io_p2v(0x56000004)=0xF6000004 。这样,((x)&~4095) 就为 0xF6000000 ,然后 (__regbase *)((x)&~4095) ,那么这里的意思是,令 0xF6000000 成为一个指向 __regbase 结构体的指针(地址),也就是 offset[4096] 数组的首地址,同时也是强制 4K 空间边界对齐。
在 offset[] 数组中, ((x)&4095)>>2 代入 x=0xF6000004 ,就为 (0xF6000004&4095)>>2 ,结果为 1 ,也就是说取元素 offset[1] 。
综合上面,完整的 __REGP() 宏的最后结果就是: (__regbase *)0xF6000000->offset[1]
再回头看 __REG2(x,y) 宏中的 (u32)&__REG(x) 部分,从中得知,得到的是一个首地址为 0xF6000000 的数组的第 2 个元素 offset[1] 的地址,这个地址的值就是 0xF6000004 .
呵呵,貌似是兜了个小圈子,那为什么这么做呢,根据 hardware.h 里的注释是这样的:
/*
* This __REG() version gives the same results as the one above, except
* that we are fooling gcc some how so it generates far better and smaller
* assembly code for access to contigous registers. It's a shame that gcc
* doesn't guess this by itself
*/
__REG() 宏给 gcc 耍了个小把戏,让 gcc 在访问连续的寄存器时,能够产生更加紧致的代码。至于代码如何紧凑,可以看下反汇编。
上面的宏可以用搬移到一个应用程序里运行看看:
#include <stdio.h>
typedef unsigned int u32;
typedef struct {
volatile u32 offset[4096];
} __regbase;
#define io_p2v(x) ((x) | 0xa0000000)
#define __REGP(x) ((__regbase *)((x)&~4095))->offset[((x)&4095)>>2]
#define __REG(x) __REGP(io_p2v(x))
int main()
{
printf ("%x\n", (u32)&__REG(0x56000004));
return (0);
}
运行与输出:
[beyes@localhost s3c2410]$ ./set_gpio.exe
f6000004
_builtin_constant_p是 GCC 内建函数,用来判断一个值是否为编译时常数,若是则返回 1 ,否则返回 0 。
测试代码:
#include <stdio.h>
#define CONSTANT 1024
int var = 1024;
int main()
{
printf ("%d\n", __builtin_constant_p(CONSTANT));
printf ("%d\n", __builtin_constant_p(var));
return (0);
}
运行输出:
[beyes@localhost builtin]$ ./builtin_constant_p.exe
1
0
程序中,大小端模式的检测
谈到字节序的问题,必然牵涉到两大CPU派系。那就是Motorola的PowerPC系列CPU和Intel的x86系列CPU。PowerPC系列采用big endian方式存储数据,而x86系列则采用little endian方式存储数据。
所有网络协议也都是采用big endian的方式来传输数据的。所以有时我们也会把big endian方式称之为网络字节序。当两台采用不同字节序的主机通信时,在发送数据之前都必须经过字节序的转换成为网络字节序后再进行传输。
能返回字节序的函数
#include <cstdio>
//0 for the CPU has LittleEndian , e.g. for intel x86 CPU
//1 for the CPU has the BigEndian, e.g. for net-byteOrder and Motorola Power-PC CPU and SUN SPARC
int nativeCode()
{
union aaaa{
int b;
char c[2];
} a;
a.b=0x0001;
return (a.c[0]==0x00);
}
int main()
{
printf("nativeCode()=%d\n",nativeCode());
return 0;
}
C中数据类型大小
不同计算机系统对基本类型数据的长度表示也有差异,下面以32位计算机系统为准,各个数据类型所占字节长度的总结:
char 1字节
short 2字节
int 4字节
long 4字节
float 4字节
double 8字节
long double 8字节
C++基本数据类型长度(VC6.0 32位机): 字节为单位
unsgined char 1
[signed] char 1
unsigned short int 2
[signed] short int 2
[signed] int 4
unsigned int 4
[signed] long int 4(在32位系统上是4,在64位系统上是8)
unsigned long int 4(在32位系统上是4,在64位系统上是8)
float 4
double 8
long double 8
指针 4(即为每个指针(地址)分配4个字节)
如果要获取特定系统数据的长度,可以用sizeof运算符,比如:sizeof(int);
【strlen()函数仅测试字符串的实际长度,不包括'\0'。】
测指针长度程序:
char a=11,*p;
p=&a;
printf("%d\n",sizeof(p));
输出为:4;
C8051中的几种关键字
data ---> 可寻址片内ram
bdata ---> 可位寻址的片内ram
idata ---> 可寻址片内ram,允许访问全部内部ram
pdata ---> 分页寻址片外ram (MOVX @R0) (256 BYTE/页)
xdata ---> 可寻址片外ram (64k 地址范围FFFFH)
code ---> 程序存储区 (64k 地址范围),对应MOVC @DPTR
#ifdef __BIG_ENDIAN
<ERROR IF BIG_ENDIAN> //这句是什么意思,看不明白大家帮解释一下
#define rFUNC_ADDR_REG (*(volatile unsigned char *)0x52000143) //Function address
#define rPWR_REG (*(volatile unsigned char *)0x52000147) //Power management
#define rEP_INT_REG (*(volatile unsigned char *)0x5200014b)
//EP Interrupt pending and clear
#define rUSB_INT_REG (*(volatile unsigned char *)0x5200015b) //USB Interrupt pending and clear
#define rEP_INT_EN_REG (*(volatile unsigned char *)0x5200015f) //Interrupt enable
这是为了防止你自己或者编译器或者后面的人把存储器格式定义为大端存储器系统就算把这句话去掉了也没什么关系,不会影响到你目前的程序编译但是如果今后的维护人员什么的,不小心把小端改为大端的话,将会产生不可预料的后果 所以加了这么一个条件编译,只要定义为大端,就报错 !
NFCONF&=~0x800; // bit11置0
NFCONF|=0x800; //bit11置1
C语言中循环函数的使用
① while(表达式) 语句
其特点:先判断表达式,后执行语句。
③for循环
先求解表达式1;在判断表达式2,为"假",则跳出for循环体;为"真",则执行语句,然后求解表达式3;继续判断表达式2的"真假",然后循环下去。
注意:
关于字符输入
循环小结
continue语句
展开阅读全文