面试题 C
8?,Zm V
reR0嵌入式开发.C语言面试题LUPA开源社区8Y-E0| L\1eZ
预处理器(Preprocessor)LUPA开源社区4g2O}%Z;I0?7SK
{r"E1e#x%M7aUG01. 用预处理指令#define 声明一个常数,用以表明1年中有多少秒(忽略闰年问题)
:tU)VZ1sV0#define SECONDS_PER_YEAR (60 * 60 * 24 * 365)UL
T&q G7i.PGTn&Ns0我在这想看到几件事情: LUPA开源社区]'g&O'mV A([N
1). #define 语法的基本知识(例如:不能以分号结束,括号的使用,等等)
y5Pfh{l:w H4]02). 懂得预处理器将为你计算常数表达式的值,因此,直接写出你是如何计算一年中有多少秒而不是计算出实际的值,是更清晰而没有代价的。 LUPA开源社区!R4o)L9Ydsc
m&g
3). 意识到这个表达式将使一个16位机的整型数溢出-因此要用到长整型符号L,告诉编译器这个常数是的长整型数。 LUPA开源社区p/@~Oj
L
Lr
4). 如果你在你的表达式中用到UL(表示无符号长整型),那么你有了一个好的起点。记住,第一印象很重要。
4t,u+XW@bO\0C++面试题集,C++面试答案,C++面试,C++试题,C++试卷
zD%j"~Bv{x#X02. 写一个"标准"宏MIN,这个宏输入两个参数并返回较小的一个。LUPA开源社区 o Q2{ wf wyOpy
#define MIN(A,B) (A<= B ?A: B)
,Zv3V;CP0C\C++试题集 C\C++ Development 这个测试是为下面的目的而设的:
wg-Nal r.H'R1@08Gg.Q8wM(H%~%~01). 标识#define在宏中应用的基本知识。这是很重要的,因为直到嵌入(inline)操作符变为标准C的一部分,宏是方便产生嵌入代码的唯一方法,对于嵌入式系统来说,为了能达到要求的性能,嵌入代码经常是必须的方法。
K[)x;A4C02). 三重条件操作符的知识。这个操作符存在C语言中的原因是它使得编译器能产生比if-then-else更优化的代码,了解这个用法是很重要的。
,]`9f$v:sv#D03). 懂得在宏中小心地把参数用括号括起来
`l"e#Z}k"jM04). 我也用这个问题开始讨论宏的副作用,例如:当你写下面的代码时会发生什么事? LUPA开源社区'Sk0_7s.P(qw9a
least = MIN(*p++, b);LUPA开源社区z/{G
P#`vkS
J9Vn;F.lRD03. 预处理器标识#error的目的是什么?LUPA开源社区 fN+m$x7Zn[3T+N
LUPA开源社区2K.oc _$nAs0w编译程序时,只要遇到 #error 就会跳出一个编译错误,既然是编译错误,要它干嘛呢?其目的就是保证程序是按照你所设想的那样进行编译的。下面举个例子:程序中往往有很多的预处理指令#ifdef XXX...#else#endif当程序比较大时,往往有些宏定义是在外部指定的(如makefile),或是在系统头文件中指定的,当你不太确定当前是否定义了 XXX 时,就可以改成如下这样进行编译:#ifdef XXX...#error "XXX has been defined"#else#endif这样,如果编译时出现错误,输出了XXX has been defined,表明宏XXX已经被定义了。用处就是这样,是不是感觉很没有用处
4o(`I~NN#Uq@EA7d+R0seBdXp/y4^6M0死循环(Infinite loops)
-{T;m-}{ |-P'yT0:Jk!_+W3G)na04. 嵌入式系统中经常要用到无限循环,你怎么样用C编写死循环呢?LUPA开源社区nm${&Zs1],Lk
BX3ql6D*R,ht0这个问题用几个解决方案。我首选的方案是: LUPA开源社区@&Ju
^7{6rR3c
while(1) { } LUPA开源社区v"`1RN},F VMBy
一些程序员更喜欢如下方案:
W'RA9~0X~P+K0for(;;) { } LUPA开源社区M M
I
b n
这个实现方式让我为难,因为这个语法没有确切表达到底怎么回事。如果一个应试者给出这个作为方案,我将用这个作为一个机会去探究他们这样做的
T!L&D2Mq1^.J0基本原理。如果他们的基本答案是:"我被教着这样做,但从没有想到过为什么。"这会给我留下一个坏印象。
%[&k#n3aE`UO0第三个方案是用 goto LUPA开源社区5Al-`(} M1D$D
Loop: LUPA开源社区:ga%i)Y&[(p
...
ejS7pH1K*LZ0goto Loop;
,m-S7uS8}W7L4J0应试者如给出上面的方案,这说明或者他是一个汇编语言程序员(这也许是好事)或者他是一个想进入新领域的BASIC/FORTRAN程序员。
2C"skl*z!r5CX}0数据声明(Data declarations) LUPA开源社区UDZ LiJ
LUPA开源社区Q#^1B3fjs1V5. 用变量a给出下面的定义
J*s:Vz.gS0D_0a) 一个整型数(An integer) LUPA开源社区7zc+RR,\]n$m
b) 一个指向整型数的指针(A pointer to an integer) LUPA开源社区AL-d$M;J%B
c) 一个指向指针的的指针,它指向的指针是指向一个整型数(A pointer to a pointer to an integer)
qx,?2b1tw8ig1_TN0d) 一个有10个整型数的数组(An array of 10 integers) LUPA开源社区*uH-Xr&q8F[
e) 一个有10个指针的数组,该指针是指向一个整型数的(An array of 10 pointers to integers)
n-C:p!l[h%mP,H0f) 一个指向有10个整型数数组的指针(A pointer to an array of 10 integers)
-D)xx5yO3Q7u:l"rBB~0g) 一个指向函数的指针,该函数有一个整型参数并返回一个整型数(A pointer to a function that takes an integer as an argument and returns an integer) LUPA开源社区`pvn5B&h#]]
h) 一个有10个指针的数组,该指针指向一个函数,该函数有一个整型参数并返回一个整型数( An array of ten pointers to functions that take an integer argument and return an integer )
8nk+m$P~
JG2g0答案是: LUPA开源社区;XS(r
h`T"b3b
a) int a; // An integer LUPA开源社区'E~hym sp-p#|7U
b) int *a; // A pointer to an integer LUPA开源社区]
_'{A0b)h6gv;m}V
c) int **a; // A pointer to a pointer to an integer
;n`/uHD5A0d) int a[10]; // An array of 10 integers LUPA开源社区`4Z-z0aDF+i-Mq
e) int *a[10]; // An array of 10 pointers to integers LUPA开源社区Z:V;Tx
qY
f) int (*a)[10]; // A pointer to an array of 10 integers
2qH*R;xzC3Z0g) int (*a)(int); // A pointer to a function a that takes an integer argument and returns an integer
,P5oE+pf4lI}0h) int (*a[10])(int); // An array of 10 pointers to functions that take an integer argument and return an integer LUPA开源社区*vE9t-J.M
_T%YF|;J
LUPA开源社区l{Lk:}dtv0~
人们经常声称这里有几个问题是那种要翻一下书才能回答的问题,我同意这种说法。当我写这篇文章时,为了确定语法的正确性,我的确查了一下书。 LUPA开源社区K`q.{.gS
但是当我被面试的时候,我期望被问到这个问题(或者相近的问题)。因为在被面试的这段时间里,我确定我知道这个问题的答案。应试者如果不知道 LUPA开源社区vqk^
U!ie
所有的答案(或至少大部分答案),那么也就没有为这次面试做准备,如果该面试者没有为这次面试做准备,那么他又能为什么出准备呢?
$^*}8o,l3@"Xr^0LUPA开源社区#~Lf9rJ(M:UnB
Static
6. 关键字static的作用是什么?LUPA开源社区$z}(l S_7n7E+s
LUPA开源社区G;T.|J,p$P%ub,n这个简单的问题很少有人能回答完全。在C语言中,关键字static有三个明显的作用:
9L.AiJ0h*h-K01). 在函数体,一个被声明为静态的变量在这一函数被调用过程中维持其值不变。
9ENAK]02). 在模块内(但在函数体外),一个被声明为静态的变量可以被模块内所用函数访问,但不能被模块外其它函数访问。它是一个本地的全局变量。
YTilQy03). 在模块内,一个被声明为静态的函数只可被这一模块内的其它函数调用。那就是,这个函数被限制在声明它的模块的本地范围内使用。
,H c
jSX@0大多数应试者能正确回答第一部分,一部分能正确回答第二部分,同是很少的人能懂得第三部分。这是一个应试者的严重的缺点,因为他显然不懂得本地化数据和代码范围的好处和重要性。LUPA开源社区AGvuty;z$^1r
LUPA开源社区5o/G*s7ia}mrX
?
Const
7.关键字const是什么含意? LUPA开源社区/i.G
n#]/q|g
我只要一听到被面试者说:"const意味着常数",我就知道我正在和一个业余者打交道。去年Dan Saks已经在他的文章里完全概括了const的所有用法,因此ESP(译者:Embedded Systems Programming)的每一位读者应该非常熟悉const能做什么和不能做什么.
,{7i1gpP
b2S4f0如果你从没有读到那篇文章,只要能说出const意味着"只读"就可以了。尽管这个答案不是完全的答案,但我接受它作为一个正确的答案。(如果你想知道更详细的答案,仔细读一下Saks的文章吧。)如果应试者能正确回答这个问题,我将问他一个附加的问题:下面的声明都是什么意思?
t#_"[F
P?;kg7l0const int a;
6}.h1vI)T_|G(s0fA0int const a; LUPA开源社区J?
y^
t}E
const int *a; LUPA开源社区Orla7X"i
int * const a;
M%i i)OhS]0int const * a const;LUPA开源社区/jVnG e#ZW
l{UMv0C0前两个的作用是一样,a是一个常整型数。第三个意味着a是一个指向常整型数的指针(也就是,整型数是不可修改的,但指针可以)。第四个意思a是一个指向整型数的常指针(也就是说,指针指向的整型数是可以修改的,但指针是不可修改的)。最后一个意味着a是一个指向常整型数的常指针(也就是说,指针指向的整型数是不可修改的,同时指针也是不可修改的)。如果应试者能正确回答这些问题,那么他就给我留下了一个好印象。顺带提一句,也许你可能会问,即使不用关键字const,也还是能很容易写出功能正确的程序,那么我为什么还要如此看重关键字const呢?我也如下的几下理由: LUPA开源社区S}h&G4~0{,n
1). 关键字const的作用是为给读你代码的人传达非常有用的信息,实际上,声明一个参数为常量是为了告诉了用户这个参数的应用目的。如果你曾花很多时间清理其它人留下的垃圾,你就会很快学会感谢这点多余的信息。(当然,懂得用const的程序员很少会留下的垃圾让别人来清理的。)
WP6n.}N
cN02). 通过给优化器一些附加的信息,使用关键字const也许能产生更紧凑的代码。 LUPA开源社区T#hqb{D/u
3). 合理地使用关键字const可以使编译器很自然地保护那些不希望被改变的参数,防止其被无意的代码修改。简而言之,这样可以减少bug的出现。
5JH8W4`+r#tS0Volatile
1u1Tg1ir6|Ps0'h#M.E6dD8Z08. 关键字volatile有什么含意 并给出三个不同的例子。LUPA开源社区x0~b"[q5]]q
LUPA开源社区Gu`&zwn*d一个定义为volatile的变量是说这变量可能会被意想不到地改变,这样,编译器就不会去假设这个变量的值了。精确地说就是,优化器在用到这个变量时必须每次都小心地重新读取这个变量的值,而不是使用保存在寄存器里的备份。下面是volatile变量的几个例子: LUPA开源社区"eBx ID_Y+iA
1). 并行设备的硬件寄存器(如:状态寄存器)
#e3KT%PmU`02). 一个中断服务子程序中会访问到的非自动变量(Non-automatic variables)
8`
g
wA(}03). 多线程应用中被几个任务共享的变量
OKBJ.b0回答不出这个问题的人是不会被雇佣的。我认为这是区分C程序员和嵌入式系统程序员的最基本的问题。嵌入式系统程序员经常同硬件、中断、RTOS等等打交道,所用这些都要求volatile变量。不懂得volatile内容将会带来灾难。 LUPA开源社区&QA/V
nh
假设被面试者正确地回答了这是问题(嗯,怀疑这否会是这样),我将稍微深究一下,看一下这家伙是不是直正懂得volatile完全的重要性。
| d3AF7h!I*b(R01). 一个参数既可以是const还可以是volatile吗?解释为什么。
i0Y,W/_h02). 一个指针可以是volatile 吗?解释为什么。 LUPA开源社区*oXq,?O&H`#T
3). 下面的函数有什么错误:
7~$YL7o:k5o0int square(volatile int *ptr)
UX!`
dD+`X0{C0{ LUPA开源社区b@%T+TD#j:C
return *ptr * *ptr; LUPA开源社区 w
rZq+Z/u
}
-xPE0VV0下面是答案: LUPA开源社区Uh
d3o,A4`*]5sU*F.X
1). 是的。一个例子是只读的状态寄存器。它是volatile因为它可能被意想不到地改变。它是const因为程序不应该试图去修改它。 LUPA开源社区p;j&f3{%fV6y
2). 是的。尽管这并不很常见。一个例子是当一个中服务子程序修该一个指向一个buffer的指针时。 LUPA开源社区*q:ZX4RZ8?c
p,mh
3). 这段代码的有个恶作剧。这段代码的目的是用来返指针*ptr指向值的平方,但是,由于*ptr指向一个volatile型参数,编译器将产生类似下面的代码: LUPA开源社区
R4LOP8tT(lL
int square(volatile int *ptr) LUPA开源社区w3EO(N#}'l!iH[
{
[D|8oPa0int a,b;
]0?JX$D!K0a = *ptr;
b8W`nba0b = *ptr;
L(KN1RFl]0return a * b; LUPA开源社区1f[4|H4Kj8P5`]
} LUPA开源社区5J+x'h)X.Cb`
由于*ptr的值可能被意想不到地该变,因此a和b可能是不同的。结果,这段代码可能返不是你所期望的平方值!正确的代码如下: LUPA开源社区["yu(dp\O"|r$?-w
long square(volatile int *ptr) LUPA开源社区 g1EArmLx
{
;g#M|X5{:J]/\XA]0int a; LUPA开源社区 R5Vv-}
]? `
a = *ptr;
!W.qoHY6B3I'b{0return a * a;
C.Q6Xs m5`2^i0}LUPA开源社区^*W_ kg`e
位操作(Bit manipulation)
3Yuv-Rk$A5u,sm0 LUPA开源社区6Up%g8w$F Q J0E9. 嵌入式系统总是要用户对变量或寄存器进行位操作。给定一个整型变量a,写两段代码,第一个设置a的bit 3,第二个清除a 的bit 3。在以上两个操作中,要保持其它位不变。LUPA开源社区2Oxa/vooH7uO4E U
C;^!bqR0对这个问题有三种基本的反应 LUPA开源社区9L)O7{C L m9q
1). 不知道如何下手。该被面者从没做过任何嵌入式系统的工作。 LUPA开源社区0Mn9ZA)d%d&GX,b~*b
2). 用bit fields。Bit fields是被扔到C语言死角的东西,它保证你的代码在不同编译器之间是不可移植的,同时也保证了的你的代码是不可重用的。我最近不幸看到Infineon为其较复杂的通信芯片写的驱动程序,它用到了bit fields因此完全对我无用,因为我的编译器用其它的方式来实现bit fields的。从道德讲:永远不要让一个非嵌入式的家伙粘实际硬件的边。 LUPA开源社区M*w$vX i|dku
3). 用 #defines 和 bit masks 操作。这是一个有极高可移植性的方法,是应该被用到的方法。最佳的解决方案如下:
.cmj5gn%iww"n0#define BIT3 (0x1<<3) LUPA开源社区C Wg*]+w W R~f
static int a; LUPA开源社区4~$T9JbY*J"F
void set_bit3(void)
Jfj1oMo#m0{ LUPA开源社区1oCbp1v%e6L4ZX R
a |= BIT3; LUPA开源社区zo/dm:d!Jd
k/u H
} LUPA开源社区&h^'P\2L
void clear_bit3(void)
0GY+VB2y z+m"{Q)@C Za0{ LUPA开源社区2|2W@,BVa4CUR/r}N*c
a &= ~BIT3;
QtZaa9F3z
v0} LUPA开源社区[P(l!s[c
F
一些人喜欢为设置和清除值而定义一个掩码同时定义一些说明常数,这也是可以接受的。我希望看到几个要点:说明常数、|=和&=~操作。LUPA开源社区uc6A1~&R/R3m
10. 嵌入式系统经常具有要求程序员去访问某特定的内存位置的特点。在某工程中,要求设置一绝对地址为0x67a9的整型变量的值为0xaa66。编译器是一个纯粹的ANSI编译器。写代码去完成这一任务。LUPA开源社区A*A'O\|(D
Ge
这一问题测试你是否知道为了访问一绝对地址把一个整型数强制转换(typecast)为一指针是合法的。这一问题的实现方式随着个人风格不同而不同。典型的类似代码如下:
#md3k!q.Ne,G9W0int *ptr;
_Lg#cL,`0ptr = (int *)0x67a9;
jhuc}x
Y4]zQ/FK0*ptr = 0xaa55;LUPA开源社区)Y'td)y1t
HmK-GYs0一个较晦涩的方法是: LUPA开源社区O:h1V0HWgMyfZUW
*(int * const)(0x67a9) = 0xaa55;