嵌入式经典面试题

1. 关键字volatile有什么含意 并给出三个不同的例子。
 
一个定义为volatile的变量是说这变量可能会被意想不到地改变,这样,编译器就不会去假设这个变量的值了。精确地说就是,优化器在用到这个变量时必须每次都小心地重新读取这个变量的值,而不是使用保存在寄存器里的备份。下面是volatile变量的几个例子:
1). 并行设备的硬件寄存器(如:状态寄存器)
2). 一个中断服务子程序中会访问到的非自动变量(Non-automatic variables)
3). 多线程应用中被几个任务共享的变量
 
 
2, 一个指针可以是volatile 吗?解释为什么。
2). 是的。尽管这并不很常见。一个例子是当一个中服务子程序修该一个指向一个buffer的指针时。
 
9. 嵌入式系统总是要用户对变量或寄存器进行位操作。给定一个整型变量a,写两段代码,第一个设置a的bit 3,第二个清除a 的bit 3。在以上两个操作中,要保持其它位不变。
#define BIT3 (0x1<<3)
static int a;
void set_bit3(void)
{
a |= BIT3;
}
void clear_bit3(void)
{
a &= ~BIT3;
}
一些人喜欢为设置和清除值而定义一个掩码同时定义一些说明常数,这也是可以接受的。我希望看到几个要点:说明常数、|=和&=~操作
 
 
11. 中断是嵌入式系统中重要的组成部分,这导致了很多编译开发商提供一种扩展—让标准C支持中断。具代表事实是,产生了一个新的关键字 __interrupt。下面的代码就使用了__interrupt关键字去定义了一个中断服务子程序(ISR),请评论一下这段代码的。
 
__interrupt double compute_area (double radius)
{
double area = PI * radius * radius;
printf(" Area = %f", area);
return area;
这个函数有太多的错误了,以至让人不知从何说起了:
1). ISR 不能返回一个值。如果你不懂这个,那么你不会被雇用的。
2). ISR 不能传递参数。如果你没有看到这一点,你被雇用的机会等同第一项。
3). 在许多的处理器/编译器中,浮点一般都是不可重入的。有些处理器/编译器需要让额处的寄存器入栈,有些处理器/编译器就是不允许在ISR中做浮点运算。此外,ISR应该是短而有效率的,在ISR中做浮点运算是不明智的
4). 与第三点一脉相承,printf()经常有重入和性能上的问题。如果你丢掉了第三和第四点,我不会太为难你的。不用说,如果你能得到后两点,那么你的被雇用前景越来越光明了。
 
13. 评价下面的代码片断:
 
unsigned int zero = 0;
unsigned int compzero = 0xFFFF;
/*1's complement of zero */
 
对于一个int型不是16位的处理器为说,上面的代码是不正确的。应编写如下:
 
unsigned int compzero = ~0;
 
这一问题真正能揭露出应试者是否懂得处理器字长的重要性。在我的经验里,好的嵌入式程序员非常准确地明白硬件的细节和它的局限,然而PC机程序往往把硬件作为一个无法避免的烦恼。
到了这个阶段,应试者或者完全垂头丧气了或者信心满满志在必得。如果显然应试者不是很好,那么这个测试就在这里结束了。但如果显然应试者做得不错,那么我就扔出下面的追加问题,这些问题是比较难的,我想仅仅非常优秀的应试者能做得不错。提出这些问题,我希望更多看到应试者应付问题的方法,而不是答案。不管如何,你就当是这个娱乐吧…
}
 
 
 
1、线形表a、b为两个有序升序的线形表,编写一程序,使两个有序线形表合并成一个有序升序线形表h;
 
 
答案在 请化大学 严锐敏《数据结构第二版》第二章例题,数据结构当中,这个叫做:两路归并排序
Linklist *unio(Linklist *p,Linklist *q){
linklist *R,*pa,*qa,*ra;
pa=p;
qa=q;
R=ra=p;
while(pa->next!=NULL&&qa->next!=NULL){
if(pa->data>qa->data){
ra->next=qa;
qa=qa->next;
}
else{
ra->next=pa;
pa=pa->next;
}
}
if(pa->next!=NULL)
ra->next=pa;
if(qa->next!=NULL)
ra->next==qa;
return R;
}
 
 
3、用递归算法判断数组a[N]是否为一个递增数组。
递归的方法,记录当前最大的,并且判断当前的是否比这个还大,大则继续,否则返回false结束:
bool fun( int a[], int n )
{
if( n= =1 )
return true;
if( n= =2 )
return a[n-1] >= a[n-2];
return fun( a,n-1) && ( a[n-1] >= a[n-2] );
}
 
 
2.单连表的建立,把'a'--'z'26个字母插入到连表中,并且倒叙,还要打印!
方法1:
typedef struct val
{    int date_1;
     struct val *next;
}*p;
 
