嵌入式常见笔试题(摘)

时间:2021-09-30 05:03:24

转载源地址:http://blog.csdn.net/cindy_cheng/article/details/50791588

1.什么是嵌入式系统?

广义上讲,凡是带有微处理器的专用软硬件系统都可称为嵌入式系统

2.嵌入式中为什么要用linux?

(1).功能齐全(2).稳定(3).对于大多数芯片,都有裁剪的配置文件

(4).Linux分而治之的思想,可以使得驱动和应用程序并行开发,加快开发速度

3、在读写速度上,Nor,Nand有什么区别?

Nor-Flash的读取速度比Nand-Flash快;

Nand-Flash的写入速度和擦除速度比Nor-Flash快。

补充:Nor Flash可以像内存一样读,但不能像内存一样写;烧写时,需要发出一些特殊的命令。在uboot代码中,通常可以根据能否直接对Flash进行写操作来判断是Nor Flash还是Nand Flash。


预处理器(Preprocessor

1 . 用预处理指令 #define 声明一个常数,用以表明 1年中有多少秒(忽略闰年问题)

#defineSECOND_PER_YEAR (365*24*60*60)UL

意识到这个表达式将使一个 16位机的整型数溢出 -因此要用到长整型符号 L,告诉编译器这个常数是的长整型数

如果你在你的表达式中用到 UL(表示无符号长整型),那么你有了一个好的起点。


2 . 写一个 "标准"宏 MIN ,这个宏输入两个参数并返回较小的一个。

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

三重条件操作符的知识。这个操作符存在 C语言中的原因是它使得编译器能产生比 if-then-else更优化的代码,了解这个用法是很重要的。

懂得在宏中小心地把参数用括号括起来


3. 预处理器标识 #error的目的是什么?

#error预处理指令的作用是,编译程序时,只要遇到#error就会生成一个编译错误提示消息并停止编译。其语法格式为:
#error error-message

注意,宏串error-message不用双引号包围。遇到#error指令时,错误信息被显示,可能同时还显示编译程序作者预先定义的其他内容。系统所支持的error-message请查找相关信息获得!

          #         空指令,无任何效果
          #include     包含一个源代码文件
          #define      定义宏
          #undef       取消已定义的宏
          #if        如果给定条件为真,则编译下面代码
          #ifdef       如果宏已经定义,则编译下面代码
          #ifndef      如果宏没有定义,则编译下面代码
          #elif       如果前面的#if给定条件不为真,当前条件为真,则编译下面代码
          #endif       结束一个#if……#else条件编译块
          #error      停止编译并显示错误信息

 

4. 嵌入式系统中经常要用到无限循环,你怎么样用 C编写死循环呢?

首选的方案是:

while(1){?}

一些程序员更喜欢如下方案:

for(;;){?}

用的空语句,无条件的循环,如果你的程序在这个for里面没有退出语句的话,那么就是死循环了

第三个方案是用 goto

Loop:

...

goto Loop;

应试者如给出上面的方案,这说明或者他是一个汇编语言程序员(这也许是好事)

 

5. 用变量 a给出下面的定义

a) 一个整型数( An integer)

b)一个指向整型数的指针( A pointer to an integer)

c)一个指向指针的的指针,它指向的指针是指向一个整型数( A pointer to apointer to an intege)r

d)一个有 10个整型数的数组( Anarrayof 10 integers)

e)一个有 10个指针的数组,该指针是指向一个整型数的。( An array of 10 pointers to integers)

f) 一个指向有 10个整型数数组的指针(Apointer to an array of 10 integers)

g) 一个指向函数的指针,该函数有一个整型参数并返回一个整型数( A pointer to afunction that takes an integer as an argument

and returns an integer)

h)一个有 10个指针的数组,该指针指向一个函数,该函数有一个整型参数并返回一个整型数( An array of ten pointers to functions that take an integer argumentand return aninteger )

答案是:

a) int a;b) int *a;c) int **a; d) int a[10];e) int *a[10]; f) int (*a)[10]; g) int (*a)(int); h) int (*a[10])(int);

6. 关键字 static的作用是什么?

C语言中,关键字 static有三个明显的作用:

1)在函数体,一个被声明为静态的变量在这一函数被调用过程中维持其值不变。

2)在模块内(但在函数体外),一个被声明为静态的变量可以被模块内所用函数访问,但不能被模块外其它函数访问。它是一个本地的全局变量。

3)在模块内,一个被声明为静态的函数只可被这一模块内的其它函数调用。那就是,这个函数被限制在声明它的模块的本地范围内使用。

