题目总结

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;

posted @ 2023-12-21 11:11  踏浪而来的人  阅读(4)  评论(0编辑  收藏  举报