面试常用八股文

发布时间:2026/7/10 22:32:31
面试常用八股文 1.内存映射的原理将一块内存空间映射到不同的进程空间中2.define和const的区别1.define 是预处理指令用于创建符号常量。const是 C 和 C 的关键字用于创建具有常量值的变量本质是只读变量。2.define在预处理阶段执行。const在编译阶段执行。3.define没有类型检查仅进行文本替换。const有类型检查可以与变量类型关联。3.数组和链表的区别1.数组内存连续链表内存不连续。2.数组访问速度比链表快3.链表增加删除操作比数组快4.指针和引用的区别1.指针指针是一个变量保存着内存地址。引用引用是已存在变量的别名没有自己的内存地址。2.指针可以具有空值(NULL)引用不能为空必须在初始化时指向一个有效的对象。3.可以修改指针的指向可以将指针重新赋值为另一个地址。一旦引用被初始化它始终指向同一个对象不可更改。4.指针需要额外的内存空间来存储地址值。引用不需要额外的内存空间因为它是对已存在变量的别名。5.解释一下QT的信号与槽信号与槽是一种用于实现对象之间通信和事件处理的机制。6.IIC为什么要加上拉电阻为什么使用开漏输出上拉电阻1.当IIC总线在空闲状态SDA和SCL需要处于高电平状态。2.开漏输出无法输出高电平使用上拉电阻可以完成高低电平之间的转换。开漏输出1.假如使用推挽输出可能导致器件的烧毁2.实现线与功能7.MQTT的通信过程1.创建客户端2.指定IP地址和端口号3.进行连接4.发布主题或者订阅主题5.数据传输6.断开连接8.在Linux中怎么实现同步1.互斥锁2.信号量3.条件变量9.TCP和UDP的应用场景1.TCP文件传输、电子邮件、网页浏览。2.UDP实时音视频传输、在线游戏、实时监控。10.什么是野指针什么情况会产生野指针什么是野指针1.指向已被释放或无效的内存地址的指针是野指针。什么情况下产生野指针1.内存释放后未置空指针int *ptr (int*)malloc(sizeof(int)); free(ptr); *ptr 10; // 这里ptr成为了野指针2.返回局部变量的指针int* getIntPointer() {int num 5;return num; // 返回局部变量的指针}int *ptr getIntPointer();*ptr 10; // getIntPointer返回一个野指针3.未初始化指针char* p;11.什么是互斥锁互斥锁是一种用于线程同步的机制用于确保同一时间只有一个线程访问共享资源。12.数组和指针的区别数组是一块连续的内存空间其大小在编译时确定访问元素使用下标操作而指针是一个变量存储地址值大小固定可以指向不同类型的数据通过解引用操作访问内存中的数据。13.如何防止重复引用头文件1.使用预处理指令#ifndef HEADER_FILE_NAME_H#define HEADER_FILE_NAME_H// 头文件内容#endif // HEADER_FILE_NAME_H2.使用#pragma once14.栈和队列的区别1.栈是后进先出的数据结构而队列是先进先出的数据结构。2.对插入和删除的限定不同队列只能在表的一端进行插入并在另一端进行删除栈只能在表的一端插入和删除3.遍历数据的速度不同队列基于地址指针进行遍历可以从头或者尾部进行遍历不能同时遍历无需开辟新空间在遍历过程中不影响数据结构所以遍历速度快。栈只能从栈顶取数据如果要取出栈底的数据需要遍历整个栈并且需要遍历的同时开辟空间保持遍历前的一致性。堆和栈的区别1.内存管理方式不同。栈是一种先进后出的数据结构通常由操作系统自动管理堆是一种树形结构通常由程序员手动管理2.数据访问速度不同。栈上的数据通常直接存放在系统内存中访问速度快。堆上的数据需要通过指针进行间接访问访问速度相对较慢。3.存储内容不同。栈主要存储局部变量、函数参数和函数调用的上下文其生命周期通常与其作用域相对应。堆主要用于存储动态分配的对象和数据结构其生命周期由程序员控制。4.内存大小不同。栈的大小通常受到限制受到操作系统和编译器的影响。堆的大小可以较大受到系统资源的限制通常比栈要大得多。结构体结构体(struct)是由一系列具有相同类型或不同类型的数据构成的数据集合。使用结构体来封装一些属性来组成新的类型通过对结构体内部变量的操作将大量的数据存储在内存中以完成对数据的存储和操作程序执行过程预处理这个阶段主要是处理源程序中的预编译指令如包含头文件、宏替换等。编译将预处理后的源程序转换成汇编语言。汇编将编译后的汇编语言转换成机器语言并生成目标文件。链接将汇编生成的目标文件和系统提供的库函数合并生成可执行文件。执行计算机加载并执行可执行文件开始程序的运行。stm32启动过程15.为什么中断不能传递参数中断是异步调用无法知道什么时候会被调用不能够像函数一样主动调用。16.串口数据帧格式起始位数据位校验位停止位。17.中断的概念中断是计算机系统中一种重要的处理机制用于响应某种事件或条件的发生。它是一种异步的事件可以打断当前正在执行的程序或任务以处理紧急情况或外部设备的请求。18.static的作用控制变量生命周期控制作用域控制文件可见性。19.中断的执行过程中断的执行过程包括中断请求、中断控制器响应、中断响应、中断向量确定、中断处理程序执行和中断处理程序结束。20.什么是多态多态是面向对象编程中的一种特性它允许以一种统一的方式处理不同类型的对象通过相同的接口可以表现出不同的行为。21.C语言中的内存分配方式有几种1.静态内存分配。2.栈上内存分配。3.堆上内存分配。22.struct和class的区别1.默认访问权限不同struct的默认访问权限是publicclass的默认访问权限是private。2.继承方式不同struct的默认继承方式是公有继承class的继承访问是私有继承。3.在一般情况下struct被用于表示数据结构而class则更多用于表示具有行为和数据的对象。23.函数和中断的区别1.函数调用不会发生上下文切换中断调用会发生上下文切换。2.函数可以主动被调用中断无法主动被调用。3.函数调用是同步的中断是异步的。4.函数可以有返回值和参数中断没有返回值和参数。外部中断与定时器区别触发方式外部中断是由外部设备或信号引起的中断而定时器是基于计时器硬件设备通过设定一个时间间隔来定期触发中断。作用对象外部中断通常用于响应外部设备的输入事件而定时器主要用于实现时间相关的任务例如定时更新显示、定时采样等。响应方式外部中断发生时CPU会暂停当前正在执行的任务保存当前执行状态并跳转到中断服务程序去处理而定时器中断是在规定的时间间隔内周期性地触发不会中断当前任务只是插入一个中断处理程序来执行特定操作。精确度外部中断的触发时间是不确定的取决于外部事件的发生时间而定时器中断是基于硬件计时器的可以提供较为精确的时间间隔。24.自旋锁和信号量的区别自旋锁是一种忙等待的方式适用于临界区执行时间短暂、锁冲突概率低的情况而信号量是一种阻塞机制适用于临界区执行时间长、锁冲突概率高的情况自旋锁不会进入休眠信号量会进入休眠。25.怎么判断链表是否有环使用快慢指针。思路创建两个指针一个指针称为快指针fast另一个指针称为慢指针slow初始时都指向链表的头节点。快指针每次向前移动两个节点慢指针每次向前移动一个节点。如果链表中存在环那么快指针和慢指针最终会相遇。如果链表中不存在环那么快指针最终会先到达链表尾部此时可以判断链表无环。26.使用多线程时需要注意什么线程安全多线程环境下多个线程同时访问共享资源可能会引发竞态条件Race Condition导致数据不一致或其他异常情况。确保共享资源的访问是线程安全的可以通过使用互斥锁Mutex、条件变量Condition Variable等同步机制来保护共享资源的访问。线程间通信在多线程编程中不同线程之间可能需要进行通信和协作。合理地设计和使用线程间通信机制如队列Queue、信号量Semaphore、事件Event等可以有效地实现线程之间的同步和传递信息。死锁死锁是指两个或多个线程在互相等待对方释放资源而无法继续执行的状态。避免死锁的方法之一是按照固定的顺序获取锁避免循环依赖。另外可以使用资源分配图等方法进行死锁检测和预防。上下文切换开销线程切换需要保存当前线程的上下文并加载下一个线程的上下文这涉及到时间和空间的开销。在设计多线程应用程序时需要注意减少线程切换的频率避免过度创建线程和过度频繁地切换线程以提高程序性能。共享资源的合理使用多线程环境下共享资源可能被多个线程同时访问需要注意共享资源的正确使用和保护。避免线程之间的竞争和冲突需要考虑线程安全性使用适当的同步机制对共享资源进行保护。有效的线程调度和任务划分在多线程编程中线程的调度和任务的划分对系统性能和响应能力有重要影响。合理规划线程数量和调度策略均衡地分配任务避免线程之间的争抢和饥饿现象以提高系统整体的吞吐量和响应性能。错误处理和异常处理在多线程环境下错误和异常的处理需要更加谨慎。及时捕获和处理线程中的异常确保程序的稳定性和可靠性。27.实现strcpy函数char* mystrcpy(char* str, const char* str1) { char* temp str; ​ while (*temp *str1); ​ return str; }28.实现strcat函数char* mystrcat(char* str1, char* str2) { char* temp str1; while (*temp ! \0) { temp; } ​ while (*str2 ! \0) { *temp *str2; temp; str2; } ​ *temp \0; ​ return str1; }29.实现strlen函数int mystrlen(char* str) { int i 0; while (*str ! \0) { i; } return i; }30.new和malloc的区别1.new是C中的关键字malloc是标准库中的函数。2.使用new创建对象会调用构造函数使用malloc创建对象不会调用构造函数。3.使用new不需要指定对象的大小使用malloc需要指定对象的大小。4.new返回的是一个对象的指针malloc返回的是void*31.可执行程序生成的过程预处理--编译--汇编--链接32.UARTSPIIIC的区别和概念1.UART概念UART是一种异步串行通信协议用于在两个设备之间实现简单的点对点通信。它使用两根传输线TX和RX进行数据传输其中TX发送线负责发送数据RX接收线负责接收数据。2.SPI概念SPI是一种同步串行通信协议用于在一个主设备主控器和一个或多个从设备之间实现全双工的高速数据传输。3.IIC概念I2C是一种串行双线制通信协议用于在多个设备之间进行数据传输。它使用两根传输线SDA和SCL进行数据传输其中SDA串行数据线负责发送和接收数据SCL串行时钟线用于数据同步。区别UART是异步通信协议用于点对点通信SPI是同步通信协议适用于高速数据传输I2C是双线制通信协议适用于连接多个低速外设的场景。33.软件中断和硬件中断的区别1.软件中断是由特殊指定触发硬件中断是由外部设备或处理器内部产生的信号触发的。2.软件中断可以是同步也可以是异步的硬件中断是异步的。3.软件中断响应时间较长硬件中断响应时间非常短。34.内联函数和宏函数的区别1.内联函数在编译时展开而宏函数在预处理时展开。2.内联函数会进行错误检查宏函数是直接替换不会进行错误检查。3.内联函数可以进行调试宏函数无法进行调试。4.内联函数有作用域规则只能在定义它的源文件内使用不能被其他源文件调用。而宏函数没有作用域限制可以在整个程序中使用。35.冒泡排序冒泡排序Bubble Sort是一种简单且直观的排序算法属于比较排序算法的一种。它通过多次迭代比较和交换相邻的元素将较大或较小的元素逐渐“浮”到数组的一端从而实现排序。void bubbleSort(int* array, int size) { for (int i 0; i size - 1; i) { for (int j 0; j size - i - 1; j) { // 比较相邻的两个元素如果顺序错误就交换它们 if (array[j] array[j 1]) { int temp array[j]; array[j] array[j 1]; array[j 1] temp; } } } }36.选择排序选择排序Selection Sort是一种简单且直观的排序算法属于比较排序算法的一种。它的基本思想是从未排序部分的数组中选择最小或最大的元素然后将其放置在已排序部分的末尾不断重复这个过程直到整个数组排序完成。void selectionSort(int* array, int size) { for (int i 0; i size - 1; i) { int minIndex i; for (int j i 1; j size; j) { // 寻找未排序部分的最小元素的索引 if (array[j] array[minIndex]) { minIndex j; } } // 将最小元素与当前位置交换 int temp array[i]; array[i] array[minIndex]; array[minIndex] temp; } }37.插入排序插入排序Insertion Sort是一种简单且直观的排序算法属于比较排序算法的一种。它的基本思想是将数组分为已排序部分和未排序部分初始时已排序部分包含第一个元素然后逐步将未排序部分的元素插入到已排序部分的合适位置直到整个数组排序完成。void insertionSort(int array[], int size) { for (int i 1; i size; i) { int key array[i]; int j i - 1; ​ // 在已排序部分中找到合适的位置插入元素 while (j 0 array[j] key) { array[j 1] array[j]; j--; } array[j 1] key; } }38.快速排序// 交换两个元素的值 void swap(int* a, int* b) { int temp *a; *a *b; *b temp; } ​ // 将数组划分为两个子区间并返回枢轴的索引 int partition(int arr[], int low, int high) { int pivot arr[high]; // 选择最后一个元素作为枢轴 int i (low - 1); // 记录小于枢轴的元素的索引 ​ for (int j low; j high - 1; j) { // 如果当前元素小于或等于枢轴则交换位置 if (arr[j] pivot) { i; swap(arr[i], arr[j]); } } ​ // 把枢轴放到正确的位置上并返回索引 swap(arr[i 1], arr[high]); return (i 1); } ​ // 快速排序 void quickSort(int arr[], int low, int high) { if (low high) { // 划分数组获取枢轴索引 int pivotIndex partition(arr, low, high); ​ // 递归对枢轴左侧的子数组进行排序 quickSort(arr, low, pivotIndex - 1); ​ // 递归对枢轴右侧的子数组进行排序 quickSort(arr, pivotIndex 1, high); } }39.归并排序40.堆排序41.C语言中struct和C中struct的区别1.C中的struct可以支持继承C语言中的struct不支持继承。2.C中的struct可以包含成员函数C语言中的struct不可以包含成员函数。42.进程和线程的区别1.线程是程序调度的基本单位进程是资源分配的基本单位。2.一个进程崩溃不会导致其他进程崩溃一个线程崩溃可能会导致整个程序崩溃。3.创建和销毁进程所消耗的资源比较多创建和销毁线程所消耗的资源比较少。4.每个进程都有独立的内存空间和系统资源线程是在进程内部创建的共享相同的内存空间和系统资源。43.volatile的作用用法volatile告诉编译器这个变量的值不能缓存每次读写都必须从原始内存地址真实读取 / 写入禁止编译器做优化。每次读直接访问内存不使用寄存器缓存每次写立即写入内存不延后编译器不对该变量相关代码做指令重排、无效读写删除。用处1. 外设寄存器最普遍硬件寄存器地址映射到内存内存地址会被硬件自动改变不受 CPU 代码控制。 如果不加volatile编译器会优化只读一次硬件寄存器放入CPU寄存器后续循环不再访问真实硬件读取不到实时状态。2. 中断服务程序 (ISR) 修改的全局变量全局变量在主循环和中断里共用中断随时会改写变量。 不加 volatile编译器会把变量缓存到寄存器主循环永远看不到中断更新后的值。总结只要变量不由当前代码顺序修改硬件 / 中断 / 其他任务自动改内存就加volatile强制每次访问真实内存屏蔽编译器优化。44.什么是大小端大小端Endian指的是在计算机存储和处理多字节数据时字节的存储顺序。大端序Big Endian高位字节存储在低地址低位字节存储在高地址。类似于将数字的高位放在左边低位放在右边的表示方式。例如整数值0x12345678在内存中按照大端序排列为12 34 56 78。小端序Little Endian低位字节存储在低地址高位字节存储在高地址。类似于将数字的低位放在左边高位放在右边的表示方式。例如整数值0x12345678在内存中按照小端序排列为78 56 34 12。45.判断系统大小端在C语言中可以使用联合union的特性来判断系统的大小端Big Endian 或 Little Endian。#include stdio.h ​ union EndianCheck { int i; char c[sizeof(int)]; }; ​ int main() { union EndianCheck ec; ec.i 1; ​ if (ec.c[0] 1) { printf(Little Endian\n); } else { printf(Big Endian\n); } ​ return 0; } ​46.define和typedef的区别1.define用于创建宏定义在预处理时进行简单的文本替换。2.typedef用于创建类型别名在编译时进行类型检查并为已有类型创建新的名称。3.define是预处理指令typedef是关键字。4.define不会进行类型检查而typedef会。5.typedef在定义新类型时更为灵活和安全。47.使用fork函数和vfork函数创建进程的区别1.使用fork创建的子进程和父进程有独立的地址空间。2.vfork 创建的子进程与父进程共享地址空间包括代码段、数据段和堆栈等。3.使用fork时父子进程的执行顺序是不确定的取决于系统调度。4.vfork会暂停父进程的执行直到子进程调用exec或_exit在此期间父进程一直等待子进程的结束。48.进程间通信方式管道Pipe匿名管道是一种单向通信机制允许一个进程向另一个进程传输数据。管道可用于具有亲缘关系的进程例如父子进程之间进行通信常用于单个读者和单个写者的情况。命名管道Named Pipe命名管道是一种通过文件系统路径来命名的管道可用于不具有亲缘关系的进程之间进行通信。命名管道可以实现多个读者和写者之间的通信。共享内存Shared Memory共享内存是一种高效的进程间通信方式允许多个进程直接访问相同的物理内存区域。通过将内存区域映射到各个进程的地址空间进程可以直接读写共享内存避免了数据的复制。信号量Semaphore信号量是一种用于多进程之间同步和互斥的机制。进程可以通过操作信号量来进行进程间的互斥访问以保护共享资源。消息队列Message Queue消息队列是一种通过内核维护的消息缓冲区实现的通信机制用于进程之间传递数据。进程可以将消息发送到消息队列并由其他进程接收和处理。套接字Socket套接字是一种网络编程中常用的进程间通信机制可用于不同主机上的进程进行通信。套接字提供了一种基于网络协议的可靠的双向数据传输方式。文件File进程可以通过读写共享文件来进行通信多个进程可以访问同一个文件并进行数据的读写。49.全局变量和局部变量的区别1.作用域全局变量全局变量在整个程序中都是可见和可访问的被定义在全局作用域中。局部变量局部变量只在其被定义的特定函数、代码块或作用域内部可见和可访问。2.生命周期全局变量全局变量的生命周期从程序开始运行到程序结束它在内存中一直存在。局部变量局部变量的生命周期仅在其定义的特定作用域内部存在。3.初始值全局变量全局变量可以在定义时显示地指定初始值如果没有指定初始值全局变量会被默认初始化为零值。局部变量局部变量在定义时不会被自动初始化其初始值是未定义的即不确定的需要显式地给定一个初始值。4.存储位置全局变量全局变量通常被存储在数据段data segment中具有固定的存储位置且在整个程序的执行期间都保持不变。局部变量局部变量通常被存储在栈stack中它们的存储位置在函数调用过程中动态分配和释放每次函数执行时都会创建一个新的副本。50.什么是交叉编译交叉编译Cross-compilation是指在一台计算机上使用特定的编译工具链将代码编译成目标平台上可执行的程序或库。51.static和const的区别1.可变性52.define和typedef的区别1.用法和语法define#define是一个预处理指令用于在代码中创建常量或宏定义。typedeftypedef关键字用于为已存在的数据类型创建新的名称可以通过typedef为存在的类型创建别名。2.处理阶段define#define指令是在预处理阶段进行处理即在编译之前进行文本替换。它将标识符替换为指定的文本不进行任何类型检查。typedeftypedef是在编译阶段进行处理在编译器解析代码时处理类型信息。它为已存在的类型创建一个新的别名。会进行类型检查3.作用范围define#define指令的作用范围是整个文件或者被它所定义的预处理区域可以在文件的任何位置进行定义和使用。typedeftypedef创建的别名具有更窄的作用域仅在指定的代码块、函数、结构体或枚举内部有效。53.构造函数能否是虚函数构造函数不能被声明为虚函数。54.析构函数能否是虚函数析构函数可以被声明为虚函数。55.有名管道和无名管道1.创建方式不同有名管道使用mkfifo函数创建无名管道使用pipe函数创建。2.生存周期有名管道可以持久存在于文件系统中无名管道的生命周期仅限于创建它的父子进程的生命周期。3.通信限制无名管道只能用于亲缘关系间的进程进行通信有名管道可以进行非亲缘关系之间的进程进行通信。56.进程有几种状态1.创建状态New当一个进程刚刚被创建时它处于新建状态。此时操作系统为进程分配必要的资源并对其进行初始化。2.就绪状态Ready当一个进程已经被创建并具备运行所需的所有资源时但尚未被调度执行它处于就绪状态。在就绪状态下进程等待操作系统的调度以便在合适的时机执行。3.运行状态Running当操作系统将 CPU 资源分配给处于就绪状态的进程时进程进入运行状态。在运行状态下进程正在执行其指令和操作。4.阻塞状态Blocked当一个进程无法继续执行因为它正在等待某种事件的发生比如等待输入/输出、等待资源释放等此时进程进入阻塞状态。在阻塞状态下进程会释放 CPU 资源让其他可运行的进程有机会执行。5.终止状态Terminated当一个进程完成了其任务或由于某种原因被操作系统终止时它进入终止状态。在终止状态下进程释放它所占用的资源并等待系统回收。57.字符设备与块设备(1)字符设备提供连续的数据流应用程序可以顺序读取通常不支持随机存取。相反此类设备支持按字节自/字符来读写数据。举例来说百键盘、串口、调制解调器都是典型的字符设备。(2) 块设备应用程序可以随机访问设备数据程序可自行确定读取数据的位置。硬盘、软盘、CD-ROM驱动器和闪存都是典型度的块设备应用程序可以寻址磁盘上的任何位置并由此读取数据。此外数据的读写知只能以块(通常是512B)的倍数进行。与字符设备不同块设备并不支持基于字符的寻址。58.内存对齐的原因和规则是什么?CPU 访问内存时不是任意字节读取而是按固定块2/4/8 字节读取原因1. CPU 硬件设计限制最关键CPU 内部总线、寄存器、内存控制器都是按固定位宽设计32 位 CPU 一次读 4 字节64 位一次 8 字节。2. 提升访问性能对齐变量一次总线周期读完数据未对齐变量数据跨两个内存块CPU 要读两次内存再拼接数据性能大幅下降3. 适配硬件外设 / DMADMA 控制器只能按对齐块搬运内存结构体作为 DMA 缓冲区如果不对齐数据传输错乱、丢字节。 硬件寄存器、外设缓冲区全部强制对齐。规则规则 1成员自身对齐每个成员的起始地址必须是该成员类型大小的整数倍char (1B)任意地址short (2B)地址 % 2 0int (4B)地址 % 4 0long long/double (8B)地址 % 8 0规则 2结构体整体对齐结构体整体占用总大小必须是最大成员大小的整数倍不足末尾填充空字节补齐。规则 3成员依次排布不足自动填充填充字节 (padding)成员之间地址不满足对齐要求时中间插入空白 padding 字节。结构体大计算结构体变量的首地址必须是其最大成员对齐数的整数倍。这个由编译器保证我们计算大小时主要关注后两条每个成员的偏移量该成员相对于结构体起始位置的偏移量必须是该成员自身对齐数的整数倍。如果不足编译器会在前一个成员后面填充Padding字节。结构体的总大小必须是其最大成员对齐数的整数倍。如果不足编译器会在末尾填充字节。链表的反转struct listnode { int node; struct listnode * next; }; struct listnode * reverslist(struct listnode * head) { struct listnode * pre NULL; struct listnode * cur head; struct listnode * next NULL; while (cur ! NULL) { next cur - next; cur - next pre; pre cur; cur next; } }