void main(void)
{    char c;
    
     for(c=122;c>=97;c--)
        { p.date=c;
          p=p->next;
         }
 
     p.next=NULL;
}
}
方法2:
node *p = NULL;
node *q = NULL;
 
node *head = (node*)malloc(sizeof(node));
head->data = ' ';head->next=NULL;
 
node *first = (node*)malloc(sizeof(node));
first->data = 'a';first->next=NULL;head->next = first;
p = first;
 
int longth = 'z' - 'b';
int i=0;
while ( i<=longth )
{
node *temp = (node*)malloc(sizeof(node));
temp->data = 'b'+i;temp->next=NULL;q=temp;
 
head->next = temp; temp->next=p;p=q;
i++;
}
 
print(head);
 
 
一个递规反向输出字符串的例子,可谓是反序的经典例程.
 
void inverse(char *p)
{
     if( *p = = '\0' )
return;
     inverse( p+1 );
     printf( "%c", *p );
}
 
int main(int argc, char *argv[])
{
     inverse("abc\0");
 
     return 0;
}
2。运行的结果为什么等于15
 
#include "stdio.h"
#include "string.h"
 
void main()
{
 
char aa[10];
printf("%d",strlen(aa));
}
 
答案:sizeof()和初不初始化,没有关系;strlen()和初始化有关。
 
 
6。int a,b,c 请写函数实现C=a+b ,不可以改变数据类型,如将c改为long int,关键是如何处理溢出问题
答案:bool add (int a, int b,int *c)
{
*c=a+b;
return (a>0 && b>0 &&(*c<a || *c<b) || (a<0 && b<0 &&(*c>a || *c>b)));
}
 
 
7。分析:
struct bit
{    int a:3;
     int b:2;
     int c:3;
};
int main()
{
   bit s;
   char *c=(char*)&s;
    cout<<sizeof(bit)<<endl;
   *c=0x99;
    cout << s.a <<endl <<s.b<<endl<<s.c<<endl;
      int a=-1;
    printf("%x",a);
   return 0;
}
输出为什么是?
 
答案:4
1
-1
-4
ffffffff
因为0x99在内存中表示为 100 11 001 , a = 001, b = 11, c = 100(在vc环境中,一般是由右到左进行分配的)
当c为有符合数时, c = 100, 最高1为表示c为负数,负数在计算机用补码表示,所以c = -4;同理
b = -1;
当c为有符合数时, c = 100,即 c = 4,同理 b = 3
 
 
9。下面这个程序执行后会有什么错误或者效果:
#define MAX 255
int main()
{
    unsigned char A[MAX],i; //i被定义为unsigned char
    for (i=0;i<=MAX;i++)
       A[i]=i;
}
 
 
答案:死循环加数组越界访问(C/C++不进行数组越界检查)
MAX=255
数组A的下标范围为:0..MAX-1,这是其一..
其二.当i循环到255时,循环内执行:
   A[255]=255;
这句本身没有问题..但是返回for (i=0;i<=MAX;i++)语句时,
由于unsigned char的取值范围在(0..255),i++以后i又为0了..无限循环下去.
 
 
2.对于一个频繁使用的短小函数,C语言中应用什么实现,C++中应用什么实现?
c用宏定义,c++inline
 
6.软件测试都有那些种类?
黑盒:针对系统功能的测试    白合:测试函数功能,各函数接口
 
  1. 进程和线程的差别。
线程是指进程内的一个执行单元,也是进程内的可调度实体.
与进程的区别:
(1)调度:线程作为调度和分配的基本单位,进程作为拥有资源的基本单位
(2)并发性:不仅进程之间可以并发执行,同一个进程的多个线程之间也可并发执行
(3)拥有资源:进程是拥有资源的一个独-立单位,线程不拥有系统资源,但可以访问隶属于进程的资源.
(4)系统开销:在创建或撤消进程时,由于系统都要为之分配和回收资源,导致系统的开销明显大于创建或撤消线程时的开销。
 
11.进程死锁的原因
资源竞争及进程推进顺序非法
12.死锁的4个必要条件
互斥、请求保持、不可剥夺、环路
13.死锁的处理
鸵鸟策略、预防策略、避免策略、检测与解除死锁
 
 
9.纯虚函数如何定义?使用时应注意什么?
virtual void f()=0;
是接口,子类必须要实现
 
 
2:int main()
   {
    int x=3;
    printf("%d",x);
    return 1;
  
   }
问函数既然不会被其它函数调用,为什么要返回1?
mian中,c标准认为0表示成功,非0表示错误。具体的值是某中具体出错信息
  1. 要对绝对地址0x100000赋值,我们可以用
