根据面经准备面试第三期-2026经纬恒润嵌入式软件工程师
#秋招笔面试记录
好像问了很多挺多项目细节,不是一次问的,有的看了别的面筋
1.左值引用与右值引用区别
2.c++中extern c作用
3.const和define区别
4.bss段和data段区别
5.死锁产生的原因? 如果解决死锁的问题
6.spi的时序,spi通信的频率,怎么配置
7.你的项目rtos任务有几个,如何配置优先级的,什么优先级最高,为什么,rtos移植过程
8指针使用的注意事项有哪些。
9共同体和结构体的区别。
10结构体的长度怎么计算。
11结构体对齐是什么,怎么启用结构体对齐,默认是否是对齐的。,c++的内容,
12指针和引用的区别。
13智能指针的用法
14.函数重载和函数重写的区别分别说说
15.函数指针和指针函数,分别说一下是什么,那个用的多。
16Linux中的shell命令有哪些
17.介绍一下freertos
18.优先队列
19.哈希表
1.左值引用与右值引用区别
答:左值引用(T&)绑定到左值,有持久身份的对象,左值是一个指向特定内存位置的表达式
右值引用(T&&)绑定到右值,临时对象或将要销毁的对象,无法获取地址,
左值引用的主要目的:需要将一个对象作为参数传递给子函数时,往往使用左值引用,因为这样可以免去创建临时对象的操作
#为一个已存在对象起一个别名
#在函数参数实现按引用传递,避免对象拷贝,同时允许函数修改传入的对象
右值引用的主要目的:
右值通常没有地址,存储在寄存器和临时内存中
不能出现在赋值号的左侧,
#右值引用是实现移动语义和完美转发的关键
#实现移动语义,这是它最重要使命,允许我们将资源(动态内存、文件句柄),从一个临时的、即将销毁的对象移动到另一个新对象,从而避免不必要的深度拷贝,大幅提升性能
#完美转发,在模板编程中,右值引用结合引用折叠规则,可以写出能够保持参数原始值类别的函数模板,从而将参数无损的转发给其他函数
- 移动语义:是目的,是为了高效地转移资源,提升性能。它的实现工具是右值引用 (T&&)。
- 万能引用:是手段,是一种在模板中用来同时接收左值和右值的特殊引用(T&&),它是实现完美转发的基石。
- 完美转发:是目的,是为了在转发参数时保持其所有属性不变。它的实现工具是万能引用 (T&&) + std::forward<T>()。
2.c++中extern c作用
答:主要作用是指示编译器按照c语言的规则来编译和链接其修饰的代码
a:禁止名字修饰
b:保证c调用约定
c++支持函数重载,即多个函数可以有相同的名字但有不同的参数列表
3.const和define区别
a.const是关键字,用于声明常量,
define是预处理指令,用于进行文本替换
b:类型检查
const声明的常量具有类型,编译器会进行严格类型检查
define只是简单文本替换,没有类型概念,不进行类型检查
c.作用域
const声明的常量有明确的作用域,在函数内部声明的const常量仅在函数内有效
define定义的常量作用域从定义处到文件结束,若想限制作用域,通过#undef来终止
d.内存分配
const声明的常量会在内存中分配空间,将其视为只读变量
define定义的常量在预处理阶段进行替换
4.bss段和data段区别
答:bss段和data段,都是程序内存布局中用于存储静态分配变量的区域,主要存在于可执行文件的静态存储区,也称为全局区
a、存储内容
Data段(数据段):也称为初始化数据段。它用于存储显式初始化了初值(且初值不为零)的全局变量和静态变量(static)
bss段,也称为未初始化数据段,用于存储未初始化,或显式初始化为0的全局变量和静态变量
b、核心区别,是否占用可执行文件的实际空间
这是它们最关键、最本质的区别,也直接影响了可执行文件的大小
data段:存储的变量有明确的、非0的初始值,所以这些初始值必须被硬解码保存在可执行文件,当程序被操作系统加载到内存,系统
5.死锁产生的原因? 如果解决死锁的问题
死锁是指两个或多个进程在执行过程中,由于争夺资源而造成的一种相互等待的现象,从而使得这些进程都无法继续执行。死锁产生的原因主要包括以下四个条件:
- 互斥条件:至少有一个资源必须以排他模式占用,即某时刻只能有一个进程使用该资源。
- 保持与等待条件:一个进程在保持某些资源的同时,正在等待其他资源。
- 不剥夺条件:已经分配给进程的资源在进程使用完之前不能被剥夺。
- 循环等待条件:存在一种进程资源的循环链,其中每个进程都在等待下一个进程所持有的资源。
解决死锁的策略
解决死锁问题的方法主要有以下几种:
- 预防死锁:破坏死锁四个必要条件中的一个或多个条件。例如,可以通过对资源请求进行限制来破坏“保持与等待”条件。
- 避免死锁:使用资源分配图(如银行家算法等)来判断资源分配的安全性。如果申请资源后会导致系统进入不安全状态,则拒绝该请求。
- 检测与恢复:定期检查系统中的进程和资源状态,识别死锁。如果发现死锁,可以采取措施,例如:终止某些进程以释放资源。强制剥夺某些资源,使其他进程能够继续执行。
- 忽略死锁:在某些系统中,可以认为死锁很少发生,因此可以选择忽略它。在这种策略下,系统可能会遭遇死锁,但通常会通过重启或其他方式进行恢复。
6.spi的时序,spi通信的频率,怎么配置
spi是一种高速、全双工、同步的串行通信总线,SPI时序的核心由四个信号线和一个通信协议决定。
由主设备产生的同步时钟,所有数据收发都以此时钟为基准。片选信号,由主设备控制,低电平有效。用于选择要进行通信的特定从设备。
通信过程(以模式0为例):
- 主设备将对应从设备的CS线拉低。
- 主设备产生SCLK时钟(此时空闲为低电平)。
- 在SCLK的上升沿,主从设备分别采样(读取)各自数据线(MOSI和MISO)上的数据。
- 在SCLK的下降沿,主从设备分别放置(输出)下一个要发送的数据位到数据线上。
- 重复步骤3和4,直至一个字节或一帧数据发送完成。
- 主设备将CS拉高,结束本次通信。
SPI的通信频率(波特率)由主设备完全控制和控制。
数据帧格式,最高位运行
7.你的项目rtos任务有几个,如何配置优先级的,什么优先级最高,为什么,rtos移植过程
rtos中,任务是调度和执行的基本单位,可以把它看作一个独立的、无限循环的函数,每个任务都有自己的栈空间和优先级
每个任务需要分配一个独立的栈空间
一般数值小的,优先级高
rtos的移植,本质上是让RTOS内核能够在该芯片上运行起来。这个过程主要涉及修改与处理器硬件相关的代码
心跳、切换、开关中断
1.心跳,在芯片初始化,配置定时器、每隔1ms中断一次,
编写一个中断服务函数
2.切换任务
移植中最难也最核心的部分,任务切换就是保存当前任务的“现场”(所有CPU寄存器的值),恢复下一个任务的“现场”
对于ARM Cortex-M芯片,这个中断叫 PendSV
pendsv函数
- 保存现场:把当前任务的所有寄存器值,压入它自己的任务栈(Task Stack)中。
- 恢复现场:从下一个任务的任务栈中,把之前保存的寄存器值 pop 回CPU寄存器中。
3.开关中断
RTOS内核在操作它的内部关键数据(如任务队列)时,需要暂时禁止被打断
4.配置 FreeRTOSConfig.h
8指针使用的注意事项有哪些。
永远初始化指针,并警惕野指针,声明一个指针变量后,如果没有显式初始化,它的值是一个随机值(垃圾值),指向一块未知的内存地址。这就是“野指针”。
确保指针解引用时的有效性,不能对空指针或非法地址进行解引用操作。
防止内存泄露,使用RAII原则:在C++中,最有效的方法是使用智能指针
杜绝指针越界访问,通过指针访问了分配的内存区域之外的空间。这通常发生在数组操作中。
注意指针的算术运算,指针的加减运算是以所指向数据类型的大小为单位的,而不是单纯的字节地址加减。
正确处理多级指针和函数参数,当需要在一个函数内部修改一个指针本身的值(而不是它指向的值)时,需要传递指针的指针(int **p
)或指针的引用(int* &p
)。
- const和指针结合有三种情况,含义完全不同,理解它们能增加代码的健壮性和可读性。
- const int *p 或 int const *p:指向常量的指针。指针指向的值不可变,但指针本身可以指向别的地址。
- int * const p:指针常量。指针本身(存储的地址)不可变,但它指向的值可以改变。
- const int * const p:指向常量的指针常量。指针本身和它指向的值都不可变。
常量指针,保护指向数据不修改,
指针常量,指针本身是常量。它的本质是 “指针的指向(存储的地址)是固定的
“左定值,右定向”
- const 在 * 的左边,定的是值(指向的数据)不可变 → 常量指针。
- const 在 * 的右边,定的是指向(指针本身)不可变 → 指针常量。
9共同体和结构体的区别。
最主要是内存占用方式,结构体内存分配,所有成员都分配各自独立的内存空间
可以同时访问和操作结构体中的任何一个或所有成员,因为它们彼此独立。
共同体内的所有成员共享同一块内存空间。共同体的总内存大小由其最大的成员决定。
同一时间只能有效地使用其中一个成员。当你给一个成员赋值后,再给另一个成员赋值,会覆盖之前成员的值,导致之前的数据失效。
10结构体的长度怎么计算。
内存对齐原则,内存对齐要求每个成员的起始地址必须是其自身类型大小(或编译器指定的对齐模数)的整数倍
结构体的总大小必须是其所有成员中最大对齐模数的整数倍
- 每个成员的起始地址必须是其对齐模数的整数倍。,不太懂这个起始地址这个规则
- 结构体的总大小必须是其最大对齐模数的整数倍。
11结构体对齐是什么,怎么启用结构体对齐,默认是否是对齐的。
默认是对齐的,
结构体第一个成员的偏移地址(offset)为0。
每个成员的起始地址 offset 必须满足:offset % min(sizeof(member_type), #pragma pack(n)) == 0。如果不满足,编译器会自动在前一个成员后面插入填充字节(Padding Bytes)。
结构体的总大小必须是其所有成员中最大对齐模数的整数倍。如果不满足,编译器会在最后一个成员后面填充字节。
12指针和引用的区别。
都是C++中处理内存地址、实现间接访问和修改对象的机制,
指针 (Pointer):它是一个变量,这个变量的值是另一个变量的内存地址
引用 (Reference):它是一个已存在变量的别名,它必须在定义时被初始化,并且一旦与某个变量绑定,就不能再成为其他变量的引用
13智能指针的用法
核心价值在于自动化、规范化地管理动态分配的内存的生命周期,从而彻底解决由于忘记调用 delete
而导致的内存泄漏问题。它们遵循RAII(Resource Acquisition Is Initialization) 原则。
std::unique_ptr,通
过 std::move
转移所有权
std::shared_ptr
(共享所有权指针)
std::weak_ptr
(弱引用指针),它是对由 shared_ptr
管理对象的非拥有性引用(弱引用)。它不会增加对象的引用计数。它主要用于解决 shared_ptr
的循环引用问题
14.函数重载和函数重写的区别分别说说
函数重载:同一个作用域内(例如同一个类中),允许存在多个同名函数,但这些函数的参数列表必须不同(参数的类型、个数或顺序不同)
- 函数名必须相同。
- 函数的参数列表必须不同(这是关键)。
- 函数的返回类型可以相同也可以不同,但仅返回类型不同不足以构成重载。
- 多态性:属于编译时多态(静态多态)。编译器在编译阶段根据函数调用时传递的实参类型和数量,就能确定具体要调用哪个重载函数。
函数重写:在继承体系中,派生类(子类) 重新定义基类(父类)中已有的函数,并且该函数必须是虚函数(通常使用 virtual
关键字声明)
- 函数名必须相同。
- 参数列表必须相同(这是与重载最根本的区别)。
- 返回类型必须相同(在C++11中,协变返回类型除外)。
- 基类中的函数必须是 virtual 虚函数(这是实现多态的关键)
多态性:属于运行时多态(动态多态)。具体调用哪个函数(父类的还是子类的)是在程序运行时根据对象的实际类型来决定的。
15.函数指针和指针函数,分别说一下是什么,那个用的多。
函数指针,指向函数的指针,本质是指针,是一个指向函数的指针变量。和普通指针保存变量的地址不同,函数指针保存的是函数的入口地址。通过这个指针,我们可以间接地调用它指向的函数。
指针函数,本质是函数,返回一个指针,返回类型,(int*)
函数指针用得多,用于虚函数表,以及回调函数,回调函数是一种函数A被作为参数传递给另一个函数B,并在函数B的内部被调用的机制。
16.Linux中的shell命令有哪些
ls 查看目录 cd切换 pwd显示当前所在目录的绝对路径 mkdir创建新目录 rm删除文件和目录
rmdir删除空目录 rm删除文件和目录 cp复制文件和目录 mv移动和重命名文件/目录 touch创建空文件、更新文件时间戳
17.介绍一下freertos
FreeRTOS 的核心是任务(Task),即一个独立执行的线程。
- 调度器(Scheduler):负责决定哪个任务在何时运行。
- 调度方式:抢占式调度(Preemptive):这是默认模式。高优先级的任务一旦就绪(例如,它等待的事件发生了),就能立即抢占低优先级任务的 CPU 使用权。时间片调度(Time Slicing):允许相同优先级的任务共享 CPU 时间,每个任务运行一个固定的时间片(tick)。
- 任务状态:任务在其生命周期内会在多种状态间转换,主要包括就绪(Ready)、运行(Running)、阻塞(Blocked)(等待事件、延时)、挂起(Suspended)(被动放弃 CPU,只能被其他任务唤醒)。
- 任务间通信(IPC):队列(Queues):是 FreeRTOS 中最基础、最安全的通信机制,用于在任务之间、任务与中断之间传递固定大小的数据。它实现了 FIFO(先进先出),并且是线程安全的。信号量(Semaphores):用于同步和资源管理。二进制信号量常用于同步(如中断通知任务),计数信号量用于管理多个 identical 资源(如缓冲池)。互斥量(Mutexes):一种特殊的二进制信号量,具有优先级继承机制,用于解决共享资源的互斥访问问题,防止优先级反转。事件组(Event Groups):允许任务等待或操作多个事件(位)的组合,非常适合需要等待多个条件中任意一个或全部满足的场景。
- 内存管理:FreeRTOS 提供了几种堆(heap)管理方案(如 heap_1.c 到 heap_5.c),用户可以根据应用对确定性、碎片化、复杂性的要求进行选择,也可以提供自己的内存管理实现。
- 软件定时器(Software Timers):允许创建周期性或单次的定时回调函数,由 FreeRTOS 的内核守护任务负责执行,简化了定时操作。
- 低功耗支持(Tickless Idle Mode):在系统空闲时,内核可以自动暂停周期性的时钟中断(tick interrupt),让处理器进入深睡眠模式,极大降低功耗,非常适合电池供电的设备。
18.优先队列
元素出队的顺序不是由入队的先后顺序决定的,而是由元素的“优先级”决定的。基于堆的实现在插入和删除操作上达到了 O(log n) 的优秀复杂度
19.哈希表
哈希表是一种非常高效的数据结构,它可以在平均情况下以 O(1) 的时间复杂度进行数据的插入、删除和查找操作。它的核心思想是通过一个哈希函数,将键(Key)映射到数组中的某个位置,从而直接访问该位置来获取值
哈希表由哈希函数、哈希桶数组、冲突解决机制
- 平均时间复杂度极低:插入、删除、查找操作都是 O(1),这是其最吸引人的地方。无序:与树结构相比,它不维持元素的任何顺序,节省了维护开销。
- 缺点:最坏情况下性能差:如果哈希函数设计极差或发生大量冲突,性能会退化为 O(n)。不支持顺序相关操作:无法像有序数组或二叉搜索树那样进行范围查询、快速找到最大/最小值等。需要额外的空间:为了避免频繁冲突,哈希桶数组通常需要比实际数据量更大的空间(空间换时间)。哈希函数设计是关键:一个好的哈希函数是高效哈希表的前提。
它是一种经典的 “以空间换时间”
20.can
数据帧、远程帧、错误帧、过载帧,数据帧,(仲裁段、控制段、数据段、crc段、)
#秋招笔面试记录#