嵌入式软件面经杂谈(一)

参考文章

C/C++编程方面

关键字const含义

被const修饰的变量意味着是“只读”变量

const int a1;
int const a2;  
//这两个的作用是一样,a1,a2是一个常整型数。

const int *a3; 
//a3是一个指向常整型数的指针(也就是指针指向的内容不能改,但可以修改指向其它内容)

int * const a4; 
//a4是一个指向整型数的常指针(也就是指针指向的内容可以修改,但不能修改指针指向其它变量)

int const * a5 const; 
//a5是一个指向常数的常指针(说明内容和指针本身都不能修改)

/*
可以这样来记忆:
const 离变量名近就是用来修饰指针变量的,离变量名远就是用来修饰指针指向的数据;
如果近的和远的都有,那么就同时修饰指针变量以及它指向的数据。
*/

(1)const可以将一个对象变成一个常量,不可被修改,所以定义的时候必须进行初始化
(2)可以修饰函数的参数、返回值、甚至函数的定义体。被const修改时的东西受到强制保护,可以预防意外的变动,提高程序的健壮性。

const变量与宏定义区别

编译器处理不同

宏定义是一个“编译时”概念,在预处理阶段展开(在编译时把所有用到宏定义值的地方用宏定义常量替换),不能对宏定义进行调试,生命周期结束于编译时期;
const常量是一个“运行时”概念,在程序运行使用,类似于一个只读行数据

存储方式不同

宏定义是直接替换,不会分配内存,存储与程序的代码段中;
const常量需要进行内存分配

类型和安全检查不同

宏定义是字符替换,没有数据类型的区别,同时这种替换没有类型安全检查,可能产生边际效应等错误;
const常量是常量的声明,有类型区别,需要在编译阶段进行类型检查

关键字static作用

全局静态变量

  • 在全局变量前加上关键字static,全局变量就定义成一个全局静态变量.
  • 内存中的位置:【静态存储区】,在整个程序运行期间一直存在。
  • 初始化:未经初始化的全局静态变量会被自动初始化为0(自动对象的值是任意的,除非他被显式初始化);
  • 作用域:全局静态变量在声明他的文件之外是不可见的,准确地说是从定义之处开始,到文件结尾。

局部静态变量

  • 在局部变量之前加上关键字static,局部变量就成为一个局部静态变量。

  • 内存中的位置:【静态存储区】

  • 初始化:未经初始化的全局静态变量会被自动初始化为0(自动对象的值是任意的,除非他被显式初始化);

  • 作用域:作用域仍为局部作用域,当定义它的函数或者语句块结束的时候,作用域结束。但是当局部静态变量离开作用域后,并没有销毁,而是仍然驻留在内存当中,只不过我们不能再对它进行访问,直到该函数再次被调用,并且值不变;

静态函数

  • 在函数返回类型前加static,函数就定义为静态函数。函数的定义和声明在默认情况下都是extern的,但静态函数只是在声明他的文件当中可见,不能被其他文件所用。
  • 函数的实现使用static修饰,那么这个函数只可在本cpp内使用,不会同其他cpp中的同名函数引起冲突;
  • warning:不要再头文件中声明static的全局函数,不要在cpp内声明非static的全局函数,如果你要在多个cpp中复用该函数,就把它的声明提到头文件里去,否则cpp内部声明需加上static修饰;

类的静态成员

在类中,静态成员可以实现多个对象之间的数据共享,并且使用静态数据成员还不会破坏隐藏的原则,即保证了安全性。

因此,静态成员是【类】的所有对象中共享的成员,而不是某个对象的成员。对多个对象来说,静态数据成员只存储一处,供所有对象共用。

类的静态函数

静态成员函数和静态数据成员一样,它们都【属于类】的静态成员,而不属于某个对象。因此,对静态成员的引用不需要用对象名。
在静态成员函数的实现中不能直接引用类中说明的非静态成员,可以引用类中说明的静态成员(这点非常重要)。如果静态成员函数中要引用非静态成员时,可通过对象来引用。从中可看出,调用静态成员函数使用如下格式:

<类名>::<静态成员函数名>(<参数表>);

C++内存模型

C++内存主要分为5个区:

  1. 堆heap

    编译器不用去管,可以使用new和malloc来申请内存,但必须在不使用的时候使用delete和free释放掉。

  2. 栈stack

    栈中存放着局部变量,函数参数,局部常量。(由编译器自动分配释放)

  3. 静态存储区(bss段)

    存放全局变量、(全局、局部)静态变量、虚函数

  4. 常量存储区(data段)

    存放常量:全局常量,函数指针,常量数组

  5. 代码区(text段)

    存放代码内容

引用和指针有什么区别

  1. 引用必须被初始化,指针不必。
  2. 引用初始化以后不能被改变,指针可以改变所指的对象。
  3. 不存在指向空值的引用,但是存在指向空值的指针。

