当前位置: 首页 > article >正文

嵌入式面试高频!!!C语言(四)(嵌入式八股文,嵌入式面经)

 更多嵌入式面试文章见下面连接,会不断更新哦!!关注一下谢谢!!!!

 ​​​​​​​https://blog.csdn.net/qq_61574541/category_12976911.html?fromshare=blogcolumn&sharetype=blogcolumn&sharerId=12976911&sharerefer=PC&sharesource=qq_61574541&sharefrom=from_linkhttps://blog.csdn.net/qq_61574541/category_12976911.html?fromshare=blogcolumn&sharetype=blogcolumn&sharerId=12976911&sharerefer=PC&sharesource=qq_61574541&sharefrom=from_link

一、volatile 关键字和 extern 关键字

1. 作用
  • 禁止编译器优化:告诉编译器 “这个变量的值可能随时以不可预知的方式被改变”,因此每次访问都必须从内存中读取,而非使用寄存器中的缓存值。
  • 典型场景
    • 硬件寄存器:如嵌入式系统中的 I/O 端口。
    • 多线程共享变量:被中断服务程序(ISR)修改的变量。
    • 内存映射设备:如 LCD 控制器的内存地址。
volatile int status_reg;  // 硬件状态寄存器// 编译器不会优化对status_reg的读取
while (status_reg != 0);  // 每次循环都从内存读取status_reg
2. 生效阶段
  • 编译阶段:编译器在生成机器码时,会强制每次都从内存读取 / 写入 volatile 变量,而非依赖寄存器缓存。

二、extern 关键字

1. 作用
  • 声明外部链接:告诉编译器 “这个变量或函数的定义在其他文件中”,从而避免重复定义。
  • 典型场景
    • 多文件项目:在头文件中声明全局变量或函数,在源文件中定义。
    • 跨语言调用:如 C++ 调用 C 函数时使用 extern "C"

// 文件1.c
int global_var = 10;  // 定义全局变量// 文件2.c
extern int global_var;  // 声明外部变量
printf("%d", global_var);  // 使用全局变量
3. 生效阶段
  • 链接阶段:编译器生成目标文件时,extern 声明的符号会被标记为 “外部引用”,由链接器在其他目标文件中查找对应的定义。

三、核心区别对比

特性volatileextern
作用禁止编译器优化,强制内存访问声明外部定义的变量或函数
生效阶段编译阶段链接阶段
影响范围单个变量或对象整个项目中的符号解析
典型场景硬件交互、多线程共享变量多文件项目、跨语言调用
与内存的关系每次访问都从内存读取 / 写入不涉及内存分配,仅声明符号存在

四、常见误区

1. volatile 用于多线程同步
  • 错误:认为 volatile 能替代同步机制(如互斥锁)。
  • 真相volatile 仅阻止编译器优化,但无法解决多线程竞争问题(如原子性)。
2. extern 用于定义变量
  • 错误:在头文件中写 extern int x = 10;
  • 真相extern 仅声明变量,定义必须在源文件中进行(int x = 10;)。

 二、堆和栈有什么不同

一、基本概念

1. 栈(Stack)
  • 内存分配:由操作系统自动管理,遵循 后进先出(LIFO) 原则。
  • 存储内容:函数调用的上下文(局部变量、参数、返回地址等)。
  • 典型场景:函数内部的局部变量、函数调用过程中的参数传递。
2. 堆(Heap)
  • 内存分配:由程序员手动管理(如 C 语言的 malloc/free,C++ 的 new/delete)。
  • 存储内容:动态分配的数据(如对象、数组等)。
  • 典型场景:需要在函数外部长期存在的数据(如动态数组、链表)。

二、核心区别对比

