嵌入式软件笔试题

题目1

下面函数是从链表中摘除节点,请补充代码使其完整:

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) {
                // 填空1
            
            }else {
                // 填空2
       

            }
            l->next = NULL;
            return;
        }
        r=l;
    }
}

答案:

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 == (struct list *) item) {
            if (r == NULL) {
                *list = l->next; // 填空1
            } else {
                r->next = l->next; // 填空2
            }
            l->next = NULL;
            return;
        }
        r = l;
    }
}



问题2

在32位CPU上,下面代码能否正常工作,如果能,请写出可能的打印结果。

#include "stdio.h"

// 在32位CPU上,下面代码能否正常工作,如果能,请写出可能的打印结果。

typedef struct a_struct {
    char ch;
    short data;
    long value;
    void *buf;
};

#define offsetof(type,member)   (&((type *)0)->member)


int main(void){
    printf("%d,%d,%d,%d\r\n",
        offsetof(struct a_struct ,ch),
        offsetof(struct a_struct ,data),
        offsetof(struct a_struct ,value),
        offsetof(struct a_struct ,buf)
    );

    // 0 2 4 8 
    // (&((type *)0)->member)是C语言中用于计算结构体成员在结构体中的偏移量的一种常用技巧。这行代码的含义可以拆解如下:
/* 
    (type *)0: 将0强制转换为指向type类型的指针。在这里,0被解释为一个空指针,然后被转换为指向type类型的指针。因为指针大小在32位平台上通常为4字节,所以这里假设type类型占用的空间大小小于等于4字节。
    ->member: 通过箭头操作符,访问结构体指针所指向的结构体中的成员member。这样做的前提是0处的内存被假设为一个type类型的结构体实例,然后通过该指针访问其中的成员。
    &: 取得成员member的地址。
    所以(&((type *)0)->member)实际上是获取了member成员在type类型结构体中的偏移量。这个技巧常用于实现offsetof宏,用来在编译期计算结构体成员的偏移量,而不需要运行时信息。 */
}

问题3

// 下面的程序由任务(线程)A,B,C调用,但他们不能工作,怎么解决这个问题?.

static init __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;
}

void counter_decrement(int count) {
    _g_counter -= count;
    os_semaphore_give(__g_sem_minus);
}

提供的代码中,存在一些问题可能导致任务A、B和C无法正常工作。主要问题在于信号量(semaphore)的使用有误以及全局变量_g_counter未正确初始化。

解决这个问题,您可以按照以下方式调整代码:

初始化全局计数器 __g_counter:

static int _g_counter = 0;

初始化信号量 __g_sem_plus 和 __g_sem_minus:
在程序开始处添加以下代码:

os_semaphore_create(&__g_sem_plus, 1); // 初始化为可用
os_semaphore_create(&__g_sem_minus, 0); // 初始化为不可用

更正函数定义:
修改 counter_increment 和 counter_decrement 函数如下:

void counter_increment(int count) {
    os_semaphore_take(__g_sem_plus);
    _g_counter += count;
    os_semaphore_give(__g_sem_minus);
}

void counter_decrement(int count) {
    os_semaphore_take(__g_sem_minus);
    _g_counter -= count;
    os_semaphore_give(__g_sem_plus);
}

通过以上改动,确保全局变量 _g_counter 被正确初始化,同时修改信号量的使用,使得 counter_increment 和 counter_decrement 函数可以正常工作,避免竞争条件和死锁情况。


问题可能在于多个任务(线程)同时访问共享资源__g_counter,而没有进行合适的同步操作,导致竞态条件(race condition)。在这种情况下,任务A、B、C可能会在不同的时刻同时修改__g_counter,导致不可预测的结果和错误行为。

要解决这个问题,你可以使用信号量或者互斥锁来对__g_counter进行保护,确保同时只有一个任务可以访问它。下面是修改后的代码:


static init __g_counter;

os_mutex_t __g_mutex; // 互斥锁,用于保护 __g_counter

void counter_increment(int count) {
    os_mutex_lock(__g_mutex); // 加锁
    _g_counter += count;
    os_mutex_unlock(__g_mutex); // 解锁
}

void counter_decrement(int count) {
    os_mutex_lock(__g_mutex); // 加锁
    _g_counter -= count;
    os_mutex_unlock(__g_mutex); // 解锁
}

在这个修改后的版本中,通过使用互斥锁(os_mutex_t)来确保在counter_increment和counter_decrement函数中对__g_counter的访问是原子的。这样,同一时间只有一个任务可以访问__g_counter,从而避免了竞态条件的发生。

另外,你也可以使用信号量来实现同样的效果,即在访问__g_counter之前先获取信号量,在访问完成后释放信号量。

问题4

下面代码包含2个函数,第一个函数是中断服务程序用于从温湿度传感器中读取数据,第一个函数用于比较温度是否一致,他是否包括错误?如果有,如果纠正错误?

回答:

在给定的代码中,存在一个潜在的竞态条件(race condition)问题。竞态条件是指多个线程或进程并发执行时,由于执行顺序的不确定性导致程序出现不一致或错误的情况。

问题出现在中断服务程序isr_temperatures_read中,该程序从两个温度传感器中读取数据并存储在__g_temperatures数组中。由于中断可能在任何时刻发生,因此当一个中断正在更新__g_temperatures[0]时,另一个中断可能同时更新__g_temperatures[1],这可能导致数据不一致的情况。

为了解决这个问题,可以使用临界区(critical section)来确保在更新__g_temperatures数组时不被中断打断。具体做法是在更新__g_temperatures数组时禁用中断,然后在更新完成后重新启用中断。

static int __g_temperatures[2];

