全面解析嵌入式的十六个问题.docx

上传人:b****0 文档编号:17053160 上传时间:2023-07-21 格式:DOCX 页数:44 大小:41.03KB
下载 相关 举报
全面解析嵌入式的十六个问题.docx_第1页
第1页 / 共44页
全面解析嵌入式的十六个问题.docx_第2页
第2页 / 共44页
全面解析嵌入式的十六个问题.docx_第3页
第3页 / 共44页
全面解析嵌入式的十六个问题.docx_第4页
第4页 / 共44页
全面解析嵌入式的十六个问题.docx_第5页
第5页 / 共44页
全面解析嵌入式的十六个问题.docx_第6页
第6页 / 共44页
全面解析嵌入式的十六个问题.docx_第7页
第7页 / 共44页
全面解析嵌入式的十六个问题.docx_第8页
第8页 / 共44页
全面解析嵌入式的十六个问题.docx_第9页
第9页 / 共44页
全面解析嵌入式的十六个问题.docx_第10页
第10页 / 共44页
全面解析嵌入式的十六个问题.docx_第11页
第11页 / 共44页
全面解析嵌入式的十六个问题.docx_第12页
第12页 / 共44页
全面解析嵌入式的十六个问题.docx_第13页
第13页 / 共44页
全面解析嵌入式的十六个问题.docx_第14页
第14页 / 共44页
全面解析嵌入式的十六个问题.docx_第15页
第15页 / 共44页
全面解析嵌入式的十六个问题.docx_第16页
第16页 / 共44页
全面解析嵌入式的十六个问题.docx_第17页
第17页 / 共44页
全面解析嵌入式的十六个问题.docx_第18页
第18页 / 共44页
全面解析嵌入式的十六个问题.docx_第19页
第19页 / 共44页
全面解析嵌入式的十六个问题.docx_第20页
第20页 / 共44页
亲,该文档总共44页,到这儿已超出免费预览范围,如果喜欢就下载吧!
下载资源
资源描述

全面解析嵌入式的十六个问题.docx

《全面解析嵌入式的十六个问题.docx》由会员分享,可在线阅读,更多相关《全面解析嵌入式的十六个问题.docx(44页珍藏版)》请在冰点文库上搜索。

全面解析嵌入式的十六个问题.docx

全面解析嵌入式的十六个问题

全面解析《嵌入式程序员应该知道的16个问题》2008-08-0715:

46:

37

分类:

1、预处理器(Preprocessor)...1

2、如何定义宏...2

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

...4

4、死循环(Infiniteloops)...4

5、数据声明(Datadeclarations)...5

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

...6

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

...7

8、Volatile的使用...9

9、位操作(Bitmanipulation)...12

10、访问固定的内存位置(Accessingfixedmemorylocations)...13

11、中断(Interrupts)...13

12、符号扩展的代码例子(Codeexamples)...15

13、处理器字长导致的数据扩展问题...16

14、动态内存分配(Dynamicmemoryallocation)...16

15、用Typedef构造复合类型...17

16、晦涩的语法及代码风格...18

C语言测试是招聘嵌入式系统程序员过程中必须而且有效的方法。

这些年,我既参加也组织了许多这种测试,在这过程中我意识到这些测试能为面试者和被面试者提供许多有用信息,此外,撇开面试的压力不谈,这种测试也是相当有趣的。

从被面试者的角度来讲,你能了解许多关于出题者或监考者的情况。

这个测试只是出题者为显示其对ANSI标准细节的知识而不是技术技巧而设计吗?

这是个愚蠢的问题吗?

如要你答出某个字符的ASCII值。

这些问题着重考察你的系统调用和内存分配策略方面的能力吗?

这标志着出题者也许花时间在微机上而不是在嵌入式系统上。

如果上述任何问题的答案是"是"的话,那么我知道我得认真考虑我是否应该去做这份工作。

从面试者的角度来讲,一个测试也许能从多方面揭示应试者的素质:

最基本的,你能了解应试者C语言的水平。

不管怎么样,看一下这人如何回答他不会的问题也是满有趣。

