前言
本篇文章继续讲解嵌入式面试笔试刷题,希望大家能够继续坚持哦。
一、进程和线程的区别
1.定义:进程是程序的执行实例,是资源分配和调度的单位;线程是进程中的执行单元,是CPU调度和执行的基本单位。
2.资源占用:每个进程都有独立的内存空间、文件描述符和其他系统资源,进程之间的通信需要使用进程间通信(IPC)机制。而线程是在进程内部共享进程的资源,包括内存空间和文件描述符等,线程之间可以直接共享数据,因此线程间通信更加方便。
3.调度和切换:在操作系统中,进程是一个独立的单位,由调度器进行调度和切换。切换进程时需要保存和恢复进程的上下文,开销较大。而线程是进程内部的执行单元,线程的切换开销比进程的切换小很多。因此,多线程的程序在切换和调度上更加高效。
4.执行并发性:由于进程之间具有独立的内存空间和资源,不同进程间的执行是相互独立的,可以同时执行不同的任务。而线程共享进程的资源,线程之间执行可以并发或并行,可以更充分利用系统的多核处理器提高执行效率。
5.容错性:由于进程的独立性,一个进程的崩溃不会影响其他进程的执行。而线程是在进程内部执行,一个线程的错误可能会导致整个进程崩溃。
二、共享内存的原理
通过将一块内存区域映射到多个进程的地址空间,使得这些进程可以直接访问和共享相同的内存内容,从而实现数据的交互和共享。
三、中断有传参和返回值吗
中断通常不直接支持传参和返回值。这是因为中断是一种异步事件处理机制,它的执行是由硬件或者特定的事件触发,并且不像函数调用那样由程序主动发起。
四、串口数据帧格式
1.起始位
2.数据位
3.奇偶校验位
4.停止位
五、进程通信有几种,哪几种需要借助内核
1.方式
1.管道
2.命名管道
3.共享内存
4.信号量
5.消息队列
6.套接字
7.信号
2.需要借助内核的
需要借助内核的通信方式包括管道、共享内存、信号量、消息队列和套接字。
六、flash有哪几种类型
1.NOR Flash:NOR Flash 主要用于存储程序代码和执行读操作。它提供了快速的随机访问速度和较长的擦除寿命。NOR Flash 的特点是可以按字节进行读写操作,且具有较低的擦除和写入时间。因此,它适用于需要频繁执行读操作的应用,如嵌入式系统中的引导程序和固件存储。
2.NAND Flash:NAND Flash 主要用于大容量存储和数据存储,例如移动设备和固态硬盘(SSD)。相对于 NOR Flash,NAND Flash 具有较高的存储密度和较低的成本,但访问速度较慢,通常以页为单位进行读写操作。NAND Flash 的特点是具有较高的数据传输速率和较高的擦写寿命,适用于需要大容量存储和频繁写操作的应用。
3.eMMC:eMMC(Embedded Multi-Media Card)是一种内嵌式多媒体卡,集成了 NAND Flash 存储芯片和控制器。它通常用于嵌入式设备和移动设备中,提供了可靠的存储解决方案。eMMC 的特点是小巧便携、低功耗,且支持随机读写操作。
4.UFS:UFS(Universal Flash Storage)是一种新型的高速闪存存储标准,可提供更快的数据传输速率和更高的性能。它被广泛用于高端移动设备和存储解决方案中。UFS 具有较低的延迟、更高的带宽和更好的可靠性,支持高速读写操作。
七、指针的本质是什么
指针的本质是一个变量,但它存储的是一个内存地址,而不是实际的数据。
指针的存储方式取决于计算机架构和编程语言。在大多数计算机体系结构中,内存地址通常使用二进制表示,并根据特定的内存寻址方案进行存储。指针变量本身也是存储在内存中的,它占用一定的内存空间,用于存储目标地址。
八、指针和数组的区别
1.数据结构:数组是一种数据结构,用于存储一系列相同类型的数据元素。它是连续的内存块,每个元素在内存中的位置相邻。指针是一个变量,存储了一个内存地址,它可以指向任何数据类型的数据,包括数组。
2.内存分配:数组在定义时需要指定长度或者使用动态内存分配(如C++中的new运算符),编译器在编译时为数组分配指定长度的内存空间。指针可以通过赋值操作指向任何有效的内存地址,包括数组的首地址或者其他内存区域。
3.大小和访问:数组的大小是固定的,一旦定义后不能改变。通过索引可以直接访问数组中的元素,索引从0开始。指针本身的大小取决于系统的位数(通常为4字节或8字节),通过解引用操作符(*)可以访问指针指向的内存位置,可以通过指针的算术运算来访问数组中的不同元素。
4.数组名 vs. 指针变量:在C语言中,数组名实际上是指向数组首元素的指针常量。它可以被隐式转换为指针类型,所以可以使用指针的方式对数组进行操作。然而,数组名本身不能被赋值或修改。指针变量可以重新赋值来指向不同的内存位置。
5.参数传递:作为函数参数传递时,数组通常以指针的形式传递。在函数内部,无法得知数组的长度,因此需要额外的参数来传递数组的长度信息。指针作为函数参数可以提供灵活的传递和访问内存的能力。
九、使用宏定义交换变量不能使用中间变量
#include <stdio.h> #define SWAP(a, b) do { \ (a) = (a) ^ (b); \ (b) = (a) ^ (b); \ (a) = (a) ^ (b); \ } while(0) int main() { int x = 10; int y = 20; printf("Before swap: x = %d, y = %d\n", x, y); SWAP(x, y); printf("After swap: x = %d, y = %d\n", x, y); return 0; }
在上面的示例中,我们定义了一个名为 SWAP 的宏函数,它使用了位异或运算符(^)来交换数据。通过连续进行三次异或操作,可以实现两个变量的值互换,而无需使用临时变量。
需要注意的是,宏函数使用 do { … } while(0) 结构来确保宏定义中的多个语句都能够正常运行,并且可以在条件语句中使用宏函数。
这样,在调用 SWAP(x, y) 时,宏展开后的代码会执行三次位异或运算,实现 x 和 y 的交换。最终,x 的值变成了原来 y 的值,y 的值变成了原来 x 的值。
十、do { … } while(0) 结构的作用
在宏定义的使用中使用 do { … } while(0) 结构的主要目的是为了确保宏定义在展开时可以正常工作。
宏定义本质上是文本替换,编译器在代码中找到宏的调用,并将其展开为宏定义中的代码。如果宏定义只是简单地展开为一行代码,那么在某些情况下可能会导致意想不到的行为。
使用 do { … } while(0) 结构可以解决以下两个问题:
1.语法上的问题:如果宏定义只是一个单独的语句,并且在某些情况下需要在条件语句中使用宏,例如:if (condition) MACRO(x); else …。这样的话,如果宏定义展开后只是一个语句,那么编译器在展开后的输出代码中会导致语法错误。通过使用 do { … } while(0) 结构,可以确保宏定义受到分号(;)的约束,从而避免语法错误。
2.嵌套问题:如果宏定义展开后包含多个语句,并且在代码中使用了条件语句、循环等结构,例如:
#define MACRO(x) do { \ statement1; \ if (condition) { \ statement2; \ } \ } while(0)
如果宏定义展开后的代码中缺少大括号,那么在使用该宏时嵌套结构可能会导致逻辑错误。通过使用 do { … } while(0) 结构,宏定义中的多个语句都被包含在一个块作用域中,确保了这些语句的正常执行。
总结来说,使用 do { … } while(0) 结构是为了保证宏定义在展开时在语法和逻辑上都是正确的。这种结构在宏定义中是一种常用的技巧,以确保宏在使用时能够像正常的代码一样正常工作。
总结
本篇文章就讲解到这里,下篇文章继续讲解。