__interrupt void isr_temperatures_read(void) {
    // 禁用中断
    disable_interrupts();

    __g_temperatures[0] = temperature_sensor1_read();
    __g_temperatures[1] = temperature_sensor2_read();

    // 重新启用中断
    enable_interrupts();
}

void main(void) {
    while(1) {
        // 临界区开始
        disable_interrupts();
        if(__g_temperatures[0] != __g_temperatures[1]) {
            /* alarm*/
        }
        // 临界区结束
        enable_interrupts();
    }
}

在新代码中,使用了disable_interrupts()和enable_interrupts()函数来分别禁用和重新启用中断。这样就确保了在更新__g_temperatures数组时不会被中断打断,从而避免了竞态条件问题。

题目5

C语言中,可以使用free对malloc申请的堆内存,进行释放2次么,为什么

在C语言中,对同一块内存使用free进行释放两次是不允许的,因为这可能导致程序的未定义行为。

当你使用malloc函数分配内存时,系统会记录该内存块的状态,并且返回一个指向该内存块的指针。当你调用free函数释放内存时,系统会将该内存块标记为可用状态,以便其他程序可以再次使用它。

如果你对同一块内存使用free进行两次释放,可能会出现以下问题之一:

内存泄漏: 如果你释放了内存两次,第二次释放可能会导致内存管理数据结构被破坏,从而使得这部分内存无法再次被分配,从而导致内存泄漏。
程序崩溃或未定义行为: 第二次释放可能会导致指向已释放内存的指针变为悬挂指针(dangling pointer),从而导致程序在访问已释放的内存时崩溃或产生未定义的行为。
为了避免这些问题,C语言规定对同一块内存只能使用一次free进行释放。通常情况下,你应该在确保不再需要这块内存时才调用free函数。

题目 6

a) 一个整型数(An integer)
b) 一个指向整型数的指针(A pointer to an integer)
c) 一个指向指针的的指针,它指向的指针是指向一个整型数(A pointer to a pointer
to an integer)
d) 一个有10个整型数的数组(An array of 10 integers)
e) 一个有10个指针的数组,该指针是指向一个整型数的(An array of 10 pointers to
integers)
f) 一个指向有10个整型数数组的指针(A pointer to an array of 10 integers)
g) 一个指向函数的指针,该函数有一个整型参数并返回一个整型数(A pointer to a fu
nction that takes an integer as an argument and returns an integer)
h) 一个有10个指针的数组,该指针指向一个函数,该函数有一个整型参数并返回一个整型
数( An array of ten pointers to functions that take an integer argument and r
eturn an integer )`函数指针数组`
 a) int a; // An integer 
 b) int *a; // A pointer to an integer 
 c) int **a; // A pointer to a pointer to an integer 
 d) int a[10]; // An array of 10 integers 
 e) int *a[10]; // An array of 10 pointers to integers 
 f) int (*a)[10]; // A pointer to an array of 10 integers 
 g) int (*a)(int); // A pointer to a function a that takes an integer argument and returns an integer 
 h) int (*a[10])(int); // An array of 10 pointers to functions that take an integer argument and return an integer 

题目7

可以使用预处理指令#define声明一个常数来表示1年中的秒数,例如:

#define SECONDS_PER_YEAR (365 * 24 * 60 * 60)

在这个定义中,SECONDS_PER_YEAR表示一年中的秒数,它被计算为365天乘以24小时乘以60分钟乘以60秒,即每天的秒数乘以365。使用括号将表达式括起来是一个好的习惯,以确保在使用时没有优先级问题

关键字volatile有什么含意(举例说明)?

一个定义为 volatile 的变量是说这变量可能会被意想不到地改变,这样,编译器就不会去假设这个变量的值了。
精确地说就是,优化器在用到这个变量时必须每次都小心地重新读取这个变量的值,而不是使用保存在寄存器里的备份。

下面是 volatile 变量的几个例子:
并行设备的硬件寄存器(如:状态寄存器)
一个中断服务子程序中会访问到的非自动变量(Non-automatic variables)
多线程应用中被几个任务共享的变量

嵌入式开发C语言,常用的数据结构有哪些

在嵌入式开发中,常用的数据结构与传统的C语言相似,但有些因为资源受限而更常用或有特定的优化选择。以下是一些在嵌入式开发中常见的数据结构:

  • 数组(Array):在嵌入式系统中,数组常用于存储一组相同类型的数据,因为它们在内存中是连续存储的,可以高效地访问。
  • 链表(Linked List):链表在嵌入式系统中常用于动态分配内存以及实现轻量级的数据结构。由于链表的节点可以动态分配,这在内存受限的环境中非常有用。
  • 栈(Stack):栈常用于存储临时数据和函数调用的上下文信息。在嵌入式系统中,栈的大小可能会受到限制,因此需要仔细管理栈空间。
  • 队列(Queue):队列常用于缓存和调度数据。在嵌入式系统中,队列通常用于任务间的通信和数据传输。
  • 树(Tree):树结构在嵌入式系统中也有应用,例如用于文件系统、数据结构的组织等。
  • 哈希表(Hash Table):虽然在资源受限的嵌入式系统中,使用哈希表可能会受到一定的限制,但它仍然是一种高效的数据结构,常用于实现快速查找。
  • 位域(Bit Fields):位域是一种将结构成员字段映射到特定位数的数据类型,通常用于节省内存或与硬件交互。
  • 堆(Heap):堆用于动态分配内存,虽然在嵌入式系统中使用堆需要谨慎考虑内存碎片和分配效率,但在某些情况下仍然有用。

这些数据结构在嵌入式系统中经常使用,但具体的选择取决于应用程序的要求、资源限制以及性能需求。在嵌入式开发中,通常需要权衡内存使用、执行效率和代码复杂性等因素来选择合适的数据结构。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值