补充:

static型变量只在它被声明的代码块中可见

static修饰的局部变量,会改变这个变量的生命周期

code1

#include <stdio.h>main(){    int a = 5;    while(a--)    {        int i = 10;	i++;	printf("i = %d\n", i);    }}

输出:

嵌入式常见笔试题(摘)

i的生命周期仅仅在“{…}”里面有效,每次执行完i++,遇到到“}”之后,i的变量就会被操作系统回收,再次进入while,重新给i分配了一一块区域,再次赋值i=10

code2

#include <stdio.h>main(){    int a = 5;    while(a--)   {	static int i = 10;	i++;	printf("i = %d\n", i);   }}

输出:

嵌入式常见笔试题(摘)

此时i的生命周期是从整个程序的开始就已经存在了,所以在内存里首先对i赋值10;在第二次循环到static int i=10;的时候,由于操作系统并没有回收这段内存,所以,我们的C语言编译器会认为此处对i进行赋值没有必要,因为之前已经赋值过,而且这个i变量没有被系统回收,故改语句只会执行一次。

 

Const(翻译:不变的,常数)

7.关键字 const有什么含意?

const意味着"只读"

const int a=5

int a=10

编译会出现错误,原因:对只读型变量a赋值

下面的声明都是什么意思?

const int a;

int const a;

const int *a;

int * const a;

int const * a const;

前两个的作用是一样, a是一个常整型数。

第三个意味着 a是一个指向常整型数的指针(也就是,整型数是不可修改的,但指针可以)。

第四个意思 a是一个指向整型数的常指针(也就是说,指针指向的整型数是可以修改的,但指针是不可修改的)。

最后一个意味着 a是一个指向常整型数的常指针(也就是说,指针指向的整型数是不可修改的,同时指针也是不可修改的)。

关键字 const的作用是为给读你代码的人传达非常有用的信息,实际上,声明一个参数为常量是为了告诉了用户这个参数的应用目的。通过给优化器一些附加的信息,使用关键字 const也许能产生更紧凑的代码。

合理地使用关键字 const可以使编译器很自然地保护那些不希望被改变的参数,防止其被无意的代码修改。


Volatile(翻译:易变的,变化无常的)

8. 关键字 volatile有什么含意 ?并给出三个不同的例子。

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

下面是volatile变量的几个例子:

1)并行设备的硬件寄存器(如:状态寄存器)

2)一个中断服务子程序中会访问到的非自动变量 (Non-automatic variables)

3)多线程应用中被几个任务共享的变量

  补充:1.变量的值可能以未在程序中明确的表达的方法改变。

2.Volatile的重要性在于防止编译器的自动优化操作:如果用一个变量等于硬件寄存器,经过一段时间后,这个值可能也发生变化,但如果我们想计算这两个值的差值的时候,编译器认为你这两个都指向同一个寄存器,而这个寄存器的值在我们整个程序里面没有发生任何变化,那么,我们这个编译器就会自作主张,认为你这两个值得差值永远为0,它便不帮你计算了,即帮你优化了,此时,我们就需要volatile,以防止编译器的自动优化。

假设被面试者正确地回答了这是问题(嗯,怀疑是否会是这样),我将稍微深究一下,看一下这家伙是不是直正懂得 volatile完全的重要性。

1) 一个参数既可以是 const还可以是 volatile吗?解释为什么。

2)一个指针可以是 volatile吗?解释为什么。

3)下面的函数有什么错误:

intsquare(volatile int *ptr){    return *ptr * *ptr;}

下面是答案:

1)是的。一个例子是只读的状态寄存器。它是 volatile因为它可能被意想不到地改变。它是 const因为程序不应该试图去修改它。

2)是的。尽管这并不很常见。一个例子是当一个中服务子程序修改一个指向一个 buffer的指针时。

3)这段代码有点变态。这段代码的目的是用来返指针 *ptr指向值的平方,但是,由于*ptr指向一个 volatile型参数,编译器将产生类似下面

的代码:

int square(volatile int *ptr){int a,b;a = *ptr;b = *ptr;return a * b;}

由于*ptr的值可能被意想不到地该变,因此 a b可能是不同的。结果,这段代码可能返不是你所期望的平方值!正确的代码如下:

long square(volatile int *ptr){int a;a = *ptr;return a * a;}

