嵌入式软件笔试总结

预处理器(Preprocessor

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

  #define SECEND_PER_YEAR (60*60*365)UL

我在这想看到几件事情:

1) #define 语法的基本知识(例如:不能以分号结束,括号的使用,等等)

2)懂得预处理器将为你计算常数表达式的值,因此,直接写出你是如何计算一年中有多少秒而不是计算出实际的值,是更清晰而没有代价的。

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

4) 如果你在你的表达式中用到UL(表示无符号长整型),那么你有了一个好的起点。记住,第一印象很重要。

 

 

6. 关键字static的作用是什么?C语言中,关键字static有三个明显的作用:

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

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

3)在模块内的函数体内,可被模块内的其他函数调用,

 

Const

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

 

 

 

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

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

这个测试是为下面的目的而设的:

1) 标识#define在宏中应用的基本知识。这是很重要的。因为在 嵌入(inline)操作符 变为标准C的一部分之前,宏是方便产生嵌入代码的唯一方法,对于嵌入式系统来说,为了能达到要求的性能,嵌入代码经常是必须的方法。

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

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

4) 我也用这个问题开始讨论宏的副作用,例如:当你写下面的代码时会发生什么事?

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

 

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

编译程序时,只要遇到 #error 就会跳出一个编译错误,既然是编译错误,要它干嘛呢?其目的就是保证程序是按照你所设想的那样进行编译的。

 

数据声明(Data declarations

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

a) 一个整型数(An integer) int a;

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

c)一个指向指针的的指针,它指向的指针是指向一个整型数( A pointer to a pointer to an integer

  int **a;

d)一个有10个整型数的数组( An array of 10 integers) int a [10];

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

 int *a[10];

f) 一个指向有10个整型数数组的指针( A pointer to an array of 10 integers)int (*a)[10];

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

int *a (int);

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


int (*a[10])(int);

 

Static

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

 

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

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

3)在模块内,一个被声明为静态的函数,只可被模块内的其他函数调用。

 

 

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

意味这常数或只读。

如果应试者能正确回答这个问题,我将问他一个附加的问题:

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

const int a;

int const a;

const int *a;

int * const a;

int const * a const;

第一个和第二个作用一样,a是个常整形数,第三个意味着a是一个指向长整型数的指针,也就是整型数不可修改,指针可以被修改。第四个意思a是一个指向整型数的常指针,变量a可以修改,指针不可修改。最后一个意味着a是一个长整型数的常指针,指针和整型数都不可修改。

什么还要如此看重关键字const呢?我也如下的几下理由:

1) 关键字const的作用是为给读你代码的人传达非常有用的信息,实际上,声明一个参数为常量是为了告诉了用户这个参数的应用目的。如果你曾花很多时间清理其它人留下的垃圾,你就会很快学会感谢这点多余的信息。(当然,懂得用const的程序员很少会留下的垃圾让别人来清理的。)

2) 通过给优化器一些附加的信息,使用关键字const也许能产生更紧凑的代码。

3) 合理地使用关键字const可以使编译器很自然地保护那些不希望被改变的参数,防止其被无意的代码修改。简而言之,这样可以减少bug的出现。

 

void set_bit3(void)

{

  a!=BIT3;

}

void clear_bit3(void){

  a&=~BIT3;

}

 

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

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

int *ptr;

ptr=(int *)0x6789;

*ptr=0xaa66;

 

中断(Interrupts

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

__interrupt double compute_area (double radius)

{

double area = PI * radius * radius;

printf("/nArea = %f", area);

return area;

}

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

1)ISR不能返回值。

2)ISR不能传递参数

3)在许多处理器/编译器中,浮点型是不能被重入的,有些处理器/编译器不能在ISR中做浮点运算的,ISR应该是短而有效率的,在ISR中坐浮点运算是不明智的。

4)printf()函数经常有重入性和性能的问题。

 

