一些高阶的嵌入式笔试题

一 单项选择题(每题4分,共40分)

1. 在中断服务程序中修改变量时,必须使用哪个关键字?

A) static

B) const

C) volatile

D) extern

答案:Cvolatile

解析:volatile 告知编译器该变量可能被意外修改(如中断中修改),禁止编译器优化对该变量的读写操作(如缓存到寄存器)。若不使用,可能导致程序读取过时值或忽略修改。

2. Cortex-M内核中,以下哪个操作会触发HardFault异常?

A)未对齐的LDRD指令访问

B) SVC指令调用

C) __disable_irq()

D) 嵌套中断

答案:A)未对齐的LDRD指令访问

解析:Cortex-M要求某些访问(如LDRD/STRD)必须地址对齐(4的倍数)。未对齐访问会触发HardFaultSVC、关中断、嵌套中断均合法。

3. C语言中,如何声明一个指向函数的指针?

A) int *func(int);

B) int (*func)(int);

C) int func(int);

D) int *func;

答案:Bint (*func)(int);

解析:int (*func)(int) 表示 func 是指向函数的指针,该函数接受一个 int 参数并返回 intA是返回指针的函数,C是函数声明,D是指向整型的指针。

4. 对于32位微控制器,以下哪个操作是原子的(不可中断)?

A) 32位变量的读取

B) 32位变量的写入

C) 64位变量的读取

D) 自增操作(如i++

答案:A32位变量的读取

解析:32MCU的数据总线为32位,对齐的32位变量读写通常单指令完成(原子)。64位变量需多条指令(非原子),自增(i++)涉及读--写三步(非原子)。

5. 在嵌入式系统中,使用位域(bit-field)的优点是什么?

A)节省内存

B) 提高访问速度

C) 便于硬件操作

D) 以上都是

答案:A)节省内存

解析:位域将多个变量压缩到同一字节,节省内存空间(如1位布尔值)。但访问速度可能降低(需位操作),且与硬件寄存器位域对齐时需谨慎。

6. I²C总线抗干扰的核心机制是:

A)开漏输出

B) 总线仲裁

C) 上拉电阻

D) 时钟拉伸

答案:A)开漏输出

解析:开漏输出(OD)允许多设备共享总线,通过上拉电阻置高电平。结合线与逻辑,可检测总线冲突(低电平覆盖高电平),实现仲裁。

7. 硬实时系统的关键指标是:

A) 平均响应时间

B) 最坏响应时间

C) CPU利用率

D) 内存占用率

答案:B)最坏响应时间

解析:硬实时系统要求任务必须在截止时间前完成,最坏响应时间是关键指标。平均响应时间和资源利用率(如CPU)是软实时系统指标。

8. 在实时操作系统中,任务的状态不包括以下哪一种?

A) 运行态

B) 阻塞态

C) 终止态

D) 挂起态

答案:C)终止态

解析:RTOS任务状态通常包括:运行态、就绪态、阻塞态、挂起态。终止态不存在(任务常设计为循环执行或删除后由系统回收资源)。

9. uC/OS-III中,就绪表(Ready List)采用的数据结构是:

A) 链表

B) 位图 + 链表

C) 红黑树

D) 队列

答案:B)位图 + 链表

解析:uC/OS-III使用位图(快速查找最高优先级任务)结合多优先级链表(同优先级任务队列),高效管理就绪任务。

10. 多核系统中Core间共享数据时,需优先采用的同步机制是:

A) 全局变量

B) Spinlock自旋锁

C) 信号量

D) 消息队列

答案:BSpinlock自旋锁

解析:自旋锁通过原子指令(如LDREX/STREX)在共享内存上实现忙等待,避免上下文切换开销,适合短期数据保护。全局变量无原子性,信号量/队列可能引发调度。

二、填空题(每空5分,共25分)

1. Cortex-M的启动文件中,向量表第一个条目是Cortex-M向量表首项为Main Stack Pointer初始值,第二个条目是复位处理函数指针(程序入口)

2. 以下结构体在ARM Cortex-M4下的内存占用为12字节:

struct

{

uint32_t id;

volatile uint8_t status;

unsigned short value;

void (*handler)(void*);

} dev;

3. Cortex-M中断优先级数值越表示优先级越高。

4. 32MCU中访问GPIOB输出寄存器(假设地址为0x40020400),使用指针操作的代码:#define GPIOB_ODR *((volatile uint32_t *)(0x40020400))

三、简答题(每题8分,共32分)

1. 解释嵌入式系统中什么是临界区(Critical Section),如何保护临界区?