头文件中ifndef/define/endif作用

防止该头文件被重复引用。

什么是预编译,何时需要预编译

预编译又称为预处理,是做些代码文本的替换工作。主要处理#开始的预编译指令,预编译指令指示了在程序正式编译前就由编译器进行的操作,可以放在程序中的任何位置。

使用宏的优缺点

优点

因为函数的调用必须要将程序执行的顺序转移到函数所存放在内存中的某个地址,将函数的内容执行完后再返回到转去执行该函数前的地方。这种转移操作要求在转去执行前要保存现场并记忆执行的地址,转回后要恢复现场,并按原来保存地址继续执行。因此函数调用要有一定的时间和空间方面的开销,于是将影响其效率。

而宏只是在预处理的地方将代码简单替换,不需要跳入跳出函数,也就不需要额外的空间和时间方面的开销,所以调用一个宏比调用一个函数更有效率。

缺点

  • 宏常量:在预处理阶段进行替换,不会进行类型检测,安全性低(如果写错因为在预处理阶段不会在文件定义宏处报错,而会在使用处报错,因此建议使用const代替)
  • 宏函数:
    1. 在预处理阶段被替换,不会进行类型检测,代码安全性低
    2. 每个使用部分都会展开---->造成代码膨胀
    3. 容易出错,每个部分需要加括号

内联函数与宏区别

宏是由预处理器对宏进行替换的,而内联函数是通过编译器控制实现的。

而且内联函数是真正的函数,只是在需要用到的时候内联函数像宏一样的展开,所以取消了函数的参数入栈,减少了调用的开销。所以可以像调用函数一样来调用内联函数,而不必担心会产生像宏出现的问题

描述实时系统的基本特性

在特定时间内完成特定的任务,实时性与可靠性。

全局变量和局部变量的区别

  1. 存储位置:全局变量储存在静态存储区,局部变量在堆栈中。
  2. 作用域:全局变量的生命周期是整个函数区间,局部变量的生命周期是声明该变量的函数区间。

全局变量和静态全局变量的区别

  1. 全局变量和静态全局变量都存储在静态数据区
  2. 全局变量的作用域是整个代码文件,静态全局变量的作用域是声明该变量的模块
  3. static全局变量只初使化一次

堆栈溢出一般原因

  1. 没有回收垃圾资源
  2. 层次太深的递归调用

描述内存分配方式以及它们的区别

  1. 从静态存储区域分配。内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。例如全局变量,static 变量。
  2. 在栈上创建。在执行函数时,系统为函数参数、局部变量在【栈】等分配存储单元,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集。
  3. 从堆上分配,亦称动态内存分配。程序在运行的时候用malloc或new申请任意多少的内存,程序员自己负责在何时用free 或delete 释放内存。动态内存的生存期由程序员决定,使用非常灵活,但问题也最多

malloc和new的区别

new/delete是C++关键字,需要编译器支持。

malloc/free是库函数,需要头文件支持。

进程与线程的区别

  • 进程:一个在内存中运行的应用程序。每个进程都有自己独立的一块内存空间,一个进程可以有多个线程,比如在Windows系统中,一个运行的xx.exe就是一个进程。进程是拥有资源的基本单位,线程是调度和分配的基本单位线程和进程都可以并发执行。

  • 线程:线程是进程中的一个执行任务(控制单元),负责当前进程中程序的执行。一个进程至少有一个线程,一个进程可以运行多个线程,多个线程可共享数据。

    与进程不同的是同类的多个线程共享进程的堆和方法区资源,但每个线程有自己的程序计数器、虚拟机栈和本地方法栈,所以系统在产生一个线程,或是在各个线程之间作切换工作时,负担要比进程小得多,也正因为如此,线程也被称为轻量级进程。

多进程和多线程的区别

多进程:操作系统中同时运行的多个程序

多线程:在同一个进程中同时运行的多个任务

多线程并不能提高运行速度,但可以提高运行效率,让CPU的使用率更高。但是如果多线程有安全问题或出现频繁的上下文切换时,运算速度可能反而更低。

三种基本的数据模型

按照数据结构类型的不同,将数据模型划分为层次模型网状模型关系模型

数组与指针的区别

数组要么在静态存储区被创建(如全局数组),要么在栈上被创建。

指针可以随时指向任意类型的内存块。

用运算符sizeof可以计算出数组的容量(字节数)。但不能得到指针所指向内存块的大小。

sizeof(指针)得到的是一个指针变量的字节数,而不是所指的内存容量。

C/C++没有办法知道指针所指的内存容量,除非在申请内存时记住它

注意当数组作为函数的参数进行传递时,该数组自动退化为同类型的指针

指针数组与数组指针

指针数组:本质上是一个数组,其中每个元素都是一个指针

数组指针:一个指向数组的指针

int *a[10];  //指针数组,a是一个数组,a[0]是一个指针
int (*a)[10]  // 数组指针,a指向一个数组

