(七)嵌入式面试题收集:8道
题目 1
请完成下面的宏定义,作用是获得 a 和 b 两个参数中的较大者:#define max(a, b) ____
答案:((a) > (b) ? (a) : (b))解析:利用三元运算符实现,为避免宏展开时的优先级问题,给 a 和 b 都加上括号。
题目 2
用预处理指令#define声明一个常数,用以表明一年中有多少秒(忽略闰年问题):#define ____
答案:ONE_YEAR_SECONDS (365 * 24 * 60 * 60)解析:一年 365 天,每天 24 小时,每小时 60 分钟,每分钟 60 秒,通过乘法计算总秒数。
题目 3
下面代码包括 2 个函数,第一个函数是中断服务程序用于从温度传感器中读取数据,第二个函数用于比较温度是否一致。它是否包括错误?如有,该如何修正?
static int __g_temperatures[2];__interrupt void isr_temperatures_read (void)
{__g_temperatures[0] = temperature_sensor1_read(); /* Get Temperature from Sensor 1 */__g_temperatures[1] = temperature_sensor2_read(); /* Get Temperature from Sensor 2 */
}void main (void)
{while (1) {if (__g_temperatures[0] != __g_temperatures[1]) {;/* Alarm */}}
}
答案:存在错误。问题分析:在中断服务程序和主程序之间共享全局变量__g_temperatures时,没有做原子操作保护,可能导致数据竞争(主程序读取时,中断程序可能正在修改数组元素)。修正方法:添加一个 ** volatile 类型的标志位或使用互斥机制 **(如关中断保护)来确保数据读取的原子性。例如:
static volatile int __g_temperatures[2];
static volatile int flag = 0;__interrupt void isr_temperatures_read (void)
{__g_temperatures[0] = temperature_sensor1_read();__g_temperatures[1] = temperature_sensor2_read();flag = 1; // 标记数据已更新
}void main (void)
{while (1) {if (flag) {flag = 0; // 重置标志if (__g_temperatures[0] != __g_temperatures[1]) {;/* Alarm */}}}
}
题目 4
请给下面代码段中的变量按存放位置(指针变量指的是指针指向的内存)进行归类:
static int static_int;
int public_int;
int initialized = 4;
- 只读存储:____
- 可读写存储:____
- 堆:____
- 栈:____
答案
- 只读存储:无(本题变量均为可读写)
- 可读写存储:
static_int、public_int、initialized - 堆:无(本题无动态内存分配)
- 栈:无(本题变量为全局 / 静态变量,非栈上的局部变量)解析:
static_int是静态全局变量,存放在可读写的静态存储区;public_int和initialized是全局变量,也存放在可读写的静态存储区。栈用于存放函数局部变量、函数参数等,堆用于动态内存(如malloc分配),本题无对应场景。
宏定义解释
对于第一段宏定义代码:
static const u16_t memp_sizes[] = {#define MEMPOOL(name,num,size,desc) (size),#include "std.h"};这段代码的作用是定义一个存储内存池块大小的数组memp_sizes。当包含头文件std.h时,其中的MEMPOOL宏会被展开为对应的size(内存块大小),从而将各个内存池的块大小依次填入memp_sizes数组中。例如MEMPOOL(B_PCB,3,30,"B_PCB")会将30作为一个元素加入该数组。
对于第二段宏定义代码:
static u8_t memp_memory[3#define MEMPOOL(name,num,size,desc) + ((num) * (size))#include "std.h"};这段代码的作用是定义一个存储内存池实际内存的数组memp_memory。MEMPOOL宏在这里展开为(num) * (size)(内存块数量乘以每个块的大小),从而计算出该内存池所需的总内存大小,多个内存池的内存大小累加后确定memp_memory数组的总长度,用于分配内存池的实际存储区域。
链表摘除节点函数补充
函数用于从链表中摘除指定节点,补充后的完整代码如下:
typedef void **list_t;struct list {struct list *next;
};void list_remove (list_t list, void *item)
{struct list *l, *r;if (*list == NULL) {return;}r = NULL;for (l = *list; l != NULL; l = l->next) {if (l == item) {if (r == NULL) {*list = l->next; // 摘除头节点时,更新链表头指针} else {r->next = l->next; // 摘除中间或尾节点时,更新前驱节点的next指针}l->next = NULL;return;}r = l; // 保存当前节点作为下一个节点的前驱}
}
解析:
- 变量
r用于保存当前节点l的前驱节点。 - 当找到要摘除的节点
item(即l == item)时:- 若
r == NULL,说明要摘除的是头节点,此时需要将链表头指针*list更新为l->next。 - 若
r != NULL,说明要摘除的是中间或尾节点,此时将前驱节点r的next指针指向l->next,即可跳过当前节点l。 - 最后将
l->next置为NULL,完成节点摘除并返回。
- 若
问题 5:多任务调用计数器函数的问题及解决
原代码:
static int __g_counter;
os_semaphore_t __g_sem_plus;
os_semaphore_t __g_sem_minus;void counter_increment(int count)
{os_semaphore_take(__g_sem_plus);__g_counter += count;os_semaphore_give(__g_sem_plus);
}void counter_decrement(int count)
{os_semaphore_take(__g_sem_minus);__g_counter -= count;os_semaphore_give(__g_sem_minus);
}
问题分析:全局变量__g_counter被counter_increment和counter_decrement两个函数共享,但分别使用__g_sem_plus和__g_sem_minus两个不同信号量保护。当多任务(A、B、C)同时调用这两个函数时,可能导致__g_counter的读写冲突(例如:一个任务执行+count时,另一个任务同时执行-count,导致计数结果错误)。
解决方法:使用同一个信号量对__g_counter的所有操作进行互斥保护,确保每次只有一个任务能修改计数器。修改后代码:
static int __g_counter;
os_semaphore_t __g_sem_counter; // 统一的互斥信号量void counter_increment(int count)
{os_semaphore_take(__g_sem_counter); // 获取锁__g_counter += count;os_semaphore_give(__g_sem_counter); // 释放锁
}void counter_decrement(int count)
{os_semaphore_take(__g_sem_counter); // 获取锁__g_counter -= count;os_semaphore_give(__g_sem_counter); // 释放锁
}
问题 6:字符接收与处理任务的错误分析
原代码逻辑:
- 任务 1(
task_get_characters):接收字符,按紧急程度投递到URGENT_QUEUE(紧急队列)或NORMAL_QUEUE(普通队列)。 - 任务 2(
task_consume_characters):死循环中先从紧急队列取字符(os_queue_pend(URGENT_QUEUE, WAIT_FOREVER)),再从普通队列取字符(os_queue_pend(NORMAL_QUEUE, WAIT_FOREVER))。
错误指出:任务 2 会导致普通队列的字符可能永远无法被处理。原因是:os_queue_pend(URGENT_QUEUE, WAIT_FOREVER)表示 “无限等待紧急队列的字符”,若紧急队列长期无数据,任务 2 会一直阻塞在该语句,即使普通队列有字符也无法处理(普通队列被 “饿死”)。
修正思路:对紧急队列使用有限超时等待(如os_queue_pend(URGENT_QUEUE, 10),超时 10 个时间单位),超时后立即检查普通队列,确保普通队列能被轮询到。
问题 7:32 位 CPU 下结构体成员偏移量计算
代码:
#define offsetof(type, member) ((size_t)&((type*)0)->member)typedef struct a_struct {char ch;short data;long value;void *buf;
} a_struct;int main(void)
{printf("%d, %d, %d, %d\n",offsetof(a_struct, ch),offsetof(a_struct, data),offsetof(a_struct, value),offsetof(a_struct, buf));
}
能否正常工作:能正常工作。打印结果:0, 2, 4, 8
解析(32 位 CPU 对齐规则):
ch(char):首成员,偏移 0 字节(1 字节大小)。data(short):需 2 字节对齐,ch占 1 字节,补 1 字节填充(padding),故偏移 2 字节(2 字节大小)。value(long):需 4 字节对齐,前两个成员总占用 4 字节(1+1+2),故偏移 4 字节(4 字节大小)。buf(void*):需 4 字节对齐,value占 4 字节(偏移 4~7),故偏移 8 字节(4 字节大小)。