参考答案:

临界区是访问共享资源的代码段,需独占执行(避免数据竞争)。

保护方法:

l  关中断:禁止中断(如__disable_irq()),适合裸机系统。

l  调度锁:禁止任务切换(如vTaskSuspendAll()),适用于RTOS

l  原子操作:利用硬件指令(如LDREX/STREX)实现单步读写。

l  互斥锁:RTOS中的互斥量(Mutex),阻塞其他任务访问。

2. 解释内存对齐(Alignment)的概念及其在嵌入式系统中的重要性。

参考答案:

内存对齐要求数据地址是自身大小的整数倍(如4字节变量地址需是4的倍数)。

重要性:

l  硬件要求:Cortex-M0等内核不支持未对齐访问,触发HardFault

l  效率优化:对齐访问可单周期完成,未对齐需多次访问。

l  缓存友好:对齐数据提升缓存命中率。

3. 为什么RTOS需要提供中断安全版”API(如xQueueSendFromISR)?在中断服务程序中调用普通API(如 xQueueSend)可能引发什么问题?

参考答案:

l  普通API(如xQueueSend)可能触发任务调度。若在中断中调用,会尝试切换到任务上下文,导致未定义行为(中断无任务控制块)。

l  中断安全API(如xQueueSendFromISR

特性:

1. 避免阻塞操作。

2. 仅标记需切换任务(如xHigherPriorityTaskWoken),退出中断后调度。

3. 使用中断专属队列或缓冲区。

4. AMP(非对称多处理)架构下,如何实现双核(Core0 & Core1)间的任务协作与负载均衡?

参考答案:

在非对称多处理(AMP)中(如Cortex-A7+Cortex-M4),各核独立运行OS/裸机。

实现方式:

l  共享内存:预留物理内存区域,双核通过地址映射访问。

l  核间中断(IPI):一核触发另一核中断,通知事件(如数据就绪)。

l  消息传递:邮箱/消息队列传递任务信息。

l  静态分配:按功能划分任务(如Core0处理通信,Core1处理算法)。

l  动态负载均衡:主核监控负载,通过IPI通知从核调整任务。

四、编程题

1. 表达式解析器(23分)

要求:实现一个支持 + - * / ()的算术表达式解析器

函数原型:double evalExpression(const char* expr)

示例:输入 "3*(4+5)/2" -> 返回 13.5

附加要求:处理浮点数并验证表达式合法性

参考答案:

#include <ctype.h>

#include <stdlib.h>

#include <string.h>

#include <math.h>

// 栈结构实现

#define MAX_STACK 128

typedef struct {

    double data[MAX_STACK];

    int top;

} NumStack;

typedef struct {

    char data[MAX_STACK];

    int top;

} OpStack;

void numPush(NumStack* s, double val) {

    s->data[++s->top] =   val;

}

double numPop(NumStack* s) {

    return   s->data[s->top--];

}

void opPush(OpStack* s, char op) {

    s->data[++s->top] =   op;

}

char opPop(OpStack* s) {

    return   s->data[s->top--];

}

// 运算符优先级比较

int precedence(char op) {

    if (op == '+' || op ==   '-') return 1;

    if (op == '*' || op ==   '/') return 2;

    return 0;  // 括号优先级最低

}

// 执行二元运算

void calculate(NumStack* num_stack, OpStack* op_stack) {

    char op = opPop(op_stack);

    double b =   numPop(num_stack);

    double a =   numPop(num_stack);

    switch (op) {

        case '+':   numPush(num_stack, a + b); break;

        case '-':   numPush(num_stack, a - b); break;

        case '*':   numPush(num_stack, a * b); break;

        case '/':

            if (fabs(b) <   1e-9) { // 处理除零错误

                 numPush(num_stack, 0);

            } else {

                numPush(num_stack,   a / b);

            }

            break;

    }

}

double evalExpression(const char* expr) {

    NumStack num_stack = {   .top = -1 };

    OpStack op_stack = { .top   = -1 };

    int expect_operand =   1;  // 标记是否期望操作数(用于处理负数)

    // 添加安全终止符防止越界

    char buf[1024];

    snprintf(buf, sizeof(buf),   "%s", expr);

    const char* p = buf;

    while (*p) {

        if (isspace(*p)) { // 跳过空格

            p++;

            continue;

        }

        // 处理数字(包括负数)

        if (expect_operand   && (*p == '-' || isdigit(*p)) || isdigit(*p) || *p == '.') {

            char* end;

            double num =   strtod(p, &end);

            if (end == p)   return NAN; // 无效表达式

             numPush(&num_stack, num);

            p = end;

            expect_operand =   0; // 下一个期望操作符

            continue;

        }

        // 处理左括号

        if (*p == '(') {

             opPush(&op_stack, '(');

            p++;

            expect_operand =   1; // 括号后期待操作数或负数

            continue;

        }

        // 处理右括号

        if (*p == ')') {

            while   (op_stack.top >= 0 && op_stack.data[op_stack.top] != '(') {

                 calculate(&num_stack, &op_stack);

            }

            if (op_stack.top   < 0 || op_stack.data[op_stack.top] != '(') {

                return NAN; //  括号不匹配

            }

            op_stack.top--; //  弹出 '('

            p++;

            expect_operand =   0; // 右括号后期待操作符

            continue;

        }

        // 处理运算符

        if (*p == '+' || *p ==   '-' || *p == '*' || *p == '/') {

            // 处理负数:当前是-号且上一个token是运算符或左括号

            if (*p == '-'   && expect_operand) {

                // 压入0以处理负号:例如 -3 → 0-3

                 numPush(&num_stack, 0);

                 opPush(&op_stack, '-');

                p++;

                continue;

            }

            // 比较运算符优先级

            while   (op_stack.top >= 0 &&

                    precedence(op_stack.data[op_stack.top]) >= precedence(*p)) {

                 calculate(&num_stack, &op_stack);

            }

             opPush(&op_stack, *p++);

            expect_operand =   1; // 运算符后期待操作数

        }

        else {

            return NAN; // 无效字符

        }

    }

    // 完成剩余计算

    while (op_stack.top >=   0) {

        if   (op_stack.data[op_stack.top] == '(') return NAN; // 未匹配括号

         calculate(&num_stack, &op_stack);

    }

    return   numPop(&num_stack); // 返回最终结果

}

// 测试用例

int main() {

    printf("%f\n",   evalExpression("3*(4+5)/2"));    // 13.5

    printf("%f\n",   evalExpression("-2*(-3+5)"));    // -4.0

    printf("%f\n",   evalExpression("2+3 * 4"));        // 14.0

    printf("%f\n",   evalExpression("(1+2)*(3+4)")); // 21.0

    return 0;

}

算法解析:

双栈处理:

num_stack 存储操作数

op_stack 存储运算符和括号

负数处理:

当遇到-号且前面没有操作数时(如表达式开头或(后),压入0转换为二元运算

优先级处理:

当前运算符优先级小于等于栈顶运算符时,先计算栈顶操作

括号处理:

(直接入栈;遇到)时计算括号内所有操作

合法性验证:

括号匹配检查

无效字符检测(如字母)

除零错误处理

2. 动态规划算法(30分)

题目:编写一个函数,计算两个字符串的最长公共子序列(Longest Common Subsequence)的长度。

要求:函数原型 int lcs_length(char *s1, char *s2)

示例:输入"ABCDGH""AEDFHR",输出3(对应"ADH")。

参考答案:

int lcs_length(char *s1, char *s2) {

    int m = strlen(s1);

    int n = strlen(s2);

    // 创建二维DP (滚动数组优化空间)

    int dp[2][n + 1];

    memset(dp, 0, sizeof(dp));

    int idx = 0; // 滚动索引

    for (int i = 1; i <= m;   i++) {

        idx = 1 - idx; // 切换行

        for (int j = 1; j   <= n; j++) {

            if (s1[i - 1] ==   s2[j - 1]) {

                dp[idx][j] =   dp[1 - idx][j - 1] + 1;

            } else {

                dp[idx][j] = fmax(dp[1 - idx][j],   dp[idx][j - 1]);

            }

        }

    }

    return dp[idx][n];

}

// 测试用例

int main() {

    printf("%d\n",   lcs_length("ABCDGH", "AEDFHR"));  // 3 (ADH)

    printf("%d\n",   lcs_length("AGGTAB", "GXTXAYB")); // 4 (GTAB)

    printf("%d\n",   lcs_length("ABC", "XYZ"));        // 0

    return 0;

算法解析:

DP状态定义:

dp[i][j]  表示   s1[0..i-1]  s2[0..j-1]  LCS  长度

状态转移方程:

if   s1[i-1] == s2[j-1]:

     dp[i][j] = dp[i-1][j-1] + 1

else:

     dp[i][j] = max(dp[i-1][j], dp[i][j-1])

空间优化:

使用滚动数组(两行交替)将空间复杂度从 O(mn) 降至 O(n)

时间效率:

时间复杂度 O(mn),空间复杂度 O(n)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值