位操作

对第3位进行操作:

#define BIT3 (0x1 << 3)
static int a;
void set_bit3(void) {
	a |= BIT3;
}
void clear_bit3(void) {
	a &= ~BIT3;
}

访问固定内存位置

为了访问一绝对地址,把一个整型数强制转换为一指针是合法的

某工程中,要求将绝对地址为0x67a9的整型变量的值设置为0xaa66

int *ptr;
ptr = (int *)0x67a9;//先让指针指向该地址
*ptr = 0xaa66;      //再把指针解引用

//一个较晦涩的方法是:
*(int * const)(0x67a9) = 0xaa55;

sizeof与strlen有以下区别

  1. sizeof是一个操作符,而strlen是库函数。
  2. sizeof的参数可以是数据的类型,也可以是变量,而strlen只能以结尾为’\0’的字符串作参数。
  3. 编译器在编译时就计算出了sizeof的结果,而strlen必须在运行时才能计算出来。
  4. sizeof计算数据类型占内存的大小,strlen计算字符串实际长度。

编译时与运行时

编译时:编译器将代码翻译为机器(Java语言是jvm)能识别的代码的过程

编译时做的就是一些简单的翻译工作,比如检查老兄你有没有粗心写错啥关键字了啊,有啥词法分析,语法分析之类的过程;就像个老师检查学生的作文中有没有错别字和病句一样,如果发现啥错误编译器就告诉你。

运行时

所谓运行时就是代码跑起来了,被装载到内存中去了(你的代码保存在磁盘上没装入内存之前是个死家伙,只有跑到内存中才变成活的)。而运行时类型检查就与前面讲的编译时类型检查(或者静态类型检查)不一样。不是简单的扫描代码,而是在内存中做些操作,做些判断。

比如你做了一个"除以0"的操作,这个在编译过程检查不出来,但在运行过程就出错了,这时产生的就是运行时错误,类似的还有数组越界错误等

哈希函数与哈希冲突

哈希函数:也叫散列函数,一个把查找表中的关键字映射成该关键字对应地址的函数,记为 addr=Hash(key)

哈希冲突:哈希函数可能会把不同的关键字映射到同一地址上,这会造成哈希冲突,即Hash(key1)=Hash(key2)就会造成哈希冲突

哈希冲突解决办法:

  • 可以把产生冲突的关键字存储在一个线性【链表】中

  • 除留余数法(假设散列表长度设置为m)

    选择哈希函数为:Hash(k)=k%p (p<=m且为最接近m的质数)

  • 数字分析法

  • 平方取中法:取关键字平方值的中间几位作为哈希地址。

硬件方面

中断与异常的区别

中断是指 CPU 对系统发生某事件时的这样一种响应:

CPU 暂停正在执行的程序,在保留现场后自动地转去执行该事件的中断处理程序;执行完后,再返回到原程序的断点处继续执行。

还可进一步把中断分为外部中断和内部中断:

  • 外部中断:由于外部设备事件所引起的中断,如通常的按键中断、磁盘中断、打印机中断等;
  • 内部中断:由于 CPU 内部事件所引起的中断,如程序出错(非法指令、地址越界、数据溢出)。

【异常】是由于执行了现行指令所引起的。由于系统调用引起的中断属于异常。

中断则是由于系统中某事件引起的,该事件与现行指令无关。

BootLoader

什么是bootloader?

  • 定义一:初始化开发板上主要硬件(时钟,内存,硬盘),把操作系统从硬盘拷贝到内存,然后让CPU跳到内存中执行操作系统。

  • 定义二:芯片上电后运行的一段裸机程序,功能是:初始化DDR(内存控制寄存器)等外设,然后将linux内核从flash(SD卡、NAND、EMMC)拷贝到DDR中,最后启动linux内核。

  • BootLoader和linux系统的关系就如同一BIOS和Windows的关系

MCU启动过程

一个工程代码总是以main函数作为入口,芯片上电时完成软硬件初始化工作,为执行main函数作准备
MCU复位时,完成以下工作:(见下图)

  1. 从中断向量表的第一项(0x00000000)中取出MSP值(main stack pointer);
  2. 从复位向量(0x00000004)中取出Reset_Handler函数地址;
  3. 跳转到Reset_Handler位置并执行;在Reset_Handler函数中,MCU执行软硬件初始化,包括Flash和RAM位置、时钟和PLL(锁相环)、静态和全局变量等。

在这里插入图片描述

DMA为什么能提高效率

DMA(Direct Memory Access,直接存储器访问)。传输方式无需CPU直接控制传输,也没有中断处理方式那样保留现场和回复现场的过程,通过硬件为RAM与I/O设备开辟一条【直接传送】数据的通路,能使得CPU效率大大提高。

posted @ 2022-08-26 23:07  aJream  阅读(67)  评论(0编辑  收藏  举报