特性栈(Stack)堆(Heap)
内存分配方式自动分配和释放(由系统管理)手动分配和释放(需程序员干预)
分配效率高(直接移动栈指针)低(需动态查找可用内存块)
内存空间大小通常较小(如几 MB 到几十 MB)通常较大(受限于物理内存和虚拟内存)
数据生命周期随函数调用结束而销毁直到显式释放(如调用 free
内存碎片问题不存在碎片可能产生碎片(频繁分配 / 释放导致)
生长方向向低地址扩展(栈顶向下移动)向高地址扩展(堆顶向上移动)
内存连续性连续不连续(碎片化)
使用场景局部变量、函数调用上下文动态数据结构(如链表、树)
内存访问方式直接访问(通过栈指针)间接访问(通过指针)

三、典型示例

1. 栈内存示例
void func() {int a = 10;         // 局部变量,分配在栈上char buffer[20];    // 数组,分配在栈上// 函数结束时,a和buffer自动释放
}

 2. 堆内存示例

void dynamic_allocation() {int* p = (int*)malloc(sizeof(int));  // 在堆上分配内存if (p != NULL) {*p = 20;                         // 通过指针访问堆内存free(p);                         // 手动释放堆内存}
}

四、常见问题

1. 栈溢出(Stack Overflow)
  • 原因:递归过深或局部变量过大,导致栈空间耗尽。
  • 解决:减少递归深度、使用堆内存替代大数组。
2. 内存泄漏(Memory Leak)
  • 原因:堆内存分配后未释放,导致内存持续占用。
  • 解决:确保每次 malloc/new 后都有对应的 free/delete
3. 性能对比
  • :分配速度快(约为堆的 10 倍),适合短期使用的数据。
  • :分配速度慢,但灵活性高,适合长期存在的数据。

五、总结

场景推荐使用栈推荐使用堆
数据大小小且固定大或动态变化
生命周期短期(函数内)长期(跨函数)
性能要求
内存管理复杂度低(自动管理)高(手动管理)

 三、堆栈溢出一般是由什么原因导致的?

一、核心原因:栈空间耗尽

栈内存由操作系统自动管理,存储函数调用帧(局部变量、参数、返回地址等)。当栈空间被占满时,会触发堆栈溢出。常见诱因包括:

二、常见场景与示例

1. 无限递归(最常见)
  • 原因:递归函数未正确设置终止条件,导致无限调用。
  • 示例

void recursive() {recursive();  // 无终止条件,无限递归
}int main() {recursive();  // 触发堆栈溢出
}
  • 分析:每次递归调用都会在栈上创建新的函数帧,最终耗尽栈空间。
2. 过深的递归调用
  • 原因:递归深度过大(如树的遍历深度超过栈容量)。
  • 示例
void deepRecursion(int n) {if (n == 0) return;int arr[1000];  // 每次递归占用大量栈空间deepRecursion(n - 1);
}int main() {deepRecursion(100000);  // 可能导致栈溢出
}

3. 大型局部变量
  • 原因:在函数内部定义过大的数组或结构体,超出栈空间限制。
  • 示例
void largeArray() {char buffer[1024 * 1024];  // 1MB数组,可能超出栈容量
}

4. 嵌套过深的函数调用
  • 原因:非递归的多层函数调用(如 A→B→C→...),每层调用都占用栈空间。
  • 示例
void func1() { func2(); }
void func2() { func3(); }
// ... 更多嵌套函数
void func1000() { /* 占用栈空间 */ }int main() {func1();  // 可能导致深层嵌套
}

5. 栈空间不足(系统限制)
  • 原因:操作系统默认栈空间较小(如 Linux 默认 8MB),无法满足程序需求。
  • 解决:通过命令行增大栈空间限制(如 Linux 的 ulimit -s)。

    三、不同语言的堆栈溢出表现

    语言错误提示常见诱因
    C/C++Segmentation fault无限递归、大型局部数组
    JavaStackOverflowError无限递归(如 toString () 循环引用)
    PythonRecursionError递归深度超过限制(默认约 1000 次)
    JavaScriptRangeError: Maximum call stack size exceeded浏览器环境中的无限递归

四、预防与解决方法 

  1. 修复递归终止条件:确保递归函数有明确的退出条件。

void safeRecursion(int n) {if (n <= 0) return;  // 正确终止条件safeRecursion(n - 1);
}

 2.使用迭代替代递归:对于深度不确定的场景,用循环代替递归。

void iterative() {while (true) {  // 循环实现,不占用栈空间// ...}
}

 3.减小局部变量大小:将大型数组或结构体改为动态分配(堆内存)。

void dynamicAllocation() {char* buffer = (char*)malloc(1024 * 1024);  // 堆分配if (buffer) {// 使用bufferfree(buffer);}
}

4.增加系统栈限制:在 Linux 中通过 ulimit -s unlimited 临时增大栈空间。

5.尾递归优化:部分语言(如 Python 不支持,C++ 支持)可将递归转换为循环。

// 尾递归示例(需编译器优化)
int tailRecursive(int n, int acc) {if (n == 0) return acc;return tailRecursive(n - 1, acc + n);  // 尾递归调用
}

  四、内存泄漏和内存池

一、内存泄漏(Memory Leak)

1. 定义
  • 程序在堆上动态分配的内存(如 malloc/new),在不再使用时未被释放(如未调用 free/delete),导致这部分内存无法被操作系统回收。
2. 常见原因
  • 忘记释放内存
void leakExample() {int* ptr = (int*)malloc(sizeof(int));// 使用ptr,但未调用free(ptr)
}  // 内存泄漏!

  • 异常导致的泄漏:函数中途抛出异常,未执行释放代码。
  • 循环分配内存:在循环中持续分配内存而不释放。
  • 指针丢失:指向内存的指针被覆盖,导致无法释放。
3. 危害
  • 长期运行的程序(如服务器)会逐渐耗尽可用内存,最终导致系统崩溃。
  • 碎片化内存:频繁分配和释放不同大小的内存块,导致内存碎片化,降低分配效率。
4. 检测工具
  • Valgrind(Linux):检测内存泄漏和越界访问。
  • AddressSanitizer(ASan):GCC/Clang 内置的快速内存错误检测工具。
  • Visual Studio 内存分析器:Windows 平台下的内存调试工具。

二、内存池(Memory Pool)

1. 定义
  • 一种内存管理技术,预先分配大块内存(池),然后按需分配小块内存给程序使用。当程序释放内存时,内存块不直接返回给操作系统,而是返回给池以便后续复用。
2. 核心思想
  • 预分配:一次性分配大块内存,减少系统调用次数。
  • 复用:回收的内存块不释放,直接复用,避免频繁分配 / 释放。
  • 减少碎片:通过固定大小的内存块或智能分配算法,减少内存碎片。
3. 简单实现示例

#include <stdlib.h>// 内存池节点结构
typedef struct MemNode {struct MemNode* next;char data[1];  // 实际数据从这里开始
} MemNode;// 内存池结构
typedef struct {MemNode* freeList;  // 空闲链表size_t blockSize;   // 每个内存块大小size_t chunkSize;   // 每次分配的块数量
} MemPool;// 初始化内存池
MemPool* createPool(size_t blockSize, size_t chunkSize) {MemPool* pool = (MemPool*)malloc(sizeof(MemPool));pool->blockSize = blockSize;pool->chunkSize = chunkSize;pool->freeList = NULL;return pool;
}// 从内存池分配内存
void* poolAlloc(MemPool* pool) {if (pool->freeList == NULL) {// 无空闲块,分配新的一组块size_t realSize = sizeof(MemNode) + pool->blockSize - 1;MemNode* chunk = (MemNode*)malloc(realSize * pool->chunkSize);// 将新分配的块加入空闲链表for (size_t i = 0; i < pool->chunkSize; i++) {MemNode* node = &chunk[i];node->next = pool->freeList;pool->freeList = node;}}// 从空闲链表取出一个块MemNode* node = pool->freeList;pool->freeList = node->next;return &node->data;
}// 释放内存回池
void poolFree(MemPool* pool, void* ptr) {MemNode* node = (MemNode*)((char*)ptr - offsetof(MemNode, data));node->next = pool->freeList;pool->freeList = node;
}// 销毁内存池
void destroyPool(MemPool* pool) {// 实际实现中需遍历所有分配的块并释放free(pool);
}
4. 优势
  • 高性能:减少系统调用(malloc/free),分配速度提升 5-10 倍。
  • 减少碎片:通过固定大小的内存块或分桶策略,降低内存碎片。
  • 控制内存:可预测的内存使用模式,避免内存泄漏。
5. 适用场景
  • 频繁分配 / 释放小对象:如网络服务器中的连接请求处理。
  • 实时系统:需要确定性的内存分配时间(如游戏引擎)。
  • 内存碎片敏感场景:长期运行的程序(如数据库、中间件)。

三、内存泄漏 vs 内存池

特性内存泄漏内存池
本质内存管理错误内存管理优化技术
原因未释放不再使用的内存预分配和复用内存块
危害内存耗尽、系统崩溃可能占用更多常驻内存
解决方法检测工具(Valgrind)、RAII自行实现或使用第三方库(如 Boost)
性能影响无直接影响(但可能导致系统变慢)显著提升分配速度
适用场景所有动态内存分配场景高频分配 / 释放相同大小内存的场景

四、高级内存池技术

  1. 分级内存池:按不同大小分桶管理内存块。
  2. 线程私有内存池:每个线程独立维护内存池,避免锁竞争。
  3. 内存池与垃圾回收结合:在某些语言(如 Python)中自动回收不再使用的内存池。

   五、指针的运算

                int *ptr//假设地址为0x00 00 00 00 (32位系统)

                prt++;//0x00 00 00 04

                对于32位系统所有类型的指针都只占4字节的空间。

http://www.lryc.cn/news/2403626.html

相关文章:

  • 数据治理在制造业的实践案例
  • 【强化学习】——03 Model-Free RL之基于价值的强化学习
  • Edge(Bing)自动领积分脚本部署——基于python和Selenium(附源码)
  • html表格转换为markdown
  • VsCode 安装 Cline 插件并使用免费模型(例如 DeepSeek)
  • 短视频矩阵系统源码新发布技术方案有那几种?
  • React 第五十二节 Router中 useResolvedPath使用详解和注意事项示例
  • 【PmHub面试篇】性能监控与分布式追踪利器Skywalking面试专题分析
  • Cursor快速梳理ipynb文件Prompt
  • 天机学堂-分页查询
  • 业态即战场:零售平台的生意模型与系统设计解构
  • 微算法科技(NASDAQ:MLGO)基于信任的集成共识和灰狼优化(GWO)算法,搭建高信任水平的区块链网络
  • 全新Xsens Animate版本是迄今为止最大的软件升级,提供更清晰的数据、快捷的工作流程以及从录制开始就更直观的体验
  • 大语言模型评测体系全解析(下篇):工具链、学术前沿与实战策略
  • python打卡day46@浙大疏锦行
  • C++.OpenGL (1/64) 创建窗口(Hello Window)
  • Excel 发现此工作表中有一处或多处公式引用错误。请检查公式中的单元格引用、区域名称、已定义名称以及到其他工作簿的链接是否均正确无误。弹窗
  • NVIDIA DRIVE AGX平台:引领智能驾驶安全新时代
  • 推荐12个wordpress企业网站模板
  • 沙市区举办资本市场赋能培训会 点赋科技分享智能消费新实践
  • Docker 容器化基础:镜像、容器与仓库的本质解析
  • 九.C++ 对引用的学习
  • 探秘鸿蒙 HarmonyOS NEXT:实战用 CodeGenie 构建鸿蒙应用页面
  • art-pi2 上手记录(二)
  • 数据库SQLite基础
  • 1.3 古典概型和几何概型
  • html-pre标签
  • 【WPF】WPF 项目实战:用ObservableCollection构建一个可增删、排序的管理界面(含源码)
  • MCU_IO驱动LED
  • 上门预约行业技术方案全解析:小程序、App还是H5?如何选择?