預(yù)處理器(Preprocessor)0 n# S1 W+ C8 m. b $ ~6 M$ \& T2 K* U& Z! |" I o 1 . 用預(yù)處理指令#define 聲明一個常數(shù),用以表明1年中有多少秒(忽略閏年問題) 2 \& |+ t, D; d3 \, H #define SECONDS_PER_YEAR (60 * 60 * 24 * 365)UL0 y" |2 O4 C1 i0 P: `! I0 f 我在這想看到幾件事情:* J6 ?8 U6 l! A8 N 1) #define 語法的基本知識(例如:不能以分號結(jié)束,括號的使用,等等)) m4 M1 _ [( t6 w 2)懂得預(yù)處理器將為你計算常數(shù)表達(dá)式的值,因此,直接寫出你是如何計算一年中有多少秒而不是計算出實際的值,是更清晰而沒有代價的。 4 N7 R& k+ g1 u" t 3) 意識到這個表達(dá)式將使一個16位機(jī)的整型數(shù)溢出-因此要用到長整型符號L,告訴編譯器這個常數(shù)是的長整型數(shù)。 : @% a- C& `7 `) e& d5 b) O9 J 4) 如果你在你的表達(dá)式中用到UL(表示無符號長整型),那么你有了一個好的起點。記住,第一印象很重要。" b' k% ^7 |- Q% l1 L! k/ t$ G + I2 ^. l! A1 X+ F 2 . 寫一個"標(biāo)準(zhǔn)"宏MIN ,這個宏輸入兩個參數(shù)并返回較小的一個。: M% ~% I- @5 D' O2 ~/ \7 I #define MIN(A,B) ((A) <= (B) ? (A) : (B)) / ?" [) s/ _, R! q$ E, b8 i9 p 這個測試是為下面的目的而設(shè)的: 7 N% M5 O6 D' G k8 h5 i& P/ {+ U 1) 標(biāo)識#define在宏中應(yīng)用的基本知識。這是很重要的。因為在 嵌入(inline)操作符 變?yōu)闃?biāo)準(zhǔn)C的一部分之前,宏是方便產(chǎn)生嵌入代碼的唯一方法,對于嵌入式系統(tǒng)來說,為了能達(dá)到要求的性能,嵌入代碼經(jīng)常是必須的方法。 * E X9 T! O) _/ f' { 2)三重條件操作符的知識。這個操作符存在C語言中的原因是它使得編譯器能產(chǎn)生比if-then-else更優(yōu)化的代碼,了解這個用法是很重要的。 2 k# n w. p0 _+ G: C 3) 懂得在宏中小心地把參數(shù)用括號括起來$ [8 c4 M+ E& x2 q* K2 r9 @ 4) 我也用這個問題開始討論宏的副作用,例如:當(dāng)你寫下面的代碼時會發(fā)生什么事? ' a( N* C! B* K least = MIN(*p++, b); ! a8 Q5 g+ e9 O* L' {! l6 i # `1 c# ^* Z7 Y6 u# z 3. 預(yù)處理器標(biāo)識#error的目的是什么?! m/ @2 _, y8 u/ t- o 如果你不知道答案,請看參考文獻(xiàn)1。這問題對區(qū)分一個正常的伙計和一個書呆子是很有用的。只有書呆子才會讀C語言課本的附錄去找出象這種問題的答案。當(dāng)然如果你不是在找一個書呆子,那么應(yīng)試者最好希望自己不要知道答案。4 C4 }5 v- ~7 o. i! e
4 b6 D6 d7 @% z z3 Y V0 o1 W9 D! `- B 死循環(huán)(Infinite loops) 7 ?7 k2 s1 {$ }0 j! _. B ' N8 j9 M: v) t' ?1 p 4. 嵌入式系統(tǒng)中經(jīng)常要用到無限循環(huán),你怎么樣用C編寫死循環(huán)呢? # h# \1 M0 u( | @" _ 這個問題用幾個解決方案。我首選的方案是: + K1 w* ]3 [% c / s4 f+ \% ^. H8 o4 @7 I! q while(1)5 |- V$ D0 \) {% c+ x( C: R {$ S& G( }8 |' Q6 D. L% B: m3 K
. v/ A, B% r4 ?5 w8 y7 j4 H2 Q } & x" d; o+ ]+ C6 l; z1 I 6 X" A- Q, Z" U, J, e0 F0 E+ J 一些程序員更喜歡如下方案: 4 d; p9 [1 w. u) v5 U' H3 g / Y. M& [6 ?' Z- H6 U8 a& A* ~ for(;;)3 Q, h( y' z2 I+ `# X& n, Q {! f& [; F! b: ^( ~# A4 { 5 J9 i" H0 s( q K }" k$ n3 S- [! ?7 i1 E5 @/ x6 h 0 i" R, S, m9 t: T 這個實現(xiàn)方式讓我為難,因為這個語法沒有確切表達(dá)到底怎么回事。如果一個應(yīng)試者給出這個作為方案,我將用這個作為一個機(jī)會去探究他們這樣做的基本原理。如果他們的基本答案是:"我被教著這樣做,但從沒有想到過為什么。"這會給我留下一個壞印象。 % U. y5 J6 Y5 N* T4 q 5 F/ Q& W" b; K" n: j) m' x 第三個方案是用 goto 1 N. g) K( P" P/ ] Loop: ' N! F0 q+ ~6 h' }) g2 m% Q+ A ..., Z3 T5 ~0 A5 W0 F" _ goto Loop; " Q' K4 A1 o1 L2 J/ P/ k8 Y 應(yīng)試者如給出上面的方案,這說明或者他是一個匯編語言程序員(這也許是好事)或者他是一個想進(jìn)入新領(lǐng)域的BASIC/FORTRAN程序員。! j$ y$ |+ c2 |# A1 D; Q! o
: p* z7 ^; w; H " Q; O; a+ ~7 P# i* n* d 數(shù)據(jù)聲明(Data declarations) ( E- V# z, ?* o5 \
( \+ V: N0 ]5 f2 t4 C6 B 5. 用變量a給出下面的定義 % v h+ a9 j4 L H9 g a) 一個整型數(shù)(An integer) K5 A# B& x0 o9 h+ M) J- V b)一個指向整型數(shù)的指針( A pointer to an integer) # u, d6 X6 Z1 P) \" E( d: l c)一個指向指針的的指針,它指向的指針是指向一個整型數(shù)( A pointer to a pointer to an intege)r : X" L" _' Q: w/ w n4 f9 D d)一個有10個整型數(shù)的數(shù)組( An array of 10 integers) 9 C6 z+ X9 _7 H1 L1 G e) 一個有10個指針的數(shù)組,該指針是指向一個整型數(shù)的。(An array of 10 pointers to integers) ; y- j) S- q4 C4 ^6 s6 t% o/ t2 u5 n; N0 x f) 一個指向有10個整型數(shù)數(shù)組的指針( A pointer to an array of 10 integers) + c- L& d8 I1 E& ^6 J$ g% ] g) 一個指向函數(shù)的指針,該函數(shù)有一個整型參數(shù)并返回一個整型數(shù)(A pointer to a function that takes an integer as an argument and returns an integer) v5 t. D. [6 ]4 Y2 n h) 一個有10個指針的數(shù)組,該指針指向一個函數(shù),該函數(shù)有一個整型參數(shù)并返回一個整型數(shù)( An array of ten pointers to functions that take an integer argument and return an integer ). s, k( g& l9 g; _0 M. f : @4 t6 M8 o$ J3 t 答案是: 5 N* N) C% t2 x7 K% x* e2 N; n5 ~ a) int a; // An integer # z: B& D4 [7 Y! ]6 Q b) int *a; // A pointer to an integer 4 x' Z- A0 z8 U c) int **a; // A pointer to a pointer to an integer 6 q, A9 R2 \4 j) x d) int a[10]; // An array of 10 integers 5 q5 m4 ?8 E: Y% i0 E; V e) int *a[10]; // An array of 10 pointers to integers 0 G: \% S1 ~1 q( S& W; n f) int (*a)[10]; // A pointer to an array of 10 integers 1 z" i! t) A5 I% o/ y g) int (*a)(int); // A pointer to a function a that takes an integer argument and returns an integer 3 R8 }4 |3 A- F" x% r) D% q3 a h) int (*a[10])(int); // An array of 10 pointers to functions that take an integer argument and return an integer 8 b. {' F5 p% [' l
; H$ H6 f( `5 ^& X 人們經(jīng)常聲稱這里有幾個問題是那種要翻一下書才能回答的問題,我同意這種說法。當(dāng)我寫這篇文章時,為了確定語法的正確性,我的確查了一下書。但是當(dāng)我被面試的時候,我期望被問到這個問題(或者相近的問題)。因為在被面試的這段時間里,我確定我知道這個問題的答案。應(yīng)試者如果不知道所有的答案(或至少大部分答案),那么也就沒有為這次面試做準(zhǔn)備,如果該面試者沒有為這次面試做準(zhǔn)備,那么他又能為什么出準(zhǔn)備呢? ) Z2 ?& ]3 X. A6 b7 L' p( i3 v% X % t/ S/ s! E7 ~8 I) ?9 U Static 2 s9 \ b3 h. R* b, h
9 K3 i$ L# Z% m 6. 關(guān)鍵字static的作用是什么?% v( y) E: E- B$ z, s 這個簡單的問題很少有人能回答完全。在C語言中,關(guān)鍵字static有三個明顯的作用:5 m: B! t+ Z; n" b. W. b9 w5 m2 r# R. M 1)在函數(shù)體,一個被聲明為靜態(tài)的變量在這一函數(shù)被調(diào)用過程中維持其值不變。 % U9 O- |% ^4 T- p0 o: u6 h; M$ W 2) 在模塊內(nèi)(但在函數(shù)體外),一個被聲明為靜態(tài)的變量可以被模塊內(nèi)所用函數(shù)訪問,但不能被模塊外其它函數(shù)訪問。它是一個本地的全局變量。 1 }* @% k* h8 u( `/ d& p 3) 在模塊內(nèi),一個被聲明為靜態(tài)的函數(shù)只可被這一模塊內(nèi)的其它函數(shù)調(diào)用。那就是,這個函數(shù)被限制在聲明它的模塊的本地范圍內(nèi)使用。 5 E8 W. a5 ~& J) W" r/ O . Q. R; N, U$ i5 W. r 大多數(shù)應(yīng)試者能正確回答第一部分,一部分能正確回答第二部分,同是很少的人能懂得第三部分。這是一個應(yīng)試者的嚴(yán)重的缺點,因為他顯然不懂得本地化數(shù)據(jù)和代碼范圍的好處和重要性。( y; w5 p) \& {; x9 H/ O
/ p- u' L1 s( @! O) u# g5 w* T ; @( s; R/ k7 Y6 x/ |( C1 ?! ` Const % y9 O* t# P8 y H4 v ; m% |0 Z7 ^$ z! x 7.關(guān)鍵字const有什么含意?" d3 f* M, k% z& n$ c 我只要一聽到被面試者說:"const意味著常數(shù)",我就知道我正在和一個業(yè)余者打交道。去年Dan Saks已經(jīng)在他的文章里完全概括了const的所有用法,因此ESP(譯者:Embedded Systems Programming)的每一位讀者應(yīng)該非常熟悉const能做什么和不能做什么.如果你從沒有讀到那篇文章,只要能說出const意味著"只讀"就可以了。盡管這個答案不是完全的答案,但我接受它作為一個正確的答案。(如果你想知道更詳細(xì)的答案,仔細(xì)讀一下Saks的文章吧。)5 H5 [: G" L0 B* W 如果應(yīng)試者能正確回答這個問題,我將問他一個附加的問題:4 G' S" F" b) K# s* U 下面的聲明都是什么意思?, O, O$ w( d" V 3 Z2 U* n* L7 Y$ c( ]# B const int a;8 |6 `& m6 U. N* G7 p5 P; S int const a; ' c: B: P ]! N1 ]6 Z const int *a; % v+ m; y) e& o int * const a; % z- O: Y g: R8 `( y {: y5 x int const * a const;, W+ i& r. Z9 {) l ; O+ l% H9 ] ]& S0 {" Q* ^ /******/3 J& @% |, Q* W1 C( u 前兩個的作用是一樣,a是一個常整型數(shù)。第三個意味著a是一個指向常整型數(shù)的指針(也就是,整型數(shù)是不可修改的,但指針可以)。第四個意思a是一個指向整型數(shù)的常指針(也就是說,指針指向的整型數(shù)是可以修改的,但指針是不可修改的)。最后一個意味著a是一個指向常整型數(shù)的常指針(也就是說,指針指向的整型數(shù)是不可修改的,同時指針也是不可修改的)。如果應(yīng)試者能正確回答這些問題,那么他就給我留下了一個好印象。順帶提一句,也許你可能會問,即使不用關(guān)鍵字 const,也還是能很容易寫出功能正確的程序,那么我為什么還要如此看重關(guān)鍵字const呢?我也如下的幾下理由:. O. E& ^- b5 ?+ K( S 1) 關(guān)鍵字const的作用是為給讀你代碼的人傳達(dá)非常有用的信息,實際上,聲明一個參數(shù)為常量是為了告訴了用戶這個參數(shù)的應(yīng)用目的。如果你曾花很多時間清理其它人留下的垃圾,你就會很快學(xué)會感謝這點多余的信息。(當(dāng)然,懂得用const的程序員很少會留下的垃圾讓別人來清理的。), m% \ J X; P! A; W: x 2) 通過給優(yōu)化器一些附加的信息,使用關(guān)鍵字const也許能產(chǎn)生更緊湊的代碼。. v& H1 I' T: V) c9 W% f. i1 S 3) 合理地使用關(guān)鍵字const可以使編譯器很自然地保護(hù)那些不希望被改變的參數(shù),防止其被無意的代碼修改。簡而言之,這樣可以減少bug的出現(xiàn)。2 l0 Y* t9 F& W7 Y( ^7 e. p( L6 c
8 ^: d# D$ h5 i) c; q ; D% o$ b/ t5 D Volatile 5 X# }4 M" s- p l2 [ . S% ^/ c* w; U4 I" x/ ], C6 A 8. 關(guān)鍵字volatile有什么含意?并給出三個不同的例子。 8 [/ ~, Z* p" w# f5 [! h1 t 一個定義為volatile的變量是說這變量可能會被意想不到地改變,這樣,編譯器就不會去假設(shè)這個變量的值了。精確地說就是,優(yōu)化器在用到這個變量時必須每次都小心地重新讀取這個變量的值,而不是使用保存在寄存器里的備份。下面是volatile變量的幾個例子:: h9 R) A( |* k 1) 并行設(shè)備的硬件寄存器(如:狀態(tài)寄存器)' G+ m$ p! Y5 D6 l v' A% C 2) 一個中斷服務(wù)子程序中會訪問到的非自動變量(Non-automatic variables)7 [5 A6 E& G U1 ]9 P" z# } 3) 多線程應(yīng)用中被幾個任務(wù)共享的變量% V$ ?& X: v8 c) T" M% ? " o) R% y+ i3 h/ l5 a) u2 L 回答不出這個問題的人是不會被雇傭的。我認(rèn)為這是區(qū)分C程序員和嵌入式系統(tǒng)程序員的最基本的問題。搞嵌入式的家伙們經(jīng)常同硬件、中斷、RTOS等等打交道,所有這些都要求用到volatile變量。不懂得volatile的內(nèi)容將會帶來災(zāi)難。 - g+ D V1 E# z$ z4 b 假設(shè)被面試者正確地回答了這是問題(嗯,懷疑是否會是這樣),我將稍微深究一下,看一下這家伙是不是直正懂得volatile完全的重要性。! K! H; b' ?, x 1)一個參數(shù)既可以是const還可以是volatile嗎?解釋為什么。 & {' o0 r! D& B/ q: T* m1 q 2); 一個指針可以是volatile 嗎?解釋為什么。 3 a$ j4 M! \4 E% Q. U& F/ C: | 3); 下面的函數(shù)有什么錯誤:* k; n; ?, O7 U" C+ V1 r ( V, Y' Y% e1 D8 R) T5 w1 b* W int square(volatile int *ptr) 2 R9 N: B- O& s! p { # o& o- s- _) C* ]8 ` return *ptr * *ptr; % Q5 H2 q2 }: h9 C- @( _% | }$ L6 P- o( J, n: Q 1 r5 f& R" {& _$ X# |0 U 下面是答案: + e# T" h3 h0 d' L 1)是的。一個例子是只讀的狀態(tài)寄存器。它是volatile因為它可能被意想不到地改變。它是const因為程序不應(yīng)該試圖去修改它。 8 b& [ N$ ^1 _5 l' ], P 2); 是的。盡管這并不很常見。一個例子是當(dāng)一個中服務(wù)子程序修該一個指向一個buffer的指針時。& {% J3 F3 N, M0 }# \3 h 3) 這段代碼有點變態(tài)。這段代碼的目的是用來返指針*ptr指向值的平方,但是,由于*ptr指向一個volatile型參數(shù),編譯器將產(chǎn)生類似下面的代碼: |- k3 U! M2 f( ?6 v' _
8 B- n, K+ x* V s2 o int square(volatile int *ptr) # b8 B& F( e; B0 Y, _3 f {8 U. g1 p" H5 Q' O# Z- M- @, u int a,b; ; O2 Q; C% S& Z8 }. Q' @ a = *ptr;, f2 V5 ^1 X% u1 Q0 I b = *ptr;" D4 B4 @% W: N, x return a * b;; b( n0 O* M" m }' x* U. _' U1 i4 q$ \# D " l: H9 f$ P1 l6 W! Z$ P. b9 M- b 由于*ptr的值可能被意想不到地該變,因此a和b可能是不同的。結(jié)果,這段代碼可能返不是你所期望的平方值!正確的代碼如下: & y) ^2 ], [" E9 A7 i# N$ F* ? ) ^, t' Q+ Y. u2 T8 g long square(volatile int *ptr) / I N5 m$ l1 X {9 Y) I/ i. S1 s( {3 u6 c8 R4 F int a; - L/ w( `% i" z; [+ B0 } a = *ptr;" Y9 B4 S6 ?: W7 T1 ] return a * a; 7 w: x3 j: o; T4 V! m }+ s* Z/ v/ H/ m% G
6 g+ X7 F2 q' P3 Q* `, S 位操作(Bit manipulation) , E$ w' G- @: {" y ( n5 j, v0 N+ d% E2 h ^+ u+ V 9. 嵌入式系統(tǒng)總是要用戶對變量或寄存器進(jìn)行位操作。給定一個整型變量a,寫兩段代碼,第一個設(shè)置a的bit 3,第二個清除a 的bit 3。在以上兩個操作中,要保持其它位不變。% j5 n+ |' `# d 對這個問題有三種基本的反應(yīng) ( ~5 g( @9 ~! i9 q. T. L, { 1)不知道如何下手。該被面者從沒做過任何嵌入式系統(tǒng)的工作。 * C. Z4 ]' ^+ @. @. R1 S# y8 g 2) 用bit fields。Bit fields是被扔到C語言死角的東西,它保證你的代碼在不同編譯器之間是不可移植的,同時也保證了的你的代碼是不可重用的。我最近不幸看到 Infineon為其較復(fù)雜的通信芯片寫的驅(qū)動程序,它用到了bit fields因此完全對我無用,因為我的編譯器用其它的方式來實現(xiàn)bit fields的。從道德講:永遠(yuǎn)不要讓一個非嵌入式的家伙粘實際硬件的邊。 % g+ w- L* s, X( C V 3) 用 #defines 和 bit masks 操作。這是一個有極高可移植性的方法,是應(yīng)該被用到的方法。最佳的解決方案如下:) Q" a2 Q" S) T4 ?
1 F" I; a1 k8 _8 W8 H #define BIT3 (0x1 << 3)6 [2 L5 C! D" o5 }3 y/ ? static int a; + b& Q! b+ ]2 b Y& _1 B m8 j/ i r+ a) O void set_bit3(void) 3 O) C, i; z# g: p3 O: W {0 c/ p8 J" Z7 G$ b a |= BIT3; ' L6 f# V. b5 A5 u7 T }$ r( S) t: s$ F4 \- X+ m o1 v void clear_bit3(void) % b' S5 Y3 |' r7 a {/ N! ]9 c0 r& V9 X1 E- V( z a &= ~BIT3;# q$ I n, J9 l8 ~& F" p) G }4 D1 L2 V" n+ K' ]2 [8 ~& C
/ e2 M- e* W. s 一些人喜歡為設(shè)置和清除值而定義一個掩碼同時定義一些說明常數(shù),這也是可以接受的。我希望看到幾個要點:說明常數(shù)、|=和&=~操作。6 x, j7 r i8 b& [0 q' n( K
% i+ D, I4 g# S& V6 ~6 p6 [8 @ Z 4 s1 A+ G$ |) A" f) v% v 訪問固定的內(nèi)存位置(Accessing fixed memory locations) , K) U' H4 T8 I* i0 ~ % @: _- ^, N# r4 R" z5 ^ 10. 嵌入式系統(tǒng)經(jīng)常具有要求程序員去訪問某特定的內(nèi)存位置的特點。在某工程中,要求設(shè)置一絕對地址為0x67a9的整型變量的值為0xaa66。編譯器是一個純粹的ANSI編譯器。寫代碼去完成這一任務(wù)。 z) n9 w3 b% N! Q( V 這一問題測試你是否知道為了訪問一絕對地址把一個整型數(shù)強制轉(zhuǎn)換(typecast)為一指針是合法的。這一問題的實現(xiàn)方式隨著個人風(fēng)格不同而不同。典型的類似代碼如下:7 g3 {/ f1 N9 A int *ptr; / f6 {& ~" _) L$ g* {1 ] ptr = (int *)0x67a9; 1 f3 m5 S4 g$ U, P *ptr = 0xaa55;8 W0 U4 m( F, ` - C2 I' G5 T; ?9 D3 m A more obscure approach is: r# a4 R6 L) r. A& [ 一個較晦澀的方法是: ) K& s! X9 h; g. |& p7 q1 U & j0 D( k4 s$ G" w3 D1 E1 L$ D( v& Z *(int * const)(0x67a9) = 0xaa55;% j8 R8 O3 m- [; o' g, x
2 _1 D. p. d; K; o# Y 即使你的品味更接近第二種方案,但我建議你在面試時使用第一種方案。 7 e9 G1 k9 k0 n. E5 c2 u % H7 U/ v& r& C" H/ ^1 P. J 中斷(Interrupts) ( u# N( o( t+ m7 f# ^* N( V * q: K3 ]. B N 11. 中斷是嵌入式系統(tǒng)中重要的組成部分,這導(dǎo)致了很多編譯開發(fā)商提供一種擴(kuò)展—讓標(biāo)準(zhǔn)C支持中斷。具代表事實是,產(chǎn)生了一個新的關(guān)鍵字 __interrupt。下面的代碼就使用了__interrupt關(guān)鍵字去定義了一個中斷服務(wù)子程序(ISR),請評論一下這段代碼的。 6 R- v. @1 s* l3 G 1 W) N: T* Q' A. c: { __interrupt double compute_area (double radius) q: f' }% K& ? {& A! B1 d1 L( N6 D double area = PI * radius * radius; % x/ Q5 N+ E4 c printf("\nArea = %f", area);( q/ A4 L* K# u6 o6 ] return area;" p! N( R& q/ `8 o1 a$ t. x$ I }: J6 `# p/ z$ V. R/ E( y
: [3 o- o3 q% L( { 這個函數(shù)有太多的錯誤了,以至讓人不知從何說起了: f* m$ a+ s! o7 P4 [ 1)ISR 不能返回一個值。如果你不懂這個,那么你不會被雇用的。5 E) k# |% B+ U" \7 h0 b 2) ISR 不能傳遞參數(shù)。如果你沒有看到這一點,你被雇用的機(jī)會等同第一項。 + q; u9 H9 K* w$ [' f 3) 在許多的處理器/編譯器中,浮點一般都是不可重入的。有些處理器/編譯器需要讓額處的寄存器入棧,有些處理器/編譯器就是不允許在ISR中做浮點運算。此外,ISR應(yīng)該是短而有效率的,在ISR中做浮點運算是不明智的。 ( F. \6 s+ ]2 D 4) 與第三點一脈相承,printf()經(jīng)常有重入和性能上的問題。如果你丟掉了第三和第四點,我不會太為難你的。不用說,如果你能得到后兩點,那么你的被雇用前景越來越光明了。 * g' [) M. I; i3 G6 a" i+ n) C ?' [! Z. D$ c0 x
; I! G6 B* M7 k* m' Q5 x. x 代碼例子(Code examples) % k- A: ^) H& Y; S# a; R, K . \+ q5 L& c C" Y& L 12 . 下面的代碼輸出是什么,為什么? : v5 M4 d1 U8 M! P& s: Q / b2 {; I+ F1 i7 o* n A void foo(void) 0 m! @+ X) y0 F7 G: y8 N5 Q% N8 X {7 K/ H, ]+ m7 C" A unsigned int a = 6; - z* U" ?6 w- Z/ \ int b = -20; 6 v" \/ Z3 m9 f9 G% K% {6 P9 d (a+b > 6) ? puts("> 6") : puts("<= 6"); ' E' }, P& a, }! w i2 s: f1 C } 0 T3 n$ }$ j" `& Z1 f 這個問題測試你是否懂得C語言中的整數(shù)自動轉(zhuǎn)換原則,我發(fā)現(xiàn)有些開發(fā)者懂得極少這些東西。不管如何,這無符號整型問題的答案是輸出是 ">6"。原因是當(dāng)表達(dá)式中存在有符號類型和無符號類型時所有的操作數(shù)都自動轉(zhuǎn)換為無符號類型。因此-20變成了一個非常大的正整數(shù),所以該表達(dá)式計算出的結(jié)果大于6。這一點對于應(yīng)當(dāng)頻繁用到無符號數(shù)據(jù)類型的嵌入式系統(tǒng)來說是豐常重要的。如果你答錯了這個問題,你也就到了得不到這份工作的邊緣。 3 U, ?# Y: V! e% B' ^ ; y/ H2 i0 z0 s8 ^7 z 13. 評價下面的代碼片斷: 9 b, I: k5 b2 R8 B3 r5 ? m# s9 H , }& d+ t# q3 ]. ?1 Y* j! F unsigned int zero = 0; . Z- q5 j2 A: }; F3 x, y% J) G; `6 k+ o unsigned int compzero = 0xFFFF; 6 x {6 b5 N! E9 E8 k4 b e /*1's complement of zero */7 o7 _0 V t F3 ^. z 5 A4 z; U6 k6 d, n& W: l' A# I 對于一個int型不是16位的處理器為說,上面的代碼是不正確的。應(yīng)編寫如下:& |5 P: S/ f& _8 w. v 1 X7 c8 F( I H9 C C* p unsigned int compzero = ~0;) T% _ m" | x2 ?5 _9 D. F: u, B3 X
, u( F$ u2 o* g' ?" U7 m 這一問題真正能揭露出應(yīng)試者是否懂得處理器字長的重要性。在我的經(jīng)驗里,好的嵌入式程序員非常準(zhǔn)確地明白硬件的細(xì)節(jié)和它的局限,然而PC機(jī)程序往往把硬件作為一個無法避免的煩惱。; \. G' E; y7 a3 S( V# ?( [ 到了這個階段,應(yīng)試者或者完全垂頭喪氣了或者信心滿滿志在必得。如果顯然應(yīng)試者不是很好,那么這個測試就在這里結(jié)束了。但如果顯然應(yīng)試者做得不錯,那么我就扔出下面的追加問題,這些問題是比較難的,我想僅僅非常優(yōu)秀的應(yīng)試者能做得不錯。提出這些問題,我希望更多看到應(yīng)試者應(yīng)付問題的方法,而不是答案。不管如何,你就當(dāng)是這個娛樂吧...( R. U0 x) H- ?/ K- H; N( { : a1 i) N0 _8 @' l) u" e
$ m" d' z$ o! G& w3 A. x- D& r 動態(tài)內(nèi)存分配(Dynamic memory allocation) $ N3 r( C5 m! R# D) J+ N1 J" U ) U1 c$ W6 }; W$ ] 14. 盡管不像非嵌入式計算機(jī)那么常見,嵌入式系統(tǒng)還是有從堆(heap)中動態(tài)分配內(nèi)存的過程的。那么嵌入式系統(tǒng)中,動態(tài)分配內(nèi)存可能發(fā)生的問題是什么?1 S% u) _9 L4 l( h4 v4 m 這里,我期望應(yīng)試者能提到內(nèi)存碎片,碎片收集的問題,變量的持行時間等等。這個主題已經(jīng)在ESP雜志中被廣泛地討論過了(主要是 P.J. Plauger, 他的解釋遠(yuǎn)遠(yuǎn)超過我這里能提到的任何解釋),所有回過頭看一下這些雜志吧!讓應(yīng)試者進(jìn)入一種虛假的安全感覺后,我拿出這么一個小節(jié)目:! P! f l' ^4 k 下面的代碼片段的輸出是什么,為什么?$ O5 @) Z! q. y* M9 I # T; e" {. T/ n* x char *ptr; ' j* O% c4 z/ B+ T7 ]7 B if ((ptr = (char *)malloc(0)) == NULL) ( T3 T, h. c+ b# H puts("Got a null pointer"); # P' ?9 ^( e9 Y6 h$ i$ u else 5 R8 t- p! P) q2 Y m" w puts("Got a valid pointer"); + l0 t" F: s1 O 6 B9 G! o& g% q, u0 z2 b1 Z 這是一個有趣的問題。最近在我的一個同事不經(jīng)意把0值傳給了函數(shù)malloc,得到了一個合法的指針之后,我才想到這個問題。這就是上面的代碼,該代碼的輸出是"Got a valid pointer"。我用這個來開始討論這樣的一問題,看看被面試者是否想到庫例程這樣做是正確。得到正確的答案固然重要,但解決問題的方法和你做決定的基本原理更重要些。: u0 l9 e5 B) B& o8 E
5 p7 s9 A% x$ ?( b8 J Typedef ; w3 `, I) i( A+ j ; `+ T) [) p; l* r" u, K! Z" S 15 Typedef 在C語言中頻繁用以聲明一個已經(jīng)存在的數(shù)據(jù)類型的同義字。也可以用預(yù)處理器做類似的事。例如,思考一下下面的例子: ) p8 ^5 ?' e* N8 @6 W! H" s 2 l5 q+ p6 i4 k# N% m! N #define dPS struct s *% O: x9 \% d) U7 a1 T0 D/ X typedef struct s * tPS; 8 j8 T* u" A6 _3 r # @+ A3 l1 M4 O' |( y9 r% ? Z 以上兩種情況的意圖都是要定義dPS 和 tPS 作為一個指向結(jié)構(gòu)s指針。哪種方法更好呢?(如果有的話)為什么? " T4 ~* V3 z* A( k% c$ o 這是一個非常微妙的問題,任何人答對這個問題(正當(dāng)?shù)脑颍┦菓?yīng)當(dāng)被恭喜的。答案是:typedef更好。思考下面的例子: 0 k2 \ A+ e) @- z + n7 U: a9 ^- y dPS p1,p2;3 B4 m$ F- M' d+ w7 [. } tPS p3,p4; ! G) {- h" M- B5 O* Q. ?; U 8 G% d$ A! d. {+ l+ l 第一個擴(kuò)展為" o) W7 P) O! d8 u( x4 p 5 ?. S" X+ D$ v$ J struct s * p1, p2;5 ?" z, y5 h: s7 ]! \# y . 0 H% T$ m% J( q {& g( x 上面的代碼定義p1為一個指向結(jié)構(gòu)的指,p2為一個實際的結(jié)構(gòu),這也許不是你想要的。第二個例子正確地定義了p3 和p4 兩個指針。 0 |' J8 @' f) ]# X* g$ w" ? 2 A- J" q% w! g, f0 I9 S . `! @7 r* z5 J8 X ' o( f$ E. s1 o2 r; z+ S 晦澀的語法 . m% @, }* z: _ 0 c# d, d. B ?3 S( T6 B 16 . C語言同意一些令人震驚的結(jié)構(gòu),下面的結(jié)構(gòu)是合法的嗎,如果是它做些什么? 5 ~& T6 |, H( e* j + \" x: g x8 L! _& s& B int a = 5, b = 7, c;+ \8 ]9 e- p, J& n, L; b c = a+++b;/ d6 q7 `' J4 j2 s! o7 |
: b+ \" U* Z+ h5 A2 i+ a 這個問題將做為這個測驗的一個愉快的結(jié)尾。不管你相不相信,上面的例子是完全合乎語法的。問題是編譯器如何處理它?水平不高的編譯作者實際上會爭論這個問題,根據(jù)最處理原則,編譯器應(yīng)當(dāng)能處理盡可能所有合法的用法。因此,上面的代碼被處理成:, |8 _0 C; \* m( o1 Y4 e" I - l: I# P, O/ e3 M+ h0 `- v c = a++ + b;7 G8 \& W/ a& K6 s
4 f2 _$ N( Q/ H x& i" N# `! D) g 因此, 這段代碼持行后a = 6, b = 7, c = 12。 ' w+ E* S; O3 q, `3 I' K. ]% @ 如果你知道答案,或猜出正確答案,做得好。如果你不知道答案,我也不把這個當(dāng)作問題。我發(fā)現(xiàn)這個問題的最大好處是這是一個關(guān)于代碼編寫風(fēng)格,代碼的可讀性,代碼的可修改性的好的話題。, M( X# L9 z# z3 x' R9 U
( f- ?! ?% ~) h: n& m3 t5 F ! k z) e3 u- K) H0 h5 X- A8 s- _: M n* w& W; f( e% @
! w! L {. l: s5 I- q / s5 q& W- o6 Q1、將一個字符串逆序 3 L1 f" I G; V8 D) o+ n2、將一個鏈表逆序 / k. J$ B! y% E! z9 U 3、計算一個字節(jié)里(byte)里面有多少bit被置1 ' o9 c' p+ O7 A" e! r a9 t 4、搜索給定的字節(jié)(byte) ! {, j# F- E" {5、在一個字符串中找到可能的最長的子字符串 1 b2 R* `& {7 I6、字符串轉(zhuǎn)換為整數(shù) : a& k+ V2 F1 }7 t5 v* r 7、整數(shù)轉(zhuǎn)換為字符串 |