嵌入式相关5

 

 

 

嵌入式常用定义整理

  1. 简述常见的嵌入式存储器和特点(4种以上)。
  2. ROM、SRAM、DRAM。

    根据掉电数据是否丢失,存储器可以分为RAM(随机存取器)和ROM(只读存储器),其中RAM的访问速度比较快,但掉电后数据会丢失,而ROM掉电后数据不会丢失。人们通常所说的内存即指系统中的RAM。

    RAM又可分为SRAM(静态存储器)和DRAM(动态存储器)

    SRAM是利用双稳态触发器来保存信息的,只要不掉电,信息是不会丢失的。

    DRAM是利用MOS(金属氧化物半导体)电容存储电荷来储存信息的,因此必须通过不停的给电容充电来维持信息。DRAM的成本、集成度、功耗明显优于SRAM。

    通常人们所说的SDRAM是DRAM的一种,它是同步动态存储器,利用单一的系统时钟同步所有的地址数据和控制信号。使用SDRAM不但能提高系统的表现,还能简化设计、提供高速的数据传输,在嵌入式系统中经常使用。

  3. Flash是一种非易失闪存技术,由于它具有和ROM一样掉电数据不会丢失的特性。Flash主要分为NOR Flash和NAND Flash两种。

    NOR Flash的特点是在芯片内执行(Execute In Place),这样应用程序可以直接在Flash内运行,不必再把代码读到系统RAM中。

    NAND Flash能提供极高的单元密度,可以达到高存储密度,NAND读和写操作采用512字节块,单元尺寸几乎是NOR器件的一半,同时由于生产过程很简单,大大降低了生产的成本。NAND Flash中每个块的最大擦写次数是100万次,是NOR Flash的10倍,这些使得NAND Flash越来越受到人们的喜爱。

  4. 简述常见嵌入式软件交叉调试方式,并简单描述。

    软件交叉调试方式主要是通过插入调试桩的方式来进行的。调试桩方式进行调试是通过目标操作系统和调试器内分别加入某些功能模块,两者互通信息来进行调试。该方式的典型调试器有Gdb调试器。

    Gdb的交叉调试器分为GdbServer和GdbClient,其中GdbServer就作为调试桩在安装在目标板上,GdbClient就是驻于本地的Gdb调试器。

    它们的调试原理图如图所示:

     

     

     

     

     

     

  5. Linux中函数库有那3种使用方式,简述之。

    Linux中函数库可以有三种使用的形式:静态、共享和动态。静态库的代码在编译时就已连接到开发人员开发的应用程序中,而共享库只是在程序开始运行时才载入。

    动态库也是程序运行时载入,但是与共享库不同的是,动态库使用的库函数不是在程序运行使开始载入,而是在程序中的语句需要时使用时才载入。动态库可以在程序运行期间释放动态库所占用的内存,腾出空间供其他程序使用。

    由于共享库和动态库并没有在程序中包括库函数的内容,只是包含了对库函数的引用,因此代码的规模比较小。

  6. 简述栈和堆的区别。

    栈是由编译器在需要时分配的,不需要时自动清除的变量存储区。里面的变量通常是局部变量、函数参数等。堆是由malloc()函数(C++语言为new运算符)分配的内存块,内存释放由程序员手动控制,在C语言为free函数完成(C++中为delete)。栈和堆的主要区别有以下几点:(1)管理方式不同。栈编译器自动管理,无需程序员手工控制;而堆空间的申请释放工作由程序员控制,容易产生内存泄漏。(2)空间大小不同。栈是向低地址扩展的数据结构,是一块连续的内存区域。这句话的意思是栈顶的地址和栈的最大容量是系统预先规定好的,当申请的空间超过栈的剩余空间时,将提示溢出。因此,用户能从栈获得的空间较小。堆是向高地址扩展的数据结构,是不连续的内存区域。因为系统是用链表来存储空闲内存地址的,且链表的遍历方向是由低地址向高地址。由此可见,堆获得的空间较灵活,也较大。栈中元素都是一一对应的,不会存在一个内存块从栈中间弹出的情况。(3)是否产生碎片。对于堆来讲,频繁的malloc/free(new/delete)势必会造成内存空间的不连续,从而造成大量的碎片,使程序效率降低(虽然程序在退出后操作系统会对内存进行回收管理)。对于栈来讲,则不会存在这个问题。

    (4)增长方向不同。堆的增长方向是向上的,即向着内存地址增加的方向;栈的增长方向是向下的,即向着内存地址减小的方向。(5)分配方式不同。堆都是程序中由malloc()函数动态申请分配并由free()函数释放的;栈的分配和释放是由编译器完成的,栈的动态分配由alloca()函数完成,但是栈的动态分配和堆是不同的,他的动态分配是由编译器进行申请和释放的,无需手工实现。(6)分配效率不同。栈是机器系统提供的数据结构,计算机会在底层对栈提供支持:分配专门的寄存器存放栈的地址,压栈出栈都有专门的指令执行。堆则是C函数库提供的,它的机制很复杂,例如为了分配一块内存,库函数会按照一定的算法(具体的算法可以参考数据结构/操作系统)在堆内存中搜索可用的足够大的空间,如果没有足够大的空间(可能是由于内存碎片太多),就有需要操作系统来重新整理内存空间,这样就有机会分到足够大小的内存,然后返回。显然,堆的效率比栈要低得多。

  7. 简述什么是系统调用;

    系统调用是操作系统提供给外部程序的接口。在C语言中,操作系统的系统调用通常通过函数调用的形式完成。因为这些函数封装了系统调用的细节,将系统的调用的入口、参数以及返回值用C语言的函数调用过程实现。在Linux系统中,系统调用函数定义在glibc中。系统调用需要注意以下几点。

    (1)系统调用函数通常在成功时返回0值,不成功时返回非0值。如果要检查失败原因,则要判断全局变量errno的值,errno中包含错误代码。

    (2)许多系统调用的返回数据通常通过引用参数传递。这时,需要在函数参数传递缓冲去地址,而返回的数据就保存在该缓冲区中。

    (3)不能认为系统调用函数比其他函数的执行效率高。因为系统调用是一个非常耗时的过程。

  8. 简述chmod 文件权限0-7的含义。

  9. Linux I/O处理5种模型的名称。

    总体来说,I/O处理的模型有5种。

    (1)阻塞I/O模型:在这种模型下,若所调用的I/O函数没有完成相关功能就会使进程挂起,直到相关数据到才会出错返回。如常见对管道设备,终端设备和网络设备进行读写时经常会出现这种情况。

    (2)非阻塞I/O模型:在这种模型下,当所请求的I/O的操作不能完成时,则不让进程睡眠,而且立即返回。非阻塞I/O使用户可以调用不会阻塞的I/O操作如open、write和read。如果该操作不能完成,则会立即返回出错或者返回0。

    (3)多路选择I/O模型,在这种模型下,如果请求的I/O操作阻塞,且它不是真正阻塞I/O,而是让其中的一个函数等待,在这期间,I/O还能进行其他操作。如select和poll函数。

    (4)信号驱动I/O模型,在这种模型下,通过安装一个信号处理程序,系统可以自动捕获特定信号的到来,从而启动I/O。

    (5)异步I/O模型,在这种模型下,当一个描述符已准备好,可以启动I/O时,进程会通知内核,但并不所有的系统都支持这种模型。

  10. 串口初始化设置波特率115200,8个数据位,1特停止位,无奇偶校验,请写出初始化程序。
  11. 简述进程和程序的区别

    进程是一个程序的一次执行的过程,同时也是资源分配的最小单元。它和程序有本质区别的,程序是静态的,它是一些保存在磁盘上的指令的有序集合,没有任何执行的概念;而进程是一个动态的概念,它是程序执行的过程,包括了动态创建、调度、和消亡的整个过程。它是程序执行的资源管理的最小单位。因此,对系统而言,当用户在系统中键入命令执行一个程序时候,它将启动一个进程。

  12. 简述Linux进程5种状态。
  13. 运行(TASK_RUNNING):一般指就绪状态,也就是指进程随时可以投入到运行和运行状态。
  14. 可中断(TASK_INTERUPTIBLE):在这个状态时进程停止运行,直到它获得满足它继续运行的条件。
  15. 不可中断(TASK_UNINTERUPTIBLE):在这个状态时进程也是停止运行,但是,即便它获得满足它继续运行的条件,它也不会马上被激活。
  16. 僵死(TASK_ZOMBIE):进程运行结束,等待父进程销毁它。
  17. 停止(TASK_STOPPED):进程停止运行,当进程收到SIGSTOP、SIGTSTP、SIGTTIN、SIGTTOU等信号,就会停止。在调试期间,进程收到任何信号,也会停止运行。
  18. 什么是管道,简要描述有名管道和无名管道。

    管道是Linux中进程间通信的一种方式,它把一个进程的输出直接连接到另一个进程的输入。Linux的管道主要包括两种:无名管道和有名管道。

    无名管道是一种专门用来实现进程间通信的特殊文件,和普通文件有一定的差异:无名管道只能存在于进程进程通信期间,通信完成后将自动消失,而且只能临时存放通信的信息,不能像普通文件一样存储大量常规信息。但是,在编程应用方式,具有和普通文件一样的特点,可以使用read/write等函数进行读写操作,但不能使用lseek函数来修订当前的读写位置,因为管道需要满足FIFO的原则。

    有名管道和普通文件一样具有文件存放路径、文件权限,存在于磁盘中;但是,有名管道和普通文件又有区别,有名管道不能直接存储文件,它存储的通信信息在两个进程结束后自动丢失。通信的两个进程结束后,有名管道的文件本身仍然存在,这是和无名管道不一样的地方。

  19. TCP/IP协议四层模型名称和功能。

    网络接口层:负责将二进制流转换为数据帧,并进行数据帧的发送和接收。数据帧是独立的网络信息传输单元。

    网络层:负责将数据帧封装成IP数据包,并运行必要的路由算法。

    传输层:负责端到端之间的通信会话连接与建立。传输协议的选择根据数据传输方式而定。

    应用层:负责应用程序的网络访问,这里通过端口号来识别各个不同的进程。

  20. 常见的Socket有哪三种,分别描述。
  21. 流式socket(SOCK_STREAM)流式:流式套接字提供可靠的、面向连接的通信流;它使用TCP协议,从而保证了数据传输的正确性和顺序性。
  22. 数据报socket(SOCK_DGRAM)数据报套接字定义了一种无连接的服务,数据通过相互独立的报文进行传输,是无序的,并且不保证是可靠的、无差错的。它使用数据报协议UDP。
  23. 原始socket:原始套接字允许对底层协议如IP和ICMP进行直接访问,它功能强大但使用较为不便,主要用于一些协议的开发。
  24. 用Socket实现TCP通信服务端程序,接收客户端内容并打印到终端。
  25. 用Socket实现TCP通信客户端程序,客户端向服务端发送hello world 信息。

     

     

     

     

     

     

     

     

    选择题

     

    1:设float a=2, b=4, c=3;,以下C语言表达式与代数式 (a+b)+c计算结果不一致的是

    A.(a+b)*c/2

    B.(1/2)*(a+b)*c

    C.(a+b)*c*1/2

    D.c/2*(a+b)

    参考答案:B,因为a,b,c三个变量都是浮点数,所以在B答案中其结果是0,因为在计算1/2是就是0,如果改成1/2.0就正确了。

     

    2:为了向二进制文件尾部增加数据,打开文件的方式应采用

    A.″ab″

    B.″rb+″

    C.″wb″

    D.″wb+″

    参考答案:D

     

    3:下述程序执行后的输出结果是

    #include

    main()

    {

    int x='f';

    printf("%c ",'a'+(x-'a'+1));

    }

    A.g

    B.h

    C.i

    D.j

    参考答案:A

     

    4:C语言中,下列运算符优先级最高的是

    A.!

    B.%

    C.>>

    D.= =

    参考答案:A

     

    5:数组定义为" int a [ 4 ] ; ",表达式 ( ) 是错误的。

    A.*a

    B.a [ 0 ]

    C.a

    D.a++

    参考答案:D

     

    6:执行语句" k=7>>1; "后,变量 k 的当前值是

    A.15

    B.31

    C.3

    D.1

    参考答案:C

     

    7:定义函数时,缺省函数的类型声明,则函数类型取缺省类型

    A.void

    B.char

    C.float

    D.int

    参考答案:D

     

    8:若main()函数带参数,参数个数最多是

    A.0

    B.1

    C.2

    D.3

    参考答案:C 只知道有定义形式main(int argc,char* argv[]))

     

    9:若有宏定义:#define MOD(x,y) x%y

    则执行以下语句后的输出结果是

    int a=13,b=94;

    printf(″%d ″,MOD(b,a+4));

    A.5

    B.7

    C.9

    D.11

    参考答案:B

     

    10:下列各个错误中,哪一个不属于编译错误

    A.改变 x 原值 3 为 5 ,写作" x==5 ;"

    B.花括号不配对

    C.复合语句中的最后一条语句后未加分号

    D.变量有引用、无定义

    参考答案:A

     

    11:下列程序段运行后, x 的值是( )

     

    a=1;b=2;x=0;

    if(!( -- a))x -- ;

    if(!b)x=7;else ++x;

    A.0

    B.3

    C.6

    D.7

    参考答案:A

     

    12:设

    #define N 3

    #define Y(n) ((N+1)*n)

    则表达式2*(N+Y(5+1))的值是

    A.42

    B.48

    C.54

    D.出错

    参考答案:B Y(5+1) 传递过去的应该是6,而不是简单的把5+1给替换掉

     

    13:若定义了char ch[]={″abcdef″},*p=ch;则执行printf(″%c″,*p+4);语句的输出结果是

    A.def

    B.d

    C.e

    D.0

    参考答案:C

     

    14:下列转义字符中错误的是

    A.′00′

    B.′14′

    C.′x111′

    D.′2′

    参考答案:C error C2022: '273' : too big for character

     

    15:算术运算符,赋值运算符和关系运算符的运算优先级按从高到低依次为

    A.算术运算、赋值运算、关系运算

    B.算术运算、关系运算、赋值运算

    C.关系运算、赋值运算、算术运算

    D.关系运算、算术运算、赋值运算

    参考答案:B

     

    16:设#define N 3

    #define Y(n) ((N+1)*n)

    则表达式2*(N+Y(5+1))的值是

    A.42

    B.48

    C.54

    D.出错

    参考答案:B

     

    17:表达式 strcmp( " 3.14 "," 3.278 " ) 的值是一个

    A.非零整数

    B.浮点数

    C.0

    D.字符

    参考答案: A

     

    18:设struct

    { short a;

    char b;

    float c;

    }cs;

    则sizeof(cs)的值是

    A.4

    B.5

    C.6

    D.7

    参考答案: D 字节对齐的话应该是8

     

    19:若变量已正确定义,表达式( j=3 , j++ )的值是

    A.3

    B.4

    C.5

    D.0

    参考答案:A

     

    20:C 语言中运算对象必须是整型的运算符是

    A.%

    B./

    C.!

    D.**

    参考答案:A

    简答题

     

    21:打印一个N*N的方阵,N为每边字符的个数( 3〈N〈20 ),要求最外层为"X",第二层为"Y",从第三层起每层依次打印数字0,1,2,3,...

    例子:当N =5,打印出下面的图形:

    X X X X X

    X Y Y Y X

    X Y 0 Y X

    X Y Y Y X

    X X X X X

     

    22:谈谈COM的线程模型。然后讨论进程内/外组件的差别。

     

    23:多态类中的虚函数表是Compile-Time,还是Run-Time时建立的?

    参考答案:

    虚拟函数表是在编译期就建立了,各个虚拟函数这时被组织成了一个虚拟函数的入口地址的数组.

    而对象的隐藏成员--虚拟函数表指针是在运行期--也就是构造函数被调用时进行初始化的,这是实现多态的关键.

     

    24:#include

     

      void main()

     

      { int c;

     

      while ((c=getchar())!= ′ n ′ )

     

      switch(c - ′ 2 ′ )

     

      { case 0:

     

      case 1:putchar(c+4);break;

     

      case 2:putchar(c+4);break;

     

      case 3:putchar(c+3);break;

     

      default:putchar(c+2);break;

     

      }

     

      printf( ″ n ″ );

     

      }

     

      运行时输入: 2473 ,输出结果:

    参考答案:6897 VC++6.0测试过

    25:用两个栈实现一个队列的功能?

    参考答案:

    设2个栈为A,B, 一开始均为空.入队:

    将新元素push入栈A;出队:

    (1)判断栈B是否为空;

    (2)如果不为空,则将栈A中所有元素依次pop出并push到栈B;

    (3)将栈B的栈顶元素pop出;这样实现的队列入队和出队的平摊复杂度都还是O(1),

    26:写一语句实现x是否为2的若干次幂的判断。

    参考答案:!(X)&(X-1)

     

    27:解二次方程:a*x*x+b*x+c

    int Quadratic( double a,double b,double c,double& x1,double& x2);

    返回值:解的个数

     

    28:如何判别一个数是unsigned。

    参考答案;

    #define issignal(x) ((x>=0 && ~x>=0) ? 1:0) //为1是无符号 为0有符号

    29:执行下述程序的输出结果是 ABCDEFCDEFEF 。

    #include

    char b[]="ABCDEF";

    main()

    {

    char *bp;

    for (bp=b;*bp;bp+=2)

    printf("%s",bp);

    printf(" ");

    }

     

    30:已知一个数组table,用一个宏定义,求出数据的元素个数。

    参考答案:#define NTBL

    #define NTBL (sizeof(table)/sizeof(table[0]))

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

    Preprocessor

    1. Using the #define statement, how would you declare a manifest constant that returns the number of seconds in a year? Disregard leap years in your answer.

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

    I'm looking for several things here:

    ? Basic knowledge of the #define syntax (for example, no semi-colon at the end, the need to parenthesize, and so on)

    ? An understanding that the pre-processor will evaluate constant expressions for you. Thus, it is clearer, and penalty-free, to spell out how you are calculating the number of seconds in a year, rather than actually doing the calculation yourself

    ? A realization that the expression will overflow an integer argument on a 16-bit machine-hence the need for the L, telling the compiler to treat the variable as a Long

    ? As a bonus, if you modified the expression with a UL (indicating unsigned long), then you are off to a great start. And remember, first impressions count!

     

     

     

     

    2. Write the "standard" MIN macro-that is, a macro that takes two arguments and returns the smaller of the two arguments.

    #define MIN(A,B) ((A) <= (B) ? (A) : (B))

    The purpose of this question is to test the following:

    ? Basic knowledge of the #define directive as used in macros. This is important because until the inline operator becomes part of standard C, macros are the only portable way of generating inline code. Inline code is often necessary in embedded systems in order to achieve the required performance level

    ? Knowledge of the ternary conditional operator. This operator exists in C because it allows the compiler to produce more optimal code than an if-then-else sequence. Given that performance is normally an issue in embedded systems, knowledge and use of this construct is important

    ? Understanding of the need to very carefully parenthesize arguments to macros

    ? I also use this question to start a discussion on the side effects of macros, for example, what happens when you write code such as:

    least = MIN(*p++, b);

     

     

     

     

     

     

     

    3. What is the purpose of the preprocessor directive #error?

    Either you know the answer to this, or you don't. If you don't, see Reference 1. This question is useful for differentiating between normal folks and the nerds. Only the nerds actually read the appendices of C textbooks to find out about such things. Of course, if you aren't looking for a nerd, the candidate better hope she doesn't know the answer.

     

     

     

     

     

     

     

     

    Infinite loops

    4. Infinite loops often arise in embedded systems. How does you code an infinite loop in C?

    There are several solutions to this question. My preferred solution is:

    while(1)

    {

    ?}

    Many programmers seem to prefer:

    for(;;)

    {

    ?}

    This construct puzzles me because the syntax doesn't exactly spell out what's going on. Thus, if a candidate gives this as a solution, I'll use it as an opportunity to explore their rationale for doing so. If their answer is basically, "I was taught to do it this way and I haven't thought about it since," it tells me something (bad) about them.

    A third solution is to use a goto :

     

    Loop:

    ...

    goto Loop;

     

    Candidates who propose this are either assembly language programmers (which is probably good), or else they are closet BASIC/FORTRAN programmers looking to get into a new field.

     

     

     

     

     

     

    Data declarations

    5. Using the variable a, give definitions for the following:

    a) An integer

    b) A pointer to an integer

    c) A pointer to a pointer to an integer

    d) An array of 10 integers

    e) An array of 10 pointers to integers

    f) A pointer to an array of 10 integers

    g) A pointer to a function that takes an integer as an argument and returns an integer

    h) An array of ten pointers to functions that take an integer argument and return an integer

    The answers are:

    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

    People often claim that a couple of these are the sorts of thing that one looks up in textbooks-and I agree. While writing this article, I consulted textbooks to ensure the syntax was correct. However, I expect to be asked this question (or something close to it) when I'm being interviewed. Consequently, I make sure I know the answers, at least for the few hours of the interview. Candidates who don't know all the answers (or at least most of them) are simply unprepared for the interview. If they can't be prepared for the interview, what will they be prepared for?

     

     

     

     

     

     

     

    Static

    6. What are the uses of the keyword static?

    This simple question is rarely answered completely. Static has three distinct uses in C:

    ? A variable declared static within the body of a function maintains its value between function invocations

    ? A variable declared static within a module, (but outside the body of a function) is accessible by all functions within that module. It is not accessible by functions within any other module. That is, it is a localized global

    ? Functions declared static within a module may only be called by other functions within that module. That is, the scope of the function is localized to the module within which it is declared

    Most candidates get the first part correct. A reasonable number get the second part correct, while a pitiful number understand the third answer. This is a serious weakness in a candidate, since he obviously doesn't understand the importance and benefits of localizing the scope of both data and code.

     

     

     

     

     

    Const

    7. What does the keyword const mean?

    As soon as the interviewee says "const means constant," I know I'm dealing with an amateur. Dan Saks has exhaustively covered const in the last year, such that every reader of ESP should be extremely familiar with what const can and cannot do for you. If you haven't been reading that column, suffice it to say that const means "read-only." Although this answer doesn't really do the subject justice, I'd accept it as a correct answer. (If you want the detailed answer, read Saks' columns-carefully!)

    If the candidate gets the answer correct, I'll ask him these supplemental questions:

    What do the following declarations mean?

    const int a;

    int const a;

    const int *a;

    int * const a;

    int const * a const;

    The first two mean the same thing, namely a is a const (read-only) integer. The third means a is a pointer to a const integer (that is, the integer isn't modifiable, but the pointer is). The fourth declares a to be a const pointer to an integer (that is, the integer pointed to by a is modifiable, but the pointer is not). The final declaration declares a to be a const pointer to a const integer (that is, neither the integer pointed to by a, nor the pointer itself may be modified). If the candidate correctly answers these questions, I'll be impressed. Incidentally, you might wonder why I put so much emphasis on const, since it is easy to write a correctly functioning program without ever using it. I have several reasons:

    ? The use of const conveys some very useful information to someone reading your code. In effect, declaring a parameter const tells the user about its intended usage. If you spend a lot of time cleaning up the mess left by other people, you'll quickly learn to appreciate this extra piece of information. (Of course, programmers who use const , rarely leave a mess for others to clean up.)

    ? const has the potential for generating tighter code by giving the optimizer some additional information

    ? Code that uses const liberally is inherently protected by the compiler against inadvertent coding constructs that result in parameters being changed that should not be. In short, they tend to have fewer bugs

     

     

     

    Volatile

    8. What does the keyword volatile mean? Give three different examples of its use.

    A volatile variable is one that can change unexpectedly. Consequently, the compiler can make no assumptions about the value of the variable. In particular, the optimizer must be careful to reload the variable every time it is used instead of holding a copy in a register. Examples of volatile variables are:

    ? Hardware registers in peripherals (for example, status registers)

    ? Non-automatic variables referenced within an interrupt service routine

    ? Variables shared by multiple tasks in a multi-threaded application

    Candidates who don't know the answer to this question aren't hired. I consider this the most fundamental question that distinguishes between a C programmer and an embedded systems programmer. Embedded folks deal with hardware, interrupts, RTOSes, and the like. All of these require volatile variables. Failure to understand the concept of volatile will lead to disaster.

    On the (dubious) assumption that the interviewee gets this question correct, I like to probe a little deeper to see if they really understand the full significance of volatile . In particular, I'll ask them the following additional questions:

    ? Can a parameter be both const and volatile ? Explain.

    ? Can a pointer be volatile ? Explain.

    ? What's wrong with the following function?:

     

    int square(volatile int *ptr)

    {

    return *ptr * *ptr;

    }

    The answers are as follows:

    ? Yes. An example is a read-only status register. It is volatile because it can change unexpectedly. It is const because the program should not attempt to modify it

    ? Yes, although this is not very common. An example is when an interrupt service routine modifies a pointer to a buffer

    ? This one is wicked. The intent of the code is to return the square of the value pointed to by *ptr . However, since *ptr points to a volatile parameter, the compiler will generate code that looks something like this:

     

    int square(volatile int *ptr)

    {

    int a,b;

    a = *ptr;

    b = *ptr;

    return a * b;

    }

    Because it's possible for the value of *ptr to change unexpectedly, it is possible for a and b to be different. Consequently, this code could return a number that is not a square! The correct way to code this is:

     

    long square(volatile int *ptr)

    {

    int a;

    a = *ptr;

    return a * a;

    }

     

     

     

     

    Bit manipulation

    9. Embedded systems always require the user to manipulate bits in registers or variables. Given an integer variable a, write two code fragments. The first should set bit 3 of a. The second should clear bit 3 of a. In both cases, the remaining bits should be unmodified.

    These are the three basic responses to this question:

    ? No idea. The interviewee cannot have done any embedded systems work

    ? Use bit fields. Bit fields are right up there with trigraphs as the most brain-dead portion of C. Bit fields are inherently non-portable across compilers, and as such guarantee that your code is not reusable. I recently had the misfortune to look at a driver written by Infineon for one of their more complex communications chips. It used bit fields and was completely useless because my compiler implemented the bit fields the other way around. The moral: never let a non-embedded person anywhere near a real piece of hardware!

    ? Use #defines and bit masks. This is a highly portable method and is the one that should be used. My optimal solution to this problem would be:

     

    #define BIT3 (0x1 << 3)

    static int a;

    void set_bit3(void) {

    a |= BIT3;

    }

    void clear_bit3(void) {

    a &= ~BIT3;

    }

    Some people prefer to define a mask together with manifest constants for the set and clear values. This is also acceptable. The element that I'm looking for is the use of manifest constants, together with the |= and &= ~ constructs

     

     

     

    Accessing fixed memory locations

    10. Embedded systems are often characterized by requiring the programmer to access a specific memory location. On a certain project it is required to set an integer variable at the absolute address 0x67a9 to the value 0xaa55. The compiler is a pure ANSI compiler. Write code to accomplish this task.

    This problem tests whether you know that it is legal to typecast an integer to a pointer in order to access an absolute location. The exact syntax varies depending upon one's style. However, I would typically be looking for something like this:

     

    int *ptr;

    ptr = (int *)0x67a9;

    *ptr = 0xaa55;

    A more obscure approach is:

    *(int * const)(0x67a9) = 0xaa55;

    Even if your taste runs more to the second solution, I suggest the first solution when you are in an interview situation.

     

     

     

    Interrupts

    11. Interrupts are an important part of embedded systems. Consequently, many compiler vendors offer an extension to standard C to support interrupts. Typically, this new keyword is __interrupt. The following code uses __interrupt to define an interrupt service routine (ISR). Comment on the code.

    __interrupt double compute_area

    (double

    radius)

    {

    double area = PI * radius *

    radius;

    printf(" Area = %f", area);

    return area;

    }

    This function has so much wrong with it, it's hard to know where to start:

    ? ISRs cannot return a value. If you don't understand this, you aren't hired

    ? ISRs cannot be passed parameters. See the first item for your employment prospects if you missed this

    ? On many processors/compilers, floating-point operations are not necessarily re-entrant. In some cases one needs to stack additional registers. In other cases, one simply cannot do floating point in an ISR. Furthermore, given that a general rule of thumb is that ISRs should be short and sweet, one wonders about the wisdom of doing floating-point math here

    ? In a vein similar to the third point, printf() often has problems with reentrancy and performance. If you missed points three and four, I wouldn't be too hard on you. Needless to say, if you got these last two points, your employment prospects are looking better and better

     

     

     

    Code examples

    12. What does the following code output and why?

     

    void foo(void)

    {

    unsigned int a = 6;

    int b = -20;

    (a+b > 6) ? puts("> 6") : puts("<= 6");

    }

    This question tests whether you understand the integer promotion rules in C-an area that I find is very poorly understood by many developers. Anyway, the answer is that this outputs "> 6." The reason for this is that expressions involving signed and unsigned types have all operands promoted to unsigned types. Thus ?comes a very large positive integer and the expression evaluates to greater than 6. This is a very important point in embedded systems where unsigned data types should be used frequently (see Reference 2). If you get this one wrong, you are perilously close to not getting the job.

     

     

     

    13. Comment on the following code fragment.

    unsigned int zero = 0;

    unsigned int compzero = 0xFFFF;

    /*1's complement of zero */

    On machines where an int is not 16 bits, this will be incorrect. It should be coded:

    unsigned int compzero = ~0;

    This question really gets to whether the candidate understands the importance of word length on a computer. In my experience, good embedded programmers are critically aware of the underlying hardware and its limitations, whereas computer programmers tend to dismiss the hardware as a necessary annoyance.

    By this stage, candidates are either completely demoralized-or they're on a roll and having a good time. If it's obvious that the candidate isn't very good, then the test is terminated at this point. However, if the candidate is doing well, then I throw in these supplemental questions. These questions are hard, and I expect that only the very best candidates will do well on them. In posing these questions, I'm looking more at the way the candidate tackles the problems, rather than the answers. Anyway, have fun...

    Dynamic memory allocation

     

     

     

    14. Although not as common as in non-embedded computers, embedded systems do still dynamically allocate memory from the heap. What are the problems with dynamic memory allocation in embedded systems?

    Here, I expect the user to mention memory fragmentation, problems with garbage collection, variable execution time, and so on. This topic has been covered extensively in ESP , mainly by P.J. Plauger. His explanations are far more insightful than anything I could offer here, so go and read those back issues! Having lulled the candidate into a sense of false security, I then offer up this tidbit:

    What does the following code fragment output and why?

     

    char *ptr;

    if ((ptr = (char *)malloc(0)) ==

    NULL)

    else

    puts("Got a null pointer");

    puts("Got a valid pointer");

    This is a fun question. I stumbled across this only recently when a colleague of mine inadvertently passed a value of 0 to malloc and got back a valid pointer! That is, the above code will output "Got a valid pointer." I use this to start a discussion on whether the interviewee thinks this is the correct thing for the library routine to do. Getting the right answer here is not nearly as important as the way you approach the problem and the rationale for your decision.

    Typedef

     

     

     

    15. Typedef is frequently used in C to declare synonyms for pre-existing data types. It is also possible to use the preprocessor to do something similar. For instance, consider the following code fragment:

     

    #define dPS struct s *

    typedef struct s * tPS;

    The intent in both cases is to define dPS and tPS to be pointers to structure s. Which method, if any, is preferred and why?

    This is a very subtle question, and anyone who gets it right (for the right reason) is to be congratulated or condemned ("get a life" springs to mind). The answer is the typedef is preferred. Consider the declarations:

    dPS p1,p2;

    tPS p3,p4;

    The first expands to:

    struct s * p1, p2;

    which defines p1 to be a pointer to the structure and p2 to be an actual structure, which is probably not what you wanted. The second example correctly defines p3 and p4 to be pointers.

    Obscure syntax

     

     

    16. C allows some appalling constructs. Is this construct legal, and if so what does this code do?

    int a = 5, b = 7, c;

    c = a+++b;

    This question is intended to be a lighthearted end to the quiz, as, believe it or not, this is perfectly legal syntax. The question is how does the compiler treat it? Those poor compiler writers actually debated this issue, and came up with the "maximum munch" rule, which stipulates that the compiler should bite off as big (and legal) a chunk as it can. Hence, this code is treated as:

    c = a++ + b;

    Thus, after this code is executed, a = 6, b = 7, and c = 12.

    If you knew the answer, or guessed correctly, well done. If you didn't know the answer then I wouldn't consider this to be a problem. I find the greatest benefit of this question is that it is good for stimulating questions on coding styles, the value of code reviews, and the benefits of using lint.

    Well folks, there you have it. That was my version of the C test. I hope you had as much fun taking it as I had writing it. If you think the test is a good test, then by all means use it in your recruitment. Who knows, I may get lucky in a year or two and end up being on the receiving end of my own work.

    Nigel Jones is a consultant living in Maryland. When not underwater, he can be found slaving away on a diverse range of embedded projects. He enjoys hearing from readers and can be reached at NAJones@compuserve.com .

    References

    ? Jones, Nigel, "In Praise of the #error directive," Embedded Systems Programming, September 1999, p. 114.

    ? Jones, Nigel, " Efficient C Code for Eight-bit MCUs ," Embedded Systems Programming, November 1998, p. 66.

     

原文地址:https://www.cnblogs.com/timssd/p/4107296.html