零碎知识点汇总
零碎知识点汇总
以下内容均来源于网络
代码编译过程
- 预处理(预编译):处理预处理命令(如:
#include #define #ifdef
等#
开头的内容),生成.i
文件 - 编译:将预处理后的源代码转换为汇编代码,生成
.s
文件 - 汇编:将汇编代码转换为机器码,生成
.o
文件 - 链接:将目标文件和库文件结合起来,生成
.out .exe
等 - 运行
中断服务子程序(ISR)
ISR
不能返回一个值。ISR
不能传递参数。- 在许多处理器/编译器中,浮点一般都是不可重入的。
有些处理器/编译器需要让额外的寄存器入栈。
有些处理器/编译器就不允许在IS
R中做浮点运算。
此外,ISR
应该是短而有效率的,在ISR
中做浮点运算是不明智的。 - 与第三点一脉相承,
printf()
经常有重入和性能上的问题,所以一般不使用printf()
。
有符号数与无符号数一起运算
1 | void foo(void) |
上面程序输出为"> 6"
!
原因为:当表达式中同时存在有符号类型和无符号类型时,所有的操作数都自动转换为无符号类型。
EXTERN
extern
数组时可以不写元素个数,只在初始化时表明,这样在修改数组元素个数时就只用修改定义的地方,推荐这样写。
数据结构种类
常用的有9种:基本数据类型、数组、结构体、枚举体、联合体、指针、链表、栈、队列
什么是中断向量表
问:什么是中断向量表?它在嵌入式系统中如何工作?
答:中断向量表是存储中断服务程序入口地址的表格。
当中断发生时,处理器根据中断类型号从中断向量表中找到对应的中断服务程序入口地址并跳转执行相应程序。
简述SPI
通信中主机和从机的工作过程
主机通过SCK
线提供时钟信号,在MOSI
线上发送数据;
从机在SCK
上升沿或下降沿采样MOSI
数据,同时从机通过MISO
线返回数据;
主机在相应时钟沿采样MISO
线上的数据,片选线CS
用于主机选择特定从机,低电平有效。
简述嵌入式系统中 ADC
(模拟数字转换器) 的工作原理
ADC
将模拟信号转换为数字信号。
通过采样保持电路对模拟信号采样并保持,再经量化编码环节将采样值量化为数字量,最后以二进制代码输出。
字符串字面量
1 |
以上宏定义含义如下:("!"[0])
是一个字符串字面量,其中""
是一个包含单个字符!
的字符串。
在C语言中,字符串字面量实际上是一个字符数组,每个字符后面跟着一个\0
(空字符),用来表示字符串的结束;因此,"!"[0]
表示取这个字符串的第一个字符,即!
所以, SIG_DATA
被定义为字符!
结构体为什么要内存对齐
性能优化:
CPU
访问内存时,通常会以一定的块大小(例如4字节、8字节等)为单位进行读取。如果结构体中的数据成员没有对齐到这样的块边界,CPU
可能需要执行额外的操作(如两次内存访问)来读取一个成员,这称为“未对齐访问”。未对齐访问会降低CPU
的访问效率,增加程序的执行时间。 内存对齐可以确保结构体中的数据成员都位于合适的块边界上,从而提高CPU
的访问效率。硬件限制:某些硬件平台对内存访问有严格的对齐要求。如果结构体中的数据成员没有正确对齐,硬件可能会抛出异常或产生错误的结果。 通过内存对齐,可以确保结构体中的数据成员满足硬件平台的对齐要求,从而避免潜在的硬件问题。
可移植性:不同的编译器和硬件平台可能对内存对齐有不同的要求。如果不进行内存对齐,同一个结构体在不同的编译器或硬件平台上可能会有不同的内存布局和大小。 通过使用标准的数据类型和编译器特定的对齐指令(如
#pragma pack
),可以确保结构体在不同平台上的内存布局和大小一致,从而提高程序的可移植性。空间利用率:虽然内存对齐可能会增加结构体所占用的总内存空间(因为编译器会在成员之间插入填充字节以确保对齐),但在某些情况下,这可以提高内存访问的效率并减少缓存未命中的可能性。 此外,合理的内存对齐还可以帮助减少内存碎片,提高内存管理的效率。
简化编译器实现:对于编译器来说,处理未对齐的内存访问需要额外的逻辑和复杂性。通过要求结构体进行内存对齐,可以简化编译器的实现并减少潜在的错误。
内存溢出和内存泄漏的区别
内存溢出:是指程序在申请内存时,没有足够的内存空间供其使用。
比如,申请了一个整数的内存,但实际存了一个需要
long
类型来存储的数,这就会导致内存溢出。系统无法满足程序需要的内存大小,导致溢出。
内存泄漏:是指程序在申请内存后,无法释放已申请的内存空间。
虽然单个内存泄漏可能不会带来太大问题,但随着内存泄漏的积累,系统的可用内存空间会逐渐减少;
内存泄漏最终可能会导致内存耗尽,也就是内存溢出。
define
和typedef
区别
define
预处理阶段完成替换,typedef
编译阶段进行替换。define
没有类型,只是简单的替换,而typedef
会进行类型检查define
不是语句,不需要加分号,typedef
需要加分号- 一般
define
用来定义常量或者书写复杂的内容;typedef
一般用于给类型起别名 - 二者对指针的定义区别很大。
const
和define
的区别
- 数据类型:
const
修饰的变量有明确的类型,而宏没有明确的数据类型 - 安全方面:
const
修饰的变量会被编译器检查,而宏没有安全检查 - 内存分配:
const
修饰的变量只会在第一次赋值时分配内存,而宏是直接替换,每次替换后的变量都会分配内存 - 作用场所:
const
修饰的变量作用在编译、运行的过程中,而宏作用在预编译中 - 代码调试:
const
方便调试,而宏在预编译中进行所以没有办法进行调试。
运算符
强制类型转换运算符的优先级大于除法
头文件中的ifndef/define/endif
的作用
防止头文件被重复包含(避免重复定义)
如果已经定义了该标识符,就不会再次包含头文件,从而避免重复定义和编译错误;
这样可以确保头文件只被包含一次,提高编译效率并避免潜在的错误。
堆栈溢出一般是由什么原因导致的
堆栈溢出通常是由递归调用或者过多的嵌套函数调用导致的。
当一个函数被调用时,其参数和局部变量会被压入堆栈中,当函数执行完毕后,这些数据会被弹出堆栈。
如果递归调用或者函数调用过多导致堆栈空间不足,就会发生堆栈溢出。
另外,如果函数内部使用了大量的局部变量或者数组,也有可能导致堆栈溢出。
不能做switch()
的参数类型
只能是整型或字符型。不能是浮点型、字符串等。
堆和栈的区别
可从2方面回答:分配方式、存储位置
- 分配内存的方式
堆:是由程序员分配和释放的,通过内存管理函数(如malloc、free
)来操作堆内存。堆内存的生命周期由程序员控制,需要手动释放,否则可能导致内存泄漏。
栈:是由编译器自动分配和释放的,编译器根据变量的作用域来管理栈内存。当一个函数被调用时,其局部变量存储在栈上,函数执行完成后,这些变量会自动释放。
- 存储方式
堆:是用于存储动态分配的内存,通常用来存储程序中需要长时间保存的大型数据结构,如数组等。堆内存的访问速度较慢,因为需要通过指针来访问。
栈:是用于存储局部变量和函数调用时的参数,栈内存的访问速度较快,因为数据的存取都在固定的位置进行。
关键字
1 | struct enum union |
sizeof
sizeof
不是函数;既是关键字也是运算符
指针与数组的区别
指针存储地址,数组存储数据;
sizeof
结果不同(指针返回地址大小,数组返回总字节数)。
如:
1 | char *p = "1234"; |
以上二者有不同吗?如有,有什么不同?
sizeof(p)
和sizeof(p1)
得到的结果不同。p
是指针,大小为4字节或8字节(取决于系统是32位还是64位);p1
包含结束符号\0
所以大小为5字节&p
和&p1
得到的数据类型不同。&p
得到的是“指向指针的指针类型”,&p1
得到的是” 指向长度为5的数组的指针类型”- 可修改性,由于
char* p
指向的是字符串常量,所以不能通过p
来更改字符串的内容,任何修改字符串的操作都是非法的。而char p1[]
是一个字符数组,它的内容可以被修改。
简述strcpy、sprintf 、memcpy
的区别
操作对象不同:
strcpy
的两个操作对象均为字符串,sprintf
的操作源对象可以是多种数据类型,目的操作对象是字符串,memcpy
的两个对象就是两个任意可操作的内存地址,并不限于何种数据类型。执行效率不同:
memcpy
最高,strcpy
次之,sprintf
的效率最低。实现功能不同:
strcpy
主要实现字符串变量间的拷贝;sprintf
主要实现其他数据类型格式到字 符串的转化;memcpy
主要是内存块间的拷贝。
DMA
CPU不参与数据传输(内存↔外设),提升效率。
应用场景:高速ADC采集、大块数据搬运。
优化代码性能方法
减少函数调用层级、使用查表法替代复杂计算、启用编译器优化(-O2)、内联汇编关键代码。
写一个宏定义SWAP(x, y)
,不使用第三个变量交换两个变量的值
1 |
解析:
1 | 假设 a = 3,b = 5 |