题目总结
1. const int a; 表示a是只读的
int const a; //常整型数
const int *a; //a是一个指向常整型数的指针 表示这个指针变量可以修改,但是不能通过这个指针变量修改其所指向地址的值
int * const a; // a是一个指向整型数的常指针 表示这个指针变量不可以修改,但是可以通过这个指针变量修改其所指向地址的值
int const * a const;
注:const离谁近,且是左结合,就修饰谁。如const int *a,就是离int近,int const *a,左结合,就是个常整型的指针;int * const a,就是离*近,就是个常指针
作用是:
为给读你代码的人传达非常有用的信息,实际上,声明一个参数为常量是为了告诉了用户这个参数的应用目的;
合理地使用关键字const可以使编译器很自然地保护那些不希望被改变的参数,防止其被无意的代码修改。
2.用预处理指令#define 声明一个常数,用以表明1年中有多少秒(忽略闰年问题)
#define SECONDS_PER_YEAR (60 * 60 * 24 * 365)UL
表达式会使一个16位机的整型数溢出-因此要用到长整型符号L,告诉编译器这个常数是的长整型数。且这个数是正整数,所以用U表示无符号
3.宏的副作用
#define MAX(a,b) ((a) > (b) ? (a) : (b))
int a = 3,b = 2;
int c = MAX(a++,b); a = 5; c = 4
所以一般使用宏最好不要传入自增自减 。如果你一定要在宏里消除这个副作用
#define MAX(a,b,type) ({type __x = (a), __y = (b);(__x > __y) ? __x : __y;})
MAX(1,2,int); MAX(1.1,1.2,double);
4. 给定一个整型变量a,写两段代码,第一个设置a的bit 3,第二个清除a 的bit 3。
#define BIT3 (0x1 << 3)
static int a;
void set_bit3(void)
a |= BIT3;
void clear_bit3(void)
a &= ~BIT3;
5. 下面的代码输出是什么,为什么?
void foo(void) {
unsigned int a = 6; int b = -20; (a+b > 6) ? puts("> 6") : puts("<= 6"); }
整数自动转换原则,当表达式(a=b)中存在 "有符号类型" 和 "无符号类型" 时所有的操作数都自动转换为 "无符号类型" ,输出是 ">6"
6. 动态内存分配
NULL = (void *)0;
if ((ptr = (char *)malloc(0)) == NULL)
puts("Got a null pointer");
else
puts("Got a valid pointer");
malloc是在堆上分配的,即使是malloc零个内存空间,ptr也不是指向(void *)0地址,它指向堆地址
7. 一个单向链表,不知道头节点,一个指针指向其中的一个节点,问如何删除这个指针指 向的节点?
void DeleteRandomNode(node* pCurrent){
Assert(pCurrent != NULL);
node* pNext = pCurrent -> next;
if(pNext != NULL){
pCurrent -> Next = pNext -> Next; //删除当前的下一个节点
pCurrent -> Data = pNext -> Data; // 将下一个节点的值赋值给当前节点
delete pNext;
}
}
8. volatile
变量的访问:读变量的本质是将该变量在内存中的值读到cpu的寄存器中;写变量的本质是将cpu寄存器中的值写到内存中
cpu多次读写内存会影响程序的效率,所以就要减少对内存的读写次数。编译器可以通过优化来减少对内存的读写。
volatile int a = 1,b,c;// 为abc申请内存空间,并初始化a
b = a; //这句话翻译成汇编码会执行两条指令:1.从内存中将a的值读到cpu的寄存器中
2.再将cpu寄存器中的值赋值给b ,即cpu起到一个搬运的作用
c = a; //同样 内存(&a)-->寄存器 ,这一步编译器就能将其优化掉,可以用上一次读到的a值,当然也可以明确不让编译器优化,就是用volatile修饰a,即每次都去内存里读值
//寄存器--->内存(&c)
有哪些情况必须要给变量加上volatile修饰?
a. 多线程程序中:当多个线程都要访问同一个变量时,要给该变量加上volatile修饰。如上面这个例子,当线程运行到b = a;这句语句时,另一线程将a改为3,如果a没有加volatile修饰的话,编译器将会优化掉从内存中读值,导致c = 1,但如果加上volatile的话,就是告诉编译器不做优化,必须从内存中读值
b. 中断:中断服务程序中修改了其他程序使用的变量,那么这个变量就得加上volatile。如上面的例子,当程序运行到b = a;语句时产生一个中断,cpu就回去执行中断程序,并且在中断服务程序中将a改为3,如果a没有加volatile修饰的话,编译器将会优化掉从内存中读值,导致c = 1,但如果加上volatile的话,就是告诉编译器不做优化,必须从内存中读值
c. 硬件寄存器:硬件寄存器的值可能会随着硬件的工作状态的变化而发生改变,所以在读取这个寄存器(就相当于内存)中的值时,每次都必须将这个寄存器的值读到cpu的寄存器中
9. ARM中断处理
当程序被加载到内存,cpu顺序执行程序时,由于某些原因(异常源)导致当前执行的程序被打断,转而去执行异常处理程序,当执行完异常处理程序后又返回刚刚被打断的地方继续执行
问题?1.cpu是怎么跳转到中断处理程序的 2. cpu怎样检测到中断
ARM的异常源有七类:
1.FIQ(快速中断请求)2.IRQ(外部中断请求)3.reset 4.software interrupt(软中断swi,系统调用可以产生)5.data abort 6.prefetch abort 7.undefined instruction
问题2:
cpu不用去检测中断,ARM芯片中一个模块叫中断控制器,它会去检测中断,然后将检测到的中断告诉cpu,然后cpu再去读中断控制器中的寄存器,看是什么类型的中断
ARM处理器有8中工作模式,其中五种是属于异常模式
问题1:
异常返回:
将SPSR寄存器中的值恢复到CPSR,即恢复之前的状态;
将LR寄存器的值恢复到PC(该寄存器里存什么地址,程序就会跳到这个地址),使程序转回到被打断的地址继续执行
10. spi
为什么没有延时?因为电平间跳变的周期是纳秒级别的(而iic时微妙级别的,所以spi更快),不需要用延时来生成时钟信号
void spi_write_byte(unsigned char dat){//从机在上升沿开始采样,所以在上升沿前就要准备好数据(即将数据放到输出数据线上)
unsigned char i; //即主机的时钟信号给一个上升沿(即从机收到一个上升沿信号),从机就会读走在上升沿前一刻的数据(即读取MOSI上的数据)
for(i = 0; i < 8; i++){
SCK = 0;//主机生成下降沿,主机就往MOSI上放数据
if((dat & 0x80) == 0x80)//在上升沿前将数据准备好
MOSI = 1;//将数据放到MOSI线上,当有上升沿时,从机就会将这个值读走
else
MOSI = 0;
dat <<= 1;
SCK = 1;//形成上升沿
}
}
unsigned char spi_write_byte(){//主机在上升沿后采样,
unsigned char i,dat; //主机产生一个上升沿时钟信号,主机就能将上升沿之后的那个时刻的数据从MISO上读到
for(i = 0; i < 8; i++){
SCK = 0;//主机生成一个下降沿,告诉从机往MISO上放数据
SCK = 1;//上升沿,在上图中的9就可以读取数据线上的数据。即主机给从机一个上升沿,主机就会从MISO上读一次值
dat <<= 1;
if(MISO == 1)
dat |= 0x01;
else
dat &= ~0x01;
}
return dat;
}
11. 看门狗
看门狗主要是解决MCU程序跑飞的问题。即程序在规定的时间内没有去“喂狗”(超过了定时时间而没有重新清零定时器),就认为MCU处于异常状态,看门狗就会强迫MCU复位,使系统重新从开头执行程序。
一般都是通过一个看门狗寄存器来配置看门狗。
第三位是配置定时时间,即如果没在这段定时时间内去喂狗(将定时值清0),MCU会复位。
所以看门狗一般还要和另一个定时器搭配使用,即启用开门狗后,会在启动一个定时器。假如看门狗的定时时间是1.5秒,另外启动一个定时器在1.5秒以内将看门狗定时器清0(将寄存器的BIT5位置1,看门狗会重新计数)
12. *p++、*(p++)、*++p、*(++p)、(*p)++、++*p区别和用法
"++"与"*"运算的优先级相同,运算顺序为从右向左.
*p++:运算顺序为从右向左,即为先++,在去*;即为*(p++)
p++为先取值,在自增,即为先取*p,然后进行p++的自增操作。如a = *p++等价于a = *p;p++;
*(p++)同*p++等价;
*++p:运算顺序为从右向左,即为先++,在去*;即为*(++p)
++p为先自增,再取值,即p先自增,指向下一个存储单元,再取值,即取到的值为下一个储存单元的值。如a = *++p等价于++p;a = *p;
*(++p)同*++p等价;
(*p)++:有括号,即先取值,再将取到的值自增。如int arrq[2] = {1,3};int *p = arrq;int c = (*p)++;c就等于arrq[0]的值为1,然后arrq[0]再自增,即arrq[0]为2,这时q还是指向数组的首地址,即*p=2;