应试者是以好的直觉做出明智的选择,还是只是瞎蒙呢?

当应试者在某个问题上卡住时是找借口呢,还是表现出对问题的真正的好奇心,把这看成学习的机会呢?

我发现这些信息与他们的测试成绩一样有用。

有了这些想法,我决定出一些真正针对嵌入式系统的考题,希望这些令人头痛的考题能给正在找工作的人一点帮助。

这些问题都是我这些年实际碰到的。

其中有些题很难,但它们应该都能给你一点启迪。

这个测试适于不同水平的应试者,大多数初级水平的应试者的成绩会很差,经验丰富的程序员应该有很好的成绩。

为了让你能自己决定某些问题的偏好,每个问题没有分配分数,如果选择这些考题为你所用,请自行按你的意思分配分数。

1、预处理器(Preprocessor)

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

#defineSECONDS_PER_YEAR(60*60*24*365)UL(大小写都行,常量后面可以加此标志,宏的命名风格要大写,多个之间用下划线)

我在这想看到几件事情:

1)#define语法的基本知识(例如:

不能以分号结束,括号的使用(表达式、参数等要括起来),等等)

2)懂得预处理器将为你计算常数表达式的值(难道不是替换么,先算再替?

会将常数合并),因此,直接写出你是如何计算一年中有多少秒而不是计算出实际的值,是更清晰而没有代价的。

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

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

记住,第一印象很重要。

2、如何定义宏

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

考点:

(表达式、参数等要括起来)

#defineMIN(A,B)((A)<=(B)?

(A):

(B))

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

1)标识#define在宏中应用的基本知识。

这是很重要的。

因为在嵌入(inline)操作符变为标准C的一部分之前,宏是方便产生嵌入代码的唯一方法,

对于嵌入式系统来说,为了能达到要求的性能(当然主要是实时性哦,牺牲代码空间换取时间效率),嵌入代码经常是必须的方法。

2)三重条件操作符的知识。

这个操作符存在C语言中的原因是它使得编译器能产生比if-then-else(存在条件转移会中断指令流水线)更优化的代码,了解这个用法是很重要的。

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

4)我也用这个问题开始讨论宏的副作用,例如:

当你写下面的代码时会发生什么事?

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

此处考点:

inline函数和宏的区别

宏只是将参数完全替换,即MIN(*p++,b)进行宏展开后为((*p++)<=(b)?

(*p++):

(b)),如果(*p++)<=(b)成立,则表达式的值为(*p++),但由于在(*p++)<=(b)判断过程中改变了p的值,使得此时的?

(*p++)非(*p++)<=(b)中的值了,违背了?

号表达式的原意。

但是内联inline函数将进行参数检查,求出参数的值后再将此值带入函数中,因此((A)<=(B)?

(A):

(B))中的A是一致的。

为什么要使用宏呢?

因为函数的调用必须要将程序执行的顺序转移到函数所存放在内存中的某个地址,将函数的程序内容执行完后,再返回到转去执行该函数前的地方。

这种转移操作要求在转去执行前要保存现场并记忆执行的地址,转回后要恢复现场,并按原来保存地址继续执行。

因此,函数调用要有一定的时间和空间方面的开销,于是将影响其效率。

而宏只是在预处理的地方把代码展开,不需要额外的空间和时间方面的开销,所以调用一个宏比调用一个函数更有效率。

但是宏也有很多的不尽人意的地方。

1、宏不能访问对象的私有成员。

2、宏的定义很容易产生二意性。

3、宏定义的常量在代码区,很多调试器不能够对其调试

我们举个例子:

#definesquare(x)(x*x)

避免这些错误的方法,一是给宏的参数都加上括号。

#definesquare(x)((x)*(x))

从上面的阐述,可以看到宏有一些难以避免的问题,怎么解决呢?

内联函数是代码被插入到调用者代码处的函数。

如同#define宏,内联函数通过避免被调用的开销来提高执行效率,尤其是它能够通过调用(“过程化集成”)被编译器优化。

内联函数和宏很类似,而本质区别在于,宏是由预处理器对宏进行替代,而内联函数是通过编译器控制来实现的。