(unsigned int*)0x100000 = 1234;
那么要是想让程序跳转到绝对地址是0x100000去执行,应该怎么做?
*((void (*)( ))0x100000 ) ( );
首先要将0x100000强制转换成函数指针,:
(void (*)())0x100000
然后再调用它:
*((void (*)())0x100000)();
typedef可以看得更直观些:
typedef void(*)() voidFuncPtr;
*((voidFuncPtr)0x100000)();
 
 
位域:  
有些信息在存储时,并不需要占用一个完整的字节,而只需占几个或一个二进制位。例如在存放一个开关量时,只有0和1 两种状态,用一位二进位即可。为了节省存储空间,并使处理简便,C语言又提供了一种数据结构,称为“位域”或“位段”。所谓“位域”是把一个字节中的二进位划分为几个不同的区域,并说明每个区域的位数。每个域有一个域名,允许在程序中按域名进行操作。这样就可以把几个不同的对象用一个字节的二进制位域来表示。一、位域的定义和位域变量的说明位域定义与结构定义相仿,其形式为:    
struct 位域结构名    
{ 位域列表 };   
其中位域列表的形式为:类型说明符位域名:位域长度    
例如:    
struct bs   
{   
int a:8;   
int b:2;   
int c:6;   
};   
位域变量的说明与结构变量说明的方式相同。可采用先定义后说明,同时定义说明或者直接说明这三种方式。例如:    
struct bs   
{   
int a:8;   
int b:2;   
int c:6;   
}data;   
说明data为bs变量,共占两个字节。其中位域a占8位,位域b占2位,位域c占6位。对于位域的定义尚有以下几点说明:   
1. 一个位域必须存储在同一个字节中,不能跨两个字节。如一个字节所剩空间不够存放另一位域时,应从下一单元起存放该位域。也可以有意使某位域从下一单元开始。例如:    
struct bs   
{   
unsigned a:4   
unsigned :0 /*空域*/   
unsigned b:4 /*从下一单元开始存放*/   
unsigned c:4   
}   
在这个位域定义中,a占第一字节的4位,后4位填0表示不使用,b从第二字节开始,占用4位,c占用4位。
2. 由于位域不允许跨两个字节,因此位域的长度不能大于一个字节的长度,也就是说不能超过8位二进位。   
3. 位域可以无位域名,这时它只用来作填充或调整位置。无名的位域是不能使用的。例如:    
struct k   
{   
int a:1   
int :2 /*该2位不能使用*/   
int b:3   
int c:2   
};   
从以上分析可以看出,位域在本质上就是一种结构类型,不过其成员是按二进位分配的。   
二、位域的使用位域的使用和结构成员的使用相同,其一般形式为:位域变量名•位域名位域允许用各种格式输出。   
main(){   
struct bs   
{   
unsigned a:1;   
unsigned b:3;   
unsigned c:4;   
} bit,*pbit;   
bit.a=1;   
bit.b=7;   
bit.c=15;   
pri
 
1、头文件中的 ifndef/define/endif 干什么用?(5分)
答:防止该头文件被重复引用。
 
(2)strcpy能把strSrc的内容复制到strDest,为什么还要char * 类型的返回值?
答:为了实现链式表达式。
 
六、编写类String的构造函数、析构函数和赋值函数(25分)
已知类String的原型为:
    class String
    {
      public:
       String(const char *str = NULL); // 普通构造函数
       String(const String &other);        // 拷贝构造函数
       ~ String(void);                     // 析构函数
       String & operate =(const String &other);  // 赋值函数
      private:
       char   *m_data;             // 用于保存字符串
    };
    请编写String的上述4个函数。
标准答案:
// String的析构函数
   String::~String(void)               // 3分
{
  delete [] m_data;                      
// 由于m_data是内部数据类型,也可以写成 delete m_data;
   }
    // String的普通构造函数            
   String::String(const char *str)      // 6分
{
  if(str==NULL)                         
  {
     m_data = new char[1];    // 若能加 NULL 判断则更好
     *m_data = ‘\0’;                     
  }                                        
  else
  {
     int length = strlen(str);          
     m_data = new char[length+1];  // 若能加 NULL 判断则更好     
     strcpy(m_data, str);               
  }
}
// 拷贝构造函数
   String::String(const String &other)   // 3分
   { 
  int length = strlen(other.m_data);
  m_data = new char[length+1];      // 若能加 NULL 判断则更好   
  strcpy(m_data, other.m_data);        
}
// 赋值函数
   String & String::operate =(const String &other)    // 13分
   { 
      // (1) 检查自赋值                     // 4分
      if(this == &other)
         return *this;
  
// (2) 释放原有的内存资源            // 3分
      delete [] m_data;
     
      // (3)分配新的内存资源,并复制内容 // 3分
  int length = strlen(other.m_data);
  m_data = new char[length+1];         // 若能加 NULL 判断则更好
      strcpy(m_data, other.m_data);
     
      // (4)返回本对象的引用            // 3分
      return *this;
}
 
 

<wiz_tmp_tag id="wiz-table-range-border" contenteditable="false" style="display: none;">

 
 
 
 





上一篇:RL向左?几何深度学习向右?通往第三代人工智能的理论之路如何走?


下一篇:即将发布的2021年度「Pop SOTA!」名录是否漏了贵组的重要工作?速来查缺补漏~