华勤驱动开发面试经验
自我介绍
1介绍项目
2项目当中遇到的难点以及怎么解决的
介绍UART IIC SPI CAN通信协议
指针
如何避免野指针
声明时立即初始化: 指针变量定义时若未明确指向有效内存,应初始化为NULL。
动态分配后检查: 使用malloc/new分配内存后,需检查指针是否为NULL,避免分配失败后误用。
释放内存后立即置空: 调用free或delete后,手动将指针置为NULL,防止成为悬挂指针。
不返回栈内存地址: 函数内局部变量的生命周期仅限于函数作用域,返回其地址会导致野指针。应返回动态分配内存或静态变量地址。
替代裸指针: std::unique_ptr(独占所有权)和std::shared_ptr(共享所有权)可自动管理内存释放,避免手动失误。
指针与数组异同点
关键差异
1内存分配方式
数组:在定义时分配连续的内存空间,大小固定(如 int a[5] 分配20字节)。
指针:是一个变量,存储的是地址,可动态指向任意内存(如 int *p = malloc(20))。
2类型与存储内容
数组:存储实际数据,数组名代表首元素地址(如 a 等价于 &a[0]) 。
指针:存储地址数据,需通过解引用(*p)访问目标值。
3大小计算(sizeof)
数组:sizeof(a) 返回数组总字节数(如 int a[5] 为20字节)。
指针:sizeof(p) 返回指针变量大小(32位系统为4字节,64位为8字节)。
相似性
1访问方式的互通性
数组可通过指针访问:a[i] 等价于 *(a + i)。
指针可模拟数组:p[i] 等价于 *(p + i)(需确保指针指向连续内存)。
2函数参数传递时的退化
数组作为函数参数时,退化为指针(如 void func(int a[]) 实际为 void func(int *a))。
3多维数组与指针的关系
二维数组名可视为指向一维数组的指针(如 int (*p)[5] = a 指向二维数组的行) 。
#define和typedef的异同点
#define预处理指令:在编译前的预处理阶段进行纯文本替换,无类型检查。可定义常量、宏函数、条件编译等,功能更广泛。
typedef编译阶段:为已有类型创建类型安全的别名,编译器会进行类型检查。仅用于类型别名:简化复杂类型(如结构体、函数指针)。
自旋锁和互斥锁的异同点,以及对应使用场景
| 特性 | 自旋锁(Spinlock) | 互斥锁(Mutex) |
|---|---|---|
| 等待机制 | 忙等待(Busy-wait),持续检查锁状态 | 阻塞等待(Sleep-wait),线程挂起并释放CPU |
| CPU占用 | 占用CPU资源(空转) | 不占用CPU(线程休眠) |
| 上下文切换 | 无切换,延迟低 | 需切换线程,延迟较高(约几微秒) |
| 实现原理 | 原子指令(如CAS、Test-And-Set) | 内核调度+等待队列 |
| 锁持有时间 | 适合极短临界区(如几条指令) | 适合较长临界区(如I/O操作) |
| 中断安全性 | 可用于中断上下文(需禁用中断防死锁) | 不可用于中断(可能引发睡眠) |
| 多核适用性 | 仅多核有效(单核忙等待无意义) | 单核/多核均适用 |
| 优先级反转风险 | 高(需配合禁用抢占) | 可通过优先级继承缓解 |
自旋锁的典型场景
短临界区:如修改标志位、计数器等极快操作,避免上下文切换开销。
中断上下文:内核中断处理、硬件驱动等不可睡眠的环境。
多核高竞争:锁竞争不激烈时,自旋锁性能优于互斥锁(如高频短时锁)。
互斥锁的典型场景
长临界区:涉及文件I/O、复杂计算等耗时操作。
用户态线程同步:如多线程共享数据结构、数据库事务等。
可睡眠环境:临界区内可能调用阻塞函数(如malloc、sleep)。
介绍一下内存对齐
内存对齐是指数据在内存中的存储起始地址必须满足特定边界条件(如2、4、8字节的整数倍),其核心目的是提升CPU访问效率和保证硬件兼容性。
原理:CPU通常以固定块(如4字节)访问内存,对齐后数据可一次性读取,而非对齐数据可能需多次操作并拼接,导致性能下降甚至硬件异常(如ARM架构) 。
规则:结构体成员按自身大小或编译器对齐系数(如#pragma pack(n))对齐,整体大小需为最大成员对齐值的整数倍,编译器自动插入填充字节 。
优化:通过调整成员顺序(如按对齐值降序排列)或显式指定对齐(如C++的alignas)可减少内存浪费,提升缓存命中率 。
手撕:用C语言声明一个结构体,结构体包含常量指针和指针常量,在写一个回调函数来打印这个结构体
1 | |