代码例子(Code examples

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;

 

 

这一问题真正能揭露出应试者是否懂得处理器字长的重要性。好的嵌入式程序员非常准确地明白硬件的细节和它的局限,然而PC机程序往往把硬件作为一个无法避免的烦恼。

 

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

这里,我期望应试者能提到内存碎片,碎片收集的问题,变量的持行时间等等。这个主题已经在ESP杂志中被广泛地讨论过了(主要是 P.J. Plauger, 他的解释远远超过我这里能提到的任何解释),所有回过头看一下这些杂志吧!让应试者进入一种虚假的安全感觉后,我拿出这么一个小节目:

下面的代码片段的输出是什么,为什么?

char *ptr;

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

puts("Got a null pointer");

else

puts("Got a valid pointer");

这是一个有趣的问题。最近在我的一个同事不经意把0值传给了函数malloc,得到了一个合法的指针之后,我才想到这个问题。这就是上面的代码,该代码的输出是"Got a valid pointer"。我用这个来开始讨论这样的一问题,看看被面试者是否想到库例程这样做是正确。得到正确的答案固然重要,但解决问题的方法和你做决定的基本原理更重要些。

 

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

如果你知道答案,或猜出正确答案,做得好。如果你不知道答案,我也不把这个当作问题。我发现这个问题的最大好处是这是一个关于代码编写风格,代码的可读性,代码的可修改性的好的话题。

 

宏代码

宏代码本身不是函数,它是但使用起来像函数,预处理器用复制代码的方式代替函数调用省去了参数的压栈,生成汇编语言的call调用返回参数,执行return的过程,从而提高速度,使用宏代码最大缺点就是容易出错,预处理器在复制宏代码过程中常产生意想不到的边际效应

指令 用途

# 空指令,无任何效果

#define 宏定义

#undef 取消已定义的宏

#if 如果条件为真,则编译下面的代码。

#ifdeif如果宏已定义则编译下面的代码

 #ifndef 如果宏没有定义,则编译下面代码
  #elif
如果前面的#if给定条件不为真,当前条件为真,则编译下面代码
  #endif
结束一个#if……#else条件编译块

  #error
停止编译并显示错误信息

break     跳出此循环
continue 跳出本次循环,进入下一次循环.

       int i;

       for (i=0 ;i<3;i++)

       {

         if(i==1)

         {

               printf("%d",i);  //只打印一次/i=1

              continue;

         }

       }

       int i;

       for (i=0 ;i<3;i++)

       {

         if(i=1)

         {

               printf("%d",i);  //无限循环i=1

              continue;

         }

       }

 

2#define,const
#define
const区别
1
#define C语言
  const C
语言 C++语言
  const
常量有数据类型,编译器会进行类型安全检查,#define没有数据类型,
  const
的常量可以进行调试,但宏常量不能进行调试.

转载请注明文章来源:笔试网 www.ipmao.com—专业的笔试、面试资料搜索网站,<br>原文网址:http://www.ipmao.com/shiti.aspx?id=535413

 

 

 

 

 

Volatile

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

volatile定义的变量,说明这个变量可能被意想不到的修改,这样,编译器就不会去假设这个变量的值,精确的说,优化器在用到这个变量时取值都会很小心,而不是使用保存在寄存器的备份。

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

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

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

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

回答不出这个问题的人是不会被雇佣的。区分C程序员和嵌入式系统程序员的最基本的问题。搞嵌入式的家伙们经常同硬件、中断、RTOS等等打交道,所有这些都要求用到volatile变量。不懂得volatile的内容将会带来灾难。

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

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

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

3)

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

int square(volatile int *ptr)

{

return *ptr * *ptr;

}

这段代码的目的是返回指针*ptr指向值的平方,但由*于ptr是个volatile型参数,

编译器将产生类似下面的代码:

int square(volatile int *ptr)

{

int a,b;

a = *ptr;

b = *ptr;

return a * b;

}

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

long square(volatile int *ptr)

{

int a;

a = *ptr;

return a * a;

}

 

位操作(Bit manipulation

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

对这个问题有三种基本的反应

1)不知道如何下手。该被面者从没做过任何嵌入式系统的工作。

2) bit fieldsBit fields是被扔到C语言死角的东西,它保证你的代码在不同编译器之间是不可移植的,同时也保证了的你的代码是不可重用的。我最近不幸看到 Infineon为其较复杂的通信芯片写的驱动程序,它用到了bit fields因此完全对我无用,因为我的编译器用其它的方式来实现bit fields的。从道德讲:永远不要让一个非嵌入式的家伙粘实际硬件的边。

3) #defines bit masks 操作。这是一个有极高可移植性的方法,是应该被用到的方法。最佳的解决方案如下:

#define BIT3 (0x1<<3)

static int a;

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

可以。只读的状态寄存器,它是volatile因为它可能会被意想不到的改变它是只读,因为程序不能试图去改变它。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值