代码拉取完成,页面将自动刷新
同步操作将从 才鲸嵌入式/嵌入式软件笔试题汇总 强制同步,此操作会覆盖自 Fork 仓库以来所做的任何修改,且无法恢复!!!
确定后同步将在后台操作,完成时将刷新页面,请耐心等待。
嵌入式常见笔试题
预处理器(Preprocessor)
1 . 用预处理指令#define 声明一个常数,用以表明 1 年中有多少秒(忽略闰年
问题)
#define SECONDS_PER_YEAR (60 * 60 * 24 * 365)UL
我在这想看到几件事情:
1) #define 语法的基本知识(例如:不能以分号结束,括号的使用,等等)
2)懂得预处理器将为你计算常数表达式的值,因此,直接写出你是如何计算一年
中有多少秒而不是计算出实际的值,是更清晰而没有代价的。
3) 意识到这个表达式将使一个 16 位机的整型数溢出-因此要用到长整型符号 L,
告诉编译器这个常数是的长整型数。
4) 如果你在你的表达式中用到 UL(表示无符号长整型),那么你有了一个好的
起点。记住,第一印象很重要。
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 的目的是什么?
如果你不知道答案,请看参考文献 1。这问题对区分一个正常的伙计和一个书呆
子是很有用的。只有书呆子才会读 C 语言课本的附录去找出象这种问题的答案。
当然如果你不是在找一个书呆子,那么应试者最好希望自己不要知道答案。
死循环(Infinite loops)
4. 嵌入式系统中经常要用到无限循环,你怎么样用 C 编写死循环呢?
这个问题用几个解决方案。我首选的方案是:
while(1){
}
一些程序员更喜欢如下方案:
for(;;)
{
}
这个实现方式让我为难,因为这个语法没有确切表达到底怎么回事。如果一个应
试者给出这个作为方案,我将用这个作为一个机会去探究他们这样做的基本原
理。如果他们的基本答案是:"我被教着这样做,但从没有想到过为什么。"这会
给我留下一个坏印象。
第三个方案是用 goto
Loop:
...
goto Loop;
应试者如给出上面的方案,这说明或者他是一个汇编语言程序员(这也许是好事)
或者他是一个想进入新领域的 BASIC/FORTRAN 程序员。
数据声明(Data declarations)
5. 用变量 a 给出下面的定义
a) 一个整型数(An integer)
b)一个指向整型数的指针( A pointer to an integer)
c)一个指向指针的的指针,它指向的指针是指向一个整型数( A pointer to a
pointer to an intege)r
d)一个有 10 个整型数的数组( An array of 10 integers)
e) 一个有 10 个指针的数组,该指针是指向一个整型数的。(An array of 10
pointers to integers)
f) 一个指向有 10 个整型数数组的指针( A pointer to an array of 10 integers)
g) 一个指向函数的指针,该函数有一个整型参数并返回一个整型数(A pointer
to a function that takes an integer as an argument and returns an integer)
h) 一个有 10 个指针的数组,该指针指向一个函数,该函数有一个整型参数并返
回一个整型数( An array of ten pointers to functions that take an integer
argument and return an integer )
答案是:
a) int a; // An integerb) int *a; // A pointer to an integer
c) int **a; // A pointer to a pointer to an integer
d) int a[10]; // An array of 10 integers
e) int *a[10]; // An array of 10 pointers to integers
f) int (*a)[10]; // A pointer to an array of 10 integers
g) int (*a)(int); // A pointer to a function a that takes an integer
argument and returns an integer
h) int (*a[10])(int); // An array of 10 pointers to functions that take
an integer argument and return an integer
人们经常声称这里有几个问题是那种要翻一下书才能回答的问题,我同意这种说
法。当我写这篇文章时,为了确定语法的正确性,我的确查了一下书。但是当我
被面试的时候,我期望被问到这个问题(或者相近的问题)。因为在被面试的这
段时间里,我确定我知道这个问题的答案。应试者如果不知道所有的答案(或至
少大部分答案),那么也就没有为这次面试做准备,如果该面试者没有为这次面
试做准备,那么他又能为什么出准备呢?
Static
6. 关键字 static 的作用是什么?
这个简单的问题很少有人能回答完全。在 C 语言中,关键字 static 有三个明显
的作用:
1)在函数体,一个被声明为静态的变量在这一函数被调用过程中维持其值不变。
2) 在模块内(但在函数体外),一个被声明为静态的变量可以被模块内所用函
数访问,但不能被模块外其它函数访问。它是一个本地的全局变量。
3) 在模块内,一个被声明为静态的函数只可被这一模块内的其它函数调用。那
就是,这个函数被限制在声明它的模块的本地范围内使用。
大多数应试者能正确回答第一部分,一部分能正确回答第二部分,同是很少的人
能懂得第三部分。这是一个应试者的严重的缺点,因为他显然不懂得本地化数据
和代码范围的好处和重要性。
Const
7.关键字 const 有什么含意?
我只要一听到被面试者说:"const 意味着常数",我就知道我正在和一个业余者
打交道。去年 Dan Saks 已经在他的文章里完全概括了 const 的所有用法,因此
ESP(译者:Embedded Systems Programming)的每一位读者应该非常熟悉 const
能做什么和不能做什么.如果你从没有读到那篇文章,只要能说出 const 意味着"
只读"就可以了。尽管这个答案不是完全的答案,但我接受它作为一个正确的答
案。(如果你想知道更详细的答案,仔细读一下 Saks 的文章吧。)
如果应试者能正确回答这个问题,我将问他一个附加的问题:下面的声明都是什么意思?
const int a;
int const a;
const int *a;
int * const a;
int const * a const;
/******/
前两个的作用是一样,a 是一个常整型数。第三个意味着 a 是一个指向常整型数
的指针(也就是,整型数是不可修改的,但指针可以)。第四个意思 a 是一个指
向整型数的常指针(也就是说,指针指向的整型数是可以修改的,但指针是不可
修改的)。最后一个意味着 a 是一个指向常整型数的常指针(也就是说,指针指
向的整型数是不可修改的,同时指针也是不可修改的)。如果应试者能正确回答
这些问题,那么他就给我留下了一个好印象。顺带提一句,也许你可能会问,即
使不用关键字 const,也还是能很容易写出功能正确的程序,那么我为什么还要
如此看重关键字 const 呢?我也如下的几下理由:
1) 关键字 const 的作用是为给读你代码的人传达非常有用的信息,实际上,声
明一个参数为常量是为了告诉了用户这个参数的应用目的。如果你曾花很多时间
清理其它人留下的垃圾,你就会很快学会感谢这点多余的信息。(当然,懂得用
const 的程序员很少会留下的垃圾让别人来清理的。)
2) 通过给优化器一些附加的信息,使用关键字 const 也许能产生更紧凑的代码。
3) 合理地使用关键字 const 可以使编译器很自然地保护那些不希望被改变的参
数,防止其被无意的代码修改。简而言之,这样可以减少 bug 的出现。
Volatile
8. 关键字 volatile 有什么含意?并给出三个不同的例子。
一个定义为 volatile 的变量是说这变量可能会被意想不到地改变,这样,编译
器就不会去假设这个变量的值了。精确地说就是,优化器在用到这个变量时必须
每次都小心地重新读取这个变量的值,而不是使用保存在寄存器里的备份。下面
是 volatile 变量的几个例子:
1) 并行设备的硬件寄存器(如:状态寄存器)
2) 一个中断服务子程序中会访问到的非自动变量(Non-automatic variables)
3) 多线程应用中被几个任务共享的变量
回答不出这个问题的人是不会被雇佣的。我认为这是区分 C 程序员和嵌入式系统
程序员的最基本的问题。搞嵌入式的家伙们经常同硬件、中断、RTOS 等等打交
道,所有这些都要求用到 volatile 变量。不懂得 volatile 的内容将会带来灾难。
假设被面试者正确地回答了这是问题(嗯,怀疑是否会是这样),我将稍微深究
一下,看一下这家伙是不是直正懂得 volatile 完全的重要性。
1)一个参数既可以是 const 还可以是 volatile 吗?解释为什么。2); 一个指针可以是 volatile 吗?解释为什么。
3); 下面的函数有什么错误:
int square(volatile int *ptr)
{
return *ptr * *ptr;
}
下面是答案:
1)是的。一个例子是只读的状态寄存器。它是 volatile 因为它可能被意想不到
地改变。它是 const 因为程序不应该试图去修改它。
2); 是的。尽管这并不很常见。一个例子是当一个中服务子程序修该一个指向一
个 buffer 的指针时。
3) 这段代码有点变态。这段代码的目的是用来返指针*ptr 指向值的平方,但是,
由于*ptr 指向一个 volatile 型参数,编译器将产生类似下面的代码:
int square(volatile int *ptr)
{
int a,b;
a = *ptr;
b = *ptr;
return a * b;
}
由于*ptr 的值可能被意想不到地该变,因此 a 和 b 可能是不同的。结果,这段
代码可能返不是你所期望的平方值!正确的代码如下:
long square(volatile int *ptr)
{
int a;
a = *ptr;
return a * a;
}
位操作(Bit manipulation)
9. 嵌入式系统总是要用户对变量或寄存器进行位操作。给定一个整型变量 a,
写两段代码,第一个设置 a 的 bit 3,第二个清除 a 的 bit 3。在以上两个操作
中,要保持其它位不变。
对这个问题有三种基本的反应
1)不知道如何下手。该被面者从没做过任何嵌入式系统的工作。
2) 用 bit fields。Bit fields 是被扔到 C 语言死角的东西,它保证你的代码在
不同编译器之间是不可移植的,同时也保证了的你的代码是不可重用的。我最近不幸看到 Infineon 为其较复杂的通信芯片写的驱动程序,它用到了 bit fields
因此完全对我无用,因为我的编译器用其它的方式来实现 bit fields 的。从道
德讲:永远不要让一个非嵌入式的家伙粘实际硬件的边。
3) 用 #defines 和 bit masks 操作。这是一个有极高可移植性的方法,是应该
被用到的方法。最佳的解决方案如下:
#define BIT3 (0x1 << 3)
static int a;
void set_bit3(void)
{
a |= BIT3;
}
void clear_bit3(void)
{
a &= ~BIT3;
}
一些人喜欢为设置和清除值而定义一个掩码同时定义一些说明常数,这也是可以
接受的。我希望看到几个要点:说明常数、|=和&=~操作。
访问固定的内存位置(Accessing fixed memory locations)
10. 嵌入式系统经常具有要求程序员去访问某特定的内存位置的特点。在某工程
中,要求设置一绝对地址为 0x67a9 的整型变量的值为 0xaa66。编译器是一个纯
粹的 ANSI 编译器。写代码去完成这一任务。
这一问题测试你是否知道为了访问一绝对地址把一个整型数强制转换
(typecast)为一指针是合法的。这一问题的实现方式随着个人风格不同而不同。
典型的类似代码如下:
int *ptr;
ptr = (int *)0x67a9;
*ptr = 0xaa55;
A more obscure approach is:
一个较晦涩的方法是:
*(int * const)(0x67a9) = 0xaa55;
即使你的品味更接近第二种方案,但我建议你在面试时使用第一种方案。
中断(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 机程序往往把
硬件作为一个无法避免的烦恼。
到了这个阶段,应试者或者完全垂头丧气了或者信心满满志在必得。如果显然应
试者不是很好,那么这个测试就在这里结束了。但如果显然应试者做得不错,那
么我就扔出下面的追加问题,这些问题是比较难的,我想仅仅非常优秀的应试者
能做得不错。提出这些问题,我希望更多看到应试者应付问题的方法,而不是答
案。不管如何,你就当是这个娱乐吧...
动态内存分配(Dynamic memory allocation)
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"。我用这个来开始讨论这样的一问题,看看被面
试者是否想到库例程这样做是正确。得到正确的答案固然重要,但解决问题的方
法和你做决定的基本原理更重要些。
Typedef
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。
如果你知道答案,或猜出正确答案,做得好。如果你不知道答案,我也不把这个
当作问题。我发现这个问题的最大好处是这是一个关于代码编写风格,代码的可
读性,代码的可修改性的好的话题。上海某全球五百强面试题(嵌入式)
1.static 变量和 static 函数各有什么特点?
3.描述一下嵌入式基于 ROM 的运行方式基于 ram 的运行方式有什么区别。
4.task 有几种状态?
5.task 有几种通讯方式?
6.C 函数允许重入吗?
7.嵌入式操作系统和通用操作系统有什么差别?嵌入式经典面试题
C 语言测试是招聘嵌入式系统程序员过程中必须而且有效的方法。这些年,我既参加也组织了许多这种测
试,在这过程中我意识到这些测试能为面试者和被面试者提供许多有用信息,此外,撇开面试的压力不谈,
这种测试也是相当有趣的。
从被面试者的角度来讲,你能了解许多关于出题者或监考者的情况。这个测试只是出题者为显示其对
ANSI 标准细节的知识而不是技术技巧而设计吗?这是个愚蠢的问题吗?如要你答出某个字符的 ASCII
值。这些问题着重考察你的系统调用和内存分配策略方面的能力吗?这标志着出题者也许花时间在微机上
而不是在嵌入式系统上。如果上述任何问题的答案是"是"的话,那么我知道我得认真考虑我是否应该去做
这份工作。
从面试者的角度来讲,一个测试也许能从多方面揭示应试者的素质:最基本的,你能了解应试者 C 语言
的水平。不管怎么样,看一下这人如何回答他不会的问题也是满有趣。应试者是以好的直觉做出明智的选
择,还是只是瞎蒙呢?当应试者在某个问题上卡住时是找借口呢,还是表现出对问题的真正的好奇心,把
这看成学习的机会呢?我发现这些信息与他们的测试成绩一样有用。
有了这些想法,我决定出一些真正针对嵌入式系统的考题,希望这些令人头痛的考题能给正在找工作的
人一点帮助。这些问题都是我这些年实际碰到的。其中有些题很难,但它们应该都能给你一点启迪。
这个测试适于不同水平的应试者,大多数初级水平的应试者的成绩会很差,经验丰富的程序员应该有很
好的成绩。为了让你能自己决定某些问题的偏好,每个问题没有分配分数,如果选择这些考题为你所用,
请自行按你的意思分配分数。
预处理器(Preprocessor)
1 . 用预处理指令#define 声明一个常数,用以表明 1 年中有多少秒(忽略闰年问题)
#define SECONDS_PER_YEAR (60 * 60 * 24 * 365)UL
我在这想看到几件事情:
1) #define 语法的基本知识(例如:不能以分号结束,括号的使用,等等)
2)懂得预处理器将为你计算常数表达式的值,因此,直接写出你是如何计算一年中有多少秒而不是计算出
实际的值,是更清晰而没有代价的。
3) 意识到这个表达式将使一个 16 位机的整型数溢出-因此要用到长整型符号 L,告诉编译器这个常数是的
长整型数。
4) 如果你在你的表达式中用到 UL(表示无符号长整型),那么你有了一个好的起点。记住,第一印象很重
要。
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 的目的是什么?
如果你不知道答案,请看参考文献 1。这问题对区分一个正常的伙计和一个书呆子是很有用的。只有书呆
子才会读 C 语言课本的附录去找出象这种问题的答案。当然如果你不是在找一个书呆子,那么应试者最好
希望自己不要知道答案。
死循环(Infinite loops)
4. 嵌入式系统中经常要用到无限循环,你怎么样用 C 编写死循环呢?
这个问题用几个解决方案。我首选的方案是:
while(1)
{
}
一些程序员更喜欢如下方案:
for(;;)
{
}
这个实现方式让我为难,因为这个语法没有确切表达到底怎么回事。如果一个应试者给出这个作为方案,
我将用这个作为一个机会去探究他们这样做的基本原理。如果他们的基本答案是:"我被教着这样做,但从
没有想到过为什么。"这会给我留下一个坏印象。
第三个方案是用 goto
Loop:
...
goto Loop;
应试者如给出上面的方案,这说明或者他是一个汇编语言程序员(这也许是好事)或者他是一个想进入新
领域的 BASIC/FORTRAN 程序员。
数据声明(Data declarations)
5. 用变量 a 给出下面的定义
a) 一个整型数(An integer)
b)一个指向整型数的指针( A pointer to an integer)
c)一个指向指针的的指针,它指向的指针是指向一个整型数( A pointer to a pointer to an intege)r
d)一个有 10 个整型数的数组( An array of 10 integers)
e) 一个有 10 个指针的数组,该指针是指向一个整型数的。(An array of 10 pointers to integers)f) 一个指向有 10 个整型数数组的指针( A pointer to an array of 10 integers)
g) 一个指向函数的指针,该函数有一个整型参数并返回一个整型数(A pointer to a function that takes
an integer as an argument and returns an integer)
h) 一个有 10 个指针的数组,该指针指向一个函数,该函数有一个整型参数并返回一个整型数( An array
of ten pointers to functions that take an integer argument and return an integer )
答案是:
a) int a; // An integer
b) int *a; // A pointer to an integer
c) int **a; // A pointer to a pointer to an integer
d) int a[10]; // An array of 10 integers
e) int *a[10]; // An array of 10 pointers to integers
f) int (*a)[10]; // A pointer to an array of 10 integers
g) int (*a)(int); // A pointer to a function a that takes an integer argument and returns an
integer
h) int (*a[10])(int); // An array of 10 pointers to functions that take an integer argument and
return an integer
人们经常声称这里有几个问题是那种要翻一下书才能回答的问题,我同意这种说法。当我写这篇文章时,
为了确定语法的正确性,我的确查了一下书。但是当我被面试的时候,我期望被问到这个问题(或者相近
的问题)。因为在被面试的这段时间里,我确定我知道这个问题的答案。应试者如果不知道所有的答案(或
至少大部分答案),那么也就没有为这次面试做准备,如果该面试者没有为这次面试做准备,那么他又能为
什么出准备呢?
Static
6. 关键字 static 的作用是什么?
这个简单的问题很少有人能回答完全。在 C 语言中,关键字 static 有三个明显的作用:
1)在函数体,一个被声明为静态的变量在这一函数被调用过程中维持其值不变。
2) 在模块内(但在函数体外),一个被声明为静态的变量可以被模块内所用函数访问,但不能被模块外其
它函数访问。它是一个本地的全局变量。
3) 在模块内,一个被声明为静态的函数只可被这一模块内的其它函数调用。那就是,这个函数被限制在声
明它的模块的本地范围内使用。
大多数应试者能正确回答第一部分,一部分能正确回答第二部分,同是很少的人能懂得第三部分。这是一
个应试者的严重的缺点,因为他显然不懂得本地化数据和代码范围的好处和重要性。
Const
7.关键字 const 有什么含意?
我只要一听到被面试者说:"const 意味着常数",我就知道我正在和一个业余者打交道。去年 Dan Saks
已经在他的文章里完全概括了 const 的所有用法,因此 ESP(译者: Embedded Systems Programming)
的每一位读者应该非常熟悉 const 能做什么和不能做什么.如果你从没有读到那篇文章,只要能说出 const意味着"只读"就可以了。尽管这个答案不是完全的答案,但我接受它作为一个正确的答案。
(如果你想知道
更详细的答案,仔细读一下 Saks 的文章吧。)
如果应试者能正确回答这个问题,我将问他一个附加的问题:
下面的声明都是什么意思?
const int a;
int const a;
const int *a;
int * const a;
int const * a const;
/******/
前两个的作用是一样,a 是一个常整型数。第三个意味着 a 是一个指向常整型数的指针(也就是,整型数
是不可修改的,但指针可以)。第四个意思 a 是一个指向整型数的常指针(也就是说,指针指向的整型数是
可以修改的,但指针是不可修改的)。最后一个意味着 a 是一个指向常整型数的常指针(也就是说,指针指
向的整型数是不可修改的,同时指针也是不可修改的)。如果应试者能正确回答这些问题,那么他就给我留
下了一个好印象。顺带提一句,也许你可能会问,即使不用关键字 const,也还是能很容易写出功能正确
的程序,那么我为什么还要如此看重关键字 const 呢?我也如下的几下理由:
1) 关键字 const 的作用是为给读你代码的人传达非常有用的信息,实际上,声明一个参数为常量是为了
告诉了用户这个参数的应用目的。如果你曾花很多时间清理其它人留下的垃圾,你就会很快学会感谢这点
多余的信息。(当然,懂得用 const 的程序员很少会留下的垃圾让别人来清理的。)
2) 通过给优化器一些附加的信息,使用关键字 const 也许能产生更紧凑的代码。
3) 合理地使用关键字 const 可以使编译器很自然地保护那些不希望被改变的参数,防止其被无意的代码
修改。简而言之,这样可以减少 bug 的出现。
Volatile
8. 关键字 volatile 有什么含意?并给出三个不同的例子。
一个定义为 volatile 的变量是说这变量可能会被意想不到地改变,这样,编译器就不会去假设这个变量的
值了。精确地说就是,优化器在用到这个变量时必须每次都小心地重新读取这个变量的值,而不是使用保
存在寄存器里的备份。下面是 volatile 变量的几个例子:
1) 并行设备的硬件寄存器(如:状态寄存器)
2) 一个中断服务子程序中会访问到的非自动变量(Non-automatic variables)
3) 多线程应用中被几个任务共享的变量
回答不出这个问题的人是不会被雇佣的。我认为这是区分 C 程序员和嵌入式系统程序员的最基本的问题。
搞嵌入式的家伙们经常同硬件、中断、RTOS 等等打交道,所有这些都要求用到 volatile 变量。不懂得
volatile 的内容将会带来灾难。
假设被面试者正确地回答了这是问题(嗯,怀疑是否会是这样),我将稍微深究一下,看一下这家伙是不是
直正懂得 volatile 完全的重要性。
1)一个参数既可以是 const 还可以是 volatile 吗?解释为什么。
2); 一个指针可以是 volatile 吗?解释为什么。
3); 下面的函数有什么错误:int square(volatile int *ptr)
{
return *ptr * *ptr;
}
下面是答案:
1)是的。一个例子是只读的状态寄存器。它是 volatile 因为它可能被意想不到地改变。它是 const 因为程
序不应该试图去修改它。
2); 是的。尽管这并不很常见。一个例子是当一个中服务子程序修该一个指向一个 buffer 的指针时。
3) 这段代码有点变态。这段代码的目的是用来返指针*ptr 指向值的平方,但是,由于*ptr 指向一个
volatile 型参数,编译器将产生类似下面的代码:
int square(volatile int *ptr)
{
int a,b;
a = *ptr;
b = *ptr;
return a * b;
}
由于*ptr 的值可能被意想不到地该变,因此 a 和 b 可能是不同的。结果,这段代码可能返不是你所期望的
平方值!正确的代码如下:
long square(volatile int *ptr)
{
int a;
a = *ptr;
return a * a;
}
位操作(Bit manipulation)
9. 嵌入式系统总是要用户对变量或寄存器进行位操作。给定一个整型变量 a,写两段代码,第一个设置 a
的 bit 3,第二个清除 a 的 bit 3。在以上两个操作中,要保持其它位不变。
对这个问题有三种基本的反应
1)不知道如何下手。该被面者从没做过任何嵌入式系统的工作。
2) 用 bit fields。Bit fields 是被扔到 C 语言死角的东西,它保证你的代码在不同编译器之间是不可移植
的,同时也保证了的你的代码是不可重用的。我最近不幸看到 Infineon 为其较复杂的通信芯片写的驱动
程序,它用到了 bit fields 因此完全对我无用,因为我的编译器用其它的方式来实现 bit fields 的。从道德
讲:永远不要让一个非嵌入式的家伙粘实际硬件的边。
3) 用 #defines 和 bit masks 操作。这是一个有极高可移植性的方法,是应该被用到的方法。最佳的
解决方案如下:#define BIT3 (0x1 << 3)
static int a;
void set_bit3(void)
{
a |= BIT3;
}
void clear_bit3(void)
{
a &= ~BIT3;
}
一些人喜欢为设置和清除值而定义一个掩码同时定义一些说明常数,这也是可以接受的。我希望看到几个
要点:说明常数、|=和&=~操作。
访问固定的内存位置(Accessing fixed memory locations)
10. 嵌入式系统经常具有要求程序员去访问某特定的内存位置的特点。在某工程中,要求设置一绝对地址
为 0x67a9 的整型变量的值为 0xaa66。编译器是一个纯粹的 ANSI 编译器。写代码去完成这一任务。
这一问题测试你是否知道为了访问一绝对地址把一个整型数强制转换(typecast)为一指针是合法的。这
一问题的实现方式随着个人风格不同而不同。典型的类似代码如下:
int *ptr;
ptr = (int *)0x67a9;
*ptr = 0xaa55;
A more obscure approach is:
一个较晦涩的方法是:
*(int * const)(0x67a9) = 0xaa55;
即使你的品味更接近第二种方案,但我建议你在面试时使用第一种方案。
中断(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 机程序往往把硬件作为一个无法避免的烦恼。
到了这个阶段,应试者或者完全垂头丧气了或者信心满满志在必得。如果显然应试者不是很好,那么这个
测试就在这里结束了。但如果显然应试者做得不错,那么我就扔出下面的追加问题,这些问题是比较难的,
我想仅仅非常优秀的应试者能做得不错。提出这些问题,我希望更多看到应试者应付问题的方法,而不是
答案。不管如何,你就当是这个娱乐吧...动态内存分配(Dynamic memory allocation)
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"。我用这个来开始讨论这
样的一问题,看看被面试者是否想到库例程这样做是正确。得到正确的答案固然重要,但解决问题的方法
和你做决定的基本原理更重要些。
Typedef
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。
如果你知道答案,或猜出正确答案,做得好。如果你不知道答案,我也不把这个当作问题。我发现这个问
题的最大好处是这是一个关于代码编写风格,代码的可读性,代码的可修改性的好的话题。
好了,伙计们,你现在已经做完所有的测试了。这就是我出的 C 语言测试题,我怀着愉快的心情写完它,
希望你以同样的心情读完它。如果是认为这是一个好的测试,那么尽量都用到你的找工作的过程中去吧。
天知道也许过个一两年,我就不做现在的工作,也需要找一个。嵌入式软件工程师笔试题
来源: ChinaUnix 博客
日期: 2008.09.08 14:18 (共有 0 条评论) 我要评论
1、将一个字符串逆序
2、将一个链表逆序
3、计算一个字节里(byte)里面有多少 bit 被置 1
4、搜索给定的字节(byte)
5、在一个字符串中找到可能的最长的子字符串
6、字符串转换为整数
7、整数转换为字符串
/*
* 题目:将一个字符串逆序
* 完成时间:2006.9.30 深圳极讯网吧
* 版权归刘志强所有
* 描述:写本程序的目的是希望练一下手,希望下午去面试能成功,不希望国庆节之后再去找工作拉!
*/
#include
using namespace std;
//#define NULL ((void *)0)
char * mystrrev(char * const dest,const char * const src)
{
if (dest==NULL && src==NULL)
return NULL;char *addr = dest;
int val_len = strlen(src);
dest[val_len] = '\0';
int i;
for (i=0; inext;
while(q!=NULL)
{
temp=q->next;
q->next=p;
p=q;
q=temp;
}
这样增加个辅助的指针就行乐。
ok 通过编译的代码:
#include
#include
#include
typedef struct List{
int data;
struct List *next;
}List;
List *list_create(void)
{
struct List *head,*tail,*p;
int e;
head=(List *)malloc(sizeof(List));tail=head;
printf("\nList Create,input numbers(end of 0):");
scanf("%d",&e);
while(e){
p=(List *)malloc(sizeof(List));
p->data=e;
tail->next=p;
tail=p;
scanf("%d",&e);}
tail->next=NULL;
return head;
}
List *list_reverse(List *head)
{
List *p,*q,*r;
p=head;
q=p->next;
while(q!=NULL)
{
r=q->next;
q->next=p;
p=q;
q=r;
}
head->next=NULL;
head=p;return head;
}
void main(void)
{
struct List *head,*p;
int d;
head=list_create();
printf("\n");
for(p=head->next;p;p=p->next)
printf("--%d--",p->data);
head=list_reverse(head);
printf("\n");
for(p=head;p->next;p=p->next)
printf("--%d--",p->data);
}
编写函数数 N 个 BYTE 的数据中有多少位是 1。
解:此题按步骤解:先定位到某一个 BYTE 数据;再计算其中有多少个 1。叠加得解。
#incluede
#define N 10
//定义 BYTE 类型别名
#ifndef BYTE
typedef unsigned char BYTE;
#endif
int comb(BYTE b[],int n)
{
int count=0;int bi,bj;
BYTE cc=1,tt;
//历遍到第 bi 个 BYTE 数据
for(bi=0;bi>1;
tt=tt/2;
}
}
return count;
}
//测试
int main()
{
BYTE b[10]={3,3,3,11,1,1,1,1,1,1};
cout
1。编写一个 C 函数,该函数在一个字符串中找到可能的最长的子字符串,且该字符串是由同一字符组成
的。
char * search(char *cpSource, char ch)
{
char *cpTemp=NULL, *cpDest=NULL;
int iTemp, iCount=0;
while(*cpSource)
{
if(*cpSource == ch)
{
iTemp = 0;
cpTemp = cpSource;while(*cpSource == ch)
++iTemp, ++cpSource;
if(iTemp > iCount)
iCount = iTemp, cpDest = cpTemp;
if(!*cpSource)
break;
}
++cpSource;
}
return cpDest;
}
#include
#include
//
// 自定义函数 MyAtoI
// 实现整数字符串转换为证书输出
// 程序不检查字符串的正确性,请用户在调用前检查
//
int MyAtoI(char str[])
{
int i;
int weight = 1; // 权重
int rtn = 0; // 用作返回
for(i = strlen(str) - 1; i >= 0; i--)
{
rtn += (str - '0')* weight; //weight *= 10; // 增重
}
return rtn;
}
void main()
{
char str[32];
printf("Input a string :");
gets(str);
printf("%d\n", MyAtoI(str));
}
#include
#include
void reverse(char s[])
{
// 字符串反转
int c, i=0, j;
for(j=strlen(s)-1;i0);
// 如果是负数,补上负号
if(sign试题 1:请写一个 C 函数,若处理器是 Big_endian 的,则返回 0;若是 Little_endian 的,
则返回 1
解答:
int checkCPU( )
{
{
union w
{
int a;
char b;
} c;
c.a = 1;
return(c.b ==1);
}
}
剖析:
嵌入式系统开发者应该对 Little-endian 和 Big-endian 模式非常了解。采用 Little-endian
模式的 CPU 对操作数的存放方式是从低字节到高字节,而 Big-endian 模式对操作数的存放
方式是从高字节到低字节。例如,16bit 宽的数 0x1234 在 Little-endian 模式 CPU 内存中的存
放方式(假设从地址 0x4000 开始存放)为:
内存地址 0x4000 0x4001
存放内容 0x34 0x12
而在 Big-endian 模式 CPU 内存中的存放方式则为:
内存地址 0x4000 0x4001
存放内容 0x12 0x34
32bit 宽的数 0x12345678 在 Little-endian 模式 CPU 内存中的存放方式(假设从地址
0x4000 开始存放)为:
内存地址 0x4000 0x4001 0x4002 0x4003
存放内容 0x78 0x56 0x34 0x12
而在 Big-endian 模式 CPU 内存中的存放方式则为:
内存地址 0x4000 0x4001 0x4002 0x4003
存放内容 0x12 0x34 0x56 0x78联合体 union 的存放顺序是所有成员都从低地址开始存放,面试者的解答利用该特性,
轻松地获得了 CPU 对内存采用 Little-endian 还是 Big-endian 模式读写。如果谁能当场给出
这个解答,那简直就是一个天才的程序员。
2.
char str[] = “Hello” ; void Func ( char str[100])
char *p = str ; {
int n = 10; 请计算
请计算 sizeof( str ) = 4 (2 分)
sizeof (str ) = 6 (2 分) }
sizeof ( p ) = 4 (2 分) void *p = malloc( 100 );
sizeof ( n ) = 4 (2 分) 请计算
sizeof ( p ) = 4 (2 分)
3 、在 C++程序中调用被 C 编译器编译后的函数,为什么要加 extern “C”? (5 分)
答:C++语言支持函数重载,C 语言不支持函数重载。函数被 C++编译后在库中的名字与 C
语言的不同。假设某个函数的原型为: void foo(int x, int y);
该函数被 C 编译器编译后在库中的名字为_foo,而 C++编译器则会产生像_foo_int_int 之类
的名字。
C++提供了 C 连接交换指定符号 extern“C”来解决名字匹配问题。
4.有关内存的思考题
void GetMemory(char *p) char *GetMemory(void)
{ {p = (char *)malloc(100); char p[] = "hello world";
} return p;
void Test(void) }
{ void Test(void)
char *str = NULL; {
GetMemory(str); char *str = NULL;
strcpy(str, "hello world"); str = GetMemory();
printf(str); printf(str);
} }
请问运行 Test 函数会有什么样的结果? 请问运行 Test 函数会有什么样的结果?
答:程序崩溃。 答:可能是乱码。
因为 GetMemory 并不能传递动态内存, 因为 GetMemory 返回的是指向“栈内存”的
指针,该指针的地址不是 NULL,但其原现
的内容已经被清除,新内容不可知。
Test 函数中的 str 一直都是 NULL。
strcpy(str, "hello world");将使程序崩溃。
void GetMemory2(char **p, int num) void Test(void)
{ {
*p = (char *)malloc(num); char *str = (char *) malloc(100);
} strcpy(str, “hello”);void Test(void) free(str);
{ if(str != NULL)
char *str = NULL; {
GetMemory(&str, 100); strcpy(str, “world”);
strcpy(str, "hello"); printf(str);
printf(str); }
} }
请问运行 Test 函数会有什么样的结果? 请问运行 Test 函数会有什么样的结果?
答: 答:篡改动态内存区的内容,后果难以预料,
非常危险。
(1)能够输出 hello
因为 free(str);之后,str 成为野指针,
(2)内存泄漏
if(str != NULL)语句不起作用。
嵌入式 c语言经典笔试题目
1 .用预处理指令#define 声明一个常数,用以表明1年中有多少秒(忽略闰年问题)
#define SECONDS_PER_YEAR (60 * 60 * 24 * 365)UL
我在这想看到几件事情:
1) #define 语法的基本知识(例如:不能以分号结束,括号的使用,等等)
2)懂得预处理器将为你计算常数表达式的值,因此,直接写出你是如何计算一年中有多少秒而不是计算
出实际的值,是更清晰而没有代价的。
3)意识到这个表达式将使一个16位机的整型数溢出-因此要用到长整型符号 L,告诉编译器这个常数是的
长整型数。
4)如果你在你的表达式中用到 UL(表示无符号长整型)
,那么你有了一个好的起点。记住,第一印象很
重要。
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 的目的是什么?
如果你不知道答案,请看参考文献1。这问题对区分一个正常的伙计和一个书呆子是很有用的。只有书呆
子才会读 C 语言课本的附录去找出象这种问题的答案。当然如果你不是在找一个书呆子,那么应试者最
好希望自己不要知道答案。
死循环(Infinite loops)
4.嵌入式系统中经常要用到无限循环,你怎么样用 C 编写死循环呢?
这个问题用几个解决方案。我首选的方案是:
while(1)
{
}
一些程序员更喜欢如下方案:
for(;;)
{
}
这个实现方式让我为难,因为这个语法没有确切表达到底怎么回事。如果一个应试者给出这个作为方案,我将用这个作为一个机会去探究他们这样做的基本原理。如果他们的基本答案是:"我被教着这样做,但
从没有想到过为什么。"这会给我留下一个坏印象。
第三个方案是用 goto
Loop:
...
goto Loop;
应试者如给出上面的方案,这说明或者他是一个汇编语言程序员(这也许是好事)或者他是一个想进入
新领域的 BASIC/FORTRAN 程序员。
数据声明(Data declarations)
5.用变量 a 给出下面的定义
a)一个整型数(An integer)
b)一个指向整型数的指针( A pointer to an integer)
c)一个指向指针的的指针,它指向的指针是指向一个整型数( A pointer to a pointer to an intege)r
d)一个有10个整型数的数组( An array of 10 integers)
e)一个有10个指针的数组,该指针是指向一个整型数的。
(An array of 10 pointers to integers)
f)一个指向有10个整型数数组的指针( A pointer to an array of 10 integers)
g)一个指向函数的指针,该函数有一个整型参数并返回一个整型数(A pointer to a function that takes an
integer as an argument and returns an integer)
h)一 个有10个指针的数组,该指针指向一个函数,该函数有一个整型参数并返回一个整型数 ( An array
of ten pointers to functions that take an integer argument and return an integer)
答案是:
a) int a; // An integer
b) int *a; // A pointer to an integer
c) int **a; // A pointer to a pointer to an integer
d) int a[10]; // An array of 10 integers
e) int *a[10]; // An array of 10 pointers to integers
f) int (*a)[10]; // A pointer to an array of 10 integers
g) int (*a)(int); // A pointer to a function a that takes an integer argument and returns an integer
h) int (*a[10])(int); // An array of 10 pointers to functions that take an integer argument and return an integer
人 们经常声称这里有几个问题是那种要翻一下书才能回答的问题,我同意这种说法。当我写这篇文章时,
为了确定语法的正确性,我的确查了一下书。但是当我被面试 的时候,我期望被问到这个问题(或者相
近的问题)
。因为在被面试的这段时间里,我确定我知道这个问题的答案。应试者如果不知道所有的答案
(或至少大部分答 案)
,那么也就没有为这次面试做准备,如果该面试者没有为这次面试做准备,那么
他又能为什么出准备呢?
Static
6.关键字 static 的作用是什么?
这个简单的问题很少有人能回答完全。在 C 语言中,关键字 static 有三个明显的作用:
1)在函数体,一个被声明为静态的变量在这一函数被调用过程中维持其值不变。
2)在模块内(但在函数体外)
,一个被声明为静态的变量可以被模块内所用函数访问,但不能被模块外其
它函数访问。它是一个本地的全局变量。3)在模块内,一个被声明为静态的函数只可被这一模块内的其它函数调用。那就是,这个函数被限制在
声明它的模块的本地范围内使用。
大多数应试者能正确回答第一部分,一部分能正确回答第二部分,同是很少的人能懂得第三部分。这是
一个应试者的严重的缺点,因为他显然不懂得本地化数据和代码范围的好处和重要性。
Const
7.关键字 const 有什么含意?
我 只要一听到被面试者说:"const 意味着常数",我就知道我正在和一个业余者打交道。去年 Dan Saks
已经在他的文章里完全概括了 const 的所有 用法,因此 ESP(译者:Embedded Systems Programming)的每
一位读者应该非常熟悉 const 能做什么和不能做什么.如果你 从没有读到那篇文章,只要能说出 const 意
味着"只读"就可以了。尽管这个答案不是完全的答案,但我接受它作为一个正确的答案。
(如果你想知道
更详细的 答案,仔细读一下 Saks 的文章吧。
)
如果应试者能正确回答这个问题,我将问他一个附加的问题:
下面的声明都是什么意思?
const int a;
int const a;
const int *a;
int * const a;
int const * a const;
/******/
前 两个的作用是一样,a 是一个常整型数。第三个意味着 a 是一个指向常整型数的指针(也就是,整型
数是不可修改的,但指针可以)
。第四个意思 a 是一个指向整型 数的常指针(也就是说,指针指向的整
型数是可以修改的,但指针是不可修改的)
。最后一个意味着 a 是一个指向常整型数的常指针(也就是说,
指针指向的整型数 是不可修改的,同时指针也是不可修改的)
。如果应试者能正确回答这些问题,那么
他就给我留下了一个好印象。顺带提一句,也许你可能会问,即使不用关键 字 const,也还是能很容易
写出功能正确的程序,那么我为什么还要如此看重关键字 const 呢?我也如下的几下理由:
1)关键字 const 的作用是为给读你代码的人传达非常有用的信息,实际上,声明一个参数为常量是为了告
诉了用户这个参数的应用目的。如果你曾花很多时间清理其它人留下的垃 圾,你就会很快学会感谢这点
多余的信息。
(当然,懂得用 const 的程序员很少会留下的垃圾让别人来清理的。
)
2)通过给优化器一些附加的信息,使用关键字 const 也许能产生更紧凑的代码。
3)合理地使用关键字 const 可以使编译器很自然地保护那些不希望被改变的参数,防止其被无意的代码修
改。简而言之,这样可以减少 bug 的出现。
Volatile
8.关键字 volatile 有什么含意?并给出三个不同的例子。
一个定义为 volatile 的变量是说这变量可能会被意想不到地改变,这样,编译器就不会去假设这个变量的
值了。精确地说就是,优化器在用到这个变量时必须每次都小心地重新读取这个变量的值,而不是使用
保存在寄存器里的备份。下面是 volatile 变量的几个例子:
1)并行设备的硬件寄存器(如:状态寄存器)2)一个中断服务子程序中会访问到的非自动变量(Non-automatic variables)
3)多线程应用中被几个任务共享的变量
回答不出这个问题的人是不会被雇佣的。
我认为这是区分 C 程序员和嵌入式系统程序员的最基本的问题。
搞嵌入式的家伙们经常同硬件、
中断、 RTOS 等等打交道,
所有这些都要求用到 volatile 变量。
不懂得 volatile
的内容将会带来灾难。
假设被面试者正确地回答了这是问题(嗯,怀疑是否会是这样)
,我将稍微深究一下,看一下这家伙是不
是直正懂得 volatile 完全的重要性。
1)一个参数既可以是 const 还可以是 volatile 吗?解释为什么。
2);一个指针可以是 volatile 吗?解释为什么。
3);下面的函数有什么错误:
int square(volatile int *ptr)
{
return *ptr * *ptr;
}
下面是答案:
1)是的。一个例子是只读的状态寄存器。它是 volatile 因为它可能被意想不到地改变。它是 const 因为程
序不应该试图去修改它。
2);是的。尽管这并不很常见。一个例子是当一个中服务子程序修该一个指向一个 buffer 的指针时。
3)这段代码有点变态。这段代码的目的是用来返指针*ptr 指向值的平方,但是,由于*ptr 指向一个 volatile
型参数,编译器将产生类似下面的代码:
int square(volatile int *ptr)
{
int a,b;
a = *ptr;
b = *ptr;
return a * b;
}
由于*ptr 的值可能被意想不到地该变,因此 a 和 b 可能是不同的。结果,这段代码可能返不是你所期望
的平方值!正确的代码如下:
long square(volatile int *ptr)
{
int a;
a = *ptr;
return a * a;
}
位操作(Bit manipulation)
9.嵌入式系统总是要用户对变量或寄存器进行位操作。给定一个整型变量 a,写两段代码,第一个设置 a
的 bit 3,第二个清除 a 的 bit 3。在以上两个操作中,要保持其它位不变。对这个问题有三种基本的反应
1)不知道如何下手。该被面者从没做过任何嵌入式系统的工作。
2)用 bit fields。Bit fields 是被扔到 C 语言死角的东西,它保证你的代码在不同编译器之间是不可移植的,
同时也保证了的你的代码是不可重用 的。我最近不幸看到 Infineon 为其较复杂的通信芯片写的驱动程
序,它用到了 bit fields 因此完全对我无用,因为我的编译器用其它的方式来 实现 bit fields 的。从道德讲:
永远不要让一个非嵌入式的家伙粘实际硬件的边。
3)用 #defines 和 bit masks 操作。这是一个有极高可移植性的方法,是应该被用到的方法。最佳的解决方
案如下:
#define BIT3 (0x1 << 3)
static int a;
void set_bit3(void)
{
a |= BIT3;
}
void clear_bit3(void)
{
a &= ~BIT3;
}
一些人喜欢为设置和清除值而定义一个掩码同时定义一些说明常数,这也是可以接受的。我希望看到几
个要点:说明常数、|=和&=~操作。
访问固定的内存位置(Accessing fixed memory locations)
10.嵌入式系统经常具有要求程序员去访问某特定的内存位置的特点。在某工程中,要求设置一绝对地址
为0x67a9的整型变量的值为0xaa66。编译器是一个纯粹的 ANSI 编译器。写代码去完成这一任务。
这一问题测试你是否知道为了访问一绝对地址把一个整型数强制转换(typecast)为一指针是合法的。这
一问题的实现方式随着个人风格不同而不同。典型的类似代码如下:
int *ptr;
ptr = (int *)0x67a9;
*ptr = 0xaa55;
A more obscure approach is:
一个较晦涩的方法是:
*(int * const)(0x67a9) = 0xaa55;
即使你的品味更接近第二种方案,但我建议你在面试时使用第一种方案。
中断(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 机程序往往把硬件作为一个无法避免的烦恼。
到 了这个阶段,应试者或者完全垂头丧气了或者信心满满志在必得。如果显然应试者不是很好,那么这
个测试就在这里结束了。但如果显然应试者做得不错,那么我就 扔出下面的追加问题,这些问题是比较
难的,我想仅仅非常优秀的应试者能做得不错。提出这些问题,我希望更多看到应试者应付问题的方法,
而不是答案。不管如 何,你就当是这个娱乐吧...
动态内存分配(Dynamic memory allocation)
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"。我用这个来开始讨
论这样的一问题,看看被面试者是否想到库例程这样做是正确。得到正确的答案固然重要, 但解决问题
的方法和你做决定的基本原理更重要些。
Typedef
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。
如果你知道答案,或猜出正确答案,做得好。如果你不知道答案,我也不把这个当作问题。我发现这个
问题的最大好处是这是一个关于代码编写风格,代码的可读性,代码的可修改性的好的话题。
嵌入式系统求职回忆录
一、引言
一年前的这个时候,我开始了人生的真正求职历程。在这个过程中,有幸参加过广嵌、迈瑞、华为、智光、周立功、理邦等几个比较大笔试和面试。虽然大部分都失败了,但是我觉得正是这一次次的失败认我真正认识自己的不足,积累经验,成长进步,为最后的成功打下坚实的基础。求职是一个展现个人综合实力的过程,除了需要专业技能之外,还需要有一定的策略。同时这也是一个逐步提高和适应的过程。就像一支球队参加真正比赛之前都要经过系列的热身赛,熟悉对手的同时也在发现自己的不足。那时候我经看看别人的面经,现在我觉得把自己的体会跟大家分享也是一件很愉快的事情。特别在这个找工作的高潮时刻,也希望我以前总结的求职经历能给大家一点借鉴和帮助哦……
二、广嵌面试
这是我人生的第一次正式面试,挺紧张的。广嵌在广州科学城那边,有点偏了,人比较少。首先是做题,没有什么时间限制,做完就交。题目不多,8道好像,全是问答和编程题,没得蒙。主要考了C语音、数据结构、操作系统、编译原理、内存分配和链表的一些东西。虽然是很基础的东西,但是平时不注意不一定做得出来。题目做完了,HR拿去给技术总监看,过了一会来告诉我,技术总监对我的答题较满意,叫我去会议室进行面试。技术总监来是一个比较年轻的工程师,谈吐之间感觉巨牛。我首先介绍了项目的情况,可能是第一次吧,讲的比较细,他听了一会感觉出我实际做了点东西,懒得听我说,直接叫我停止,开始问了我几个问题。看起来不是什么技术难题,但却不好答。我印象比较深刻的是下面三个题目:
1、在学校实验室中编程做项目和实际编程做项目有什么不同,需要注意什么?
2、如果在公司中碰到了行政问题和技术难题应该怎么样处理?
3、你的职业规划或者是你未来几年打算怎么样?
这些的回答见仁见智。我没想到他会问这些问题,开始愣了几秒中,脑子一下转不过来。不过我对自己的回答比较满意,可能技术总监也认同我的回答。接着他问我做TCP/IP方面的东西怎么样。我说没有怎么样做过网络底层的东西,主要做的应用层上面的套接字编程。然后他问我在windows和linux的驱动层和应用层做过什么东西,我就讲相关的项目和工作稍稍说了一说。然后他给我介绍了进来公司要做的一些工作,主要是做TCP/IP应用层的测试验证工作。后面也没什么问了。他出去后,HR进来和我谈了谈公司的具体情况和福利方面的东西。大概三个小时,我的第一次面试就这样结束了。
三、迈瑞笔试
收到迈瑞的笔试其实不是很意外,但是比较突然,没有想到当天宣讲完第二天下午就开始笔试,一点准备的机会都没有,去开开眼界吧。迈瑞通知我去考软件工程师,但我C++其实不怎么懂,肯定得交白卷。还好可以和来霸王面的人一起进了硬件工程师的考场。进去后我又要了系统研究工程师的题目,觉得系统的东西具体不是很清楚,思路说说还是可以的。然而,这次考试实在对自己的打击太大了,总算看清楚自己的真是水平了。
在硬件的题目中,考试范围很广,模电,数电,CPLD,操作系统,算法,智力,设计等都涉及到。对打击自己的是,明明看着知道怎么做,但是忘记相关的公式概念,没法下笔,更伤心的是会做的粗心做错了,唉...无药可救了。
第1题是单位的换算,我居然把1Gbyte=1000Mbyte,出来想想才知道错了,应该是1024。 第2题是一个模电的题目,考查共射三极管的电容对上限截止频率和下限截止频率影响,包括耦合电容,极间电容和布线电容。第3题是问什么是摩尔定律,摩尔定律对硬件开发有什么影响?我一个字也写不下来。第4题又是一道伤心的题目,这是我唯一一道有点把握的题目,给了两个运放,要求写出名称和表达式,一个是差动比例放大器,一个积分电路,但是由于粗心,将差动比例放大器推导推错了。接下来的题目是什么呢......第5题做不出来,题目是说电容在高频的时候可以等效成一个电感、一个电容和一个电阻的串连,问实际中在什么频率时候使用做合适。第6题是数电的题目,什么是冒险与竞争,怎么样去消除冒险与竞争。数电的最基本题目,我又空白,你说这样的题目我都做不出来,在简历上吹你熟悉模电和数电还有人会相信吗?下面一题是英语翻译题目,给一个PCI的读时序,让你翻译将上面的英文翻译成中文。接着是一个智力题目:一个人从A城市下班后乘火车到火车站,他的妻子从家里骑车刚好到车站接他回家,一天,他坐了早班火车,5:30就到了火车站,然后走路回家,他的妻子在半路上接了他,回到家里发现比平时早了10分钟,问他坐了多少时间的路?这题我又挂了。后面是选做题,3选1。一道模电,给了运放、二极管,一个AD前端信号调理电路,要找出其中的错误。唉,又没头绪,估计是二极管钳位,写了几个字。另外一个是要用CPLD语言描述出JK触发器,再用JK触发器和与、非门搭出D触发器。这题我又放弃了。最后一个是操作系统的题目,关于PCI总线的三个读取方式的,只能放弃了。后面还有哦,一个二叉树遍历的程序补充,呵呵,这是数据结构最基本最简单的题目,我又缴械了。应该是最后一题了,说说你的一个成功的项目或者课程设计,画出结构框图,说出工作原理,碰到了什么难题怎么样解决。这个当然谁到会写拉,不就吹牛嘛。
再看看系统工程师的题目,分三个部分。第一部分有四个可选题目,和公司的四条产品线密切相关,第一个是描述心电信号的特点和测量方法。第二个是描述人体生理信号的特点与测量方法。第三个是说说数字滤波器的特征和实现,最后一个是说说光谱吸收装置的原理好像。本来觉得第三个很眼熟,但是又不知道怎么下笔,算了,反正都不会,选了第一到吹了一通。第二题是编程题,一个是补充汉诺塔的程序,还有一部分是计算冒泡法的比较次数和用顺序法找一个最大值的平均次数。下面一道题目是设计题目,设计一个数码相机,从市场需求、功能设计、质量方面、可行性和市场风险去设计。还好这个可以写点东西,不然正的是交白卷了。最后是一个4选1的题目,考查的是精度方面的东西。第一个是说说游标卡尺的精度影响因素,第二个是分析数控机床的精度影响因素,第三个是分析光强测量装置的精度影响因素。第四个忘记了。我选了数控机床那个东西写了几个字。
虽然是深深收到了打击,但是也真正的看到了自己的水平。 学了那么多年,最基本的东西都没有掌握,正是惭愧啊。看来是时候开始好好找找自己的方向了,明确一个目标就去复习复习吧。软件东西我懂的都是皮毛,看来和它有缘无分拉,和硬件看来还有点尘缘未了啊。
11月1号补充:虽然没有面试的机会,但是还是关注了迈瑞的面试情况,顺便记录了些在浙大BBS上看到的浙大牛人的面经:
面试官是那天监考的工作人员,考卷也是他看的。好像他是根据考试情况来定面试内容的。从来开始讲一下他的问题吧:
1.你选择深圳工作,有考虑过深圳离你家近?
2.如果老板给你一个很急的任务,按理来讲要7天完成,但是却要你5天之内完成,你会怎么办?
3.你的身边有这么一个老工程师,他可能不如你厉害,但是他脾气还很不好,你会怎么和他相处?
4.你的老板有一套方案已经确定了,而且马上要实施,而你认为自己的方案也不错,并且在某点还很有创新,你会怎么办?
5.你对选择做硬件有什么特别的想法?
6.我看你最后一道题答的很详细也很工整,是我想要的结果,但我还是想让你给我解释一下关键问题的解决思路。他会给我一张纸,在我画框图的过程中打断我,顺便问几个技术问题,比如运放做比较器和专门的比较器会有什么不同点等等。
最后他说可能不一定会有二面,但是11月1号上午会确定是否给offer。
我的感觉是:在答最后一道自由发挥的题时,大家最好把自己最熟悉的项目写上,并且写清楚,这样会给人家留下非常好的印象。另外项目如果不是自己做的,或者解释不清楚的,简历上最好不要提,被他们盯上就不好说了。但是他们还是很Nice的。
四、华为笔试
人家说得不错,要做好一些事情,不但要有目标还需要有充分的准备。虽然我没有什么明确的目标,但是这次我有了稍稍的准备。自从被迈瑞打击了一下,我重新认识了自己。回来正经地复习了一下功课。
虽然华为的硬件笔试题目出得很基础,但我也有不少做不好。但总算可以从迈瑞的失败中稍稍恢复了一点点信心,不管结果怎么样,我终于可以从头到尾做完,还提前了半个小时交卷。下面稍稍总结一下吧。
对于华为,我感觉浙大的牛人很不屑一顾。BBS上面关注的人不多,有也是华为负面的消息。迈瑞笔试那天有四个教室人,华为今天也就一个阶梯教室人。在笔试之前,华为都给参加笔试的人打了电话,问问情况。我也收了两个,不过没带手机,没接上。接到电话的人有两个被调整到技术服务类了。不过听说技术服务类比研发的待遇要好哦。在考场上,什么也不用检查,去了就考,没有通知到的也可以考,总之是来者不拒。
试卷有10道填空题,12道单选,12道多选和一道分析题。其中分析题就叫写出2-4译码器的真值表,是不是比期末考试还简单呢,20分哦。填空题第一道就是一道运放题目,给一些已知条件求运放的输出电阻,出来听大家讨论,好像我作错了。第二道又不会,问2DPSK是什么键控,是通信方面的内容,在百度搜了一下,原来二相差分移相键控。下面有什么是误码率啊,什么PCM一次群,8086的寄存器是几位,给你一个128K的静态RAM和起始地址,问你最后一个字节的地址,还有VHDL和数字逻辑等等。有一道题我真写不下东西,问组合逻辑输出用做时钟容易产生什么。在单选题目考还是那些基础的东西,有问IBM-PC和兼容机的I/O端口空间,PCM32一次群一个复帧的时间,还有是问码元长0.1us,2.5秒产生一个误码,其误码率是多少,不会就蒙吧。还问到RAM和ROM的区别,静态RAM和动态RAM的存储单元结构等等。多选比较变态点。第一题问下面的逻辑门必须上来电阻的是:OC门、OE门、OD门、以上都要。第二题是问你下面是可编程逻辑器的是:ASIC、PAL、GAL、FPGA、CPLD。第三题是RISC和CSIC的区别。第四题是总线的功能,我不知道驱动是不是总线的功能,所以没选。第五题有点难,问下面避免竞争冒险的是:采用同步时序电路,采用异步时序电路,修改状态转移表,增加延时逻辑。第六是ROM和RAM的区别,下面是8051的P3脚复用功能,接着是检波器的组成,还有常见的滤波器。第10题不会:问8KHz和10KHz的正弦波经过混频器之后,能产生下面那些频率的信号:-2K、2K、18K、1K。11题考8051的中断,最后一道不容易,问下面对负反馈那里两个是对的。
从题目的难度和广度上看,比迈瑞的简单,但是没有准备过的人也很难做出来的哦,范围广,重基础,关键还有可恶的多选选择。总的来说,我对自己的表现比较满意。没有迈瑞的那么狼狈。但是,估计没有下文了。我想这份题目做得好的人大大的是啊。听上午接到电话的人说,华为搞硬件的主要是3个方面:DSP和算法、逻辑电路、ASIC。唉,这三个我没有一个会,更不用说懂。就算有幸进到一面,也会被刷的,我想主要还是专业不太对有点影响。未来的事情就等它发生了再说吧。好好准备眼前的事情才是正道。
五、华为一面
华为的硬件笔试考完之后感觉不错,果然得到了一面的机会。不过我们学校去参加笔试的都可以去参加面试。不过听说华为这次做硬件的只招收DSP和逻辑两个方面,我一听就觉得自己就此终止华为的征程了,看看大公司的面试也是长长经验吧。我的面试时间被安排在6号的9点,搞到那天6点45就得起来从下沙赶到黄龙那边的一个酒店,极度的痛苦。
不知道怎么搞的,和我一起来的几个9点20的都面试完了还没有轮到我们。后来问问才知道我被安排到DSP组里面去了,后来赶紧叫他们改为逻辑。大概等到11点,终于到我了。不过当我走到面世官的前面,他却说要上洗手间,叫我等几分中,看来真是出师不利啊。
面试官很和蔼客气,大家say hello之后,我紧张的心情松了不少。然后他开始瞄我的简历,我估算了一下,不超过3秒钟。还好我做的是一页简历。他看到我后面有两个项目的实物图,就指着我在04年机械设计大赛的获奖作品问:“这个好像是什么车?”我说这个是我在省里面竞赛的一个作品,主要用一个多单片机系统控制直流电机和舵机完成相应的功能。不过他好像不感兴趣,然后就问:“我看你的简历好像做DSP和逻辑的比较少嘛”(其实我根本就没有做过)。我赶紧说:“是的,我们研究生虽然有相关的课程,但是我们学到只要是一些入门的基础,离实际的应用还很远,我在本科的时候主要是一些基础知识的系统学习,在研究生阶段主要是跟老师做做项目,其实我现在的技能主要和我做的项目相关,在以前的基础上的其中一两个方面进行比较深入的研究。”面试官看了看的简历说:“我看你做单片机的项目比较多嘛,我们也有做单板机软件,也有用一些小型的CPU和AD转换、DA转换的一些东西,你愿不愿意做呢?”我想DSP和逻辑肯定挂了,这个还有一线生机,赶紧说:“OK,没有问题,其实我的毕业设计就说一个单片机的数据采集和处理系统,我把实物带来了,你要不要看一下?”他点头示意可以。我心里一阵高兴,终于可以使出我的武器了。
下面的面试内容的完全按照我事先预料的方向进行了。首先我详细的介绍我带来的系统,从原理到框图,从性能到关键点说了一通。他听完,问我:“这个系统是你设计的吗?”对于这个问题我觉得还是老实做答好,不然会死得很惨的。我说不是,我参加这个项目的时候,这个系统已经设计好了,我做的工作就是对老的版本进行测试,改善,从硬件和软件两个角度去进行性能和稳定性的提高。他说:“那好,我就问你几个问题吧?”首先问我上面一个三极管是干什么用的,我说那其实不是三极管,是一个稳压器79L05,产生一个-5V的电压给AD。然后他问我上面两个二极管是干什么用的,我说那是起保护作用,主要是限制电流反流。他一听很奇怪,他说二极管好像没有限流作用吧,我觉得他可能听错了,就补充了二极管的单向导通性,圆了一下场。接着他叫我将系统上面的有源滤波画出来,完了,这个系统我就那个地方没有弄懂。怎么办呢?只有画吧,凭着记忆画了一个二阶的有源滤波电路,跟他说我其实忘记了,大概说这样吧。他说那你讲讲这个滤波器的工作原理吧,我连画都画不出来,原理更讲不清了。我就跟他说:“其实二阶的滤波器和一阶滤波器的推导大致原理一样的,我画个一阶的来解析一下吧”。然后就画画讲讲,其实我还讲的比较清楚的拉。他问了最后一个问题,如果你的那个传感器的线比较长,会对你的系统有什么影响,其实我也是按照大致的方向去答的拉,我说这样就会导致信号的衰减和干扰的增大。他稍稍停了一下,问我喜欢做偏硬一点的还是偏软一点的。我说相对来说,我平时对偏软一点的做的事情比较多。他说:“那我就给你定底软的岗位吧,我现在要考考你;两道C语言的题目。”然后取了一叠试卷,边找边说给我找两个简单点的题目。
第一个:写一个宏名为MIN的宏,输入两个参数,求最小的那个?晕倒!我见了很多次,但是为什么偏偏在那个时候忘记呢?但是总不能说不会吧,人家都说给我找简单的了。只有硬着头皮写拉。我的答案是:
#define MIN (A,B) ?(A>=B)B:A
他看了我的答案,马上指出我的几个错误,应该是:
#define MIN (A,B) (A>=B)?(B):(A)
第二个是读程序写结果,在一个mian()函数里面循环3次调用一个有静态变量的函数,输出一个和的结果。这个我最熟悉的了,三两下就把结果和过程都写出来了。
这时,面试也差不多要结束了。面试官问我工作地点的问题。我说我是广东人,希望在深圳,他说这个没有问题。然后我再一个确认我的工作内容,他说主要是底软吧,到时候部门之间再调配一下,他看了看我的简历,发现我有ARM和LINUX的经验,他说其实他们也会做相关的东西,例如VxWorks等。
这时,面试官对我说,他对我的能力比较满意,我心里一阵狂喜,不知道是骄傲还是高兴。他主动站起来和我握手,说叫我等秘书的通知,下一轮面试通知会在这两天发出。这时我都不知道自己在做什么了,收拾了下我的东西,临走前我主动和面试官再次握了握手,感谢他的面试与指导。
六、华为二面
没想到华为的工作效率那么快,本来还以为要到8号才有二面的通知,所以6号晚上玩了一把,到1点才睡觉。当我睡得正香的时候,来了两个短信,心里一阵狂怒,凌晨两点半什么人那么无聊发短信啊。不过一看原来是华为通知7好早上9点半面试,果然和大家说的一样,集体群殴PK,不管它了,继续睡觉。本来想睡到7点起来的,不过6点20分左右,又来了短信,还是华为通知面试的,他们不会加班到通宵吧。
我们学校去参加研发一面的有4个人,其中有3个都进了二面。到了指定的酒店后,先是填表,还真是详细,什么都得填。本来安排是9点30的面试,后来拖到了10点20左右。之前,我们组的12个人都到得差不多了,大家赶紧先认识一下,到时候PK的时候也比较熟悉嘛。
时间到了,我们在一个MM的带领下进了房间,三个桌子排成三角型,中间一牌是面试官4男1女。两外两个是AB两组,反正大家随便坐,12个人分成两组PK。首先是每个人用一张纸折一个铭牌,将自己的姓名和应聘岗位写在上面。然后面试官开始叫我们自我介绍,要说得让面试官和大家对你留下深刻的影响,说说你的特点和以后的职业规划。我发现浙大的人理想很大哦,职业规划都是要做什么技术管理结合的职业经理人。我都忘记了我是怎么样介绍我的,好像说了说我一些竞赛获奖,说了说我的业余爱好,喜欢踢足球拉,还有就是我觉得我的硬件基础比较扎实,在华为里面能发挥所长,体现价值,华为是一个大企业,我在里面也能得到锻炼和成长。不知道这样说有没有问题?
接着开始一个活动,在10分钟里面,每组按照发的题目将里面属于白领最忧虑的事情进行排序,然后由组里面的一个人进行陈述理由,然后其他人可以进行补充。活动刚开始我提了一个意见,但是有一个人的意见比我的更好,那就按照他的意见去做吧,我顺势将他推举为我们组的leader。在10分钟里面,HR们过来看大家的表现,然后打分,反正我是没有怎么去排列,我只是协调一下大家的动作啊,看看时间啊。很快时间到了,他们组先进行了陈述,在陈述的过程中,面试官会打断陈述,叫另外有个人补充表述。我们这组也是。在这个表述中,我基本上没有什么话,算是话最少的了。
两个组的排列顺序很不一样,这时候面试官说,你们两个组分别陈述理由,让对方同意你的观点。然后,两个组的人就纷纷展现自己,在这个过程中,我几乎一言不发,看来犯了群殴的大忌了。最后,面试官叫没有发过言的同学起来说两句,我是最后一个起来发言的,我就总结性的说了两点,然后就休息5分钟。
其实休息5分钟是让我们两个组在外面交流一下,统一一下观点。我知道他的意思,出来后我问两个组是不是应该统一一下观点啊,好像他们也没有退步的感觉,那就算了吧。
很快有进去了,首先面试官叫对方组的一个人起来,说说他的看法,他说同意了我们组的一个观点但是也有不同的地方。他说完之后,面试官看着我说,你们好像在外面统一了一下观点,你说说将B组的12点排在前面,你认不认同。我突然晕了,这怎么回答呢,答认同嘛,不知道会不会被我们组认说,说不认同嘛,那面试官明说说我们统一观点,而且B组的人也妥协了。那我只有用了找迂回战术,顺着B组刚刚的内容说了些总结性的东西,既不认同也不否认认同。面试官看我答非所问,又将问题问了一遍,我又将刚才的话说了一边。晕倒,这时那面官来了句其实我只是问你认不认同,这时候我发现我们组的leader在我的纸上写了认同两个字,我赶紧说认同认同,这才能坐下啊。
接着就是PK时段,想不到我是我们组唯一参与的。B组也有一个人,面试官说,给你一个机会你把你的特点和长处说出来,让我们觉得你应该加入华为。对方那人说啊说,面试官还让他举了一个案例。真想不到会轮到我要参与PK,是不是因为我话太少,不够积极呢,还是什么......面试官看我有点紧张,就对我说,你看我现在给了你这么好的一个机会,你要把你的特点和长处说出来,其他人我都没有给机会啊。不知道是不是安慰我的话。然后我就慢条斯理的说,我自己性格比较随和,喜欢跟别人交流,我觉得在交流的过程中会得到很大的提高,我的基础知识比较扎实,在项目的实践过程中,发现基础技能很重要,很多高端的问题都可以用基础的知识来解决。说我本科是学硬件的,研究生学的是软件,在实践的过程中发现现在不管是硬件开始还是软件开始都是一个协同的过程,不能机械的拆分两者,说我考虑问题会从系统的角度去分析,个体不一定是最好的,但是可能对系统的贡献是最大的。最后我总结说,我的优点是我为人随和,有独立分析问题和解决问题的能力,自我适应能力和学习能力比较强,在知识结构上面,软硬件结合,实际项目经验较多。面试官接着问我平时爱好干什么啊,我就说我喜欢体育运功,特别喜欢踢足球。他居然问我踢那个位置,不知道他是不是也踢足球的,我是中场。他又问我平时都和什么人踢啊,是不是和同学踢,我说不一定,有时候和同学踢,有时候到了球场上,碰到谁就和谁踢,还说我参加过一个大型的足球比赛。
就这样传说中的华为群殴结束了,虽然没有网上说得那么恐怖,但是也领我感到有点不爽,不知道是不是我被问的缘故。后面还有10分钟的提问时间,有人问了华为集体辞职和加班的问题,还有下面的一些安排。这时我才知道下面还有一个单对单的综合面试和综合素质测试、性格测试和英语测试。
长路漫漫啊......不知道还能不能收到三面的通知呢?等等吧......
七、智光面试
非常感谢智光电气给我面试的机会拉。由于18号中午学校要进行毕业生的图像数据采集,我没有办法参加智光电气的笔试,虽然在宣讲会那天和相关的人员打了招呼,但是那天一直到下午三点多还没有收到任何通知,在浙大88上看到已经有人开始面试了,感觉这次是不是又没戏了呢?干脆回去寝室睡觉算了。这时居然接到了智光电气的电话,说明天什么时候来杭州百脑汇8楼面试......
没有参加笔试和一面,直接参加19号的面试,这样的待遇我还是头一次,很有受尊重的感觉。有机会就不能错过,剩下的时间我就开始临时抱佛脚,听说很有用,不过面试的时候我都没有用上,因为我的面试估计也就10几分钟吧。之前我等了大概半个小时。
面试我的是一个博士,还有另外三个人。主要就是根据简历上的东西随便问问你,但是每个技术都问得很到点子上,但是我也答得很到点上,而且还很老实,很多东西都直接说不是我设计或者不是我一个人做的,或许这样他们就不再往下问了。现在的硕士大家都知道什么水平的拉......总不能说自己是什么大牛,估计我是说了肯定逃不过他们的法眼。这次我仍然带了我的毕业课题做的板子过去,不过他们就随便看了看,也没说什么,我一点发挥的机会都没有......是不是他们看不上呢?他们问为什么用华邦的单片机做呢?我说便宜啊。就这样。另外还问我有没有做过电机方面的东西,那我就把以前机械设计竞赛和指导电子设计竞赛的一些东西说了说,大概就五六句话吧。后来有个人问我做的一个数据汇集机用什么进行电话网通信的,我说是MODEM,本来以为可以大说一顿的拉,那个工程师说可以了,知道了。那我也没什么说的了。基本上聊了一些基本问题面试也就结束。
接下来就谈待遇了,那个主面的博士问我期望多少钱,经过多次的打击,我也不好意思说了,就说我没有很高的要求,中等就可以了。他叫我尽管说。我说5K到6K吧。他说5K应该没什么问题的。估计也就这个身价了。后来聊了一些工作内容和时间的问题,基本都比较合意吧。然后就叫我准备三方了。我说两天内答复。找工作实在太累太耗时间和精力了,其实也没有什么可以选择的了,就这样......回去再想想,卖了吧。
八、理邦面试
自从上次在智光面试完之后,也没有什么合适的单位到来。在智光面试完,我正准备签三方的时候,他们的HR告诉我,要等广州总部的总工电话面试,但是快两个礼拜了都没有音讯,估计我是被他们放弃了。正好1号深圳理邦来浙大宣讲,就去碰碰运气吧。
那天宣讲完马上开始笔试,还好之前抱了下佛脚。原本我应聘的是嵌入式软件工程师职位,需要考一份软件题,不过一想到软件的算法,心里有点慌慌,要了份硬件的题目来做。题目不多,八九道简答题,主要涉及晶体管、运放、滤波、触发器、AD、通信、单片机等一些很基础的东西。我已经是身经百考的人了,每个公司的硬件题目都差不多的,大部分做起来没有什么问题,除了一个压控电流源和一道场效应管的题目没做,其他的问题不是很大,估计有下文,果然晚上接到了面试的电话。
理邦公司给你第一感觉很实在,很规范化,而且很守时。面试安排在9点20,我怕迟到早点到了,说20就是20,我在面试的时候发现他们原来是有表的,难怪时间那么准,一次面试,经历了三个面试官的车轮战,对于我来说,还是头一次。
第一战:技术面试
我原来是他们通知的第二个来面试的人。面试的时候,和他有好几米的距离,感觉有点紧张。上来第一个问题是你喜欢做什么样的工作,然后开始按照你的简历上面的所做的项目来问,我都忘了我怎么回答的了,总之就是不停的说。我第一次感觉到我的简历没有被浪费,他们用黄色的荧光笔在上面画了不少东西,看来我还是有一点东西能让他们感兴趣的。它对我应聘嵌入式软件工程师去考硬件题目有点奇怪,那也没什么好解释的,就觉得自己对硬件更有把握一点。这时又问了一些linux和ucos方面的东西等等,最后问我技术上面有什么问题需要问的。这个我还真的没有怎么备,不问感觉好像对公司和个人不关心的,我问他们像我这种非生物医疗专业的人进去做会不会很困难。他说没关系,我看你的计算机学得很不错,呵呵….这句话我搞得我很不好意思。也就是十几分钟,就结束了,要走的时候,他说我考的是硬件题,技术总监要面试我硬件方面,面就面罗。
第二战:技术总监
技术总监给你感觉和蔼而威严。他看了看我的成绩单,好像更关心的是我本科的成绩,他说你本科是机械自动化的,那你觉得你和电子、计算机专业的学生竞争有什么优势吗?说真的,在专业技术上面没有任何的优势,我主要围绕着软硬件结合和项目经验比较丰富这两方面来讲,好像说得过去了。接着他问了我单片机方面的一些东西,好像他们对UCOS这个东西比较感兴趣,问我UCOS移植到MSP430上面行不行,我说没什么大的问题,然后说了说这个移植的过程。这个过程中,我感觉我一直在不停的说,嘴都干了,都不知道我那来那么东西说。在他问我为什么要在6月份毕业的时候,一个女的HR进来了,正好,技术总监叫我跟她出去了。
第三战:面对女HR
也许这次是我第一次真正的面对HR,感觉很不自然,而且问题回答的很糟糕。在整个过程中都非常的严肃,我不知道其他公司的HR面试是不是也这样。
在面试的时候,我看到她在一张写着资格审查的表上写了我回答的问题,她问的问题我基本上都没有什么准备过,总之,问什么就答什么,但是我犯了个错误,有些问题不需要答得太多,说多了就会有问题…
首先问我的职业规划是什么样的,我还真没有考虑过,经过上次华为二面的教训,我大概把3-5年的打算说了说,就一句话,我这段时间想在一个行业里面做研发,在技术上面多点积累,五年后可能做做技术管理方面的东西。然后问你要找什么样的公司?工作地点在那里?有没有女朋友?平时喜欢干什么?性格是偏内向还是偏外向?其实这个我自己都不是很清楚,应该是有点内向吧。问我身边都是些什么朋友?这个问题我答得比较糟糕,我也不想多说拉。接着问你父母对你的最大影响是什么啊?对加班的看法?你最艰难的时候是什么时候,这个地方我又说多了,唉…..还问了毕业时间和实习的一些情况,最后问我有没有offer,我还真的是没有啊,问我以前有没有offer,我说智光电气给我了口头的offer,她好像很关心,把这个公司和我应聘的岗位都写下了,问我他们的待遇是多少,她顺便问我期望薪资多少,我想他们也不会给太多,说了个5K到6K,估计最后成的话也就5K吧。最后她例行的问我有没有什么问题要问的,我之前还真没有考虑过哦,问了她在公司里面三年这个发展过程怎么样。
1号上午理邦一面回来感觉不是很好,但是晚上还是收到了二面的通知。在宣讲会的时候,他们说是要小组面试,我心里想一面的时候,技术专家面了,技术总监也见了,HR也谈了,二面面什么呢,小组面试会不会是跟华为他们一样的呢?心里没有什么底,也不知道怎么准备,干脆就不准备了。
冬天早起的确是一件很艰难的事情,好不容易才爬起来,九点钟面试,我八点五十五才感到面试地点。当我进去面试的房间才知道他们的小组面试是什么回事,原来所谓的小组,是他们小组,面试我一个人,昨天面试的3个人一起来面试我,大概30分钟吧。原来还是问技术方面的问题,但是问的很深入,有很多问题我知道也做过,但是答的不完整,有些问题他们继续深入问,我只能说不知道。他们完全按照我简历上面写的项目和技能来进行考查,所以说没有做过,甚至没有什么把握的东西,最好少出现在简历上面,一旦有严格考查的公司面试,一下就暴露出来了,而且还觉得你有吹嘘的嫌疑。
面试一开始,公司技术总监就拿着我的本科成绩单和笔试题目说,我是机电毕业的学生,但是从笔试来看,我的基础不是很好,这个怎么办好呢?问我准备以后怎么样去补救。其实我觉得这份题目做的还不错的拉,我看他在那道压控二极点高通滤波器上面打了一个大叉叉,我本来还觉得这道题目会做对的呢?然后他问我一些做“月球车”比赛的事情,我回答得应该不错,他说我是这个团队的军师了,这我科万万不能接受啊,赶紧把队友的贡献说了一通,强调他们的重要作用,这个团队里面少了谁也不行。接着问我一些单片机相关的项目,这个我都答得没有什么问题。然后是一个应该是偏软件一点的技术专家问我。首先就我用VC做的一个项目问我windows消息的机制是怎么样实现,这个问题其实我答得不是很好,我就项目上面基于消息机制的异步网络传输说了一些windows消息机制的一些东西,他接着我说的问我知不知道将关联消息和消息响应函数的那个宏是怎么样实现的,有没有看过代码,我只能说没有看过,但是我说我知道它的原理,是用一个静态的函数表将消息和消息响应函数关联在一起的,他又顺着问题往下问我知不知道这个函数表叫什么,我又只能说不知道了。下面他看我上面写着有linux下C/C++开发的经验,就开始从这里问我了。看我上面写着做过字符设备的驱动,其实我只是做做实验看看代码而已,我就把字符设备驱动的框架说了一说,他又接着我的回答,问我知不知道操作系统怎么样找到设备相应的处理函数的,我也没怎么样想,就直接说不知道了,但是我说我觉得他们应该是怎么样的,我想应该回答得到点上面了。接着他问我有没有做过linux上面的应用开发,我还是老老实实的告诉他吧,没有做过。他好像对我在简历上面的不老实有点不满。然后他继续问我相关于嵌入式和linux方面的东西,比如说移植啊,bootloader啊,会不会做randisk啊等等,大部分我只能say no了,但是我也不是完全举手投降,把相关的知识点答了一些,可能这也为我前面的不足做了一下弥补吧。最后是HR发问,还是一些常规但是不好答的问题:你觉得什么是成功?你觉得人生的意义是什么?你觉得自己有什么缺点?你为什么要选择理邦?你觉得到了理邦公司你能做些什么样的贡献?如果现在给你offer你会不会签?等等…我觉得这些问题的回答非常关键,可能前面你都答的不错,但是这问题回答不好,也会被out的。我觉得今天我这些问题答的还是可以的,虽然不是很好,总之这些问题怎么回答还是见仁见智的了。最后一个环节是例行的问我有没有什么要问的,我其实也没什么问的了,但是不问不是很好,就问了一些关于实习的事情。面试结果要到下午才能知道。
想不到他们效率那么的快,在中午我正在食堂打饭的时候,他们的电话来了。虽然有点吵,但是还要赶紧接拉。HR先把面试结论告诉我,没怎么听清,大概是说我知识面比较宽,但是深度不够,基础不是很扎实等等等等,但是作为一个嵌入式软件工程师来说,还是可以培养的,下午可以来签约了,然后是待遇的情况,说了一通时候,告诉我要带什么什么。
还考虑什么呢,下午赶紧把需要的东西带齐,奔赴浙大签约了。签约只是一个5分钟的过程,他们把资料收好,给我出了张公司的接收函,再给我一张联系方式,整个签约就结束了,巨快。我就这样卖出去了,尽管有很大的遗憾,但是最后我选择了一个高校,跟理邦办了违约手续。
九、经验总结
1、要有备而来,尽量将面试官引导到你最擅长的地方。其实他们也希望这样的吧,否则他很难发现你的能力和特长,而他不知道你的长处,就只有按照他的思路来考你,对你乱开枪,这样大家都没有得到想要的结果。
2、诚实最重要,懂就懂,不会就不会,东西不是你做就不要乱认。面试你的都是这一行的资深工程师,要忽悠他们可没那么容易。
3、对于不会回答的题目,不一定要给出准确的答案,只要往正确地将大致的思路说清楚就可以了,关键是你把你思考的过程告诉面试官。
4、要有自信心,当被问到确实不会的问题是,可以问问面试官,他们会给你一些提示或者思路,你或许能做出来呢?
5、平时要主要积累和准备,不然当面试机会来临的时候就手忙脚乱的拉,不过有时候临时抱佛脚也挺有用的,总之,没有准备很难有好发挥。
6、投简历要有目的性,瞄准一家之后,做好各方面的情报工作,在网上收集试题、面经等等。再投一些同类的公司,他们只是您的热身对手,重在参与,发现不足,锻炼心理,演练战术。
7、笔试不可怕,笔过两会就经验丰富了,考什么你心里很自然会清楚的。硬件的当然是数模电基础、单片机接口和C语言拉。嵌入式方面少不了操作系统、网络原理、驱动等一些内容,但都是基础,比期末考试还要容易呢。
8、求职是一个复杂的过程,职场如战场。您完全可以把它看作一场战斗,优胜劣汰,除了有足够的枪支弹药,还需要有战略战术的指导。
祝大家求职顺利,个个早日找到理想的工作岗位。
嵌入式常见经典笔试题
预处理器(
预处理器 ( Preprocessor)
Preprocessor )
1 . 用预处理指令#define 声明一个常数,用以表明 1 年中有多少秒(忽略闰年
问题)
#define SECONDS_PER_YEAR (60 * 60 * 24 * 365)UL
我在这想看到几件事情:
(1) #define 语法的基本知识(例如:不能以分号结束,括号的使用,等等)
(2)懂得预处理器将为你计算常数表达式的值,因此,直接写出你是如何计算
一年中有多少秒而不是计算出实际的值,是更清晰而没有代价的。
(3) 意识到这个表达式将使一个 16 位机的整型数溢出-因此要用到长整型符号
L,告诉编译器这个常数是的长整型数。
(4) 如果你在你的表达式中用到 UL(表示无符号长整型),那么你有了一个
好的起点。记住,第一印象很重要。
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 的目的是什么?
如果你不知道答案,请看参考文献 1。这问题对区分一个正常的伙计和一个书呆
子是很有用的。只有书呆子才会读 C 语言课本的附录去找出象这种问题的答案。
#error 预处理指令的作用是,编译程序时,只要遇到#error 就会生成一个编译
错误提示消息,并停止编译。其语法格式为: #error error-message
注意,宏串 error-message 不用双引号包围。遇到#error 指令时,错误信息被
显示,可能同时还显示编译程序作者预先定义的其他内容。死循环(
死循环 ( Infinite loops)
loops )
4. 嵌入式系统中经常要用到无限循环,你怎么样用 C 编写死循环呢?
这个问题用几个解决方案。我首选的方案是:
while(1)
{
}
一些程序员更喜欢如下方案:
for(;;)
{
}
这个实现方式让我为难,因为这个语法没有确切表达到底怎么回事。如果一个应
试者给出这个作为方案,我将用这个作为一个机会去探究他们这样做的基本原
理。如果他们的基本答案是:"我被教着这样做,但从没有想到过为什么。"这会
给我留下一个坏印象。
第三个方案是用 goto
Loop:
...
goto Loop;
应试者如给出上面的方案,这说明或者他是一个汇编语言程序员(这也许是好事)
或者他是一个想进入新领域的 BASIC/FORTRAN 程序员。
数据声明(
数据声明 ( Data declarations)
declarations )
5. 用变量 a 给出下面的定义
a) 一个整型数(An integer)
b)一个指向整型数的指针( A pointer to an integer)
c)一个指向指针的的指针,它指向的指针是指向一个整型数( A pointer to a
pointer to an intege)r
d)一个有 10 个整型数的数组( An array of 10 integers)
e) 一个有 10 个指针的数组,该指针是指向一个整型数的。(An array of 10
pointers to integers)
f) 一个指向有 10 个整型数数组的指针
( A pointer to an array of 10 integers)
g) 一个指向函数的指针,该函数有一个整型参数并返回一个整型数(A pointer
to a function that takes an integer as an argument and returns an integer)
h) 一个有 10 个指针的数组,该指针指向一个函数,该函数有一个整型参数并返
回一个整型数( An array of ten pointers to functions that take an integer
argument and return an integer )答案是:
a) int a; // An integer
b) int *a; // A pointer to an integer
c) int **a; // A pointer to a pointer to an integer
d) int a[10]; // An array of 10 integers
e) int *a[10]; // An array of 10 pointers to integers
f) int (*a)[10]; // A pointer to an array of 10 integers
g) int (*a)(int); // A pointer to a function a that takes an integer
argument and returns an integer
h) int (*a[10])(int); // An array of 10 pointers to functions that take
an integer argument and return an integer
人们经常声称这里有几个问题是那种要翻一下书才能回答的问题,我同意这种说
法。当我写这篇文章时,为了确定语法的正确性,我的确查了一下书。但是当我
被面试的时候,我期望被问到这个问题(或者相近的问题)。因为在被面试的这
段时间里,我确定我知道这个问题的答案。应试者如果不知道所有的答案(或至
少大部分答案),那么也就没有为这次面试做准备,如果该面试者没有为这次面
试做准备,那么他又能为什么出准备呢?
Static
6. 关键字 static 的作用是什么?
这个简单的问题很少有人能回答完全。在 C 语言中,关键字 static 有三个明显
的作用:
1)在函数体,一个被声明为静态的变量在这一函数被调用过程中维持其值不变。
2) 在模块内(但在函数体外),一个被声明为静态的变量可以被模块内所用函
数访问,但不能被模块外其它函数访问。它是一个本地的全局变量。
3) 在模块内,一个被声明为静态的函数只可被这一模块内的其它函数调用。那
就是,这个函数被限制在声明它的模块的本地范围内使用。
大多数应试者能正确回答第一部分,一部分能正确回答第二部分,同是很少的人
能懂得第三部分。这是一个应试者的严重的缺点,因为他显然不懂得本地化数据
和代码范围的好处和重要性。
Const
7.关键字 const 有什么含意?
我只要一听到被面试者说:"const 意味着常数",我就知道我正在和一个业余者
打交道。去年 Dan Saks 已经在他的文章里完全概括了 const 的所有用法,因此
ESP(译者:Embedded Systems Programming)的每一位读者应该非常熟悉 const
能做什么和不能做什么.如果你从没有读到那篇文章,只要能说出 const 意味着"
只读"就可以了。尽管这个答案不是完全的答案,但我接受它作为一个正确的答
案。(如果你想知道更详细的答案,仔细读一下 Saks 的文章吧。)如果应试者能正确回答这个问题,我将问他一个附加的问题:
下面的声明都是什么意思?
const int a;
int const a;
const int *a;
int * const a;
int const * a const;
/******/
前两个的作用是一样,a 是一个常整型数。第三个意味着 a 是一个指向常整型数
的指针(也就是,整型数是不可修改的,但指针可以)。第四个意思 a 是一个指
向整型数的常指针(也就是说,指针指向的整型数是可以修改的,但指针是不可
修改的)。最后一个意味着 a 是一个指向常整型数的常指针(也就是说,指针指
向的整型数是不可修改的,同时指针也是不可修改的)。如果应试者能正确回答
这些问题,那么他就给我留下了一个好印象。顺带提一句,也许你可能会问,即
使不用关键字 const,也还是能很容易写出功能正确的程序,那么我为什么还要
如此看重关键字 const 呢?我也如下的几下理由:
1) 关键字 const 的作用是为给读你代码的人传达非常有用的信息,实际上,声
明一个参数为常量是为了告诉了用户这个参数的应用目的。如果你曾花很多时间
清理其它人留下的垃圾,你就会很快学会感谢这点多余的信息。(当然,懂得用
const 的程序员很少会留下的垃圾让别人来清理的。)
2) 通过给优化器一些附加的信息,使用关键字 const 也许能产生更紧凑的代码。
3) 合理地使用关键字 const 可以使编译器很自然地保护那些不希望被改变的参
数,防止其被无意的代码修改。简而言之,这样可以减少 bug 的出现。
Volatile
8. 关键字 volatile 有什么含意?并给出三个不同的例子。
一个定义为 volatile 的变量是说这变量可能会被意想不到地改变,这样,编译
器就不会去假设这个变量的值了。精确地说就是,优化器在用到这个变量时必须
每次都小心地重新读取这个变量的值,而不是使用保存在寄存器里的备份。
下面是 volatile 变量的几个例子:
1) 并行设备的硬件寄存器(如:状态寄存器)
2) 一个中断服务子程序中会访问到的非自动变量(Non-automatic variables)
3) 多线程应用中被几个任务共享的变量
回答不出这个问题的人是不会被雇佣的。我认为这是区分 C 程序员和嵌入式系统
程序员的最基本的问题。搞嵌入式的家伙们经常同硬件、中断、RTOS 等等打交
道,所有这些都要求用到 volatile 变量。不懂得 volatile 的内容将会带来灾难。
假设被面试者正确地回答了这是问题(嗯,怀疑是否会是这样),我将稍微深究
一下,看一下这家伙是不是直正懂得 volatile 完全的重要性。
1)一个参数既可以是 const 还可以是 volatile 吗?解释为什么。2); 一个指针可以是 volatile 吗?解释为什么。
3); 下面的函数有什么错误:
int square(volatile int *ptr)
{
return *ptr * *ptr;
}
下面是答案:
1)是的。一个例子是只读的状态寄存器。它是 volatile 因为它可能被意想不到
地改变。它是 const 因为程序不应该试图去修改它。
2); 是的。尽管这并不很常见。一个例子是当一个中服务子程序修该一个指向一
个 buffer 的指针时。
3) 这段代码有点变态。这段代码的目的是用来返指针*ptr 指向值的平方,但是,
由于*ptr 指向一个 volatile 型参数,编译器将产生类似下面的代码:
int square(volatile int *ptr)
{
int a,b;
a = *ptr;
b = *ptr;
return a * b;
}
由于*ptr 的值可能被意想不到地该变,因此 a 和 b 可能是不同的。结果,这段
代码可能返不是你所期望的平方值!正确的代码如下:
long square(volatile int *ptr)
{
int a;
a = *ptr;
return a * a;
}
位操作(
位操作 ( Bit manipulation)
manipulation )
9. 嵌入式系统总是要用户对变量或寄存器进行位操作。给定一个整型变量 a,
写两段代码,第一个设置 a 的 bit 3,第二个清除 a 的 bit 3。在以上两个操作
中,要保持其它位不变。
对这个问题有三种基本的反应
1)不知道如何下手。该被面者从没做过任何嵌入式系统的工作。
2) 用 bit fields。Bit fields 是被扔到 C 语言死角的东西,它保证你的代码在
不同编译器之间是不可移植的,同时也保证了的你的代码是不可重用的。我最近
不幸看到 Infineon 为其较复杂的通信芯片写的驱动程序,它用到了 bit fields因此完全对我无用,因为我的编译器用其它的方式来实现 bit fields 的。从道
德讲:永远不要让一个非嵌入式的家伙粘实际硬件的边。
3) 用 #defines 和 bit masks 操作。这是一个有极高可移植性的方法,是应该
被用到的方法。最佳的解决方案如下:
#define BIT3 (0x1 << 3)
static int a;
void set_bit3(void)
{
a |= BIT3;
}
void clear_bit3(void)
{
a &= ~BIT3;
}
一些人喜欢为设置和清除值而定义一个掩码同时定义一些说明常数,这也是可以
接受的。我希望看到几个要点:说明常数、|=和&=~操作。
访问固定的内存位置(
访问固定的内存位置 ( Accessing fixed
fi xed memory locations)
locations )
10. 嵌入式系统经常具有要求程序员去访问某特定的内存位置的特点。在某工程
中,要求设置一绝对地址为 0x67a9 的整型变量的值为 0xaa66。编译器是一个纯
粹的 ANSI 编译器。写代码去完成这一任务。
这一问题测试你是否知道为了访问一绝对地址把一个整型数强制转换
(typecast)为一指针是合法的。这一问题的实现方式随着个人风格不同而不同。
典型的类似代码如下:
int *ptr;
ptr = (int *)0x67a9;
*ptr = 0xaa55;
A more obscure approach is:
一个较晦涩的方法是:
*(int * const)(0x67a9) = 0xaa55;
即使你的品味更接近第二种方案,但我建议你在面试时使用第一种方案。
中断(
中断 ( Interrupts)
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)
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 机程序往往把
硬件作为一个无法避免的烦恼。
到了这个阶段,应试者或者完全垂头丧气了或者信心满满志在必得。如果显然应
试者不是很好,那么这个测试就在这里结束了。但如果显然应试者做得不错,那
么我就扔出下面的追加问题,这些问题是比较难的,我想仅仅非常优秀的应试者
能做得不错。提出这些问题,我希望更多看到应试者应付问题的方法,而不是答
案。不管如何,你就当是这个娱乐吧...
动态内存分配(
动态内存分配 ( Dynamic memory allocation)
allocation )
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"。我用这个来开始讨论这样的一问题,看看被面
试者是否想到库例程这样做是正确。得到正确的答案固然重要,但解决问题的方
法和你做决定的基本原理更重要些。
Typedef
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。
如果你知道答案,或猜出正确答案,做得好。如果你不知道答案,我也不把这个
当作问题。我发现这个问题的最大好处是这是一个关于代码编写风格,代码的可
读性,代码的可修改性的好的话题。
1、将一个字符串逆序
2、将一个链表逆序
3、计算一个字节里(byte)里面有多少bit被置1
4、搜索给定的字节(byte)
5、在一个字符串中找到可能的最长的子字符串
6、字符串转换为整数
7、整数转换为字符串
/*
* 题目:将一个字符串逆序
* 完成时间:2006.9.30深圳极讯网吧
* 版权归刘志强所有
* 描述:写本程序的目的是希望练一下手,希望下午去面试能成功,不希望国庆节之后再去找工作拉!
*/
#include <iostream>
using namespace std;
//#define NULL ((void *)0)
char * mystrrev(char * const dest,const char * const src)
{
if (dest==NULL && src==NULL)
return NULL;
char *addr = dest;
int val_len = strlen(src);
dest[val_len] = '\0';
int i;
for (i=0; i<val_len; i++)
{
*(dest+i) = *(src+val_len-i-1);
}
return addr;
}
main()
{
char *str="asdfa";
char *str1=NULL;
str1 = (char *)malloc(20);
if (str1 == NULL)
cout<<"malloc failed";
cout<<mystrrev(str1,str);
free(str1);
str1=NULL;//杜绝野指针
}
p=head;
q=p->next;
while(q!=NULL)
{
temp=q->next;
q->next=p;
p=q;
q=temp;
}
这样增加个辅助的指针就行乐。
ok 通过编译的代码:
#include <stdio.h>
#include <ctype.h>
#include <stdlib.h>
typedef struct List{
int data;
struct List *next;
}List;
List *list_create(void)
{
struct List *head,*tail,*p;
int e;
head=(List *)malloc(sizeof(List));
tail=head;
printf("\nList Create,input numbers(end of 0):");
scanf("%d",&e);
while(e){
p=(List *)malloc(sizeof(List));
p->data=e;
tail->next=p;
tail=p;
scanf("%d",&e);}
tail->next=NULL;
return head;
}
List *list_reverse(List *head)
{
List *p,*q,*r;
p=head;
q=p->next;
while(q!=NULL)
{
r=q->next;
q->next=p;
p=q;
q=r;
}
head->next=NULL;
head=p;
return head;
}
void main(void)
{
struct List *head,*p;
int d;
head=list_create();
printf("\n");
for(p=head->next;p;p=p->next)
printf("--%d--",p->data);
head=list_reverse(head);
printf("\n");
for(p=head;p->next;p=p->next)
printf("--%d--",p->data);
}
编写函数数N个BYTE的数据中有多少位是1。
解:此题按步骤解:先定位到某一个BYTE数据;再计算其中有多少个1。叠加得解。
#incluede<iostream>
#define N 10
//定义BYTE类型别名
#ifndef BYTE
typedef unsigned char BYTE;
#endif
int comb(BYTE b[],int n)
{
int count=0;
int bi,bj;
BYTE cc=1,tt;
//历遍到第bi个BYTE数据
for(bi=0;bi<n;bi++)
{
//计算该BYTE的8个bit中有多少个1
tt=b[bi];
for(bj=0;bj<8;bj++)
{
//与1相与或模2结果是否是1?测试当前bit是否为1
//if(tt%2==1)
if((tt&cc)==1)
{
count++;
}
//右移一位或除以2,效果相同
//tt=tt>>1;
tt=tt/2;
}
}
return count;
}
//测试
int main()
{
BYTE b[10]={3,3,3,11,1,1,1,1,1,1};
cout<<comb(b,N)<<endl;
return 0;
}
1。编写一个 C 函数,该函数在一个字符串中找到可能的最长的子字符串,且该字符串是由同一字符组成的。
char * search(char *cpSource, char ch)
{
char *cpTemp=NULL, *cpDest=NULL;
int iTemp, iCount=0;
while(*cpSource)
{
if(*cpSource == ch)
{
iTemp = 0;
cpTemp = cpSource;
while(*cpSource == ch)
++iTemp, ++cpSource;
if(iTemp > iCount)
iCount = iTemp, cpDest = cpTemp;
if(!*cpSource)
break;
}
++cpSource;
}
return cpDest;
}
#include <stdio.h>
#include <string.h>
//
// 自定义函数MyAtoI
// 实现整数字符串转换为证书输出
// 程序不检查字符串的正确性,请用户在调用前检查
//
int MyAtoI(char str[])
{
int i;
int weight = 1; // 权重
int rtn = 0; // 用作返回
for(i = strlen(str) - 1; i >= 0; i--)
{
rtn += (str[i] - '0')* weight; //
weight *= 10; // 增重
}
return rtn;
}
void main()
{
char str[32];
printf("Input a string :");
gets(str);
printf("%d\n", MyAtoI(str));
}
#include<stdio.h>
#include<string.h>
void reverse(char s[])
{ //字符串反转
int c, i=0, j;
for(j=strlen(s)-1;i<j;j--)
{ c=s[i];
s[i]=s[j];
s[j]=c;
i++;
}
}
void IntegerToString(char s[],int n)
{ int i=0,sign;
if((sign=n)<0)//如果是负数,先转成正数
n=-n;
do //从个位开始变成字符,直到最高位,最后应该反转
{ s[i++]=n%10+'0';
}while((n=n/10)>0);
//如果是负数,补上负号
if(sign<0)
s[i++]='-';
s[i]='\0';//字符串结束
reverse(s);
}
void main()
{ int m;
char c[100];
printf("请输入整数m: ");
scanf("%d",&m);
IntegerToString(c,m);
printf("integer = %d string = %s\n", m, c);
}
嵌入式软件工程师应知道的0x10个基本问题(经典收藏版)
C语言测试是招聘嵌入式系统程序员过程中必须而且有效的方法。这些年,我既参加也组织了许多这种测试,在这过程中我意识到这些测试能为面试者和被面试者提供许多有用信息,此外,撇开面试的压力不谈,这种测试也是相当有趣的。
从被面试者的角度来讲,你能了解许多关于出题者或监考者的情况。这个测试只是出题者为显示其对ANSI标准细节的知识而不是技术技巧而设计吗?这是个愚蠢的问题吗?如要你答出某个字符的ASCII值。这些问题着重考察你的系统调用和内存分配策略方面的能力吗?这标志着出题者也许花时间在微机上而不是在嵌入式系统上。如果上述任何问题的答案是"是"的话,那么我知道我得认真考虑我是否应该去做这份工作。
从面试者的角度来讲,一个测试也许能从多方面揭示应试者的素质:最基本的,你能了解应试者C语言的水平。不管怎么样,看一下这人如何回答他不会的问题也是满有趣。应试者是以好的直觉做出明智的选择,还是只是瞎蒙呢?当应试者在某个问题上卡住时是找借口呢,还是表现出对问题的真正的好奇心,把这看成学习的机会呢?我发现这些信息与他们的测试成绩一样有用。
有了这些想法,我决定出一些真正针对嵌入式系统的考题,希望这些令人头痛的考题能给正在找工作的人一点帮助。这些问题都是我这些年实际碰到的。其中有些题很难,但它们应该都能给你一点启迪。
这个测试适于不同水平的应试者,大多数初级水平的应试者的成绩会很差,经验丰富的程序员应该有很好的成绩。为了让你能自己决定某些问题的偏好,每个问题没有分配分数,如果选择这些考题为你所用,请自行按你的意思分配分数。
预处理器(Preprocessor)
1 . 用预处理指令#define 声明一个常数,用以表明1年中有多少秒(忽略闰年问题)
#define SECONDS_PER_YEAR (60 * 60 * 24 * 365)UL
我在这想看到几件事情:
1) #define 语法的基本知识(例如:不能以分号结束,括号的使用,等等)
2)懂得预处理器将为你计算常数表达式的值,因此,直接写出你是如何计算一年中有多少秒而不是计算出实际的值,是更清晰而没有代价的。
3) 意识到这个表达式将使一个16位机的整型数溢出-因此要用到长整型符号L,告诉编译器这个常数是的长整型数。
4) 如果你在你的表达式中用到UL(表示无符号长整型),那么你有了一个好的起点。记住,第一印象很重要。
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的目的是什么?
如果你不知道答案,请看参考文献1。这问题对区分一个正常的伙计和一个书呆子是很有用的。只有书呆子才会读C语言课本的附录去找出象这种问题的答案。当然如果你不是在找一个书呆子,那么应试者最好希望自己不要知道答案。
死循环(Infinite loops)
4. 嵌入式系统中经常要用到无限循环,你怎么样用C编写死循环呢?
这个问题用几个解决方案。我首选的方案是:
while(1)
{
}
一些程序员更喜欢如下方案:
for(;;)
{
}
这个实现方式让我为难,因为这个语法没有确切表达到底怎么回事。如果一个应试者给出这个作为方案,我将用这个作为一个机会去探究他们这样做的基本原理。如果他们的基本答案是:"我被教着这样做,但从没有想到过为什么。"这会给我留下一个坏印象。
第三个方案是用 goto
Loop:
...
goto Loop;
应试者如给出上面的方案,这说明或者他是一个汇编语言程序员(这也许是好事)或者他是一个想进入新领域的BASIC/FORTRAN程序员。
数据声明(Data declarations)
5. 用变量a给出下面的定义
a) 一个整型数(An integer)
b)一个指向整型数的指针( A pointer to an integer)
c)一个指向指针的的指针,它指向的指针是指向一个整型数( A pointer to a pointer to an intege)r
d)一个有10个整型数的数组( An array of 10 integers)
e) 一个有10个指针的数组,该指针是指向一个整型数的。(An array of 10 pointers to integers)
f) 一个指向有10个整型数数组的指针( A pointer to an array of 10 integers)
g) 一个指向函数的指针,该函数有一个整型参数并返回一个整型数(A pointer to a function that takes an integer as an argument and returns an integer)
h) 一个有10个指针的数组,该指针指向一个函数,该函数有一个整型参数并返回一个整型数( An array of ten pointers to functions that take an integer argument and return an integer )
答案是:
a) int a; // An integer
b) int *a; // A pointer to an integer
c) int **a; // A pointer to a pointer to an integer
d) int a[10]; // An array of 10 integers
e) int *a[10]; // An array of 10 pointers to integers
f) int (*a)[10]; // A pointer to an array of 10 integers
g) int (*a)(int); // A pointer to a function a that takes an integer argument and returns an integer
h) int (*a[10])(int); // An array of 10 pointers to functions that take an integer argument and return an integer
人们经常声称这里有几个问题是那种要翻一下书才能回答的问题,我同意这种说法。当我写这篇文章时,为了确定语法的正确性,我的确查了一下书。但是当我被面试的时候,我期望被问到这个问题(或者相近的问题)。因为在被面试的这段时间里,我确定我知道这个问题的答案。应试者如果不知道所有的答案(或至少大部分答案),那么也就没有为这次面试做准备,如果该面试者没有为这次面试做准备,那么他又能为什么出准备呢?
Static
6. 关键字static的作用是什么?
这个简单的问题很少有人能回答完全。在C语言中,关键字static有三个明显的作用:
1)在函数体,一个被声明为静态的变量在这一函数被调用过程中维持其值不变。
2) 在模块内(但在函数体外),一个被声明为静态的变量可以被模块内所用函数访问,但不能被模块外其它函数访问。它是一个本地的全局变量。
3) 在模块内,一个被声明为静态的函数只可被这一模块内的其它函数调用。那就是,这个函数被限制在声明它的模块的本地范围内使用。
大多数应试者能正确回答第一部分,一部分能正确回答第二部分,同是很少的人能懂得第三部分。这是一个应试者的严重的缺点,因为他显然不懂得本地化数据和代码范围的好处和重要性。
Const
7.关键字const有什么含意?
我只要一听到被面试者说:"const意味着常数",我就知道我正在和一个业余者打交道。去年Dan Saks已经在他的文章里完全概括了const的所有用法,因此ESP(译者:Embedded Systems Programming)的每一位读者应该非常熟悉const能做什么和不能做什么.如果你从没有读到那篇文章,只要能说出const意味着"只读"就可以了。尽管这个答案不是完全的答案,但我接受它作为一个正确的答案。(如果你想知道更详细的答案,仔细读一下Saks的文章吧。)
如果应试者能正确回答这个问题,我将问他一个附加的问题:
下面的声明都是什么意思?
const int a;
int const a;
const int *a;
int * const a;
int const * a const;
/******/
前两个的作用是一样,a是一个常整型数。第三个意味着a是一个指向常整型数的指针(也就是,整型数是不可修改的,但指针可以)。第四个意思a是一个指向整型数的常指针(也就是说,指针指向的整型数是可以修改的,但指针是不可修改的)。最后一个意味着a是一个指向常整型数的常指针(也就是说,指针指向的整型数是不可修改的,同时指针也是不可修改的)。如果应试者能正确回答这些问题,那么他就给我留下了一个好印象。顺带提一句,也许你可能会问,即使不用关键字 const,也还是能很容易写出功能正确的程序,那么我为什么还要如此看重关键字const呢?我也如下的几下理由:
1) 关键字const的作用是为给读你代码的人传达非常有用的信息,实际上,声明一个参数为常量是为了告诉了用户这个参数的应用目的。如果你曾花很多时间清理其它人留下的垃圾,你就会很快学会感谢这点多余的信息。(当然,懂得用const的程序员很少会留下的垃圾让别人来清理的。)
2) 通过给优化器一些附加的信息,使用关键字const也许能产生更紧凑的代码。
3) 合理地使用关键字const可以使编译器很自然地保护那些不希望被改变的参数,防止其被无意的代码修改。简而言之,这样可以减少bug的出现。
Volatile
8. 关键字volatile有什么含意?并给出三个不同的例子。
一个定义为volatile的变量是说这变量可能会被意想不到地改变,这样,编译器就不会去假设这个变量的值了。精确地说就是,优化器在用到这个变量时必须每次都小心地重新读取这个变量的值,而不是使用保存在寄存器里的备份。下面是volatile变量的几个例子:
1) 并行设备的硬件寄存器(如:状态寄存器)
2) 一个中断服务子程序中会访问到的非自动变量(Non-automatic variables)
3) 多线程应用中被几个任务共享的变量
回答不出这个问题的人是不会被雇佣的。我认为这是区分C程序员和嵌入式系统程序员的最基本的问题。搞嵌入式的家伙们经常同硬件、中断、RTOS等等打交道,所有这些都要求用到volatile变量。不懂得volatile的内容将会带来灾难。
假设被面试者正确地回答了这是问题(嗯,怀疑是否会是这样),我将稍微深究一下,看一下这家伙是不是直正懂得volatile完全的重要性。
1)一个参数既可以是const还可以是volatile吗?解释为什么。
2); 一个指针可以是volatile 吗?解释为什么。
3); 下面的函数有什么错误:
int square(volatile int *ptr)
{
return *ptr * *ptr;
}
下面是答案:
1)是的。一个例子是只读的状态寄存器。它是volatile因为它可能被意想不到地改变。它是const因为程序不应该试图去修改它。
2); 是的。尽管这并不很常见。一个例子是当一个中服务子程序修该一个指向一个buffer的指针时。
3) 这段代码有点变态。这段代码的目的是用来返指针*ptr指向值的平方,但是,由于*ptr指向一个volatile型参数,编译器将产生类似下面的代码:
int square(volatile int *ptr)
{
int a,b;
a = *ptr;
b = *ptr;
return a * b;
}
由于*ptr的值可能被意想不到地该变,因此a和b可能是不同的。结果,这段代码可能返不是你所期望的平方值!正确的代码如下:
long square(volatile int *ptr)
{
int a;
a = *ptr;
return a * a;
}
位操作(Bit manipulation)
9. 嵌入式系统总是要用户对变量或寄存器进行位操作。给定一个整型变量a,写两段代码,第一个设置a的bit 3,第二个清除a 的bit 3。在以上两个操作中,要保持其它位不变。
对这个问题有三种基本的反应
1)不知道如何下手。该被面者从没做过任何嵌入式系统的工作。
2) 用bit fields。Bit fields是被扔到C语言死角的东西,它保证你的代码在不同编译器之间是不可移植的,同时也保证了的你的代码是不可重用的。我最近不幸看到 Infineon为其较复杂的通信芯片写的驱动程序,它用到了bit fields因此完全对我无用,因为我的编译器用其它的方式来实现bit fields的。从道德讲:永远不要让一个非嵌入式的家伙粘实际硬件的边。
3) 用 #defines 和 bit masks 操作。这是一个有极高可移植性的方法,是应该被用到的方法。最佳的解决方案如下:
#define BIT3 (0x1 << 3)
static int a;
void set_bit3(void)
{
a |= BIT3;
}
void clear_bit3(void)
{
a &= ~BIT3;
}
一些人喜欢为设置和清除值而定义一个掩码同时定义一些说明常数,这也是可以接受的。我希望看到几个要点:说明常数、|=和&=~操作。
访问固定的内存位置(Accessing fixed memory locations)
10. 嵌入式系统经常具有要求程序员去访问某特定的内存位置的特点。在某工程中,要求设置一绝对地址为0x67a9的整型变量的值为0xaa66。编译器是一个纯粹的ANSI编译器。写代码去完成这一任务。
这一问题测试你是否知道为了访问一绝对地址把一个整型数强制转换(typecast)为一指针是合法的。这一问题的实现方式随着个人风格不同而不同。典型的类似代码如下:
int *ptr;
ptr = (int *)0x67a9;
*ptr = 0xaa55;
A more obscure approach is:
一个较晦涩的方法是:
*(int * const)(0x67a9) = 0xaa55;
即使你的品味更接近第二种方案,但我建议你在面试时使用第一种方案。
中断(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;
/*1s complement of zero */
对于一个int型不是16位的处理器为说,上面的代码是不正确的。应编写如下:
unsigned int compzero = ~0;
这一问题真正能揭露出应试者是否懂得处理器字长的重要性。在我的经验里,好的嵌入式程序员非常准确地明白硬件的细节和它的局限,然而PC机程序往往把硬件作为一个无法避免的烦恼。
到了这个阶段,应试者或者完全垂头丧气了或者信心满满志在必得。如果显然应试者不是很好,那么这个测试就在这里结束了。但如果显然应试者做得不错,那么我就扔出下面的追加问题,这些问题是比较难的,我想仅仅非常优秀的应试者能做得不错。提出这些问题,我希望更多看到应试者应付问题的方法,而不是答案。不管如何,你就当是这个娱乐吧...
动态内存分配(Dynamic memory allocation)
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"。我用这个来开始讨论这样的一问题,看看被面试者是否想到库例程这样做是正确。得到正确的答案固然重要,但解决问题的方法和你做决定的基本原理更重要些。
Typedef
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。
如果你知道答案,或猜出正确答案,做得好。如果你不知道答案,我也不把这个当作问题。我发现这个问题的最大好处是这是一个关于代码编写风格,代码的可读性,代码的可修改性的好的话题。
好了,伙计们,你现在已经做完所有的测试了。这就是我出的C语言测试题,我怀着愉快的心情写完它,希望你以同样的心情读完它。如果是认为这是一个好的测试,那么尽量都用到你的找工作的过程中去吧。天知道也许过个一两年,我就不做现在的工作,也需要找一个。
作者介绍:
Nigel Jones 是一个顾问,现在住在Maryland,当他不在水下时,你能在多个范围的嵌入项目中找到他。 他很高兴能收到读者的来信,他的email地址是: NAJones@compuserve.com
参考文献
1) Jones, Nigel, "In Praise of the #error directive," Embedded Systems Programming, September 1999, p. 114.
2) Jones, Nigel, " Efficient C Code for Eight-bit MCUs ," Embedded Systems Programming, November 1998, p. 66.
考查嵌入式C开发人员的最好的0x10道题
考查一个初级嵌入式系统开发人员的C基本功,附有答案题目由资深嵌入式系统专家拟定,目的是考查入门级的嵌入式软件开发人员 Gavin Shaw提供详细解答
编者按:非常基本关于C语言的问题,一个信息类(计算机,资讯工程,电子工程, 通信工程)专业的本科毕业生应该达到的水平。题目不难,全部都能快速地答完,当然也需要一定的知识储备。
对于大多数人,我们预期你可能答错 3) 4) 15)题,所以答错3道以内的,我们认为你很棒
答错5道题以内,我们认为你还不错(你还可能答错第9题)
如果你有6道以上的题目不能答对,基本上我们都不好说什么了....
约定:
1) 下面的测试题中,认为所有必须的头文件都已经正确的包含了
2)数据类型
char 一个字节 1 byte
int 两个字节 2 byte (16位系统,认为整型是2个字节)
long int 四个字节 4 byte
float 四个字节4 byet
double 八个字节 8 byte
long double 十个字节 10 byte
pointer 两个字节 2 byte(注意,16位系统,地址总线只有16位)
第1题: 考查对volatile关键字的认识
#include<setjmp.h>
static jmp_buf buf;
main()
{
volatile int b;
b =3;
if(setjmp(buf)!=0)
{
printf("%d ", b);
exit(0);
}
b=5;
longjmp(buf , 1);
}
请问,这段程序的输出是
(a) 3
(b) 5
(c) 0
(d) 以上均不是
第2题:考查类型转换
main()
{
struct node
{
int a;
int b;
int c;
};
struct node s= { 3, 5,6 };
struct node *pt = &s;
printf("%d" , *(int*)pt);
}
这段程序的输出是:
(a) 3
(b) 5
(c) 6
(d) 7
//----
第4题:考查指针,这道题只适合于那些特别细心且对指针和数组有深入理解的人
main()
{
int a[5] = {1,2,3,4,5};
int *ptr = (int*)(&a+1);
printf("%d %d" , *(a+1), *(ptr-1) );
}
这段程序的输出是:
(a) 2 2
(b) 2 1
(c) 2 5
(d) 以上均不是
第5题:考查多维数组与指针
void foo(int [][3] );
main()
{
int a [3][3]= { { 1,2,3} , { 4,5,6},{7,8,9}};
foo(a);
printf("%d" , a[2][1]);
}
void foo( int b[][3])
{
++ b;
b[1][1] =9;
}
这段程序的输出是:
(a) 8
(b) 9
(c) 7
(d)以上均不对
第6题目:考查逗号表达式
main()
{
int a, b,c, d;
a=3;
b=5;
c=a,b;
d=(a,b);
printf("c=%d" ,c);
printf("d=%d" ,d);
}
这段程序的输出是:
(a) c=3 d=3
(b) c=5 d=3
(c) c=3 d=5
(d) c=5 d=5
第7题:考查指针数组
main()
{
int a[][3] = { 1,2,3 ,4,5,6};
int (*ptr)[3] =a;
printf("%d %d " ,(*ptr)[1], (*ptr)[2] );
++ptr;
printf("%d %d" ,(*ptr)[1], (*ptr)[2] );
}
这段程序的输出是:
(a) 2 3 5 6
(b) 2 3 4 5
(c) 4 5 0 0
(d) 以上均不对
第8题:考查函数指针
int *f1(void)
{
int x =10;
return(&x);
}
int *f2(void)
{
int*ptr;
*ptr =10;
return ptr;
}
int *f3(void)
{
int *ptr;
ptr=(int*) malloc(sizeof(int));
return ptr;
}
上面这3个函数哪一个最可能引起指针方面的问题
(a) 只有 f3
(b) 只有f1 and f3
(c) 只有f1 and f2
(d) f1 , f2 ,f3
第9题:考查自加操作(++)
main()
{
int i=3;
int j;
j = sizeof(++i+ ++i);
printf("i=%d j=%d", i ,j);
}
这段程序的输出是:
(a) i=4 j=2
(b) i=3 j=2
(c) i=3 j=4
(d) i=3 j=6
第10题:考查形式参数,实际参数,指针和数组
void f1(int *, int);
void f2(int *, int);
void(*p[2]) ( int *, int);
main()
{
int a;
int b;
p[0] = f1;
p[1] = f2;
a=3;
b=5;
p[0](&a , b);
printf("%d\t %d\t" , a ,b);
p[1](&a , b);
printf("%d\t %d\t" , a ,b);
}
void f1( int* p , int q)
{
int tmp;
tmp =*p;
*p = q;
q= tmp;
}
void f2( int* p , int q)
{
int tmp;
tmp =*p;
*p = q;
q= tmp;
}
这段程序的输出是:
(a) 5 5 5 5
(b) 3 5 3 5
(c) 5 3 5 3
(d) 3 3 3 3
第11题:考查自减操作(--)
void e(int );
main()
{
int a;
a=3;
e(a);
}
void e(int n)
{
if(n>0)
{
e(--n);
printf("%d" , n);
e(--n);
}
}
这段程序的输出是:
(a) 0 1 2 0
(b) 0 1 2 1
(c) 1 2 0 1
(d) 0 2 1 1
第12题:考查typedef类型定义,函数指针
typedef int (*test) ( float * , float*)
test tmp;
tmp 的类型是
(a) 函数的指针,该函数以 两个指向浮点数(float)的指针(pointer)作为参数(arguments)
Pointer to function of having two arguments that is pointer to float
(b) 整型
(c) 函数的指针,该函数以 两个指向浮点数(float)的指针(pointer)作为参数(arguments),并且函数的返回值类型是整型
Pointer to function having two argument that is pointer to float and return int
(d) 以上都不是
第13题:数组与指针的区别与联系
main()
{
char p;
char buf[10] ={ 1,2,3,4,5,6,9,8};
p = (buf+1)[5];
printf("%d" , p);
}
这段程序的输出是:
(a) 5
(b) 6
(c) 9
(d) 以上都不对
第14题: 考查指针数组的指针
Void f(char**);
main()
{
char * argv[] = { "ab" ,"cd" , "ef" ,"gh", "ij" ,"kl" };
f( argv );
}
void f( char **p )
{
char* t;
t= (p+= sizeof(int))[-1];
printf( "%s" , t);
}
这段程序的输出是:
(a) ab
(b) cd
(c) ef
(d) gh
第15题:此题考查的是C的变长参数,就像标准函数库里printf()那样,这个话题一般国内大学课堂是不会讲到的,不会也情有可原呵呵,
#include<stdarg.h>
int ripple ( int , ...);
main()
{
int num;
num = ripple ( 3, 5,7);
printf( " %d" , num);
}
int ripple (int n, ...)
{
int i , j;
int k;
va_list p;
k= 0;
j = 1;
va_start( p , n);
for (; j<n; ++j)
{
i = va_arg( p , int);
for (; i; i &=i-1 )
++k;
}
return k;
}
这段程序的输出是:
(a) 7
(b) 6
(c) 5
(d) 3
第16题:考查静态变量的知识
int counter (int i)
{
static int count =0;
count = count +i;
return (count );
}
main()
{
int i , j;
for (i=0; i <=5; i++)
j = counter(i);
}
本程序执行到最后,j的值是:
(a) 10
(b) 15
(c) 6
(d) 7
详细参考答案
第1题: (b)
volatile字面意思是易于挥发的。这个关键字来描述一个变量时,意味着 给该变量赋值(写入)之后,马上再读取,写入的值与读取的值可能不一样,所以说它"容易挥发"的。
这是因为这个变量可能一个寄存器,直接与外部设备相连,你写入之后,该寄存器也有可能被外部设备的写操作所改变;或者,该变量被一个中断程序,或另一个进程改变了.
volatile 不会被编译器优化影响,在longjump 后,它的值 是后面假定的变量值,b最后的值是5,所以5被打印出来.
setjmp : 设置非局部跳转 /* setjmp.h*/
Stores context information such as register values so that the lomgjmp function can return control to the statement following the one calling setjmp.Returns 0 when it is initially called.
Lonjjmp: 执行一个非局部跳转 /* setjmp.h*/
Transfers control to the statement where the call to setjmp (which initialized buf) was made. Execution continues at this point as if longjmp cannot return the value 0.A nonvolatile automatic variable might be changed by a call to longjmp.When you use setjmp and longjmp, the only automatic variables guaranteed to remain valid are those declared volatile.
Note: Test program without volatile qualifier (result may very)
更详细介绍,请参阅 C语言的setjmp和longjmp
第2题: (a)
结构题的成员在内存中的地址是按照他们定义的位置顺序依次增长的。如果一个结构体的指针被看成 它的第一个成员的指针,那么该指针的确指向第一个成员
第3题: (a)
此题目较难.
这个程序的非递归版本
int what ( int x , int n)
{
int val;
int product;
product =1;
val =x;
while(n>0)
{
if (n%2 == 1)
product = product*val; /*如果是奇数次幂, x(val)
要先乘上一次,;
偶数次幂,最后返回时才会到这里
乘以1*/
val = val* val;
n = n/2;
}
return product;
}
/* 用二元复乘策略 */
算法描述
(while n>0)
{
if next most significant binary digit of n( power) is one
then multiply accumulated product by current val ,
reduce n(power) sequence by a factor of two using integer division .
get next val by multiply current value of itself
}
第4题: (c)
a的类型是一个整型数组,它有5个成员
&a的类型是一个整型数组的指针
所以&a + 1指向的地方等同于 a[6]
所以*(a+1) 等同于a[1]
ptr等同 a[6], ptr-1就等同与a[5]
第5题: (b)
题目自身就给了足够的提示
b[0][0] = 4
b[1][0] = 7
第6题: (c)
考查逗号表达式,逗号表达式的优先级是很低的,比 赋值(=)的优先级 低. 逗号表达式的值就是最后一个元素的值
逗号表达式的还有一个作用就是分割函数的参数列表..
E1, E2, ..., En
上面这个表示式的左右是,E1, E2,... En的值被分别计算出来,En计算出来的结构赋给整个逗号表达式
c=a,b; / *yields c=a* /
d=(a,b); /* d =b */
第7题: (a)
ptr是一个数组的指针,该数组有3个int成员
第8题: (c)
f1显然有问题,它返回一个局部变量的指针,局部变量是保存在stack中的,退出函数后,局部变量就销毁了,保留其指针没有意义,因为其指向的stack空间可能被其他变量覆盖了
f2也有问题, ptr是局部变量,未初始化,它的值是未知的,*ptr不知道指向哪里了,直接给*ptr赋值可能会覆盖重要的系统变量,这就是通常说的野指针的一种
第9题: (b)
sizeof 操作符给出其操作数需要占用的空间大小,它是在编译时就可确定的,所以其操作数即使是一个表达式,也不需要在运行时进行计算.( ++i + ++ i )是不会执行的,所以
i 的值还是3
第10题: (a)
很显然选a.
f1交换*p 和 q的值,f1执行完后, *p 和 q的值的确交换了, 但 q的改变不会影响到 b的改变, *p 实际上就是 a
所以执行f1后, a=b=5
这道题考查的知识范围很广,包括typedef自定义类型,函数指针,指针数组
void(*p[ 2 ]) ( int *, int);
定义了一个函数指针的数组p,p有两个指针元素. 元素是函数的指针,函数指针指向的函数是一个带2个参数,返回void的函数,所带的两个参数是 指向整型的指针,和整型
p[ 0 ] = f1; p[ 1 ] = f2 contain address of function .function name without parenthesis represent address of function Value and address of variable is passed to function only argument that is effected is a (address is passed). Because of call by value f1, f2 can not effect b
第11题: (a)
考查--操作和递归调用,仔细分析一下就可以了
第12题: (c)
分析函数声明,建议不会的看看《C专家编程》
这里介绍一个简单规则:从左往右,遇到括号停下来,将第一个括号里的东西看成一个整体
第13题: (c)
考查什么时候数组就是指针.对某些类型T而言,如果一个表达式是 T[] (T的数组), 这个表达式的值实际上就是指向该数组的第一个元素的指针.所以(buf+1)[5]实际上就是*(buf +6)或者buf[6]
第14题: (b)
sizeof(int)的值是2,所以p+=sizeof(int) 指向 argv[2],这点估计大家都没有什么疑问
(p+=sizeof(int))[-1] 指向 argv[1],能理解吗,因为(p+=sizeof(int))[-1] 就相当于 (p+=2)[-1] ,也就是(p+2-1)
第15题: (c)
在C编译器通常提供了一系列处理可变参数的宏,以屏蔽不同的硬件平台造成的差异,增加程序的可移植性。这些宏包括va_start、 va_arg和va_end等。
采用ANSI标准形式时,参数个数可变的函数的原型声明是:
type funcname(type para1, type para2, ...)
这种形式至少需要一个普通的形式参数,后面的省略号不表示省略,而是函数原型的一部分。type是函数返回值和形式参数的类型。
不同的编译器,对这个可变长参数的实现不一样 ,gcc4.x中是内置函数.
关于可变长参数,可参阅
http://www.upsdn.net/html/2004-11/26.html
http://www.upsdn.net/html/2004-11/24.html
程序分析
va_list p; /*定义一个变量 ,保存 函数参数列表 的指针*/
va_start( p , n); /*用va_start宏 初始化 变量p,
va_start宏的第2个参数n ,
是一个固定的参数,
必须是我们自己定义的变长函数的最后一个入栈的参数
也就是调用的时候参数列表里的第1个参数*/
for (; j<n; ++j) /* j从1开始, 遍历所有可变参数 */
{
i = va_arg( p , int); /*va_arg取出当前的参数,
并认为取出的参数是一个整数(int) */
for (; i; i &=i-1 ) /*判断取出的i是否为0*/
++k; /* 如果i不为0, k自加,
i与i-1进行与逻辑运算, 直到i 为0
这是一个技巧,下面会谈到它的功能*/
}
当我们调用ripple函数时,传递给ripple函数的 参数列表的第一个参数n的值是3 .
va_start 初始化 p士气指向第一个未命名的参数(n是有名字的参数) ,也就是 is 5 (第一个).
每次对 va_arg的调用,都将返回一个参数,并且把 p 指向下一个参数.
va_arg 用一个类型名来决定返回的参数是何种类型,以及在 var_arg的内部实现中决定移动多大的距离才到达下一个 参数
(; i; i&=i-1) k++ /* 计算i有多少bit被置1 */
5用二进制表示是 (101) 2
7用二进制表示 (111) 3
所以 k 返回 5(2+3),也即本题应该选c
举个例子,就很好理解了
令 i= 9 = 1001
i-1 = 1000
(i-1) +1 = i
1000
+1
1 001
因为i与i-1的最右边的那位(最低位) 肯定是不同,如果i1,i-1肯定是0,反之亦然. i & i-1 这个运算,在二相补的数字系统中,将会 消除最右边的1位
第16题: (b)
答案是 (b)
相传高斯小学一年级的时候就会做这类等比数列的题目了.这道题考查的是静态变量的知识,当每次调用完函数之后,静态变量的值不会丢失,这与栈中的临时局部变量明显不同的地方.
所以,第一次调用counter(0)之后,count =0
第二次调用 counter(1)后 count = 0+1;
第三次调用 counter(2) count = 1+2; /* count = count +i */
第四次调用 counter(3) count = 3+3;
第五次调用 counter(4) count = 6+4;
第六次调用 counter(5) count = 10+5;
命题人信息 Ashok K. Pathak a member ( Research Staff ) at Bharat Electronics Limited (CRL) , Ghaziabad. He has been developing embedded application for the past five years .Ashok holds a M.E in computer science and engineering . Ashok recently completed a book about' "Advanced Test in C and Embedded System Programming" , Published by BPB , ND .
选择题
1:设float a=2, b=4, c=3;,以下C语言表达式与代数式 (a+b)+c计算结果不一致的是
A.(a+b)*c/2
B.(1/2)*(a+b)*c
C.(a+b)*c*1/2
D.c/2*(a+b)
参考答案:B,因为a,b,c三个变量都是浮点数,所以在B答案中其结果是0,因为在计算1/2是就是0,如果改成1/2.0就正确了。
2:为了向二进制文件尾部增加数据,打开文件的方式应采用
A.″ab″
B.″rb+″
C.″wb″
D.″wb+″
参考答案:D
3:下述程序执行后的输出结果是
#include
main()
{
int x='f';
printf("%c\n",'a'+(x-'a'+1));
}
A.g
B.h
C.i
D.j
参考答案:A
4:C语言中,下列运算符优先级最高的是
A.!
B.%
C.>>
D.= =
参考答案:A
5:数组定义为“ int a [ 4 ] ; ”,表达式 ( ) 是错误的。
A.*a
B.a [ 0 ]
C.a
D.a++
参考答案:D
6:执行语句“ k=7>>1; ”后,变量 k 的当前值是
A.15
B.31
C.3
D.1
参考答案:C
7:定义函数时,缺省函数的类型声明,则函数类型取缺省类型
A.void
B.char
C.float
D.int
参考答案:D
8:若main()函数带参数,参数个数最多是
A.0
B.1
C.2
D.3
参考答案:C 只知道有定义形式main(int argc,char* argv[]))
9:若有宏定义:#define MOD(x,y) x%y
则执行以下语句后的输出结果是
int a=13,b=94;
printf(″%d\n″,MOD(b,a+4));
A.5
B.7
C.9
D.11
参考答案:B
10:下列各个错误中,哪一个不属于编译错误
A.改变 x 原值 3 为 5 ,写作“ x==5 ;”
B.花括号不配对
C.复合语句中的最后一条语句后未加分号
D.变量有引用、无定义
参考答案:A
11:下列程序段运行后, x 的值是( )
a=1;b=2;x=0;
if(!( -- a))x -- ;
if(!b)x=7;else ++x;
A.0
B.3
C.6
D.7
参考答案:A
12:设
#define N 3
#define Y(n) ((N+1)*n)
则表达式2*(N+Y(5+1))的值是
A.42
B.48
C.54
D.出错
参考答案:B Y(5+1) 传递过去的应该是6,而不是简单的把5+1给替换掉
13:若定义了char ch[]={″abc\0def″},*p=ch;则执行printf(″%c″,*p+4);语句的输出结果是
A.def
B.d
C.e
D.0
参考答案:C
14:下列转义字符中错误的是
A.′\000′
B.′\14′
C.′\x111′
D.′\2′
参考答案:C error C2022: '273' : too big for character
15:算术运算符,赋值运算符和关系运算符的运算优先级按从高到低依次为
A.算术运算、赋值运算、关系运算
B.算术运算、关系运算、赋值运算
C.关系运算、赋值运算、算术运算
D.关系运算、算术运算、赋值运算
参考答案:B
16:设#define N 3
#define Y(n) ((N+1)*n)
则表达式2*(N+Y(5+1))的值是
A.42
B.48
C.54
D.出错
参考答案:B
17:表达式 strcmp( “ 3.14 ”,“ 3.278 ” ) 的值是一个
A.非零整数
B.浮点数
C.0
D.字符
参考答案: A
18:设struct
{ short a;
char b;
float c;
}cs;
则sizeof(cs)的值是
A.4
B.5
C.6
D.7
参考答案: D 字节对齐的话应该是8
19:若变量已正确定义,表达式( j=3 , j++ )的值是
A.3
B.4
C.5
D.0
参考答案:A
20:C 语言中运算对象必须是整型的运算符是
A.%
B./
C.!
D.**
参考答案:A
简答题
21:打印一个N*N的方阵,N为每边字符的个数( 3〈N〈20 ),要求最外层为“X”,第二层为“Y”,从第三层起每层依次打印数字0,1,2,3,...
例子:当N =5,打印出下面的图形:
X X X X X
X Y Y Y X
X Y 0 Y X
X Y Y Y X
X X X X X
22:谈谈COM的线程模型。然后讨论进程内/外组件的差别。
23:多态类中的虚函数表是Compile-Time,还是Run-Time时建立的?
参考答案:
虚拟函数表是在编译期就建立了,各个虚拟函数这时被组织成了一个虚拟函数的入口地址的数组.
而对象的隐藏成员--虚拟函数表指针是在运行期--也就是构造函数被调用时进行初始化的,这是实现多态的关键.
24:#include
void main()
{ int c;
while ((c=getchar())!= ′\ n ′ )
switch(c - ′ 2 ′ )
{ case 0:
case 1:putchar(c+4);break;
case 2:putchar(c+4);break;
case 3:putchar(c+3);break;
default:putchar(c+2);break;
}
printf( ″\ n ″ );
}
运行时输入: 2473 ,输出结果:
参考答案:6897 VC++6.0测试过
25:用两个栈实现一个队列的功能?
参考答案:
设2个栈为A,B, 一开始均为空.入队:
将新元素push入栈A;出队:
(1)判断栈B是否为空;
(2)如果不为空,则将栈A中所有元素依次pop出并push到栈B;
(3)将栈B的栈顶元素pop出;这样实现的队列入队和出队的平摊复杂度都还是O(1),
26:写一语句实现x是否为2的若干次幂的判断。
参考答案:!(X)&(X-1)
27:解二次方程:a*x*x+b*x+c
int Quadratic( double a,double b,double c,double& x1,double& x2);
返回值:解的个数
28:如何判别一个数是unsigned。
参考答案;
#define issignal(x) ((x>=0 && ~x>=0) ? 1:0) //为1是无符号 为0有符号
29:执行下述程序的输出结果是 ABCDEFCDEFEF 。
#include
char b[]="ABCDEF";
main()
{
char *bp;
for (bp=b;*bp;bp+=2)
printf("%s",bp);
printf("\n");
}
30:已知一个数组table,用一个宏定义,求出数据的元素个数。
参考答案:#define NTBL
#define NTBL (sizeof(table)/sizeof(table[0]))
嵌入式软件工程师笔试题(含答案).txt∞-一人行,必会发情 二人行,必会激情 三人行,必有奸情 就不会被珍惜。真实的女孩不完美,完美的女孩不真实。得之坦然,失之淡然,顺其自然,争其必然。选择题
1:设float a=2, b=4, c=3;,以下C语言表达式与代数式 (a+b)+c计算结果不一致的是
A.(a+b)*c/2
B.(1/2)*(a+b)*c
C.(a+b)*c*1/2
D.c/2*(a+b)
参考答案:B,因为a,b,c三个变量都是浮点数,所以在B答案中其结果是0,因为在计算1/2是就是0,如果改成1/2.0就正确了。
2:为了向二进制文件尾部增加数据,打开文件的方式应采用
A.″ab″
B.″rb+″
C.″wb″
D.″wb+″
参考答案:D
3:下述程序执行后的输出结果是
#include
main()
{
int x='f';
printf("%c\n",'a'+(x-'a'+1));
}
A.g
B.h
C.i
D.j
参考答案:A
4:C语言中,下列运算符优先级最高的是
A.!
B.%
C.>>
D.= =
参考答案:A
5:数组定义为“ int a [ 4 ] ; ”,表达式 ( ) 是错误的。
A.*a
B.a [ 0 ]
C.a
D.a++
参考答案:D
6:执行语句“ k=7>>1; ”后,变量 k 的当前值是
A.15
B.31
C.3
D.1
参考答案:C
7:定义函数时,缺省函数的类型声明,则函数类型取缺省类型
A.void
B.char
C.float
D.int
参考答案:D
8:若main()函数带参数,参数个数最多是
A.0
B.1
C.2
D.3
参考答案:C 只知道有定义形式main(int argc,char* argv[]))
9:若有宏定义:#define MOD(x,y) x%y
则执行以下语句后的输出结果是
int a=13,b=94;
printf(″%d\n″,MOD(b,a+4));
A.5
B.7
C.9
D.11
参考答案:B
10:下列各个错误中,哪一个不属于编译错误
A.改变 x 原值 3 为 5 ,写作“ x==5 ;”
B.花括号不配对
C.复合语句中的最后一条语句后未加分号
D.变量有引用、无定义
参考答案:A
11:下列程序段运行后, x 的值是( )
a=1;b=2;x=0;
if(!( -- a))x -- ;
if(!b)x=7;else ++x;
A.0
B.3
C.6
D.7
参考答案:A
12:设
#define N 3
#define Y(n) ((N+1)*n)
则表达式2*(N+Y(5+1))的值是
A.42
B.48
C.54
D.出错
参考答案:B Y(5+1) 传递过去的应该是6,而不是简单的把5+1给替换掉
13:若定义了char ch[]={″abc\0def″},*p=ch;则执行printf(″%c″,*p+4);语句的输出结果是
A.def
B.d
C.e
D.0
参考答案:C
14:下列转义字符中错误的是
A.′\000′
B.′\14′
C.′\x111′
D.′\2′
参考答案:C error C2022: '273' : too big for character
15:算术运算符,赋值运算符和关系运算符的运算优先级按从高到低依次为
A.算术运算、赋值运算、关系运算
B.算术运算、关系运算、赋值运算
C.关系运算、赋值运算、算术运算
D.关系运算、算术运算、赋值运算
参考答案:B
16:设#define N 3
#define Y(n) ((N+1)*n)
则表达式2*(N+Y(5+1))的值是
A.42
B.48
C.54
D.出错
参考答案:B
17:表达式 strcmp( “ 3.14 ”,“ 3.278 ” ) 的值是一个
A.非零整数
B.浮点数
C.0
D.字符
参考答案: A
18:设struct
{ short a;
char b;
float c;
}cs;
则sizeof(cs)的值是
A.4
B.5
C.6
D.7
参考答案: D 字节对齐的话应该是8
19:若变量已正确定义,表达式( j=3 , j++ )的值是
A.3
B.4
C.5
D.0
参考答案:A
20:C 语言中运算对象必须是整型的运算符是
A.%
B./
C.!
D.**
参考答案:A
简答题
21:打印一个N*N的方阵,N为每边字符的个数( 3〈N〈20 ),要求最外层为“X”,第二层为“Y”,从第三层起每层依次打印数字0,1,2,3,...
例子:当N =5,打印出下面的图形:
X X X X X
X Y Y Y X
X Y 0 Y X
X Y Y Y X
X X X X X
22:谈谈COM的线程模型。然后讨论进程内/外组件的差别。
23:多态类中的虚函数表是Compile-Time,还是Run-Time时建立的?
参考答案:
虚拟函数表是在编译期就建立了,各个虚拟函数这时被组织成了一个虚拟函数的入口地址的数组.
而对象的隐藏成员--虚拟函数表指针是在运行期--也就是构造函数被调用时进行初始化的,这是实现多态的关键.
24:#include
void main()
{ int c;
while ((c=getchar())!= ′\ n ′ )
switch(c - ′ 2 ′ )
{ case 0:
case 1:putchar(c+4);break;
case 2:putchar(c+4);break;
case 3:putchar(c+3);break;
default:putchar(c+2);break;
}
printf( ″\ n ″ );
}
运行时输入: 2473 ,输出结果:
参考答案:6897 VC++6.0测试过
25:用两个栈实现一个队列的功能?
参考答案:
设2个栈为A,B, 一开始均为空.入队:
将新元素push入栈A;出队:
(1)判断栈B是否为空;
(2)如果不为空,则将栈A中所有元素依次pop出并push到栈B;
(3)将栈B的栈顶元素pop出;这样实现的队列入队和出队的平摊复杂度都还是O(1),
26:写一语句实现x是否为2的若干次幂的判断。
参考答案:!(X)&(X-1)
27:解二次方程:a*x*x+b*x+c
int Quadratic( double a,double b,double c,double& x1,double& x2);
返回值:解的个数
28:如何判别一个数是unsigned。
参考答案;
#define issignal(x) ((x>=0 && ~x>=0) ? 1:0) //为1是无符号 为0有符号
29:执行下述程序的输出结果是 ABCDEFCDEFEF 。
#include
char b[]="ABCDEF";
main()
{
char *bp;
for (bp=b;*bp;bp+=2)
printf("%s",bp);
printf("\n");
}
30:已知一个数组table,用一个宏定义,求出数据的元素个数。
参考答案:#define NTBL
#define NTBL (sizeof(table)/sizeof(table[0]))
1、将一个字符串逆序
2、将一个链表逆序
3、计算一个字节里(byte)里面有多少bit被置1
4、搜索给定的字节(byte)
5、在一个字符串中找到可能的最长的子字符串
6、字符串转换为整数
7、整数转换为字符串
/*
* 题目:将一个字符串逆序
* 完成时间:2006.9.30深圳极讯网吧
* 版权归刘志强所有
* 描述:写本程序的目的是希望练一下手,希望下午去面试能成功,不希望国庆节之后再去找工作拉!
*/
#include <iostream>
using namespace std;
//#define NULL ((void *)0)
char * mystrrev(char * const dest,const char * const src)
{
if (dest==NULL && src==NULL)
return NULL;
char *addr = dest;
int val_len = strlen(src);
dest[val_len] = '\0';
int i;
for (i=0; i<val_len; i++)
{
*(dest+i) = *(src+val_len-i-1);
}
return addr;
}
main()
{
char *str="asdfa";
char *str1=NULL;
str1 = (char *)malloc(20);
if (str1 == NULL)
cout<<"malloc failed";
cout<<mystrrev(str1,str);
free(str1);
str1=NULL;//杜绝野指针
}
p=head;
q=p->next;
while(q!=NULL)
{
temp=q->next;
q->next=p;
p=q;
q=temp;
}
这样增加个辅助的指针就行乐。
ok 通过编译的代码:
#include <stdio.h>
#include <ctype.h>
#include <stdlib.h>
typedef struct List{
int data;
struct List *next;
}List;
List *list_create(void)
{
struct List *head,*tail,*p;
int e;
head=(List *)malloc(sizeof(List));
tail=head;
printf("\nList Create,input numbers(end of 0):");
scanf("%d",&e);
while(e){
p=(List *)malloc(sizeof(List));
p->data=e;
tail->next=p;
tail=p;
scanf("%d",&e);}
tail->next=NULL;
return head;
}
List *list_reverse(List *head)
{
List *p,*q,*r;
p=head;
q=p->next;
while(q!=NULL)
{
r=q->next;
q->next=p;
p=q;
q=r;
}
head->next=NULL;
head=p;
return head;
}
void main(void)
{
struct List *head,*p;
int d;
head=list_create();
printf("\n");
for(p=head->next;p;p=p->next)
printf("--%d--",p->data);
head=list_reverse(head);
printf("\n");
for(p=head;p->next;p=p->next)
printf("--%d--",p->data);
}
编写函数数N个BYTE的数据中有多少位是1。
解:此题按步骤解:先定位到某一个BYTE数据;再计算其中有多少个1。叠加得解。
#incluede<iostream>
#define N 10
//定义BYTE类型别名
#ifndef BYTE
typedef unsigned char BYTE;
#endif
int comb(BYTE b[],int n)
{
int count=0;
int bi,bj;
BYTE cc=1,tt;
//历遍到第bi个BYTE数据
for(bi=0;bi<n;bi++)
{
//计算该BYTE的8个bit中有多少个1
tt=b[bi];
for(bj=0;bj<8;bj++)
{
//与1相与或模2结果是否是1?测试当前bit是否为1
//if(tt%2==1)
if((tt&cc)==1)
{
count++;
}
//右移一位或除以2,效果相同
//tt=tt>>1;
tt=tt/2;
}
}
return count;
}
//测试
int main()
{
BYTE b[10]={3,3,3,11,1,1,1,1,1,1};
cout<<comb(b,N)<<endl;
return 0;
}
1。编写一个 C 函数,该函数在一个字符串中找到可能的最长的子字符串,且该字符串是由同一字符组成的。
char * search(char *cpSource, char ch)
{
char *cpTemp=NULL, *cpDest=NULL;
int iTemp, iCount=0;
while(*cpSource)
{
if(*cpSource == ch)
{
iTemp = 0;
cpTemp = cpSource;
while(*cpSource == ch)
++iTemp, ++cpSource;
if(iTemp > iCount)
iCount = iTemp, cpDest = cpTemp;
if(!*cpSource)
break;
}
++cpSource;
}
return cpDest;
}
#include <stdio.h>
#include <string.h>
//
// 自定义函数MyAtoI
// 实现整数字符串转换为证书输出
// 程序不检查字符串的正确性,请用户在调用前检查
//
int MyAtoI(char str[])
{
int i;
int weight = 1; // 权重
int rtn = 0; // 用作返回
for(i = strlen(str) - 1; i >= 0; i--)
{
rtn += (str[i] - '0')* weight; //
weight *= 10; // 增重
}
return rtn;
}
void main()
{
char str[32];
printf("Input a string :");
gets(str);
printf("%d\n", MyAtoI(str));
}
#include<stdio.h>
#include<string.h>
void reverse(char s[])
{ //字符串反转
int c, i=0, j;
for(j=strlen(s)-1;i<j;j--)
{ c=s[i];
s[i]=s[j];
s[j]=c;
i++;
}
}
void IntegerToString(char s[],int n)
{ int i=0,sign;
if((sign=n)<0)//如果是负数,先转成正数
n=-n;
do //从个位开始变成字符,直到最高位,最后应该反转
{ s[i++]=n%10+'0';
}while((n=n/10)>0);
//如果是负数,补上负号
if(sign<0)
s[i++]='-';
s[i]='\0';//字符串结束
reverse(s);
}
void main()
{ int m;
char c[100];
printf("请输入整数m: ");
scanf("%d",&m);
IntegerToString(c,m);
printf("integer = %d string = %s\n", m, c);
}
1、将一个字符串逆序
2、将一个链表逆序
3、计算一个字节里(byte)里面有多少bit被置1
4、搜索给定的字节(byte)
5、在一个字符串中找到可能的最长的子字符串
6、字符串转换为整数
7、整数转换为字符串
/*
* 题目:将一个字符串逆序
* 完成时间:2006.9.30深圳极讯网吧
* 版权归刘志强所有
* 描述:写本程序的目的是希望练一下手,希望下午去面试能成功,不希望国庆节之后再去找工作拉!
*/
#include <iostream>
using namespace std;
//#define NULL ((void *)0)
char * mystrrev(char * const dest,const char * const src)
{
if (dest==NULL && src==NULL)
return NULL;
char *addr = dest;
int val_len = strlen(src);
dest[val_len] = '\0';
int i;
for (i=0; i<val_len; i++)
{
*(dest+i) = *(src+val_len-i-1);
}
return addr;
}
main()
{
char *str="asdfa";
char *str1=NULL;
str1 = (char *)malloc(20);
if (str1 == NULL)
cout<<"malloc failed";
cout<<mystrrev(str1,str);
free(str1);
str1=NULL;//杜绝野指针
}
p=head;
q=p->next;
while(q!=NULL)
{
temp=q->next;
q->next=p;
p=q;
q=temp;
}
这样增加个辅助的指针就行乐。
ok 通过编译的代码:
#include <stdio.h>
#include <ctype.h>
#include <stdlib.h>
typedef struct List{
int data;
struct List *next;
}List;
List *list_create(void)
{
struct List *head,*tail,*p;
int e;
head=(List *)malloc(sizeof(List));
tail=head;
printf("\nList Create,input numbers(end of 0):");
scanf("%d",&e);
while(e){
p=(List *)malloc(sizeof(List));
p->data=e;
tail->next=p;
tail=p;
scanf("%d",&e);}
tail->next=NULL;
return head;
}
List *list_reverse(List *head)
{
List *p,*q,*r;
p=head;
q=p->next;
while(q!=NULL)
{
r=q->next;
q->next=p;
p=q;
q=r;
}
head->next=NULL;
head=p;
return head;
}
void main(void)
{
struct List *head,*p;
int d;
head=list_create();
printf("\n");
for(p=head->next;p;p=p->next)
printf("--%d--",p->data);
head=list_reverse(head);
printf("\n");
for(p=head;p->next;p=p->next)
printf("--%d--",p->data);
}
编写函数数N个BYTE的数据中有多少位是1。
解:此题按步骤解:先定位到某一个BYTE数据;再计算其中有多少个1。叠加得解。
#incluede<iostream>
#define N 10
//定义BYTE类型别名
#ifndef BYTE
typedef unsigned char BYTE;
#endif
int comb(BYTE b[],int n)
{
int count=0;
int bi,bj;
BYTE cc=1,tt;
//历遍到第bi个BYTE数据
for(bi=0;bi<n;bi++)
{
//计算该BYTE的8个bit中有多少个1
tt=b[bi];
for(bj=0;bj<8;bj++)
{
//与1相与或模2结果是否是1?测试当前bit是否为1
//if(tt%2==1)
if((tt&cc)==1)
{
count++;
}
//右移一位或除以2,效果相同
//tt=tt>>1;
tt=tt/2;
}
}
return count;
}
//测试
int main()
{
BYTE b[10]={3,3,3,11,1,1,1,1,1,1};
cout<<comb(b,N)<<endl;
return 0;
}
1。编写一个 C 函数,该函数在一个字符串中找到可能的最长的子字符串,且该字符串是由同一字符组成的。
char * search(char *cpSource, char ch)
{
char *cpTemp=NULL, *cpDest=NULL;
int iTemp, iCount=0;
while(*cpSource)
{
if(*cpSource == ch)
{
iTemp = 0;
cpTemp = cpSource;
while(*cpSource == ch)
++iTemp, ++cpSource;
if(iTemp > iCount)
iCount = iTemp, cpDest = cpTemp;
if(!*cpSource)
break;
}
++cpSource;
}
return cpDest;
}
#include <stdio.h>
#include <string.h>
//
// 自定义函数MyAtoI
// 实现整数字符串转换为证书输出
// 程序不检查字符串的正确性,请用户在调用前检查
//
int MyAtoI(char str[])
{
int i;
int weight = 1; // 权重
int rtn = 0; // 用作返回
for(i = strlen(str) - 1; i >= 0; i--)
{
rtn += (str[i] - '0')* weight; //
weight *= 10; // 增重
}
return rtn;
}
void main()
{
char str[32];
printf("Input a string :");
gets(str);
printf("%d\n", MyAtoI(str));
}
#include<stdio.h>
#include<string.h>
void reverse(char s[])
{ //字符串反转
int c, i=0, j;
for(j=strlen(s)-1;i<j;j--)
{ c=s[i];
s[i]=s[j];
s[j]=c;
i++;
}
}
void IntegerToString(char s[],int n)
{ int i=0,sign;
if((sign=n)<0)//如果是负数,先转成正数
n=-n;
do //从个位开始变成字符,直到最高位,最后应该反转
{ s[i++]=n%10+'0';
}while((n=n/10)>0);
//如果是负数,补上负号
if(sign<0)
s[i++]='-';
s[i]='\0';//字符串结束
reverse(s);
}
void main()
{ int m;
char c[100];
printf("请输入整数m: ");
scanf("%d",&m);
IntegerToString(c,m);
printf("integer = %d string = %s\n", m, c);
}
嵌入式软件工程师应知道的0x10个基本问题(经典收藏版)
C语言测试是招聘嵌入式系统程序员过程中必须而且有效的方法。这些年,我既参加也组织了许多这种测试,在这过程中我意识到这些测试能为面试者和被面试者提供许多有用信息,此外,撇开面试的压力不谈,这种测试也是相当有趣的。
从被面试者的角度来讲,你能了解许多关于出题者或监考者的情况。这个测试只是出题者为显示其对ANSI标准细节的知识而不是技术技巧而设计吗?这是个愚蠢的问题吗?如要你答出某个字符的ASCII值。这些问题着重考察你的系统调用和内存分配策略方面的能力吗?这标志着出题者也许花时间在微机上而不是在嵌入式系统上。如果上述任何问题的答案是"是"的话,那么我知道我得认真考虑我是否应该去做这份工作。
从面试者的角度来讲,一个测试也许能从多方面揭示应试者的素质:最基本的,你能了解应试者C语言的水平。不管怎么样,看一下这人如何回答他不会的问题也是满有趣。应试者是以好的直觉做出明智的选择,还是只是瞎蒙呢?当应试者在某个问题上卡住时是找借口呢,还是表现出对问题的真正的好奇心,把这看成学习的机会呢?我发现这些信息与他们的测试成绩一样有用。
有了这些想法,我决定出一些真正针对嵌入式系统的考题,希望这些令人头痛的考题能给正在找工作的人一点帮助。这些问题都是我这些年实际碰到的。其中有些题很难,但它们应该都能给你一点启迪。
这个测试适于不同水平的应试者,大多数初级水平的应试者的成绩会很差,经验丰富的程序员应该有很好的成绩。为了让你能自己决定某些问题的偏好,每个问题没有分配分数,如果选择这些考题为你所用,请自行按你的意思分配分数。
预处理器(Preprocessor)
1 . 用预处理指令#define 声明一个常数,用以表明1年中有多少秒(忽略闰年问题)
#define SECONDS_PER_YEAR (60 * 60 * 24 * 365)UL
我在这想看到几件事情:
1) #define 语法的基本知识(例如:不能以分号结束,括号的使用,等等)
2)懂得预处理器将为你计算常数表达式的值,因此,直接写出你是如何计算一年中有多少秒而不是计算出实际的值,是更清晰而没有代价的。
3) 意识到这个表达式将使一个16位机的整型数溢出-因此要用到长整型符号L,告诉编译器这个常数是的长整型数。
4) 如果你在你的表达式中用到UL(表示无符号长整型),那么你有了一个好的起点。记住,第一印象很重要。
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的目的是什么?
如果你不知道答案,请看参考文献1。这问题对区分一个正常的伙计和一个书呆子是很有用的。只有书呆子才会读C语言课本的附录去找出象这种问题的答案。当然如果你不是在找一个书呆子,那么应试者最好希望自己不要知道答案。
死循环(Infinite loops)
4. 嵌入式系统中经常要用到无限循环,你怎么样用C编写死循环呢?
这个问题用几个解决方案。我首选的方案是:
while(1)
{
}
一些程序员更喜欢如下方案:
for(;;)
{
}
这个实现方式让我为难,因为这个语法没有确切表达到底怎么回事。如果一个应试者给出这个作为方案,我将用这个作为一个机会去探究他们这样做的基本原理。如果他们的基本答案是:"我被教着这样做,但从没有想到过为什么。"这会给我留下一个坏印象。
第三个方案是用 goto
Loop:
...
goto Loop;
应试者如给出上面的方案,这说明或者他是一个汇编语言程序员(这也许是好事)或者他是一个想进入新领域的BASIC/FORTRAN程序员。
数据声明(Data declarations)
5. 用变量a给出下面的定义
a) 一个整型数(An integer)
b)一个指向整型数的指针( A pointer to an integer)
c)一个指向指针的的指针,它指向的指针是指向一个整型数( A pointer to a pointer to an intege)r
d)一个有10个整型数的数组( An array of 10 integers)
e) 一个有10个指针的数组,该指针是指向一个整型数的。(An array of 10 pointers to integers)
f) 一个指向有10个整型数数组的指针( A pointer to an array of 10 integers)
g) 一个指向函数的指针,该函数有一个整型参数并返回一个整型数(A pointer to a function that takes an integer as an argument and returns an integer)
h) 一个有10个指针的数组,该指针指向一个函数,该函数有一个整型参数并返回一个整型数( An array of ten pointers to functions that take an integer argument and return an integer )
答案是:
a) int a; // An integer
b) int *a; // A pointer to an integer
c) int **a; // A pointer to a pointer to an integer
d) int a[10]; // An array of 10 integers
e) int *a[10]; // An array of 10 pointers to integers
f) int (*a)[10]; // A pointer to an array of 10 integers
g) int (*a)(int); // A pointer to a function a that takes an integer argument and returns an integer
h) int (*a[10])(int); // An array of 10 pointers to functions that take an integer argument and return an integer
人们经常声称这里有几个问题是那种要翻一下书才能回答的问题,我同意这种说法。当我写这篇文章时,为了确定语法的正确性,我的确查了一下书。但是当我被面试的时候,我期望被问到这个问题(或者相近的问题)。因为在被面试的这段时间里,我确定我知道这个问题的答案。应试者如果不知道所有的答案(或至少大部分答案),那么也就没有为这次面试做准备,如果该面试者没有为这次面试做准备,那么他又能为什么出准备呢?
Static
6. 关键字static的作用是什么?
这个简单的问题很少有人能回答完全。在C语言中,关键字static有三个明显的作用:
1)在函数体,一个被声明为静态的变量在这一函数被调用过程中维持其值不变。
2) 在模块内(但在函数体外),一个被声明为静态的变量可以被模块内所用函数访问,但不能被模块外其它函数访问。它是一个本地的全局变量。
3) 在模块内,一个被声明为静态的函数只可被这一模块内的其它函数调用。那就是,这个函数被限制在声明它的模块的本地范围内使用。
大多数应试者能正确回答第一部分,一部分能正确回答第二部分,同是很少的人能懂得第三部分。这是一个应试者的严重的缺点,因为他显然不懂得本地化数据和代码范围的好处和重要性。
Const
7.关键字const有什么含意?
我只要一听到被面试者说:"const意味着常数",我就知道我正在和一个业余者打交道。去年Dan Saks已经在他的文章里完全概括了const的所有用法,因此ESP(译者:Embedded Systems Programming)的每一位读者应该非常熟悉const能做什么和不能做什么.如果你从没有读到那篇文章,只要能说出const意味着"只读"就可以了。尽管这个答案不是完全的答案,但我接受它作为一个正确的答案。(如果你想知道更详细的答案,仔细读一下Saks的文章吧。)
如果应试者能正确回答这个问题,我将问他一个附加的问题:
下面的声明都是什么意思?
const int a;
int const a;
const int *a;
int * const a;
int const * a const;
/******/
前两个的作用是一样,a是一个常整型数。第三个意味着a是一个指向常整型数的指针(也就是,整型数是不可修改的,但指针可以)。第四个意思a是一个指向整型数的常指针(也就是说,指针指向的整型数是可以修改的,但指针是不可修改的)。最后一个意味着a是一个指向常整型数的常指针(也就是说,指针指向的整型数是不可修改的,同时指针也是不可修改的)。如果应试者能正确回答这些问题,那么他就给我留下了一个好印象。顺带提一句,也许你可能会问,即使不用关键字 const,也还是能很容易写出功能正确的程序,那么我为什么还要如此看重关键字const呢?我也如下的几下理由:
1) 关键字const的作用是为给读你代码的人传达非常有用的信息,实际上,声明一个参数为常量是为了告诉了用户这个参数的应用目的。如果你曾花很多时间清理其它人留下的垃圾,你就会很快学会感谢这点多余的信息。(当然,懂得用const的程序员很少会留下的垃圾让别人来清理的。)
2) 通过给优化器一些附加的信息,使用关键字const也许能产生更紧凑的代码。
3) 合理地使用关键字const可以使编译器很自然地保护那些不希望被改变的参数,防止其被无意的代码修改。简而言之,这样可以减少bug的出现。
Volatile
8. 关键字volatile有什么含意?并给出三个不同的例子。
一个定义为volatile的变量是说这变量可能会被意想不到地改变,这样,编译器就不会去假设这个变量的值了。精确地说就是,优化器在用到这个变量时必须每次都小心地重新读取这个变量的值,而不是使用保存在寄存器里的备份。下面是volatile变量的几个例子:
1) 并行设备的硬件寄存器(如:状态寄存器)
2) 一个中断服务子程序中会访问到的非自动变量(Non-automatic variables)
3) 多线程应用中被几个任务共享的变量
回答不出这个问题的人是不会被雇佣的。我认为这是区分C程序员和嵌入式系统程序员的最基本的问题。搞嵌入式的家伙们经常同硬件、中断、RTOS等等打交道,所有这些都要求用到volatile变量。不懂得volatile的内容将会带来灾难。
假设被面试者正确地回答了这是问题(嗯,怀疑是否会是这样),我将稍微深究一下,看一下这家伙是不是直正懂得volatile完全的重要性。
1)一个参数既可以是const还可以是volatile吗?解释为什么。
2); 一个指针可以是volatile 吗?解释为什么。
3); 下面的函数有什么错误:
int square(volatile int *ptr)
{
return *ptr * *ptr;
}
下面是答案:
1)是的。一个例子是只读的状态寄存器。它是volatile因为它可能被意想不到地改变。它是const因为程序不应该试图去修改它。
2); 是的。尽管这并不很常见。一个例子是当一个中服务子程序修该一个指向一个buffer的指针时。
3) 这段代码有点变态。这段代码的目的是用来返指针*ptr指向值的平方,但是,由于*ptr指向一个volatile型参数,编译器将产生类似下面的代码:
int square(volatile int *ptr)
{
int a,b;
a = *ptr;
b = *ptr;
return a * b;
}
由于*ptr的值可能被意想不到地该变,因此a和b可能是不同的。结果,这段代码可能返不是你所期望的平方值!正确的代码如下:
long square(volatile int *ptr)
{
int a;
a = *ptr;
return a * a;
}
位操作(Bit manipulation)
9. 嵌入式系统总是要用户对变量或寄存器进行位操作。给定一个整型变量a,写两段代码,第一个设置a的bit 3,第二个清除a 的bit 3。在以上两个操作中,要保持其它位不变。
对这个问题有三种基本的反应
1)不知道如何下手。该被面者从没做过任何嵌入式系统的工作。
2) 用bit fields。Bit fields是被扔到C语言死角的东西,它保证你的代码在不同编译器之间是不可移植的,同时也保证了的你的代码是不可重用的。我最近不幸看到 Infineon为其较复杂的通信芯片写的驱动程序,它用到了bit fields因此完全对我无用,因为我的编译器用其它的方式来实现bit fields的。从道德讲:永远不要让一个非嵌入式的家伙粘实际硬件的边。
3) 用 #defines 和 bit masks 操作。这是一个有极高可移植性的方法,是应该被用到的方法。最佳的解决方案如下:
#define BIT3 (0x1 << 3)
static int a;
void set_bit3(void)
{
a |= BIT3;
}
void clear_bit3(void)
{
a &= ~BIT3;
}
一些人喜欢为设置和清除值而定义一个掩码同时定义一些说明常数,这也是可以接受的。我希望看到几个要点:说明常数、|=和&=~操作。
访问固定的内存位置(Accessing fixed memory locations)
10. 嵌入式系统经常具有要求程序员去访问某特定的内存位置的特点。在某工程中,要求设置一绝对地址为0x67a9的整型变量的值为0xaa66。编译器是一个纯粹的ANSI编译器。写代码去完成这一任务。
这一问题测试你是否知道为了访问一绝对地址把一个整型数强制转换(typecast)为一指针是合法的。这一问题的实现方式随着个人风格不同而不同。典型的类似代码如下:
int *ptr;
ptr = (int *)0x67a9;
*ptr = 0xaa55;
A more obscure approach is:
一个较晦涩的方法是:
*(int * const)(0x67a9) = 0xaa55;
即使你的品味更接近第二种方案,但我建议你在面试时使用第一种方案。
中断(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;
/*1s complement of zero */
对于一个int型不是16位的处理器为说,上面的代码是不正确的。应编写如下:
unsigned int compzero = ~0;
这一问题真正能揭露出应试者是否懂得处理器字长的重要性。在我的经验里,好的嵌入式程序员非常准确地明白硬件的细节和它的局限,然而PC机程序往往把硬件作为一个无法避免的烦恼。
到了这个阶段,应试者或者完全垂头丧气了或者信心满满志在必得。如果显然应试者不是很好,那么这个测试就在这里结束了。但如果显然应试者做得不错,那么我就扔出下面的追加问题,这些问题是比较难的,我想仅仅非常优秀的应试者能做得不错。提出这些问题,我希望更多看到应试者应付问题的方法,而不是答案。不管如何,你就当是这个娱乐吧...
动态内存分配(Dynamic memory allocation)
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"。我用这个来开始讨论这样的一问题,看看被面试者是否想到库例程这样做是正确。得到正确的答案固然重要,但解决问题的方法和你做决定的基本原理更重要些。
Typedef
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。
如果你知道答案,或猜出正确答案,做得好。如果你不知道答案,我也不把这个当作问题。我发现这个问题的最大好处是这是一个关于代码编写风格,代码的可读性,代码的可修改性的好的话题。
好了,伙计们,你现在已经做完所有的测试了。这就是我出的C语言测试题,我怀着愉快的心情写完它,希望你以同样的心情读完它。如果是认为这是一个好的测试,那么尽量都用到你的找工作的过程中去吧。天知道也许过个一两年,我就不做现在的工作,也需要找一个。
作者介绍:
Nigel Jones 是一个顾问,现在住在Maryland,当他不在水下时,你能在多个范围的嵌入项目中找到他。 他很高兴能收到读者的来信,他的email地址是: NAJones@compuserve.com
参考文献
1) Jones, Nigel, "In Praise of the #error directive," Embedded Systems Programming, September 1999, p. 114.
2) Jones, Nigel, " Efficient C Code for Eight-bit MCUs ," Embedded Systems Programming, November 1998, p. 66.
考查嵌入式C开发人员的最好的0x10道题
考查一个初级嵌入式系统开发人员的C基本功,附有答案题目由资深嵌入式系统专家拟定,目的是考查入门级的嵌入式软件开发人员 Gavin Shaw提供详细解答
编者按:非常基本关于C语言的问题,一个信息类(计算机,资讯工程,电子工程, 通信工程)专业的本科毕业生应该达到的水平。题目不难,全部都能快速地答完,当然也需要一定的知识储备。
对于大多数人,我们预期你可能答错 3) 4) 15)题,所以答错3道以内的,我们认为你很棒
答错5道题以内,我们认为你还不错(你还可能答错第9题)
如果你有6道以上的题目不能答对,基本上我们都不好说什么了....
约定:
1) 下面的测试题中,认为所有必须的头文件都已经正确的包含了
2)数据类型
char 一个字节 1 byte
int 两个字节 2 byte (16位系统,认为整型是2个字节)
long int 四个字节 4 byte
float 四个字节4 byet
double 八个字节 8 byte
long double 十个字节 10 byte
pointer 两个字节 2 byte(注意,16位系统,地址总线只有16位)
第1题: 考查对volatile关键字的认识
#include<setjmp.h>
static jmp_buf buf;
main()
{
volatile int b;
b =3;
if(setjmp(buf)!=0)
{
printf("%d ", b);
exit(0);
}
b=5;
longjmp(buf , 1);
}
请问,这段程序的输出是
(a) 3
(b) 5
(c) 0
(d) 以上均不是
第2题:考查类型转换
main()
{
struct node
{
int a;
int b;
int c;
};
struct node s= { 3, 5,6 };
struct node *pt = &s;
printf("%d" , *(int*)pt);
}
这段程序的输出是:
(a) 3
(b) 5
(c) 6
(d) 7
第3题:考查递归调用
int foo ( int x , int n)
{
int val;
val =1;
if (n>0)
{
if (n%2 == 1) val = val *x;
val = val * foo(x*x , n/2);
}
return val;
}
这段代码对x和n完成什么样的功能(操作)?
(a) x^n (x的n次幂)
(b) x*n(x与n的乘积)
(c) n^x(n的x次幂)
(d) 以上均不是
第4题:考查指针,这道题只适合于那些特别细心且对指针和数组有深入理解的人
main()
{
int a[5] = {1,2,3,4,5};
int *ptr = (int*)(&a+1);
printf("%d %d" , *(a+1), *(ptr-1) );
}
这段程序的输出是:
(a) 2 2
(b) 2 1
(c) 2 5
(d) 以上均不是
第5题:考查多维数组与指针
void foo(int [][3] );
main()
{
int a [3][3]= { { 1,2,3} , { 4,5,6},{7,8,9}};
foo(a);
printf("%d" , a[2][1]);
}
void foo( int b[][3])
{
++ b;
b[1][1] =9;
}
这段程序的输出是:
(a) 8
(b) 9
(c) 7
(d)以上均不对
第6题目:考查逗号表达式
main()
{
int a, b,c, d;
a=3;
b=5;
c=a,b;
d=(a,b);
printf("c=%d" ,c);
printf("d=%d" ,d);
}
这段程序的输出是:
(a) c=3 d=3
(b) c=5 d=3
(c) c=3 d=5
(d) c=5 d=5
第7题:考查指针数组
main()
{
int a[][3] = { 1,2,3 ,4,5,6};
int (*ptr)[3] =a;
printf("%d %d " ,(*ptr)[1], (*ptr)[2] );
++ptr;
printf("%d %d" ,(*ptr)[1], (*ptr)[2] );
}
这段程序的输出是:
(a) 2 3 5 6
(b) 2 3 4 5
(c) 4 5 0 0
(d) 以上均不对
第8题:考查函数指针
int *f1(void)
{
int x =10;
return(&x);
}
int *f2(void)
{
int*ptr;
*ptr =10;
return ptr;
}
int *f3(void)
{
int *ptr;
ptr=(int*) malloc(sizeof(int));
return ptr;
}
上面这3个函数哪一个最可能引起指针方面的问题
(a) 只有 f3
(b) 只有f1 and f3
(c) 只有f1 and f2
(d) f1 , f2 ,f3
第9题:考查自加操作(++)
main()
{
int i=3;
int j;
j = sizeof(++i+ ++i);
printf("i=%d j=%d", i ,j);
}
这段程序的输出是:
(a) i=4 j=2
(b) i=3 j=2
(c) i=3 j=4
(d) i=3 j=6
第10题:考查形式参数,实际参数,指针和数组
void f1(int *, int);
void f2(int *, int);
void(*p[2]) ( int *, int);
main()
{
int a;
int b;
p[0] = f1;
p[1] = f2;
a=3;
b=5;
p[0](&a , b);
printf("%d\t %d\t" , a ,b);
p[1](&a , b);
printf("%d\t %d\t" , a ,b);
}
void f1( int* p , int q)
{
int tmp;
tmp =*p;
*p = q;
q= tmp;
}
void f2( int* p , int q)
{
int tmp;
tmp =*p;
*p = q;
q= tmp;
}
这段程序的输出是:
(a) 5 5 5 5
(b) 3 5 3 5
(c) 5 3 5 3
(d) 3 3 3 3
第11题:考查自减操作(--)
void e(int );
main()
{
int a;
a=3;
e(a);
}
void e(int n)
{
if(n>0)
{
e(--n);
printf("%d" , n);
e(--n);
}
}
这段程序的输出是:
(a) 0 1 2 0
(b) 0 1 2 1
(c) 1 2 0 1
(d) 0 2 1 1
第12题:考查typedef类型定义,函数指针
typedef int (*test) ( float * , float*)
test tmp;
tmp 的类型是
(a) 函数的指针,该函数以 两个指向浮点数(float)的指针(pointer)作为参数(arguments)
Pointer to function of having two arguments that is pointer to float
(b) 整型
(c) 函数的指针,该函数以 两个指向浮点数(float)的指针(pointer)作为参数(arguments),并且函数的返回值类型是整型
Pointer to function having two argument that is pointer to float and return int
(d) 以上都不是
第13题:数组与指针的区别与联系
main()
{
char p;
char buf[10] ={ 1,2,3,4,5,6,9,8};
p = (buf+1)[5];
printf("%d" , p);
}
这段程序的输出是:
(a) 5
(b) 6
(c) 9
(d) 以上都不对
第14题: 考查指针数组的指针
Void f(char**);
main()
{
char * argv[] = { "ab" ,"cd" , "ef" ,"gh", "ij" ,"kl" };
f( argv );
}
void f( char **p )
{
char* t;
t= (p+= sizeof(int))[-1];
printf( "%s" , t);
}
这段程序的输出是:
(a) ab
(b) cd
(c) ef
(d) gh
第15题:此题考查的是C的变长参数,就像标准函数库里printf()那样,这个话题一般国内大学课堂是不会讲到的,不会也情有可原呵呵,
#include<stdarg.h>
int ripple ( int , ...);
main()
{
int num;
num = ripple ( 3, 5,7);
printf( " %d" , num);
}
int ripple (int n, ...)
{
int i , j;
int k;
va_list p;
k= 0;
j = 1;
va_start( p , n);
for (; j<n; ++j)
{
i = va_arg( p , int);
for (; i; i &=i-1 )
++k;
}
return k;
}
这段程序的输出是:
(a) 7
(b) 6
(c) 5
(d) 3
第16题:考查静态变量的知识
int counter (int i)
{
static int count =0;
count = count +i;
return (count );
}
main()
{
int i , j;
for (i=0; i <=5; i++)
j = counter(i);
}
本程序执行到最后,j的值是:
(a) 10
(b) 15
(c) 6
(d) 7
详细参考答案
第1题: (b)
volatile字面意思是易于挥发的。这个关键字来描述一个变量时,意味着 给该变量赋值(写入)之后,马上再读取,写入的值与读取的值可能不一样,所以说它"容易挥发"的。
这是因为这个变量可能一个寄存器,直接与外部设备相连,你写入之后,该寄存器也有可能被外部设备的写操作所改变;或者,该变量被一个中断程序,或另一个进程改变了.
volatile 不会被编译器优化影响,在longjump 后,它的值 是后面假定的变量值,b最后的值是5,所以5被打印出来.
setjmp : 设置非局部跳转 /* setjmp.h*/
Stores context information such as register values so that the lomgjmp function can return control to the statement following the one calling setjmp.Returns 0 when it is initially called.
Lonjjmp: 执行一个非局部跳转 /* setjmp.h*/
Transfers control to the statement where the call to setjmp (which initialized buf) was made. Execution continues at this point as if longjmp cannot return the value 0.A nonvolatile automatic variable might be changed by a call to longjmp.When you use setjmp and longjmp, the only automatic variables guaranteed to remain valid are those declared volatile.
Note: Test program without volatile qualifier (result may very)
更详细介绍,请参阅 C语言的setjmp和longjmp
第2题: (a)
结构题的成员在内存中的地址是按照他们定义的位置顺序依次增长的。如果一个结构体的指针被看成 它的第一个成员的指针,那么该指针的确指向第一个成员
第3题: (a)
此题目较难.
这个程序的非递归版本
int what ( int x , int n)
{
int val;
int product;
product =1;
val =x;
while(n>0)
{
if (n%2 == 1)
product = product*val; /*如果是奇数次幂, x(val)
要先乘上一次,;
偶数次幂,最后返回时才会到这里
乘以1*/
val = val* val;
n = n/2;
}
return product;
}
/* 用二元复乘策略 */
算法描述
(while n>0)
{
if next most significant binary digit of n( power) is one
then multiply accumulated product by current val ,
reduce n(power) sequence by a factor of two using integer division .
get next val by multiply current value of itself
}
第4题: (c)
a的类型是一个整型数组,它有5个成员
&a的类型是一个整型数组的指针
所以&a + 1指向的地方等同于 a[6]
所以*(a+1) 等同于a[1]
ptr等同 a[6], ptr-1就等同与a[5]
第5题: (b)
题目自身就给了足够的提示
b[0][0] = 4
b[1][0] = 7
第6题: (c)
考查逗号表达式,逗号表达式的优先级是很低的,比 赋值(=)的优先级 低. 逗号表达式的值就是最后一个元素的值
逗号表达式的还有一个作用就是分割函数的参数列表..
E1, E2, ..., En
上面这个表示式的左右是,E1, E2,... En的值被分别计算出来,En计算出来的结构赋给整个逗号表达式
c=a,b; / *yields c=a* /
d=(a,b); /* d =b */
第7题: (a)
ptr是一个数组的指针,该数组有3个int成员
第8题: (c)
f1显然有问题,它返回一个局部变量的指针,局部变量是保存在stack中的,退出函数后,局部变量就销毁了,保留其指针没有意义,因为其指向的stack空间可能被其他变量覆盖了
f2也有问题, ptr是局部变量,未初始化,它的值是未知的,*ptr不知道指向哪里了,直接给*ptr赋值可能会覆盖重要的系统变量,这就是通常说的野指针的一种
第9题: (b)
sizeof 操作符给出其操作数需要占用的空间大小,它是在编译时就可确定的,所以其操作数即使是一个表达式,也不需要在运行时进行计算.( ++i + ++ i )是不会执行的,所以
i 的值还是3
第10题: (a)
很显然选a.
f1交换*p 和 q的值,f1执行完后, *p 和 q的值的确交换了, 但 q的改变不会影响到 b的改变, *p 实际上就是 a
所以执行f1后, a=b=5
这道题考查的知识范围很广,包括typedef自定义类型,函数指针,指针数组
void(*p[ 2 ]) ( int *, int);
定义了一个函数指针的数组p,p有两个指针元素. 元素是函数的指针,函数指针指向的函数是一个带2个参数,返回void的函数,所带的两个参数是 指向整型的指针,和整型
p[ 0 ] = f1; p[ 1 ] = f2 contain address of function .function name without parenthesis represent address of function Value and address of variable is passed to function only argument that is effected is a (address is passed). Because of call by value f1, f2 can not effect b
第11题: (a)
考查--操作和递归调用,仔细分析一下就可以了
第12题: (c)
分析函数声明,建议不会的看看《C专家编程》
这里介绍一个简单规则:从左往右,遇到括号停下来,将第一个括号里的东西看成一个整体
第13题: (c)
考查什么时候数组就是指针.对某些类型T而言,如果一个表达式是 T[] (T的数组), 这个表达式的值实际上就是指向该数组的第一个元素的指针.所以(buf+1)[5]实际上就是*(buf +6)或者buf[6]
第14题: (b)
sizeof(int)的值是2,所以p+=sizeof(int) 指向 argv[2],这点估计大家都没有什么疑问
(p+=sizeof(int))[-1] 指向 argv[1],能理解吗,因为(p+=sizeof(int))[-1] 就相当于 (p+=2)[-1] ,也就是(p+2-1)
第15题: (c)
在C编译器通常提供了一系列处理可变参数的宏,以屏蔽不同的硬件平台造成的差异,增加程序的可移植性。这些宏包括va_start、 va_arg和va_end等。
采用ANSI标准形式时,参数个数可变的函数的原型声明是:
type funcname(type para1, type para2, ...)
这种形式至少需要一个普通的形式参数,后面的省略号不表示省略,而是函数原型的一部分。type是函数返回值和形式参数的类型。
不同的编译器,对这个可变长参数的实现不一样 ,gcc4.x中是内置函数.
关于可变长参数,可参阅
http://www.upsdn.net/html/2004-11/26.html
http://www.upsdn.net/html/2004-11/24.html
程序分析
va_list p; /*定义一个变量 ,保存 函数参数列表 的指针*/
va_start( p , n); /*用va_start宏 初始化 变量p,
va_start宏的第2个参数n ,
是一个固定的参数,
必须是我们自己定义的变长函数的最后一个入栈的参数
也就是调用的时候参数列表里的第1个参数*/
for (; j<n; ++j) /* j从1开始, 遍历所有可变参数 */
{
i = va_arg( p , int); /*va_arg取出当前的参数,
并认为取出的参数是一个整数(int) */
for (; i; i &=i-1 ) /*判断取出的i是否为0*/
++k; /* 如果i不为0, k自加,
i与i-1进行与逻辑运算, 直到i 为0
这是一个技巧,下面会谈到它的功能*/
}
当我们调用ripple函数时,传递给ripple函数的 参数列表的第一个参数n的值是3 .
va_start 初始化 p士气指向第一个未命名的参数(n是有名字的参数) ,也就是 is 5 (第一个).
每次对 va_arg的调用,都将返回一个参数,并且把 p 指向下一个参数.
va_arg 用一个类型名来决定返回的参数是何种类型,以及在 var_arg的内部实现中决定移动多大的距离才到达下一个 参数
(; i; i&=i-1) k++ /* 计算i有多少bit被置1 */
5用二进制表示是 (101) 2
7用二进制表示 (111) 3
所以 k 返回 5(2+3),也即本题应该选c
举个例子,就很好理解了
令 i= 9 = 1001
i-1 = 1000
(i-1) +1 = i
1000
+1
1 001
因为i与i-1的最右边的那位(最低位) 肯定是不同,如果i1,i-1肯定是0,反之亦然. i & i-1 这个运算,在二相补的数字系统中,将会 消除最右边的1位
第16题: (b)
答案是 (b)
相传高斯小学一年级的时候就会做这类等比数列的题目了.这道题考查的是静态变量的知识,当每次调用完函数之后,静态变量的值不会丢失,这与栈中的临时局部变量明显不同的地方.
所以,第一次调用counter(0)之后,count =0
第二次调用 counter(1)后 count = 0+1;
第三次调用 counter(2) count = 1+2; /* count = count +i */
第四次调用 counter(3) count = 3+3;
第五次调用 counter(4) count = 6+4;
第六次调用 counter(5) count = 10+5;
命题人信息 Ashok K. Pathak a member ( Research Staff ) at Bharat Electronics Limited (CRL) , Ghaziabad. He has been developing embedded application for the past five years .Ashok holds a M.E in computer science and engineering . Ashok recently completed a book about' "Advanced Test in C and Embedded System Programming" , Published by BPB , ND .
9. 嵌入式系统总是要用户对变量或寄存器进行位操作。给定一个整型变量a,写两段代码,第一个设置a的bit 3,第二个清除a 的bit 3。在以上两个操作中,要保持其它位不变。
对这个问题有三种基本的反应
1). 不知道如何下手。该被面者从没做过任何嵌入式系统的工作。
2). 用bit fields。Bit fields是被扔到C语言死角的东西,它保证你的代码在不同编译器之间是不可移植的,同时也保证了的你的代码是不可重用的。我最近不幸看到Infineon为其较复杂的通信芯片写的驱动程序,它用到了bit fields因此完全对我无用,因为我的编译器用其它的方式来实现bit fields的。从道德讲:永远不要让一个非嵌入式的家伙粘实际硬件的边。
3). 用 #defines 和 bit masks 操作。这是一个有极高可移植性的方法,是应该被用到的方法。最佳的解决方案如下:
#define BIT3 (0x1<<3)
static int a;
void set_bit3(void)
{
a |= BIT3;
}
void clear_bit3(void)
{
a &= ~BIT3;
}
一些人喜欢为设置和清除值而定义一个掩码同时定义一些说明常数,这也是可以接受的。我希望看到几个要点:说明常数、|=和&=~操作。
Volatile
8. 关键字volatile有什么含意 并给出三个不同的例子。
一个定义为volatile的变量是说这变量可能会被意想不到地改变,这样,编译器就不会去假设这个变量的值了。精确地说就是,优化器在用到这个变量时必须每次都小心地重新读取这个变量的值,而不是使用保存在寄存器里的备份。下面是volatile变量的几个例子:
1). 并行设备的硬件寄存器(如:状态寄存器)
2). 一个中断服务子程序中会访问到的非自动变量(Non-automatic variables)
3). 多线程应用中被几个任务共享的变量
回答不出这个问题的人是不会被雇佣的。我认为这是区分C程序员和嵌入式系统程序员的最基本的问题。嵌入式系统程序员经常同硬件、中断、RTOS等等打交道,所用这些都要求volatile变量。不懂得volatile内容将会带来灾难。
假设被面试者正确地回答了这是问题(嗯,怀疑这否会是这样),我将稍微深究一下,看一下这家伙是不是直正懂得volatile完全的重要性。
1). 一个参数既可以是const还可以是volatile吗?解释为什么。
2). 一个指针可以是volatile 吗?解释为什么。
3). 下面的函数有什么错误:
int square(volatile int *ptr)
{
return *ptr * *ptr;
}
下面是答案:
1). 是的。一个例子是只读的状态寄存器。它是volatile因为它可能被意想不到地改变。它是const因为程序不应该试图去修改它。
2). 是的。尽管这并不很常见。一个例子是当一个中服务子程序修该一个指向一个buffer的指针时。
3). 这段代码的有个恶作剧。这段代码的目的是用来返指针*ptr指向值的平方,但是,由于*ptr指向一个volatile型参数,编译器将产生类似下面的代码:
int square(volatile int *ptr)
{
int a,b;
a = *ptr;
b = *ptr;
return a * b;
}
由于*ptr的值可能被意想不到地该变,因此a和b可能是不同的。结果,这段代码可能返不是你所期望的平方值!正确的代码如下:
long square(volatile int *ptr)
{
int a;
a = *ptr;
return a * a;
}
6. 关键字static的作用是什么?
这个简单的问题很少有人能回答完全。在C语言中,关键字static有三个明显的作用:
1). 在函数体,一个被声明为静态的变量在这一函数被调用过程中维持其值不变。
2). 在模块内(但在函数体外),一个被声明为静态的变量可以被模块内所用函数访问,但不能被模块外其它函数访问。它是一个本地的全局变量。
3). 在模块内,一个被声明为静态的函数只可被这一模块内的其它函数调用。那就是,这个函数被限制在声明它的模块的本地范围内使用。
大多数应试者能正确回答第一部分,一部分能正确回答第二部分,同是很少的人能懂得第三部分。这是一个应试者的严重的缺点,因为他显然不懂得本地化数据和代码范围的好处和重要性。
死循环(Infinite loops)
4. 嵌入式系统中经常要用到无限循环,你怎么样用C编写死循环呢?
这个问题用几个解决方案。我首选的方案是:
while(1) { }
一些程序员更喜欢如下方案:
for(;;) { }
这个实现方式让我为难,因为这个语法没有确切表达到底怎么回事。如果一个应试者给出这个作为方案,我将用这个作为一个机会去探究他们这样做的
基本原理。如果他们的基本答案是:“我被教着这样做,但从没有想到过为什么。”这会给我留下一个坏印象。
第三个方案是用 goto
Loop:
…
goto Loop;
应试者如给出上面的方案,这说明或者他是一个汇编语言程序员(这也许是好事)或者他是一个想进入新领域的BASIC/FORTRAN程序员。
数据声明(Data declarations)
5. 用变量a给出下面的定义
a) 一个整型数(An integer)
b) 一个指向整型数的指针(A pointer to an integer)
c) 一个指向指针的的指针,它指向的指针是指向一个整型数(A pointer to a pointer to an integer)
d) 一个有10个整型数的数组(An array of 10 integers)
e) 一个有10个指针的数组,该指针是指向一个整型数的(An array of 10 pointers to integers)
f) 一个指向有10个整型数数组的指针(A pointer to an array of 10 integers)
g) 一个指向函数的指针,该函数有一个整型参数并返回一个整型数(A pointer to a function that takes an integer as an argument and returns an integer)
h) 一个有10个指针的数组,该指针指向一个函数,该函数有一个整型参数并返回一个整型数( An array of ten pointers to functions that take an integer argument and return an integer )
答案是:
a) int a; // An integer
b) int *a; // A pointer to an integer
c) int **a; // A pointer to a pointer to an integer
d) int a[10]; // An array of 10 integers
e) int *a[10]; // An array of 10 pointers to integers
f) int (*a)[10]; // A pointer to an array of 10 integers
g) int (*a)(int); // A pointer to a function a that takes an integer argument and returns an integer
h) int (*a[10])(int); // An array of 10 pointers to functions that take an integer argument and return an integer
1. 用预处理指令#define 声明一个常数,用以表明1年中有多少秒(忽略闰年问题)
#define SECONDS_PER_YEAR (60 * 60 * 24 * 365)UL
我在这想看到几件事情:
1). #define 语法的基本知识(例如:不能以分号结束,括号的使用,等等)
2). 懂得预处理器将为你计算常数表达式的值,因此,直接写出你是如何计算一年中有多少秒而不是计算出实际的值,是更清晰而没有代价的。
3). 意识到这个表达式将使一个16位机的整型数溢出-因此要用到长整型符号L,告诉编译器这个常数是的长整型数。
4). 如果你在你的表达式中用到UL(表示无符号长整型),那么你有了一个好的起点。记住,第一印象很重要。
2. 写一个“标准”宏MIN,这个宏输入两个参数并返回较小的一个。
#define MIN(A,B) ((A) <= (B) (A) : ))
这个测试是为下面的目的而设的:
1). 标识#define在宏中应用的基本知识。这是很重要的,因为直到嵌入(inline)操作符变为标准C的一部分,宏是方便产生嵌入代码的唯一方法,对于嵌入式系统来说,为了能达到要求的性能,嵌入代码经常是必须的方法。
2). 三重条件操作符的知识。这个操作符存在C语言中的原因是它使得编译器能产生比if-then-else更优化的代码,了解这个用法是很重要的。
3). 懂得在宏中小心地把参数用括号括起来
4). 我也用这个问题开始讨论宏的副作用,例如:当你写下面的代码时会发生什么事?
least = MIN(*p++, b);
3. 预处理器标识#error的目的是什么?
如果你不知道答案,请看参考文献1。这问题对区分一个正常的伙计和一个书呆子是很有用的。只有书呆子才会读C语言课本的附录去找出象这种
问题的答案。当然如果你不是在找一个书呆子,那么应试者最好希望自己不要知道答案。
死循环(Infinite loops)
4. 嵌入式系统中经常要用到无限循环,你怎么样用C编写死循环呢?
这个问题用几个解决方案。我首选的方案是:
while(1) { }
一些程序员更喜欢如下方案:
for(;;) { }
这个实现方式让我为难,因为这个语法没有确切表达到底怎么回事。如果一个应试者给出这个作为方案,我将用这个作为一个机会去探究他们这样做的
基本原理。如果他们的基本答案是:“我被教着这样做,但从没有想到过为什么。”这会给我留下一个坏印象。
第三个方案是用 goto
Loop:
...
goto Loop;
应试者如给出上面的方案,这说明或者他是一个汇编语言程序员(这也许是好事)或者他是一个想进入新领域的BASIC/FORTRAN程序员。
数据声明(Data declarations)
5. 用变量a给出下面的定义
a) 一个整型数(An integer)
b) 一个指向整型数的指针(A pointer to an integer)
c) 一个指向指针的的指针,它指向的指针是指向一个整型数(A pointer to a pointer to an integer)
d) 一个有10个整型数的数组(An array of 10 integers)
e) 一个有10个指针的数组,该指针是指向一个整型数的(An array of 10 pointers to integers)
f) 一个指向有10个整型数数组的指针(A pointer to an array of 10 integers)
g) 一个指向函数的指针,该函数有一个整型参数并返回一个整型数(A pointer to a function that takes an integer as an argument and returns an integer)
h) 一个有10个指针的数组,该指针指向一个函数,该函数有一个整型参数并返回一个整型数( An array of ten pointers to functions that take an integer argument and return an integer )
答案是:
a) int a; // An integer
b) int *a; // A pointer to an integer
c) int **a; // A pointer to a pointer to an integer
d) int a[10]; // An array of 10 integers
e) int *a[10]; // An array of 10 pointers to integers
f) int (*a)[10]; // A pointer to an array of 10 integers
g) int (*a)(int); // A pointer to a function a that takes an integer argument and returns an integer
h) int (*a[10])(int); // An array of 10 pointers to functions that take an integer argument and return an integer
人们经常声称这里有几个问题是那种要翻一下书才能回答的问题,我同意这种说法。当我写这篇文章时,为了确定语法的正确性,我的确查了一下书。
但是当我被面试的时候,我期望被问到这个问题(或者相近的问题)。因为在被面试的这段时间里,我确定我知道这个问题的答案。应试者如果不知道
所有的答案(或至少大部分答案),那么也就没有为这次面试做准备,如果该面试者没有为这次面试做准备,那么他又能为什么出准备呢?
Static
6. 关键字static的作用是什么?
这个简单的问题很少有人能回答完全。在C语言中,关键字static有三个明显的作用:
1). 在函数体,一个被声明为静态的变量在这一函数被调用过程中维持其值不变。
2). 在模块内(但在函数体外),一个被声明为静态的变量可以被模块内所用函数访问,但不能被模块外其它函数访问。它是一个本地的全局变量。
3). 在模块内,一个被声明为静态的函数只可被这一模块内的其它函数调用。那就是,这个函数被限制在声明它的模块的本地范围内使用。
大多数应试者能正确回答第一部分,一部分能正确回答第二部分,同是很少的人能懂得第三部分。这是一个应试者的严重的缺点,因为他显然不懂得本地化数据和代码范围的好处和重要性。
Const
7.关键字const是什么含意?
我只要一听到被面试者说:“const意味着常数”,我就知道我正在和一个业余者打交道。去年Dan Saks已经在他的文章里完全概括了const的所有用法,因此ESP(译者:Embedded Systems Programming)的每一位读者应该非常熟悉const能做什么和不能做什么.
如果你从没有读到那篇文章,只要能说出const意味着 “只读”就可以了。尽管这个答案不是完全的答案,但我接受它作为一个正确的答案。(如果你想知道更详细的答案,仔细读一下Saks的文章吧。)如果应试者能正确回答这个问题,我将问他一个附加的问题:下面的声明都是什么意思?
const int a;
int const a;
const int *a;
int * const a;
int const * a const;
前两个的作用是一样,a是一个常整型数。第三个意味着a是一个指向常整型数的指针(也就是,整型数是不可修改的,但指针可以)。第四个意思a是一个指向整型数的常指针(也就是说,指针指向的整型数是可以修改的,但指针是不可修改的)。最后一个意味着a是一个指向常整型数的常指针(也就是说,指针指向的整型数是不可修改的,同时指针也是不可修改的)。如果应试者能正确回答这些问题,那么他就给我留下了一个好印象。顺带提一句,也许你可能会问,即使不用关键字 const,也还是能很容易写出功能正确的程序,那么我为什么还要如此看重关键字const呢?我也如下的几下理由:
1). 关键字const的作用是为给读你代码的人传达非常有用的信息,实际上,声明一个参数为常量是为了告诉了用户这个参数的应用目的。如果你曾花很多时间清理其它人留下的垃圾,你就会很快学会感谢这点多余的信息。(当然,懂得用const的程序员很少会留下的垃圾让别人来清理的。)
2). 通过给优化器一些附加的信息,使用关键字const也许能产生更紧凑的代码。
3). 合理地使用关键字const可以使编译器很自然地保护那些不希望被改变的参数,防止其被无意的代码修改。简而言之,这样可以减少bug的出现。
Volatile
8. 关键字volatile有什么含意 并给出三个不同的例子。
一个定义为volatile的变量是说这变量可能会被意想不到地改变,这样,编译器就不会去假设这个变量的值了。精确地说就是,优化器在用到这个变量时必须每次都小心地重新读取这个变量的值,而不是使用保存在寄存器里的备份。下面是volatile变量的几个例子:
1). 并行设备的硬件寄存器(如:状态寄存器)
2). 一个中断服务子程序中会访问到的非自动变量(Non-automatic variables)
3). 多线程应用中被几个任务共享的变量
回答不出这个问题的人是不会被雇佣的。我认为这是区分C程序员和嵌入式系统程序员的最基本的问题。嵌入式系统程序员经常同硬件、中断、RTOS等等打交道,所用这些都要求volatile变量。不懂得volatile内容将会带来灾难。
假设被面试者正确地回答了这是问题(嗯,怀疑这否会是这样),我将稍微深究一下,看一下这家伙是不是直正懂得volatile完全的重要性。
1). 一个参数既可以是const还可以是volatile吗?解释为什么。
2). 一个指针可以是volatile 吗?解释为什么。
3). 下面的函数有什么错误:
int square(volatile int *ptr)
{ return *ptr * *ptr;
} 下面是答案:
1). 是的。一个例子是只读的状态寄存器。它是volatile因为它可能被意想不到地改变。它是const因为程序不应该试图去修改它。
2). 是的。尽管这并不很常见。一个例子是当一个中服务子程序修该一个指向一个buffer的指针时。
3). 这段代码的有个恶作剧。这段代码的目的是用来返指针*ptr指向值的平方,但是,由于*ptr指向一个volatile型参数,编译器将产生类似下面的代码:
int square(volatile int *ptr)
{ int a,b;
a = *ptr;
b = *ptr;
return a * b;
} 由于*ptr的值可能被意想不到地该变,因此a和b可能是不同的。结果,这段代码可能返不是你所期望的平方值!正确的代码如下:
long square(volatile int *ptr)
{ int a;
a = *ptr;
return a * a;
}
位操作(Bit manipulation)
9. 嵌入式系统总是要用户对变量或寄存器进行位操作。给定一个整型变量a,写两段代码,第一个设置a的bit 3,第二个清除a 的bit 3。在以上两个操作中,要保持其它位不变。
对这个问题有三种基本的反应
1). 不知道如何下手。该被面者从没做过任何嵌入式系统的工作。
2). 用bit fields。Bit fields是被扔到C语言死角的东西,它保证你的代码在不同编译器之间是不可移植的,同时也保证了的你的代码是不可重用的。我最近不幸看到 Infineon为其较复杂的通信芯片写的驱动程序,它用到了bit fields因此完全对我无用,因为我的编译器用其它的方式来实现bit fields的。从道德讲:永远不要让一个非嵌入式的家伙粘实际硬件的边。
3). 用 #defines 和 bit masks 操作。这是一个有极高可移植性的方法,是应该被用到的方法。最佳的解决方案如下:
#define BIT3 (0x1<<3)
static int a;
void set_bit3(void)
{ a |= BIT3;
} void clear_bit3(void)
{ a &= ~BIT3;
} 一些人喜欢为设置和清除值而定义一个掩码同时定义一些说明常数,这也是可以接受的。我希望看到几个要点:说明常数、|=和&=~操作。
10. 嵌入式系统经常具有要求程序员去访问某特定的内存位置的特点。在某工程中,要求设置一绝对地址为0x67a9的整型变量的值为0xaa66。编译器是一个纯粹的ANSI编译器。写代码去完成这一任务。
这一问题测试你是否知道为了访问一绝对地址把一个整型数强制转换(typecast)为一指针是合法的。这一问题的实现方式随着个人风格不同而不同。典型的类似代码如下:
int *ptr;
ptr = (int *)0x67a9;
*ptr = 0xaa55;
一个较晦涩的方法是:
*(int * const)(0x67a9) = 0xaa55;
即使你的品味更接近第二种方案,但我建议你在面试时使用第一种方案。
中断(Interrupts)
11. 中断是嵌入式系统中重要的组成部分,这导致了很多编译开发商提供一种扩展—让标准C支持中断。具代表事实是,产生了一个新的关键字 __interrupt。下面的代码就使用了__interrupt关键字去定义了一个中断服务子程序(ISR),请评论一下这段代码的。
__interrupt double compute_area (double radius)
{ double area = PI * radius * radius;
printf(" Area = %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机程序往往把硬件作为一个无法避免的烦恼。
到了这个阶段,应试者或者完全垂头丧气了或者信心满满志在必得。如果显然应试者不是很好,那么这个测试就在这里结束了。但如果显然应试者做得不错,那么我就扔出下面的追加问题,这些问题是比较难的,我想仅仅非常优秀的应试者能做得不错。提出这些问题,我希望更多看到应试者应付问题的方法,而不是答案。不管如何,你就当是这个娱乐吧…
动态内存分配(Dynamic memory allocation)
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”。我用这个来开始讨论这样的一问题,看看被面试者是否想到库例程这样做是正确。得到正确的答案固然重要,但解决问题的方法和你做决定的基本原理更重要些。
Typedef
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。
如果你知道答案,或猜出正确答案,做得好。如果你不知道答案,我也不把这个当作问题。我发现这个问题的最大好处是:这是一个关于代码编写风格,代码的可读性,代码的可修改性的好的话题
What will print out?
main()
{ char *p1=“name”;
char *p2;
p2=(char*)malloc(20);
memset (p2, 0, 20);
while(*p2++ = *p1++);
printf(“%sn”,p2);
}
Answer:empty string.
What will be printed as the result of the operation below:
main()
{ int x=20,y=35;
x=y++ + x++;
y= ++y + ++x;
printf(“%d%dn”,x,y);
}
Answer : 5794
What will be printed as the result of the operation below:
main()
{ int x=5;
printf(“%d,%d,%dn”,x,x< <2,x>>2);
}
Answer: 5,20,1
What will be printed as the result of the operation below:
#define swap(a,b) a=a+b;b=a-b;a=a-b;
void main()
{ int x=5, y=10;
swap (x,y);
printf(“%d %dn”,x,y);
swap2(x,y);
printf(“%d %dn”,x,y);
}
int swap2(int a, int b)
{ int temp;
temp=a;
b=a;
a=temp;
return 0;
}
Answer: 10, 5
10, 5
What will be printed as the result of the operation below:
main()
{ char *ptr = ” Cisco Systems”;
*ptr++; printf(“%sn”,ptr);
ptr++;
printf(“%sn”,ptr);
}
Answer:Cisco Systems
isco systems
What will be printed as the result of the operation below:
main()
{ char s1[]=“Cisco”;
char s2[]= “systems”;
printf(“%s”,s1);
} Answer: Cisco
What will be printed as the result of the operation below:
main()
{ char *p1;
char *p2;
p1=(char *)malloc(25);
p2=(char *)malloc(25);
strcpy(p1,”Cisco”);
strcpy(p2,“systems”);
strcat(p1,p2);
printf(“%s”,p1);
}
Answer: Ciscosystems
The following variable is available in file1.c, who can access it?:
static int average;
Answer: all the functions in the file1.c can access the variable.
WHat will be the result of the following code?
#define TRUE 0 // some code
while(TRUE)
{
// some code
}
Answer: This will not go into the loop as TRUE is defined as 0.
What will be printed as the result of the operation below:
int x;
int modifyvalue()
{ return(x+=10);
} int changevalue(int x)
{ return(x+=1);
}
void main()
{ int x=10;
x++;
changevalue(x);
x++;
modifyvalue();
printf("First output:%dn",x);
x++;
changevalue(x);
printf("Second output:%dn",x);
modifyvalue();
printf("Third output:%dn",x);
}
Answer: 12 , 13 , 13
What will be printed as the result of the operation below:
main()
{ int x=10, y=15;
x = x++;
y = ++y;
printf(“%d %dn”,x,y);
}
Answer: 11, 16
What will be printed as the result of the operation below:
main()
{ int a=0;
if(a==0)
printf(“Cisco Systemsn”);
printf(“Cisco Systemsn”);
}
Answer: Two lines with “Cisco Systems” will be printed.
再次更新C++相关题集
1. 以下三条输出语句分别输出什么?[C易]
char str1[] = "abc";
char str2[] = "abc";
const char str3[] = "abc";
const char str4[] = "abc";
const char* str5 = "abc";
const char* str6 = "abc";
cout << boolalpha << ( str1==str2 ) << endl; // 输出什么?
cout << boolalpha << ( str3==str4 ) << endl; // 输出什么?
cout << boolalpha << ( str5==str6 ) << endl; // 输出什么?
13. 非C++内建型别 A 和 B,在哪几种情况下B能隐式转化为A?[C++中等]
答:
a. class B : public A { ……} // B公有继承自A,可以是间接继承的
b. class B { operator A( ); } // B实现了隐式转化为A的转化
c. class A { A( const B& ); } // A实现了non-explicit的参数为B(可以有其他带默认值的参数)构造函数
d. A& operator= ( const A& ); // 赋值操作,虽不是正宗的隐式类型转换,但也可以勉强算一个
12. 以下代码中的两个sizeof用法有问题吗?[C易]
void UpperCase( char str[] ) // 将 str 中的小写字母转换成大写字母
{ for( size_t i=0; i<sizeof(str)/sizeof(str[0]); ++i )
if( 'a'<=str[i] && str[i]<='z' )
str[i] -= ('a'-'A' );
} char str[] = "aBcDe";
cout << "str字符长度为: " << sizeof(str)/sizeof(str[0]) << endl;
UpperCase( str );
cout << str << endl;
7. 以下代码有什么问题?[C难]
void char2Hex( char c ) // 将字符以16进制表示
{ char ch = c/0x10 + '0'; if( ch > '9' ) ch += ('A'-'9'-1);
char cl = c%0x10 + '0'; if( cl > '9' ) cl += ('A'-'9'-1);
cout << ch << cl << ' ';
} char str[] = "I love 中国";
for( size_t i=0; i<strlen(str); ++i )
char2Hex( str[i] );
cout << endl;
4. 以下代码有什么问题?[C++易]
struct Test
{ Test( int ) {}
Test() {}
void fun() {}
};
void main( void )
{ Test a(1);
a.fun();
Test b();
b.fun();
}
5. 以下代码有什么问题?[C++易]
cout << (true?1:"1") << endl;
8. 以下代码能够编译通过吗,为什么?[C++易]
unsigned int const size1 = 2;
char str1[ size1 ];
unsigned int temp = 0;
cin >> temp;
unsigned int const size2 = temp;
char str2[ size2 ];
9. 以下代码中的输出语句输出0吗,为什么?[C++易]
struct CLS
{ int m_i;
CLS( int i ) : m_i(i) {}
CLS()
{ CLS(0);
} };
CLS obj;
cout << obj.m_i << endl;
10. C++中的空类,默认产生哪些类成员函数?[C++易]
答:
class Empty
{ public:
Empty(); // 缺省构造函数
Empty( const Empty& ); // 拷贝构造函数
~Empty(); // 析构函数
Empty& operator=( const Empty& ); // 赋值运算符
Empty* operator&(); // 取址运算符
const Empty* operator&() const; // 取址运算符 const
};
3. 以下两条输出语句分别输出什么?[C++难]
float a = 1.0f;
cout << (int)a << endl;
cout << (int&)a << endl;
cout << boolalpha << ( (int)a == (int&)a ) << endl; // 输出什么?
float b = 0.0f;
cout << (int)b << endl;
cout << (int&)b << endl;
cout << boolalpha << ( (int)b == (int&)b ) << endl; // 输出什么?
2. 以下反向遍历array数组的方法有什么错误?[STL易]
vector array;
array.push_back( 1 );
array.push_back( 2 );
array.push_back( 3 );
for( vector::size_type i=array.size()-1; i>=0; --i ) // 反向遍历array数组
{ cout << array[i] << endl;
}
6. 以下代码有什么问题?[STL易]
typedef vector IntArray;
IntArray array;
array.push_back( 1 );
array.push_back( 2 );
array.push_back( 2 );
array.push_back( 3 );
// 删除array数组中所有的2
for( IntArray::iterator itor=array.begin(); itor!=array.end(); ++itor )
{ if( 2 == *itor ) array.erase( itor );
}
11. 写一个函数,完成内存之间的拷贝。[考虑问题是否全面]
答:
void* mymemcpy( void *dest, const void *src, size_t count )
{
char* pdest = static_cast<char*>( dest );
const char* psrc = static_cast<const char*>( src );
if( pdest>psrc && pdest<psrc+cout ) 能考虑到这种情况就行了
{
for( size_t i=count-1; i!=-1; --i )
pdest[i] = psrc[i];
}
else
{
for( size_t i=0; i<count; ++i )
pdest[i] = psrc[i];
}
return dest;
}
int main( void )
{
char str[] = "0123456789";
mymemcpy( str+1, str+0, 9 );
cout << str << endl;
system( "Pause" );
return 0;
}
嵌入式面试 01
16、那种排序方法最快? (华为面试题)
17、写出两个排序算法,问哪个好?(威盛)
18、编一个简单的求n!的程序 。(Infineon笔试试题)
19、用一种编程语言写n!的算法。(威盛VIA 2003.11.06 上海笔试试题)
20、用C语言写一个递归算法求N!;(华为面试题)
21、给一个C的函数,关于字符串和数组,找出错误;(华为面试题)
22、防火墙是怎么实现的? (华为面试题)
23、你对哪方面编程熟悉?(华为面试题)
24、冒泡排序的原理。(新太硬件面题)
25、操作系统的功能。(新太硬件面题)
26、学过的计算机语言及开发的系统。(新太硬件面题)
27、一个农夫发现围成正方形的围栏比长方形的节省4个木桩但是面积一样.羊的数目和正 方形围栏的桩子的个数一样但是小于36,问有多少羊?(威盛)
28、C语言实现统计某个cell在某.v文件调用的次数(这个题目真bt) (威盛VIA2003.11.06 上海笔试试题)
29、用C语言写一段控制手机中马达振子的驱动程序。(威胜)
30、用perl或TCL/Tk实现一段字符串识别和比较的程序。(未知)
31、给出一个堆栈的结构,求中断后显示结果,主要是考堆栈压入返回地址存放在低端地 址还是高端。(未知)
32、一些DOS命令,如显示文件,拷贝,删除。(未知)
33、设计一个类,使得该类任何形式的派生类无论怎么定义和实现,都无法产生任何对象 实例。(IBM)
34、What is pre-emption? (Intel)
35、What is the state of a process if a resource is not available? (Intel)
36、三个 float a,b,c;问值(a b) c==(b a) c, (a b) c==(a c) b。(Intel)
37、把一个链表反向填空。 (lucent) 38、x^4 a*x^3 x^2 c*x d 最少需要做几次乘法? (Dephi) ____________________________________________________________
六、主观题
1、你认为你从事研发工作有哪些特点?(仕兰微面试题目)
2、说出你的最大弱点及改进方法。(威盛VIA 2003.11.06 上海笔试试题)
3、说出你的理想。说出你想达到的目标。 题目是英文出的,要用英文回答。(威盛VIA 2003.11.06 上海笔试试题)
4、我们将研发人员分为若干研究方向,对协议和算法理解(主要应用在网络通信、图象语音压缩方面)、电子系统方案的研究、用MCU、DSP编程实现电路功能、用ASIC设计技术 设计电路(包括MCU、DSP本身)、电路功能模块设计(包括模拟电路和数字电路)、集成 电路后端设计(主要是指综合及自动布局布线技术)、集成电路设计与工艺接口的研究. 你希望从事哪方面的研究?(可以选择多个方向。另外,已经从事过相关研发的人员可以详细描述你的研发经历)。(仕兰微面试题目)
5、请谈谈对一个系统设计的总体思路。针对这个思路,你觉得应该具备哪些方面的知识?(仕兰微面试题目)
6、设想你将设计完成一个电子电路方案。请简述用EDA软件(如PROTEL)进行设计(包括 原理图和PCB图)到调试出样机的整个过程。在各环节应注意哪些问题?电源的稳定,电 容的选取,以及布局的大小。(汉王笔试)
七、共同的注意点
1.一般情况下,面试官主要根据你的简历提问,所以一定要对自己负责,把简历上的东西搞明白;
2.个别招聘针对性特别强,就招目前他们确的方向的人,这种情况下,就要投其所好,尽量介绍其所关心的东西。
3.其实技术面试并不难,但是由于很多东西都忘掉了,才觉得有些难。所以最好在面试前把该看的书看看。
4.虽然说技术面试是实力的较量与体现,但是不可否认,由于不用面试官/公司所专领域及爱好不同,也有面试也有很大的偶然性,需要冷静对待。不能因为被拒,就否认自己或责骂公司。
5.面试时要take it easy,对越是自己钟情的公司越要这样。
嵌入式面试 0
DSP、嵌入式、软件等
1、请用方框图描述一个你熟悉的实用数字信号处理系统,并做简要的分析;如果没有,也可以自己设计一个简单的数字信号处理系统,并描述其功能及用途。(仕兰微面试题目)
2、数字滤波器的分类和结构特点。(仕兰微面试题目)
3、IIR,FIR滤波器的异同。(新太硬件面题)
4、拉氏变换与Z变换公式等类似东西,随便翻翻书把如.h(n)=-a*h(n-1) b*δ(n) a.求h(n)的z变换;b.问该系统是否为稳定系统;c.写出FIR数字滤波器的差分方程;(未知)
5、DSP和通用处理器在结构上有什么不同,请简要画出你熟悉的一种DSP结构图。(信威dsp软件面试题)
6、说说定点DSP和浮点DSP的定义(或者说出他们的区别)(信威dsp软件面试题)
7、说说你对循环寻址和位反序寻址的理解.(信威dsp软件面试题)
8、请写出【-8,7】的二进制补码,和二进制偏置码。用Q15表示出0.5和-0.5.(信威dsp软件面试题)
9、DSP的结构(哈佛结构);(未知)
10、嵌入式处理器类型(如ARM),操作系统种类(Vxworks,ucos,winCE,linux),操作系统方面偏CS方向了,在CS篇里面讲了;(未知)
11、有一个LDO芯片将用于对手机供电,需要你对他进行评估,你将如何设计你的测试项目?
12、某程序在一个嵌入式系统(200M CPU,50M SDRAM)中已经最优化了,换到零一个系统(300M CPU,50M SDRAM)中是否还需要优化? (Intel)
13、请简要描述HUFFMAN编码的基本原理及其基本的实现方法。(仕兰微面试题目)
14、说出OSI七层网络协议中的四层(任意四层)。(仕兰微面试题目)
15、A) (仕兰微面试题目) #i nclude void testf(int*p) { *p =1; } main() { int *n,m[2]; n=m; m[0]=1; m[1]=8; testf(n); printf("Data v alue is %d ",*n); } ------------------------------ B) #i nclude void testf(int**p) { *p =1; } main() {int *n,m[2]; n=m; m[0]=1; m[1]=8; testf(&n); printf(Data v alue is %d",*n); } 下面的结果是程序A还是程序B的? Data v alue is 8 那么另一段程序的结果是什么?
预处理器(Preprocessor)
1. 用预处理指令#define 声明一个常数,用以表明1年中有多少秒(忽略闰年问题)
#define SECONDS_PER_YEAR (60 * 60 * 24 * 365)UL
我在这想看到几件事情:
1). #define 语法的基本知识(例如:不能以分号结束,括号的使用,等等)
2). 懂得预处理器将为你计算常数表达式的值,因此,直接写出你是如何计算一年中有多少秒而不是计算出实际的值,是更清晰而没有代价的。
3). 意识到这个表达式将使一个16位机的整型数溢出-因此要用到长整型符号L,告诉编译器这个常数是的长整型数。
4). 如果你在你的表达式中用到UL(表示无符号长整型),那么你有了一个好的起点。记住,第一印象很重要。
2. 写一个“标准”宏MIN,这个宏输入两个参数并返回较小的一个。
#define MIN(A,B) ((A) <= (B) (A) : )) ( A>)=(B)?(B):(A)
C\C++试题集 C\C++ Development这个测试是为下面的目的而设的:
1). 标识#define在宏中应用的基本知识。这是很重要的,因为直到嵌入(inline)操作符变为标准C的一部分,宏是方便产生嵌入代码的唯一方法,对于嵌入式系统来说,为了能达到要求的性能,嵌入代码经常是必须的方法。
2). 三重条件操作符的知识。这个操作符存在C语言中的原因是它使得编译器能产生比if-then-else更优化的代码,了解这个用法是很重要的。
3). 懂得在宏中小心地把参数用括号括起来
4). 我也用这个问题开始讨论宏的副作用,例如:当你写下面的代码时会发生什么事?
Least = MIN(*p++, b);
3. 预处理器标识#error的目的是什么?
如果你不知道答案,请看参考文献1。这问题对区分一个正常的伙计和一个书呆子是很有用的。只有书呆子才会读C语言课本的附录去找出象这种
问题的答案。当然如果你不是在找一个书呆子,那么应试者最好希望自己不要知道答案。
死循环(Infinite loops)
4. 嵌入式系统中经常要用到无限循环,你怎么样用C编写死循环呢?
这个问题用几个解决方案。我首选的方案是:
while(1) { }
一些程序员更喜欢如下方案:
for(;;) { }
这个实现方式让我为难,因为这个语法没有确切表达到底怎么回事。如果一个应试者给出这个作为方案,我将用这个作为一个机会去探究他们这样做的
基本原理。如果他们的基本答案是:“我被教着这样做,但从没有想到过为什么。”这会给我留下一个坏印象。
第三个方案是用 goto
Loop:
…
goto Loop;
应试者如给出上面的方案,这说明或者他是一个汇编语言程序员(这也许是好事)或者他是一个想进入新领域的BASIC/FORTRAN程序员。
数据声明(Data declarations)
5. 用变量a给出下面的定义
a) 一个整型数(An integer)
b) 一个指向整型数的指针(A pointer to an integer)
c) 一个指向指针的的指针,它指向的指针是指向一个整型数(A pointer to a pointer to an integer)
d) 一个有10个整型数的数组(An array of 10 integers)
e) 一个有10个指针的数组,该指针是指向一个整型数的(An array of 10 pointers to integers)
f) 一个指向有10个整型数数组的指针(A pointer to an array of 10 integers)
g) 一个指向函数的指针,该函数有一个整型参数并返回一个整型数(A pointer to a function that takes an integer as an argument and returns an integer)
h) 一个有10个指针的数组,该指针指向一个函数,该函数有一个整型参数并返回一个整型数( An array of ten pointers to functions that take an integer argument and return an integer )
答案是:
a) int a; // An integer
b) int *a; // A pointer to an integer
c) int **a; // A pointer to a pointer to an integer
d) int a[10]; // An array of 10 integers
e) int *a[10]; // An array of 10 pointers to integers
f) int (*a)[10]; // A pointer to an array of 10 integers
g) int (*a)(int); // A pointer to a function a that takes an integer argument and returns an integer
h) int (*a[10])(int); // An array of 10 pointers to functions that take an integer argument and return an integer
人们经常声称这里有几个问题是那种要翻一下书才能回答的问题,我同意这种说法。当我写这篇文章时,为了确定语法的正确性,我的确查了一下书。
但是当我被面试的时候,我期望被问到这个问题(或者相近的问题)。因为在被面试的这段时间里,我确定我知道这个问题的答案。应试者如果不知道
所有的答案(或至少大部分答案),那么也就没有为这次面试做准备,如果该面试者没有为这次面试做准备,那么他又能为什么出准备呢?
Static
6. 关键字static的作用是什么?
这个简单的问题很少有人能回答完全。在C语言中,关键字static有三个明显的作用:
1). 在函数体,一个被声明为静态的变量在这一函数被调用过程中维持其值不变。
2). 在模块内(但在函数体外),一个被声明为静态的变量可以被模块内所用函数访问,但不能被模块外其它函数访问。它是一个本地的全局变量。
3). 在模块内,一个被声明为静态的函数只可被这一模块内的其它函数调用。那就是,这个函数被限制在声明它的模块的本地范围内使用。
大多数应试者能正确回答第一部分,一部分能正确回答第二部分,同是很少的人能懂得第三部分。这是一个应试者的严重的缺点,因为他显然不懂得本地化数据和代码范围的好处和重要性。
Const
7.关键字const是什么含意?
我只要一听到被面试者说:“const意味着常数”,我就知道我正在和一个业余者打交道。去年Dan Saks已经在他的文章里完全概括了const的所有用法,因此ESP(译者:Embedded Systems Programming)的每一位读者应该非常熟悉const能做什么和不能做什么.
如果你从没有读到那篇文章,只要能说出const意味着“只读”就可以了。尽管这个答案不是完全的答案,但我接受它作为一个正确的答案。(如果你想知道更详细的答案,仔细读一下Saks的文章吧。)如果应试者能正确回答这个问题,我将问他一个附加的问题:下面的声明都是什么意思?
Const int a;
int const a;
const int *a;
int * const a;
int const * a const;
前两个的作用是一样,a是一个常整型数。第三个意味着a是一个指向常整型数的指针(也就是,整型数是不可修改的,但指针可以)。第四个意思a是一个指向整型数的常指针(也就是说,指针指向的整型数是可以修改的,但指针是不可修改的)。最后一个意味着a是一个指向常整型数的常指针(也就是说,指针指向的整型数是不可修改的,同时指针也是不可修改的)。如果应试者能正确回答这些问题,那么他就给我留下了一个好印象。顺带提一句,也许你可能会问,即使不用关键字const,也还是能很容易写出功能正确的程序,那么我为什么还要如此看重关键字const呢?我也如下的几下理由:
1). 关键字const的作用是为给读你代码的人传达非常有用的信息,实际上,声明一个参数为常量是为了告诉了用户这个参数的应用目的。如果你曾花很多时间清理其它人留下的垃圾,你就会很快学会感谢这点多余的信息。(当然,懂得用const的程序员很少会留下的垃圾让别人来清理的。)
2). 通过给优化器一些附加的信息,使用关键字const也许能产生更紧凑的代码。
3). 合理地使用关键字const可以使编译器很自然地保护那些不希望被改变的参数,防止其被无意的代码修改。简而言之,这样可以减少bug的出现。
Volatile
8. 关键字volatile有什么含意 并给出三个不同的例子。
一个定义为volatile的变量是说这变量可能会被意想不到地改变,这样,编译器就不会去假设这个变量的值了。精确地说就是,优化器在用到这个变量时必须每次都小心地重新读取这个变量的值,而不是使用保存在寄存器里的备份。下面是volatile变量的几个例子:
1). 并行设备的硬件寄存器(如:状态寄存器)
2). 一个中断服务子程序中会访问到的非自动变量(Non-automatic variables)
3). 多线程应用中被几个任务共享的变量
回答不出这个问题的人是不会被雇佣的。我认为这是区分C程序员和嵌入式系统程序员的最基本的问题。嵌入式系统程序员经常同硬件、中断、RTOS等等打交道,所用这些都要求volatile变量。不懂得volatile内容将会带来灾难。
假设被面试者正确地回答了这是问题(嗯,怀疑这否会是这样),我将稍微深究一下,看一下这家伙是不是直正懂得volatile完全的重要性。
1). 一个参数既可以是const还可以是volatile吗?解释为什么。
2). 一个指针可以是volatile 吗?解释为什么。
3). 下面的函数有什么错误:
int square(volatile int *ptr)
{
return *ptr * *ptr;
}
下面是答案:
1). 是的。一个例子是只读的状态寄存器。它是volatile因为它可能被意想不到地改变。它是const因为程序不应该试图去修改它。
2). 是的。尽管这并不很常见。一个例子是当一个中服务子程序修该一个指向一个buffer的指针时。
3). 这段代码的有个恶作剧。这段代码的目的是用来返指针*ptr指向值的平方,但是,由于*ptr指向一个volatile型参数,编译器将产生类似下面的代码:
int square(volatile int *ptr)
{
int a,b;
a = *ptr;
b = *ptr;
return a * b;
}
由于*ptr的值可能被意想不到地该变,因此a和b可能是不同的。结果,这段代码可能返不是你所期望的平方值!正确的代码如下:
long square(volatile int *ptr)
{
int a;
a = *ptr;
return a * a;
}
位操作(Bit manipulation)
9. 嵌入式系统总是要用户对变量或寄存器进行位操作。给定一个整型变量a,写两段代码,第一个设置a的bit 3,第二个清除a 的bit 3。在以上两个操作中,要保持其它位不变。
对这个问题有三种基本的反应
1). 不知道如何下手。该被面者从没做过任何嵌入式系统的工作。
2). 用bit fields。Bit fields是被扔到C语言死角的东西,它保证你的代码在不同编译器之间是不可移植的,同时也保证了的你的代码是不可重用的。我最近不幸看到Infineon为其较复杂的通信芯片写的驱动程序,它用到了bit fields因此完全对我无用,因为我的编译器用其它的方式来实现bit fields的。从道德讲:永远不要让一个非嵌入式的家伙粘实际硬件的边。
3). 用 #defines 和 bit masks 操作。这是一个有极高可移植性的方法,是应该被用到的方法。最佳的解决方案如下:
#define BIT3 (0x1<<3)
static int a;
void set_bit3(void)
{
a |= BIT3;
}
void clear_bit3(void)
{
a &= ~BIT3;
}
一些人喜欢为设置和清除值而定义一个掩码同时定义一些说明常数,这也是可以接受的。我希望看到几个要点:说明常数、|=和&=~操作。
访问固定的内存位置(Accessing fixed memory locations) C\C++ Development
10. 嵌入式系统经常具有要求程序员去访问某特定的内存位置的特点。在某工程中,要求设置一绝对地址为0x67a9的整型变量的值为0xaa66。编译器是一个纯粹的ANSI编译器。写代码去完成这一任务。
这一问题测试你是否知道为了访问一绝对地址把一个整型数强制转换(typecast)为一指针是合法的。这一问题的实现方式随着个人风格不同而不同。典型的类似代码如下:
int *ptr;
ptr = (int *)0x67a9;
*ptr = 0xaa55;
一个较晦涩的方法是:
*(int * const)(0x67a9) = 0xaa55;
即使你的品味更接近第二种方案,但我建议你在面试时使用第一种方案。
中断(Interrupts)
11. 中断是嵌入式系统中重要的组成部分,这导致了很多编译开发商提供一种扩展—让标准C支持中断。具代表事实是,产生了一个新的关键字__interrupt。下面的代码就使用了__interrupt关键字去定义了一个中断服务子程序(ISR),请评论一下这段代码的。
__interrupt double compute_area (double radius)
{
double area = PI * radius * radius;
printf(" Area = %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位(2个字节)的处理器为说,上面的代码是不正确的。应编写如下:
unsigned int compzero = ~0;
这一问题真正能揭露出应试者是否懂得处理器字长的重要性。在我的经验里,好的嵌入式程序员非常准确地明白硬件的细节和它的局限,然而PC机程序往往把硬件作为一个无法避免的烦恼。
到了这个阶段,应试者或者完全垂头丧气了或者信心满满志在必得。如果显然应试者不是很好,那么这个测试就在这里结束了。但如果显然应试者做得不错,那么我就扔出下面的追加问题,这些问题是比较难的,我想仅仅非常优秀的应试者能做得不错。提出这些问题,我希望更多看到应试者应付问题的方法,而不是答案。不管如何,你就当是这个娱乐吧…
动态内存分配(Dynamic memory allocation)
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”。我用这个来开始讨论这样的一问题,看看被面试者是否想到库例程这样做是正确。得到正确的答案固然重要,但解决问题的方法和你做决定的基本原理更重要些。
Typedef
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。
如果你知道答案,或猜出正确答案,做得好。如果你不知道答案,我也不把这个当作问题。我发现这个问题的最大好处是:这是一个关于代码编写风格,代码的可读性,代码的可修改性的好的话题
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。