而且内联函数是真正的函数,只是在需要用到的时候,内联函数像宏一样的展开,所以取消了函数的参数压栈,减少了调用的开销。

你可以象调用函数一样来调用内联函数,而不必担心会产生于处理宏的一些问题。

声明内联函数看上去和普通函数非常相似:

voidf(inti,charc);

当你定义一个内联函数时,在函数定义前加上inline关键字,并且将定义放入头文件:

inlinevoidf(inti,charc)

{

//...

}

内联函数必须是和函数体的定义申明在一起,才有效。

像这样的申明inlinefunction(inti)是没有效果的,编译器只是把函数作为普通的函数申明,我们必须定义函数体。

inlineintfunction(inti){returni*i;}

这样我们才算定义了一个内联函数。

我们可以把它作为一般的函数一样调用。

但是执行速度确比一般函数的执行速度要快。

当然,内联函数也有一定的局限性。

就是函数中的执行代码不能太多了,如果,内联函数的函数体过大,一般的编译器会放弃内联方式,而采用普通的方式调用函数。

这样,内联函数就和普通函数执行效率一样了。

有上面的两者的特性,我们可以用内联函数完全取代预处理宏。

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

如果你不知道答案,请看参考文献1。

这问题对区分一个正常的伙计和一个书呆子是很有用的。

只有书呆子才会读C语言课本的附录去找出象这种问题的答案。

当然如果你不是在找一个书呆子,那么应试者最好希望自己不要知道答案。

4、死循环(Infiniteloops)

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

这个问题用几个解决方案。

我首选的方案是:

while

(1)

{

}

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

for(;;)(此处的判断效率要低的多,在汇编代码中看看?

{

}

这个实现方式让我为难,因为这个语法没有确切表达到底怎么回事。

如果一个应试者给出这个作为方案,我将用这个作为一个机会去探究他们这样做的基本原理。

如果他们的基本答案是:

"我被教着这样做,但从没有想到过为什么。

"这会给我留下一个坏印象。

(很多时候面试官关注你思考问题的方式,是否留意某些东西善于思考,可能并没有对错,只是偏好而已,比如memset和memcopy以及strcpy都能拷贝字符串,到底有什么区别呢?

看你是否善于比较是否关注细节)

第三个方案是用goto(goto语句在C中是应该尽量避免的,只在处理错误代码时用)

Loop:

...

gotoLoop;

应试者如给出上面的方案,这说明或者他是一个汇编语言程序员(这也许是好事)或者他是一个想进入新领域的BASIC/FORTRAN程序员。

5、数据声明(Datadeclarations)

用变量a给出下面的定义

a)一个整型数(Aninteger)

b)一个指向整型数的指针(Apointertoaninteger)

c)一个指向指针的的指针,它指向的指针是指向一个整型数(Apointertoapointertoanintegers)

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

e)一个有10个指针的数组,该指针是指向一个整型数的。

(Anarrayof10pointerstointegers)

f)一个指向有10个整型数数组的指针(Apointertoanarrayof10integers)

g)一个指向函数的指针,该函数有一个整型参数并返回一个整型数(Apointertoafunctionthattakesanintegerasanargumentandreturnsaninteger)

h)一个有10个指针的数组,该指针指向一个函数,该函数有一个整型参数并返回一个整型数(Anarrayoftenpointerstofunctionsthattakeanintegerargumentandreturnaninteger)

答案是:

a)inta;//Aninteger

b)int*a;//Apointertoaninteger

c)int**a;//Apointertoapointertoaninteger

d)inta[10];//Anarrayof10integers

e)int*a[10];//Anarrayof10pointerstointegers

f)int(*a)[10];//Apointertoanarrayof10integers

g)int(*a)(int);//Apointertoafunctionathattakesanintegerargumentandreturnsaninteger//不是(intx),不需要具体的参数

h)int(*a[10])(int)(可以从e、g类比得到);//Anarrayof10pointerstofunctionsthattakeanintegerargumentandreturnaninteger

人们经常声称这里有几个问题是那种要翻一下书才能回答的问题,我同意这种说法。

