6 }; ^5 a' {3 c7 n/ C' fwhile(1)4 N- f# ?& I* I" _
{ 0 c4 [ S3 x' {" R ; N2 I- c2 e2 g) C7 `! G9 S, ~8 ?' K} ! W3 x* |! l' n2 w8 o # D& z' _: M! h. }8 N2 `一些程序员更喜欢如下方案: 4 h3 A I" ~, ~: ]* B ' c! S% h9 r9 c8 s6 Wfor(;;)5 z5 O: Y5 Z& R/ ^
{ , k6 L% t! { N, ^* j! ]# y# F& O- W+ n/ R% R
} 2 O: Q% F- h& C9 } - C4 A1 \: z: t' W; |; ^4 v 这个实现方式让我为难,因为这个语法没有确切表达到底怎么回事。如果一个应试者给出这个作为方案,我将用这个作为一个机会去探究他们这样做的基本原理。如果他们的基本答案是:"我被教着这样做,但从没有想到过为什么。"这会给我留下一个坏印象。/ S- i/ k# J( r5 H( H4 t
0 e p6 S8 ]; X% J+ Q1 ?第三个方案是用 goto \/ T X* c% J* g( }; aLoop: ! u7 F9 e' _: m9 s...: U, }! G, n8 a' ?
goto Loop; - B8 X/ }2 |' | O+ w% w* i应试者如给出上面的方案,这说明或者他是一个汇编语言程序员(这也许是好事)或者他是一个想进入新领域的BASIC/FORTRAN程序员。& _+ `; X9 q" F0 v9 w' @) r: E
" h! u; X+ H4 c- y4 k数据声明(Data declarations) ; f: Z6 W: Q7 j/ b+ i, {) r7 } ( N7 C# U5 R6 q5. 用变量a给出下面的定义9 L2 `$ E7 a2 h$ @8 z" A
a) 一个整型数(An integer) 6 ^8 ?3 O% O' ?* r+ [b)一个指向整型数的指针( A pointer to an integer)0 T, J* v# x3 k& S8 p7 r
c)一个指向指针的的指针,它指向的指针是指向一个整型数( A pointer to a pointer to an intege)r# h( l& z* h0 F0 R, S$ G1 g
d)一个有10个整型数的数组( An array of 10 integers)0 E, \- ~$ C* x+ ]4 C/ J* B. t
e) 一个有10个指针的数组,该指针是指向一个整型数的。(An array of 10 pointers to integers) - j2 t7 e, }; }1 B2 o3 F8 Af) 一个指向有10个整型数数组的指针( A pointer to an array of 10 integers)! X. N/ l% G$ w& Z. n! Y) u
g) 一个指向函数的指针,该函数有一个整型参数并返回一个整型数(A pointer to a function that takes an integer as an argument and returns an integer) 6 \' g! y, ~# b6 O1 H& Hh) 一个有10个指针的数组,该指针指向一个函数,该函数有一个整型参数并返回一个整型数( An array of ten pointers to functions that take an integer argument and return an integer ) `# h, c- Q- j$ F1 M5 P 3 a% V y. z; s7 k& {2 M答案是:& z z3 M3 y6 j+ R" a
; z* f. p" @( H+ e 大多数应试者能正确回答第一部分,一部分能正确回答第二部分,同是很少的人能懂得第三部分。这是一个应试者的严重的缺点,因为他显然不懂得本地化数据和代码范围的好处和重要性。 & G$ I) e, O. b* U6 } ?) A : b8 L* |/ Z9 o3 oConst. f" b, z" @+ W5 ~1 y @
- s" V# x6 m6 f( j7.关键字const有什么含意?; ?4 P7 }1 }- D' }
我只要一听到被面试者说:"const意味着常数",我就知道我正在和一个业余者打交道。去年Dan Saks已经在他的文章里完全概括了const的所有用法,因此ESP(译者:Embedded Systems Programming)的每一位读者应该非常熟悉const能做什么和不能做什么.如果你从没有读到那篇文章,只要能说出const意味着"只读"就可以了。尽管这个答案不是完全的答案,但我接受它作为一个正确的答案。(如果你想知道更详细的答案,仔细读一下Saks的文章吧。)" c B: M* r( }3 V. `+ W% m
如果应试者能正确回答这个问题,我将问他一个附加的问题: . R+ m1 n& c: J4 k 下面的声明都是什么意思?) {9 ]0 q: w$ h3 `0 f
! B2 L8 g ^8 ]( A
const int a;$ _3 P" @3 P- q
int const a;- J- b j l1 {3 d
const int *a;+ [' h- U7 X. K+ p, M ?, N
int * const a;* E2 t' x# f1 {: c
int const * a const;6 z, g3 p2 U- c; n; O, R" i
% y0 B! d; R* c( ?: {/******/ - C% J3 \) E& e8 f 前两个的作用是一样,a是一个常整型数。第三个意味着a是一个指向常整型数的指针(也就是,整型数是不可修改的,但指针可以)。第四个意思a是一个指向整型数的常指针(也就是说,指针指向的整型数是可以修改的,但指针是不可修改的)。最后一个意味着a是一个指向常整型数的常指针(也就是说,指针指向的整型数是不可修改的,同时指针也是不可修改的)。如果应试者能正确回答这些问题,那么他就给我留下了一个好印象。顺带提一句,也许你可能会问,即使不用关键字 const,也还是能很容易写出功能正确的程序,那么我为什么还要如此看重关键字const呢?我也如下的几下理由:) a3 V5 S. J! S1 U$ ]0 d
1) 关键字const的作用是为给读你代码的人传达非常有用的信息,实际上,声明一个参数为常量是为了告诉了用户这个参数的应用目的。如果你曾花很多时间清理其它人留下的垃圾,你就会很快学会感谢这点多余的信息。(当然,懂得用const的程序员很少会留下的垃圾让别人来清理的。) R! x9 ^- y2 _, W" `+ o
2) 通过给优化器一些附加的信息,使用关键字const也许能产生更紧凑的代码。- x d! I1 b+ |, h% Y5 a
3) 合理地使用关键字const可以使编译器很自然地保护那些不希望被改变的参数,防止其被无意的代码修改。简而言之,这样可以减少bug的出现。 3 o2 J# i# C" n- V 9 l8 I$ R& J. k' s- FVolatile2 N9 a' Q# C* H2 F
2 M/ k2 t# Y* V, I# q! P8. 关键字volatile有什么含意?并给出三个不同的例子。4 ? F; r9 B( E: u, w Y
一个定义为volatile的变量是说这变量可能会被意想不到地改变,这样,编译器就不会去假设这个变量的值了。精确地说就是,优化器在用到这个变量时必须每次都小心地重新读取这个变量的值,而不是使用保存在寄存器里的备份。下面是volatile变量的几个例子: 6 d+ w9 c; G( Z" E8 Z$ }/ w1) 并行设备的硬件寄存器(如:状态寄存器) ) |( m) v! X% z7 {' Z2) 一个中断服务子程序中会访问到的非自动变量(Non-automatic variables)8 W. M( X4 m J1 c. L, C/ g
3) 多线程应用中被几个任务共享的变量 7 t% H5 C8 p1 \0 E . v; p# U, E/ A! L! T! O 回答不出这个问题的人是不会被雇佣的。我认为这是区分C程序员和嵌入式系统程序员的最基本的问题。搞嵌入式的家伙们经常同硬件、中断、RTOS等等打交道,所有这些都要求用到volatile变量。不懂得volatile的内容将会带来灾难。 5 ~9 `! j( s8 x* a 假设被面试者正确地回答了这是问题(嗯,怀疑是否会是这样),我将稍微深究一下,看一下这家伙是不是直正懂得volatile完全的重要性。7 z( Q! F$ O7 o) V! J" X n8 g
1) 一个参数既可以是const还可以是volatile吗?解释为什么。 : ~+ {/ e0 c( T1 P M2) 一个指针可以是volatile 吗?解释为什么。$ f, Y( q& n/ K. [ K5 q
3) 下面的函数有什么错误: s, n5 |1 v2 E" D, C5 u" |
; L3 \0 `0 I3 a1 |/ u% |int square(volatile int *ptr)0 Q9 } H/ P! J* k' _' Q" G$ W( y/ K/ Q
{ 2 P3 n- \; j$ j& _3 n return *ptr * *ptr;0 E6 k% X9 H5 d2 i/ g, X5 _4 T) {
}( t+ k3 p5 ^% Z x% A4 c/ {3 b
9 J7 ^3 W9 }* W5 ]2 p& w
下面是答案: " V" I( ?. J7 U5 | u; c3 l/ e M, ^/ q. a7 G- F. }
int square(volatile int *ptr)0 d K$ Z8 z) z3 X; Z5 M* U5 L( k
{ + c. t; }) i1 L% u+ K int a,b;4 E* h2 x& i" r* J; ]3 ~
a = *ptr; ; e. d- `4 P9 O# u b = *ptr;$ w) F; B& c+ c! V0 s6 A, y+ f
return a * b;8 [) w4 b& I0 m0 P+ X( `( I2 e, R
} 0 n) S0 v! q8 q- L . H. F( R! g5 Q2 A" O由于*ptr的值可能被意想不到地该变,因此a和b可能是不同的。结果,这段代码可能返不是你所期望的平方值!正确的代码如下:$ L; z1 L4 j0 w6 L) L, C* |8 P
; ~* P/ ?! a3 ^8 F* u# O
long square(volatile int *ptr) 2 |5 Y0 a/ N' l8 j' Y{ ' v3 J( X" g5 o. S4 C- E int a; 4 b7 I) H3 T* f a = *ptr;! y' J+ T, J. L4 b
return a * a;! E4 Z5 V0 K9 x+ o, }- t
} * N6 F; l% T1 w0 ] 3 S8 p3 R% g+ F& H位操作(Bit manipulation)( @0 ~% t8 s/ ^% I
# H3 J9 S, Q, ?4 g- r4 z+ Y Y
9. 嵌入式系统总是要用户对变量或寄存器进行位操作。给定一个整型变量a,写两段代码,第一个设置a的bit 3,第二个清除a 的bit 3。在以上两个操作中,要保持其它位不变。" f& \) V5 }! H6 ?
对这个问题有三种基本的反应 * t' z( N& J4 K8 h) K6 K) B1) 不知道如何下手。该被面者从没做过任何嵌入式系统的工作。 ) _3 @$ f6 y! ~6 M8 @! L/ ]2) 用bit fields。Bit fields是被扔到C语言死角的东西,它保证你的代码在不同编译器之间是不可移植的,同时也保证了的你的代码是不可重用的。我最近不幸看到 Infineon为其较复杂的通信芯片写的驱动程序,它用到了bit fields因此完全对我无用,因为我的编译器用其它的方式来实现bit fields的。从道德讲:永远不要让一个非嵌入式的家伙粘实际硬件的边。1 M. m; ~7 T) X& H
3) 用 #defines 和 bit masks 操作。这是一个有极高可移植性的方法,是应该被用到的方法。最佳的解决方案如下: 5 m4 M: Y v) ~8 B m 4 P' y8 l% r* A( b; g% s: j#define BIT3 (0x1 << 3)* @) v- w2 E+ h4 M: W1 e3 l
static int a; + l; B% o8 R5 N, b' }6 v8 @" S. p/ O$ j) B4 t/ j
void set_bit3(void) & }6 f' `/ a* m5 q{3 o& Y" O3 b, |8 l
a |= BIT3; 3 J/ i% [/ { L: c6 w: g+ k8 b}/ q# u! g4 H" L/ T8 `5 o$ c
void clear_bit3(void) 9 l7 Q. j8 F: H2 F$ z, l3 v8 R{ + N5 D1 y7 D% u* Z" t a &= ~BIT3;, M& r, { T7 w7 z5 D/ J7 ?
}% H9 M7 k( Z0 Y! o5 C' Q
8 g @. z9 q5 G2 D" q
一些人喜欢为设置和清除值而定义一个掩码同时定义一些说明常数,这也是可以接受的。我希望看到几个要点:说明常数、|=和&=~操作。 2 y/ l i1 o2 c, d' V1 l7 C' |% M( n" u3 a: S) U" H
访问固定的内存位置(Accessing fixed memory locations) . x$ c) G5 b; c/ B E" p' v0 `' T & {! A+ C- i3 F& s$ u10. 嵌入式系统经常具有要求程序员去访问某特定的内存位置的特点。在某工程中,要求设置一绝对地址为0x67a9的整型变量的值为0xaa66。编译器是一个纯粹的ANSI编译器。写代码去完成这一任务。) H' B, r8 \) w( j9 l' k* u
这一问题测试你是否知道为了访问一绝对地址把一个整型数强制转换(typecast)为一指针是合法的。这一问题的实现方式随着个人风格不同而不同。典型的类似代码如下: 9 v# b5 r1 B8 f" I int *ptr;8 {* Z8 D, n! h
ptr = (int *)0x67a9;- s9 D1 Y1 T. E4 v5 [/ S
*ptr = 0xaa55; 9 r e) m& c; ?& F6 Y1 {6 U3 N% ~6 A7 o) |4 I- F6 f" l3 C/ p
A more obscure approach is:% m- R$ a0 ~! j9 U$ A0 t- ?
一个较晦涩的方法是:) H9 X4 @, K: w
0 q0 e1 o* v9 L
*(int * const)(0x67a9) = 0xaa55;4 P" V+ S) W# M9 V4 j, `
9 Z& I" R8 P. J; ]即使你的品味更接近第二种方案,但我建议你在面试时使用第一种方案。 $ N* X/ ]7 P) d1 Y9 H4 s" O9 `6 b( p
中断(Interrupts) K& s4 f% \$ ]0 Z! L; K9 @8 R
5 p: L$ d' h5 j. _
11. 中断是嵌入式系统中重要的组成部分,这导致了很多编译开发商提供一种扩展—让标准C支持中断。具代表事实是,产生了一个新的关键字 __interrupt。下面的代码就使用了__interrupt关键字去定义了一个中断服务子程序(ISR),请评论一下这段代码的。( ]1 ]( Z! e5 K: ^' u: A; K9 ~
' S9 Q, I' o# o! T% b- B$ k
__interrupt double compute_area (double radius)8 E A- C0 X( q& U5 L& I$ r
{ , P! {& E2 p; z2 G# q3 E9 y1 u double area = PI * radius * radius; * e* H1 Y! E4 u- ?; j printf("\nArea = %f", area); , }7 c8 c2 T( r! ~( b return area;( o( |& G. E3 r5 ?! G5 y3 p
} e- i2 b8 J& ?- H" B% P- W " m! ]; j( G2 g* [/ j8 p4 ^这个函数有太多的错误了,以至让人不知从何说起了:# A: z1 U, L- s* x* ], Q
1) ISR 不能返回一个值。如果你不懂这个,那么你不会被雇用的。! x5 e1 W; L) i8 j7 V
2) ISR 不能传递参数。如果你没有看到这一点,你被雇用的机会等同第一项。 ' Q6 b: E4 v! u0 k3 [ x. b3) 在许多的处理器/编译器中,浮点一般都是不可重入的。有些处理器/编译器需要让额处的寄存器入栈,有些处理器/编译器就是不允许在ISR中做浮点运算。此外,ISR应该是短而有效率的,在ISR中做浮点运算是不明智的。 - q. G2 Z: p9 V9 W L* R4) 与第三点一脉相承,printf()经常有重入和性能上的问题。如果你丢掉了第三和第四点,我不会太为难你的。不用说,如果你能得到后两点,那么你的被雇用前景越来越光明了。 2 ~$ Z( E) r7 L ]* u) G8 v+ t0 I" v. r' M" d& n) _
代码例子(Code examples)/ @# ?! L. d2 P/ V' U
( f; V) K, ?3 u4 Q9 C. P $ c. w Q1 Z8 }! B4 L12 . 下面的代码输出是什么,为什么?8 [& u5 \. @* l# [% g" P
b7 Z1 j' t, A4 |
void foo(void) , [3 C8 ~* ?2 b: X8 T{8 t, u( J6 ^, f+ P, m+ _4 [
unsigned int a = 6; ) @% N2 ^8 F- [ int b = -20;8 ^- ] f/ u3 H1 o; F+ e) e& l' W& @
(a+b > 6) ? puts("> 6") : puts("<= 6");0 a8 y' _& q4 S* |0 s- A
} 5 f/ p- [+ a, H: ^, z+ l2 ]0 f 这个问题测试你是否懂得C语言中的整数自动转换原则,我发现有些开发者懂得极少这些东西。不管如何,这无符号整型问题的答案是输出是 ">6"。原因是当表达式中存在有符号类型和无符号类型时所有的操作数都自动转换为无符号类型。因此-20变成了一个非常大的正整数,所以该表达式计算出的结果大于6。这一点对于应当频繁用到无符号数据类型的嵌入式系统来说是丰常重要的。如果你答错了这个问题,你也就到了得不到这份工作的边缘。 7 ?2 O, N; s5 k* y" q. ~" B 3 T9 a# R3 X) F: W+ ?: E13. 评价下面的代码片断:) a- r! V1 f3 W5 c! v( j
3 \$ N/ T0 c$ Y) e
unsigned int zero = 0;, @: R R3 H9 z% [5 I# U; l
unsigned int compzero = 0xFFFF;* C* z9 j) q* R1 L0 b+ x: d
/*1''s complement of zero */8 l* v+ `& M3 Z" w# A: [* m% t3 j" G2 S2 n
! _- p/ M5 H# q/ o1 f9 Z对于一个int型不是16位的处理器为说,上面的代码是不正确的。应编写如下:* B8 x8 ]7 C) w0 t8 D1 K+ n
# P' Y z1 ^8 P- j
unsigned int compzero = ~0; ( j9 v R+ X3 z. i7 t# |4 k ( d$ J% B3 F, m2 |+ C6 b v( u 这一问题真正能揭露出应试者是否懂得处理器字长的重要性。在我的经验里,好的嵌入式程序员非常准确地明白硬件的细节和它的局限,然而PC机程序往往把硬件作为一个无法避免的烦恼。 $ a) R9 c$ p, Q: S 到了这个阶段,应试者或者完全垂头丧气了或者信心满满志在必得。如果显然应试者不是很好,那么这个测试就在这里结束了。但如果显然应试者做得不错,那么我就扔出下面的追加问题,这些问题是比较难的,我想仅仅非常优秀的应试者能做得不错。提出这些问题,我希望更多看到应试者应付问题的方法,而不是答案。不管如何,你就当是这个娱乐吧... F! A$ [! R# F) I- q