1. 关键字 static
的作用是什么?
- 在函数内:
static
修饰的变量为静态局部变量,生命周期从程序开始到结束,且在多次函数调用间保持值不变。 - 在文件内:
static
修饰的全局变量或函数具有文件内链接,限制其作用域仅限于定义它的源文件,不能被其他文件访问。 - 在类中:
static
修饰的成员变量或成员函数属于类而非对象,可以通过类名直接访问。
2. 关键字 const
是什么意思?
const
表示常量修饰符,用于声明变量值不能被修改。它可以修饰变量、指针、函数参数以及函数返回值。- 修饰变量:变量值不可变。
- 修饰指针:可以是指针指向的值不可变,或指针自身不可变,或两者都不可变。
- 修饰函数参数:函数内部不能修改传入的参数值。
- 修饰函数返回值:防止返回值被修改。
3. const
和宏定义的区别
- 类型检查:
const
有类型检查,编译时检测类型错误,而宏定义没有类型,替换时直接进行文本替换。 - 作用域:
const
有作用域,遵循变量的作用域规则,宏定义是全局有效的。 - 调试:
const
变量在调试时可以看到值,宏替换后看不到值,只能看到替换后的结果。
4. 关键字 volatile
有什么含义并给出三个不同的例子
-
volatile
告诉编译器该变量可能被系统、硬件或者其他线程修改,不要对其进行优化,即每次使用该变量时都从内存中读取值。例子:
- 硬件寄存器:
volatile int *statusReg = (int *)0x1234;
,硬件设备的状态寄存器,可能被外部硬件更改。 - 多线程变量:
volatile bool flag = false;
,多线程访问的标志位,可能被其他线程修改。 - 中断服务程序中的变量:在中断服务程序中更新的全局变量,需要使用
volatile
修饰,以防止主程序的优化。
- 硬件寄存器:
5. 引用和指针有什么区别
- 引用:必须初始化,不能为NULL,创建后不可更改指向,类似别名。
- 指针:可以为NULL,可以改变指向,可以指向不同的内存地址。
6. .h
头文件中的 #ifndef/#define/#endif
的作用
- 防止头文件重复包含(头文件保护符),确保头文件内容只被包含一次。
7. 描述实时系统的基本特性
- 确定性:操作在规定时间内完成。
- 可靠性:系统在任何时候都能正常工作。
- 响应性:能够及时响应外部事件。
8. 全局变量和局部变量的区别
- 作用域:全局变量在整个程序中有效,局部变量只在声明它的块内有效。
- 生命周期:全局变量在程序运行期间一直存在,局部变量在其作用域结束时销毁。
9. 全局变量和静态全局变量的区别
- 作用域:静态全局变量的作用域仅限于定义它的文件,普通全局变量可被其他文件访问(通过
extern
声明)。 - 生命周期:二者在程序生命周期内一直存在。
10. static
函数与普通函数
- 作用域:
static
函数的作用域限制在定义它的源文件内,普通函数可以在整个程序中使用。
11. 什么是平衡二叉树?
- 平衡二叉树(AVL树):任意节点的左右子树高度差不超过1的二叉搜索树。
12. 堆栈溢出一般是由什么原因导致的?
- 递归调用过深:函数调用自身过多次导致栈空间耗尽。
- 大数组分配在栈上:数组过大导致栈空间不足。
- 无限递归:没有正确终止条件的递归调用。
13. 什么函数不能声明为虚函数?
- 构造函数:不能声明为虚函数,因为在对象创建时,虚函数表还未构建完成。
14. 不能做 switch()
的参数类型
switch
语句的参数类型必须是整型(包括char
、int
、enum
)。不支持浮点数、字符串或指针类型。
15. 程序的内存分配
- 栈:用于函数调用时的参数传递和局部变量的存储,自动管理。
- 堆:动态内存分配,需手动管理(如
malloc
、free
)。 - 数据段:存放全局变量和静态变量。
- 代码段:存放程序的可执行代码。
16. 堆与栈的区别
- 管理方式:堆是动态分配的,需要程序员手动管理;栈由编译器自动分配和释放。
- 空间大小:堆的大小由系统决定且通常较大,栈的大小受限。
- 碎片问题:堆存在内存碎片问题,栈不存在。
17. 描述内存分配方式以及它们的区别
- 静态内存分配:在编译时分配,大小固定,例子:全局变量、静态变量。
- 动态内存分配:在运行时分配,大小可变,例子:
malloc
、new
。
18. malloc
和 new
的区别是什么?
- 初始化:
malloc
只分配内存,不调用构造函数;new
除分配内存外,还调用构造函数初始化对象。 - 返回类型:
malloc
返回void*
,需强制类型转换;new
返回具体类型的指针。
19. 进程与线程的区别
- 内存空间:进程拥有独立的内存空间,线程共享进程的内存空间。
- 通信:进程间通信(IPC)复杂,线程间通信相对简单。
- 资源消耗:创建和切换进程的开销比线程大。
20. 多进程和多线程的区别
- 多进程:程序的多个独立执行单元,独立的内存空间,适合CPU密集型任务。
- 多线程:进程内部的多个执行单元,共享内存空间,适合IO密集型任务。
21. 什么是预编译,何时需要预编译
- 预编译:编译前对代码进行处理,如宏替换、头文件展开等,通常用于加快编译速度或处理跨平台代码。
22. 三种基本的数据模型
- ILP32:
int
、long
和pointer
都是 32 位。 - LP64:
long
和pointer
是 64 位,int
是 32 位。 - LLP64:
long long
和pointer
是 64 位,int
是 32 位。
23. 简述数组与指针的区别?
- 数组:存储一组连续的相同类型的元素,大小固定。
- 指针:存储内存地址,可以指向数组的元素或其他变量。
24. 位操作
- 常见操作:与(&)、或(|)、异或(^)、取反(~)、左移(<<)、右移(>>)。
- 用途:用于硬件操作、权限管理、状态标志等。
25. 访问固定的内存位置(Accessing fixed memory locations)
- 通过指针访问固定的内存地址,比如硬件寄存器:
int *reg = (int *)0x12345678; *reg = 0xFF;
26. 中断与异常的区别
- 中断:由外部事件触发,通常由硬件发起。
- 异常:由CPU执行指令时内部事件触发,如除零错误。
27. 变量的定义总结
- 自动变量:局部变量,生命周期在作用域内,存储在栈中。
- 静态变量:生命周期为整个程序运行期间,存储在数据段。
- 寄存器变量:尽
量存储在CPU寄存器中,生命周期在作用域内。
- 全局变量:生命周期为整个程序运行期间,作用域为整个程序。
28. 为什么要使用宏,宏有什么优缺点?
- 优点:提高代码可读性和复用性,条件编译,代码简洁。
- 缺点:无类型检查,调试困难,滥用可能导致代码难以维护。
29. 内联函数及与宏的区别
- 内联函数:通过
inline
关键字修饰,函数调用时展开,支持类型检查,调试友好。 - 宏:直接文本替换,无类型检查,容易出错。
30. MCU 启动过程
- 典型过程:
- 电源上电或复位信号触发。
- 程序计数器跳转到复位向量。
- 初始化堆栈指针。
- 执行初始化代码(如数据段初始化)。
- 跳转到
main
函数。
31. ARM 体系结构
- ARM 架构:精简指令集计算(RISC)架构,具有高效能耗比。
- 特点:Thumb 指令集、低功耗、广泛用于嵌入式设备。
32. 什么是嵌入式?
- 嵌入式系统是一个专用计算机系统,通常集成于控制设备中,具有特定的功能。
33. 进程与线程中的通信方式
- 进程通信:管道、消息队列、共享内存、信号量。
- 线程通信:共享内存、条件变量、信号量、互斥锁。
34. 如何将 PC 上的程序移植到嵌入式系统上,需要注意些什么?
- 硬件差异:不同处理器架构、存储限制。
- 实时性:嵌入式系统对实时性有更高要求。
- 资源管理:嵌入式系统资源有限,需要优化内存和功耗。
35. 设计一种通信方式,从一台主机向另外一台主机传递数据,那么应该怎么选择。
- 选择合适的通信协议和方式:如以太网、UART、SPI、I2C,基于通信距离、带宽需求、实时性等因素。
36. FreeRTOS 之全配置项详解、裁剪(FreeRTOSConfig.h)
- 配置项:如任务优先级、内存管理、调度策略、钩子函数等,裁剪是根据需求去除不必要的功能以减少资源占用。
37. DMA 为什么能提高效率?
- DMA(直接内存访问)能够在不占用CPU的情况下,直接在内存与外设之间传输数据,提高了系统的整体效率。
38. 优先级反转以及解决方法
- 优先级反转:低优先级任务持有资源时,高优先级任务被阻塞,中优先级任务占用CPU,导致高优先级任务无法执行。
- 解决方法:使用优先级继承机制,提升持有资源的低优先级任务的优先级。
39. 信号量及信号量与自旋锁的区别
- 信号量:用于线程间同步或资源计数,支持线程休眠等待。
- 自旋锁:用于短时间锁定资源,忙等待,不释放CPU。
40. strcpy
和 strncpy
的缺陷
strcpy
:不检查目标缓冲区大小,可能导致缓冲区溢出。strncpy
:如果源字符串长度超过目标缓冲区,目标字符串可能不以\0
结尾,需要手动添加\0
。