当我写这篇文章时,为了确定语法的正确性,我的确查了一下书。

但是当我被面试的时候,我期望被问到这个问题(或者相近的问题)。

因为在被面试的这段时间里,我确定我知道这个问题的答案。

应试者如果不知道所有的答案(或至少大部分答案),那么也就没有为这次面试做准备,如果该面试者没有为这次面试做准备,那么他又能为什么做准备呢?

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

这个简单的问题很少有人能回答完全。

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

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

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

它是一个本地的全局变量。

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

那就是,这个函数被限制在声明它的模块的本地范围内使用。

大多数应试者能正确回答第一部分,一部分能正确回答第二部分,但是很少的人能懂得第三部分。

这是一个应试者的严重的缺点,因为他显然不懂得本地化数据和代码范围的好处和重要性。

考点:

在嵌入式系统中,要时刻懂得移植的重要性,程序可能是很多程序员共同协作同时完成,在定义变量及函数的过程,可能会重名,这给系统的集成带来麻烦,因此保证不冲突的办法是显示的表示此变量或者函数是本地的,static即可。

在Linux的模块编程中,这一条很明显,所有的函数和全局变量都要用static关键字声明,将其作用域限制在本模块内部,与其他模块共享的函数或者变量要EXPORT到内核中。

static关键字至少有下列n个作用:

(1)设置变量的存储域,函数体内static变量的作用范围为该函数体,不同于auto变量,该变量的内存只被分配一次,因此其值在下次调用时仍维持上次的值;

(2)限制变量的作用域,在模块内的static全局变量可以被模块内所用函数访问,但不能被模块外其它函数访问;

(3)限制函数的作用域,在模块内的static函数只可被这一模块内的其它函数调用,这个函数的使用范围被限制在声明它的模块内;

(4)在类中的static成员变量意味着它为该类的所有实例所共享,也就是说当某个类的实例修改了该静态成员变量,其修改值为该类的其它所有实例所见;

(5)在类中的static成员函数属于整个类所拥有,这个函数不接收this指针,因而只能访问类的static成员变量。

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

我只要一听到被面试者说:

"const意味着常数"(不是常数,可以是变量,只是你不能修改它),我就知道我正在和一个业余者打交道。

去年DanSaks已经在他的文章里完全概括了const的所有用法,因此ESP(译者:

EmbeddedSystemsProgramming)的每一位读者应该非常熟悉const能做什么和不能做什么.如果你从没有读到那篇文章,只要能说出const意味着"只读"就可以了。

尽管这个答案不是完全的答案,但我接受它作为一个正确的答案。

(如果你想知道更详细的答案,仔细读一下Saks的文章吧。

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

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

Const只是一个修饰符,不管怎么样a仍然是一个int型的变量

constinta;

intconsta;

constint*a;

int*consta;

intconst*aconst;

本质:

const在谁后面谁就不可修改,const在最前面则将其后移一位即可,二者等效

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

第三个意味着a是一个指向常整型数的指针(也就是,指向的整型数是不可修改的,但指针可以,此最常见于函数的参数,当你只引用传进来指针所指向的值时应该加上const修饰符,程序中修改编译就不通过,可以减少程序的bug)。

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

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

如果应试者能正确回答这些问题,那么他就给我留下了一个好印象。

顺带提一句,也许你可能会问,即使不用关键字,也还是能很容易写出功能正确的程序,那么我为什么还要如此看重关键字const呢?

我也如下的几下理由:

1)关键字const的作用是为给读你代码的人传达非常有用的信息,实际上,声明一个参数为常量是为了告诉了用户这个参数的应用目的。

如果你曾花很多时间清理其它人留下的垃圾,你就会很快学会感谢这点多余的信息。