位操作(Bitmanipulation

9. 嵌入式系统总是要用户对变量或寄存器进行位操作。给定一个整型变量 a,写两段代码,第一个设置 a bit 3,第二个清除 a bit 3。在以上两个操作中,要保持其它位不变。

 #defines  bitmasks操作。这是一个有极高可移植性的方法,是应该被用到的方法。

最佳的解决方案如下:

#define BIT3 (0x1<< 3)static int a;void set_bit3(void) {a |= BIT3;}
void clear_bit3(void) {a &= ~BIT3;}

访问固定的内存位置(Accessing fixed memory locations

10. 嵌入式系统经常具有要求程序员去访问某特定的内存位置的特点。在某工程中,要求设置一绝对地址为0x67a9的整型变量的值为 0xaa66。编译器是一个纯粹的ANSI编译器。写代码去完成这一任务。

这一问题测试你是否知道为了访问一绝对地址把一个整型数强制转换( typecast)为一指针是合法的。这一问题的实现方式随着个人风格不同

而不同。

典型的类似代码如下:

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

一个较晦涩的方法是:

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

即使你的品味更接近第二种方案,但我建议你在面试时使用第一种方案。


中断(Interrupts

11. 中断是嵌入式系统中重要的组成部分,这导致了很多编译开发商提供一种扩展让标准 C支持中断。具代表事实是,产生了一个新的关键字__interrupt。下面的代码就使用了 __interrupt关键字去定义了一个中断服务子程序 (ISR),请评论一下这段代码的。

__interrupt double compute_area (doubleradius){double area = PI* radius * radius;printf("\nArea= %f", area);return area;}

这个函数有太多的错误了,以至让人不知从何说起了:

1ISR不能返回一个值

2ISR不能传递参数

3在许多的处理器 /编译器中,浮点一般都是不可重入的。有些处理器 /编译器需要让额处的寄存器入栈,有些处理器 /编译器就是不允许在 ISR中做浮点运算。此外, ISR应该是短而有效率的,在 ISR中做浮点运算是不明智的。

4)与第三点一脉相承,printf()经常有重入和性能上的问题。如果你丢掉了第三和第四点,我不会太为难你的。

 

代码例子( Codeexamples

12 . 下面的代码输出是什么,为什么?

void foo(void){    unsigned int a = 6;    int b = -20;    (a+b> 6) ? puts("> 6") : puts("<= 6");}

这个问题测试你是否懂得 C语言中的整数自动转换原则。

答案是输出是">6"

原因是当表达式中存在有符号类型和无符号类型时所有的操作数都自动转换为无符号类型。因此 -20变成了一个非常大的正整数,所以该表达式计算出的结果大于 6。这一点对于应当频繁用到无符号数据类型的嵌入式系统来说是丰常重要的。

 

13. 评价下面的代码片断:

unsigned int zero = 0;unsigned int compzero = 0xFFFF;/*1's complement of zero */

对于一个 int型不是 16位的处理器为说,上面的代码是不正确的。应编写如下:

unsigned int compzero =~0;

这道题的意思是如果在int型不是16位的机器上,这样定义unsigned int compzero = 0xFFFF;就会出现问题,而这样定义unsigned int compzero = ~0;就可以保证代码良好的可移植性。

处理器字长的重要性.

(通常:处理器位数=CPU处理一个字的长度=数据总线的位数)

14. 尽管不像非嵌入式计算机那么常见,嵌入式系统还是有从堆(heap)中动态分配内存的过程的。那么嵌入式系统中,动态分配内存可能发生的问题是什么?

内存碎片,碎片收集的问题,变量的持行时间等等

15. Typedef 在C语言中频繁用以声明一个已经存在的数据类型的同义字。也可以用预处理器做类似的事。例如,思考一下下面的例子:

#define dPS struct s *typedef struct s * tPS;

以上两种情况的意图都是要定义dPS 和 tPS 作为一个指向结构s指针。哪种方法更好呢?(如果有的话)为什么?

答案是:typedef更好。

dPS p1, p2;tPS p3, p4;

第一个扩展为

struct s * p1, p2;

上面的代码第一个例子定义p1为一个指向结构的指,p2为一个实际的结构,这也许不是你想要的。

第二个例子正确地定义了p3 和p4 两个指针。


16 . C语言同意一些令人震惊的结构,下面的结构是合法的吗,如果是它做些什么?

int a = 5, b = 7, c;c = a++ + b;
上面的例子是完全合乎语法的。根据最处理原则,编译器应当能处理尽可能所有合法的用法。因此,上面的代码被处理成:
c = a++ + b;
因此, 这段代码持行后a = 6, b = 7, c = 12。

这个问题的最大好处是这是一个关于代码编写风格,代码的可读性,代码的可修改性的好的话题。