嵌入式常见面试题——C语言篇
1、关键字static有什么作用?
static在不同场景下有不同含义:
-
静态局部变量:函数内用static修饰的变量,仅在首次调用时初始化,后续调用保留上次结束时的值,生命周期延长至整个程序运行期。
-
静态全局变量:全局变量前加static,作用域被限制在定义它的源文件内,其他文件无法通过extern引用。
-
静态函数:用static修饰的函数,作用域同样限于定义它的源文件,可避免不同文件的函数名冲突。
2、volatile关键字的作用是什么?在什么场景下使用?
volatile告诉编译器该变量可能被意外修改,禁止编译器对其访问进行优化,每次必须从内存中重新读取。
三大使用场景:
-
硬件寄存器映射:寄存器值可能被硬件自行改变,如定时器计数寄存器。
-
中断服务程序中修改的变量:ISR中修改的全局标志位。
-
多任务共享变量:多线程/多任务环境下各任务共享的标志位。
典型代码示例:
c
volatile unsigned int *timer_reg = (volatile unsigned int *)0x40000000; unsigned int value = *timer_reg; // 每次都从硬件寄存器读取
3、const关键字的含义和用法?
const表示“只读”,防止变量被意外修改。常见用法:
-
const int a;或int const a;— a是常整型数,值不可修改。 -
const int *p;— 指针指向的内容不可变,但指针本身可变(常量指针)。 -
int * const p;— 指针本身不可变,但指向的内容可变(指针常量)。 -
const int * const p;— 指针和指向的内容都不可变。
4、局部变量、全局变量、动态申请数据分别存放在哪里?
-
局部变量存放在 栈区(stack),函数返回后自动释放。
-
全局变量和静态变量存放在 静态区(data/bss段)。
-
动态申请的数据(malloc等)存放在 堆区(heap),需手动释放。
5、堆和栈的区别是什么?
| 比较项 | 栈(Stack) | 堆(Heap) |
|---|---|---|
| 管理方式 | 系统自动分配释放 | 手动分配释放(malloc/free) |
| 空间大小 | 较小(通常几KB到几十KB) | 较大(受系统可用内存限制) |
| 存储内容 | 局部变量、函数参数、返回地址 | 动态分配的数据 |
| 访问速度 | 快 | 慢 |
| 内存分布 | 连续 | 可能不连续,易产生碎片 |
6、什么是栈溢出?如何预防?
栈溢出是指栈中存储的数据超出栈的最大容量,覆盖其他数据(如函数返回地址),导致程序崩溃或异常。
常见导致栈溢出的操作:
-
在函数内定义过大的局部数组或结构体。
-
函数递归调用过深。
-
函数参数过多或传递大型结构体(值传递)。
预防措施:
-
避免定义超大局部变量,改用静态变量或动态内存。
-
控制递归深度,尽量改用迭代。
-
传递大型数据时使用指针,减少栈内存占用。
-
配置合理的栈大小,并使用工具监控栈使用情况。
7、malloc、calloc、realloc的区别?
-
malloc:分配指定字节大小的连续内存,内容未初始化。
-
calloc:分配指定个数和每个元素大小的连续内存,并将内存初始化为0。
-
realloc:调整已分配内存的大小,若原内存后空间不足则重新分配并拷贝数据。
常见问题:内存泄漏(未free)、野指针(free后未置NULL)、重复释放、内存越界。
8、sizeof和strlen的区别?
-
sizeof是编译时运算符,返回变量或类型所占内存的字节数,计算时包含末尾的\0。 -
strlen是运行时库函数,返回字符串的长度,不包含末尾的\0。
9、结构体字节对齐是什么?为什么需要关注?
数据在内存中的起始地址必须为特定值(通常是数据类型大小的整数倍)的整数倍。
为什么需要关注:
-
现代计算机按字(如4字节或8字节)访问内存,未对齐的数据可能需要多次访问,降低性能。
-
部分处理器只能访问对齐数据,否则会产生硬件异常。
结构体对齐规则:基本数据类型对齐值通常等于自身大小,结构体对齐值为其成员中最大对齐值,成员间可能存在填充字节。
10、C程序从源代码到可执行文件经历了哪些步骤?
预处理 → 编译 → 汇编 → 链接:
-
预处理:处理#开头的指令(宏替换、头文件展开、条件编译、删除注释)。
-
编译:将预处理后的代码转换为汇编代码,进行语法语义分析。
-
汇编:将汇编代码转换为机器码,生成目标文件(.o/.obj)。
-
链接:将多个目标文件和库文件链接成可执行文件,解析符号引用。
11、define和const的区别?
| 比较项 | #define | const |
|---|---|---|
| 处理阶段 | 预处理阶段替换 | 编译阶段生效 |
| 类型检查 | 无 | 有 |
| 内存分配 | 不分配内存(占代码段) | 分配内存(占数据段) |
| 调试 | 无法调试(已被替换) | 可调试 |
12、大端(Big-Endian)和小端(Little-Endian)的区别?
-
大端:数据的高位字节存储在内存的低地址端,低位字节存储在内存的高地址端(网络协议多使用大端)。
-
小端:数据的低位字节存储在内存的低地址端,高位字节存储在内存的高地址端(ARM/x86默认使用小端)。
检测代码:可用union或指针方式,读取一个多字节整数的最低地址字节来判断。
13、如何用C语言编写死循环?
两种常用方式:
-
while(1) { ... } -
for(;;) { ... }
14、指针和数组名有什么区别?
-
数组名是常量,指向数组首元素的地址,不可被赋值修改。
-
指针是变量,可以指向任意地址,可以被赋值修改。
-
sizeof(数组名)返回整个数组的大小,sizeof(指针)返回指针变量本身的大小(如4字节或8字节)。
15、用宏定义写出求两个数最小值的宏,并说明注意事项
c
#define MIN(a, b) ((a) < (b) ? (a) : (b))
注意事项:
-
参数必须加括号,防止运算符优先级问题。
-
整体表达式也需加括号,防止在复杂表达式中出错。
-
宏可能导致副作用(如
MIN(a++, b)中a++被计算两次)。
更多推荐
所有评论(0)