(当然,懂得用const的程序员很少会留下的垃圾让别人来清理的。

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

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

简而言之,这样可以减少bug的出现。

const关键字至少有下列n个作用:

(1)欲阻止一个变量被改变,可以使用const关键字。

在定义该const变量时,通常需要对它进行初始化,因为以后就没有机会再去改变它了;

(2)对指针来说,可以指定指针本身为const,也可以指定指针所指的数据为const,或二者同时指定为const;

(3)在一个函数声明中,const可以修饰形参,表明它是一个输入参数,在函数内部不能改变其值;

(4)对于类的成员函数,若指定其为const类型,则表明其是一个常函数,不能修改类的成员变量;

(5)对于类的成员函数,有时候必须指定其返回值为const类型,以使得其返回值不为“左值”。

例如:

constclassAoperator*(constclassA&a1,constclassA&a2);

  operator*的返回结果必须是一个const对象。

如果不是,这样的变态代码也不会编译出错:

classAa,b,c;

(a*b)=c;//对a*b的结果赋值

  操作(a*b)=c显然不符合编程者的初衷,也没有任何意义。

8、Volatile的使用

关键字volatile有什么含意?

并给出三个不同的例子。

一个定义为volatile的变量是说这变量可能会被意想不到地改变,这样,编译器就不会去假设这个变量的值了。

精确地说就是,优化器在用到这个变量时必须每次都小心地重新读取这个变量的值,而不是使用保存在寄存器里的备份(由于访问寄存器的速度要快过RAM,所以编译器一般都会作减少存取外部RAM的优化)。

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

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

状态寄存器,通常在头文件中将硬件寄存器地址define为某个意义明确的表达式)

2)一个中断服务子程序中会访问到的非自动变量(Non-automaticvariables,即static变量);在中断服务程序中修改的供其他程序检测用的变量需要加volatile声明;否则编译器可能对变量更新一次后每次都使用缓存值不再立即更新;

3)多线程应用中被几个任务共享的变量(可能被多个线程随时修改)

回答不出这个问题的人是不会被雇佣的。

我认为这是区分C程序员和嵌入式系统程序员的最基本的问题。

搞嵌入式的家伙们经常同硬件、中断、RTOS等等打交道,所有这些都要求用到volatile变量。

不懂得volatile的内容将会带来灾难。

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

1)一个参数既可以是const还可以是volatile吗?

解释为什么。

2);一个指针可以是volatile吗?

解释为什么。

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

intsquare(volatileint*ptr)

{

return*ptr**ptr;

}

下面是答案:

1)是的。

一个例子是只读的状态寄存器。

它是volatile因为它可能被意想不到地改变。

它是const因为程序不应该试图去修改它。

2);是的。

尽管这并不很常见。

一个例子是当一个中断服务子程序修改一个指向一个buffer的指针时。

3)这段代码有点变态。

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

intsquare(volatileint*ptr)

{

inta,b;

a=*ptr;

b=*ptr;

returna*b;

}

由于*ptr的值可能被意想不到地该变,因此a和b可能是不同的。

结果,这段代码可能返不是你所期望的平方值!

正确的代码如下:

longsquare(volatileint*ptr)

{

inta;

a=*ptr;

returna*a;

}

关于volatile关键字在中断函数中的影响实例

串口发送数据,中断中对其检测,当中断产生后,置接收标志,主循环中检测此主标志,未用valotile修饰时,编译结果如下:

[0xe59f41bc]ldrr4,0x30203378;=#0x302096f0

0x302031b8[0xe5d40000]ldrbr0,[r4,#0]

while(!

uart1_rxFlag);//uart1_rxFlag为全局变量,在串口接收中断中置1

0x302031bc[0xe3500000]cmpr0,#0

0x302031c0[0x0afffffd]beq0x302031bc;(Can_Int_Test+0x17c)

即编译器对其进行了优化,读取一次uart1_rxFlag的值之后,将其存放在寄存器r0中,比较后,条件不满足,继续等待,但未重新取存储器中uart1_rxFlag的值,此时即使中断服务函数中修改了uart1_rxFlag的值,比较处仍然不能发现,就出现了无论如何程序就停在此处的问题。

//加了volatile关键字后,编译的结果

302031b4ldrr4,0x30203378;=

展开阅读全文
相关资源
猜你喜欢
相关搜索
资源标签

当前位置:首页 > 经管营销 > 经济市场

copyright@ 2008-2023 冰点文库 网站版权所有

经营许可证编号:鄂ICP备19020893号-2