【亚嵌】嵌入式程序员必须知道的16个问题

    C语言测试是招聘嵌入式系统程序员过程中必须而且有效的方法。这些年,我既参加也组织了许多这种测试,在这过程中我意识到这些测试能为带面试者和被面试者提供许多有用信息,此外,撇开面试的压力不谈,这种测试也是相当有趣的。
从被面试者的角度来讲,你能了解许多关于出题者或监考者的情况。这个测试只是出题者为显示其对ANSI标准细节的知识而不是技术技巧而设计吗?这个愚蠢的问题吗?如要你答出某个字符的ASCII值。这些问题着重考察你的系统调用和内存分配策略方面的能力吗?这标志着出题者也许花时间在微机上而不上在嵌入式系统上。如果上述任何问题的答案是“是”的话,那么我知道我得认真考虑我是否应该去做这份工作。
从面试者的角度来讲,一个测试也许能从多方面揭示应试者的素质:最基本的,你能了解应试者C语言的水平。不管怎么样,看一下这人如何回答他不会的问题也是满有趣。应试者是以好的直觉做出明智的选择,还是只是瞎蒙呢?当应试者在某个问题上卡住时是找借口呢,还是表现出对问题的真正的好奇心,把这看成学习的机会呢?我发现这些信息与他们的测试成绩一样有用。
有了这些想法,我决定出一些真正针对嵌入式系统的考题,希望这些令人头痛的考题能给正在找工作的人一点帮住。这些问题都是我这些年实际碰到的。其中有些题很难,但它们应该都能给你一点启迪。
这个测试适于不同水平的应试者,大多数初级水平的应试者的成绩会很差,经验丰富的程序员应该有很好的成绩。为了让你能自己决定某些问题的偏好,每个问题没有分配分数,如果选择这些考题为你所用,请自行按你的意思分配分数。
预处理器(Preprocessor)
1 . 用预处理指令#define 声明一个常数,用以表明1年中有多少秒(忽略闰年问题)

#define SECONDS_PER_YEAR (60 * 60 * 24 * 365)UL
我在这想看到几件事情:
?; #define 语法的基本知识(例如:不能以分号结束,括号的使用,等等)

?; 懂得预处理器将为你计算常数表达式的值,因此,直接写出你是如何计算一年中有多少秒而不是计算出实际的值,是更清晰而没有代价的。
?; 意识到这个表达式将使一个16位机的整型数溢出-因此要用到长整型符号L,告诉编译器这个常数是的长整型数。
?; 如果你在你的表达式中用到UL(表示无符号长整型),那么你有了一个好的起点。记住,第一印象很重要。
2 . 写一个“标准”宏MIN ,这个宏输入两个参数并返回较小的一个。

#define MIN(A,B) ((A) <= (B) ? (A) : (B))
这个测试是为下面的目的而设的:
?; 标识#define在宏中应用的基本知识。这是很重要的,因为直到嵌入(inline)操作符变为标准C的一部分,宏是方便产生嵌入代码的唯一方法,对于嵌入式系统来说,为了能达到要求的性能,嵌入代码经常是必须的方法。
?; 三重条件操作符的知识。这个操作符存在C语言中的原因是它使得编译器能产生比if-then-else更优化的代码,了解这个用法是很重要的。
?; 懂得在宏中小心地把参数用括号括起来
?; 我也用这个问题开始讨论宏的副作用,例如:当你写下面的代码时会发生什么事?
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有三个明显的作用:
?; 在函数体,一个被声明为静态的变量在这一函数被调用过程中维持其值不变。
?; 在模块内(但在函数体外),一个被声明为静态的变量可以被模块内所用函数访问,但不能被模块外其它函数访问。它是一个本地的全局变量。
?; 在模块内,一个被声明为静态的函数只可被这一模块内的其它函数调用。那就是,这个函数被限制在声明它的模块的本地范围内使用。
大多数应试者能正确回答第一部分,一部分能正确回答第二部分,同是很少的人能懂得第三部分。这是一个应试者的严重的缺点,因为他显然不懂得本地化数据和代码范围的好处和重要性。

了解更多:www.akaedu.org,或者在QQ群:30332464下载

Const

  • 0
    点赞
  • 15
    评论
  • 0
    收藏
  • 打赏
    打赏
  • 扫一扫,分享海报

目录 历史 前言 I. C语言入门 1. 程序的基本概念 1. 程序和编程语言 2. 自然语言和形式语言 3. 程序的调试 4. 第一程序 2. 常量、变量和表达式 1. 继续Hello World 2. 常量 3. 变量 4. 赋值 5. 表达式 6. 字符类型与字符编码 3. 简单函数 1. 数学函数 2. 自定义函数 3. 形参和实参 4. 全局变量、局部变量和作用域 4. 分支语句 1. if语句 2. if/else语句 3. 布尔代数 4. switch语句 5. 深入理解函数 1. return语句 2. 增量式开发 3. 递归 6. 循环语句 1. while语句 2. do/while语句 3. for语句 4. break和continue语句 5. 套循环 6. goto语句和标号 7. 结构体 1. 复合类型与结构体 2. 数据抽象 3. 数据类型标志 4. 套结构体 8. 数组 1. 数组的基本概念 2. 数组应用实例:统计随机数 3. 数组应用实例:直方图 4. 字符串 5. 多维数组 9. 编码风格 1. 缩进和空白 2. 注释 3. 标识符命名 4. 函数 5. indent工具 10. gdb 1. 单步执行和跟踪函数调用 2. 断点 3. 观察点 4. 段错误 11. 排序与查找 1. 算法的概念 2. 插入排序 3. 算法的时间复杂度分析 4. 归并排序 5. 线性查找 6. 折半查找 12. 栈与队列 1. 数据结构的概念 2. 堆栈 3. 深度优先搜索 4. 队列与广度优先搜索 5. 环形队列 13. 本阶段总结 II. C语言本质 14. 计算机中数的表示 1. 为什么计算机用二进制计数 2. 不同进制之间的换算 3. 整数的加减运算 3.1. Sign and Magnitude表示法 3.2. 1's Complement表示法 3.3. 2's Complement表示法 3.4. 有符号数和无符号数 4. 浮点数 15. 数据类型详解 1. 整型 2. 浮点型 3. 类型转换 3.1. Integer Promotion 3.2. Usual Arithmetic Conversion 3.3. 由赋值产生的类型转换 3.4. 强制类型转换 3.5. 编译器如何处理类型转换 16. 运算符详解 1. 位运算 1.1. 按位与、或、异或、取反运算 1.2. 移位运算 1.3. 掩码 1.4. 异或运算的一些特性 2. 其它运算符 2.1. 复合赋值运算符 2.2. 条件运算符 2.3. 逗号运算符 2.4. sizeof运算符与typedef类型声明 3. Side Effect与Sequence Point 4. 运算符总结 17. 计算机体系结构基础 1. 内存与地址 2. CPU 3. 设备 4. MMU 5. Memory Hierarchy 18. x86汇编程序基础 1. 最简单的汇编程序 2. x86的寄存器 3. 第二汇编程序 4. 寻址方式 5. ELF文件 5.1. 目标文件 5.2. 可执行文件 19. 汇编与C之间的关系 1. 函数调用 2. main函数和启动例程 3. 变量的存储布局 4. 结构体和联合体 5. C内联汇编 6. volatile限定符 20. 链接详解 1. 多目标文件的链接 2. 定义和声明 2.1. extern和static关键字 2.2. 头文件 2.3. 定义和声明的详细规则 3. 静态库 4. 共享库 4.1. 编译、链接、运行 4.2. 动态链接的过程 4.3. 共享库的命名惯例 5. 虚拟内存管理 21. 预处理 1. 预处理的步骤 2. 宏定义 2.1. 函数式宏定义 2.2. 内联函数 2.3. #、##运算符和可变参数 2.4. 宏展开的步骤 3. 条件预处理指示 4. 其它预处理特性 22. Makefile基础 1. 基本规则 2. 隐含规则和模式规则 3. 变量 4. 自动处理头文件的依赖关系 5. 常用的make命令行选项 23. 指针 1. 指针的基本概念 2. 指针类型的参数和返回值 3. 指针与数组 4. 指针与const限定符 5. 指针与结构体 6. 指向指针的指针与指针数组 7. 指向数组的指针与多维数组 8. 函数类型和函数指针类型 9. 不完全类型和复杂声明 24. 函数接口 1. 本章的预备知识 1.1. strcpy与strncpy 1.2. malloc与free 2. 传入参数与传出参数 3. 两层指针的参数 4. 返回值是指针的情况 5. 回调函数 6. 可变参数 25. C标准库 1. 字符串操作函数 1.1. 初始化字符串 1.2. 取字符串的长度 1.3. 拷贝字符串 1.4. 连接字符串 1.5. 比较字符串 1.6. 搜索字符串 1.7. 分割字符串 2. 标准I/O库函数 2.1. 文件的基本概念 2.2. fopen/fclose 2.3. stdin/stdout/stderr 2.4. errno与perror函数 2.5. 以字节为单位的I/O函数 2.6. 操作读写位置的函数 2.7. 以字符串为单位的I/O函数 2.8. 以记录为单位的I/O函数 2.9. 格式化I/O函数 2.10. C标准库的I/O缓冲区 2.11. 本节综合练习 3. 数值字符串转换函数 4. 分配内存的函数 26. 链表、二叉树和哈希表 1. 链表 1.1. 单链表 1.2. 双向链表 1.3. 静态链表 1.4. 本节综合练习 2. 二叉树 2.1. 二叉树的基本概念 2.2. 排序二叉树 3. 哈希表 27. 本阶段总结 III. Linux系统编程 28. 文件与I/O 1. 汇编程序的Hello world 2. C标准I/O库函数与Unbuffered I/O函数 3. open/close 4. read/write 5. lseek 6. fcntl 7. ioctl 8. mmap 29. 文件系统 1. 引言 2. ext2文件系统 2.1. 总体存储布局 2.2. 实例剖析 2.3. 数据块寻址 2.4. 文件和目录操作的系统函数 3. VFS 3.1. 内核数据结构 3.2. dup和dup2函数 30. 进程 1. 引言 2. 环境变量 3. 进程控制 3.1. fork函数 3.2. exec函数 3.3. wait和waitpid函数 4. 进程间通信 4.1. 管道 4.2. 其它IPC机制 5. 练习:实现简单的Shell 31. Shell脚本 1. Shell的历史 2. Shell如何执行命令 2.1. 执行交互式命令 2.2. 执行脚本 3. Shell的基本语法 3.1. 变量 3.2. 文件名代换(Globbing):* ? [] 3.3. 命令代换:`或 $() 3.4. 算术代换:$(()) 3.5. 转义字符\ 3.6. 单引号 3.7. 双引号 4. bash启动脚本 4.1. 作为交互登录Shell启动,或者使用--login参数启动 4.2. 以交互非登录Shell启动 4.3. 非交互启动 4.4. 以sh命令启动 5. Shell脚本语法 5.1. 条件测试test [ 5.2. if/then/elif/else/fi 5.3. case/esac 5.4. for/do/done 5.5. while/do/done 5.6. 位置参数和特殊变量 5.7. 函数 6. Shell脚本的调试方法 32. 正则表达式 1. 引言 2. 基本语法 3. sed 4. awk 5. 练习:在C语言中使用正则表达式 33. 信号 1. 信号的基本概念 2. 产生信号 2.1. 通过终端按键产生信号 2.2. 调用系统函数向进程发信号 2.3. 由软件条件产生信号 3. 阻塞信号 3.1. 信号在内核中的表示 3.2. 信号集操作函数 3.3. sigprocmask 3.4. sigpending 4. 捕捉信号 4.1. 内核如何实现信号的捕捉 4.2. sigaction 4.3. pause 4.4. 可重入函数 4.5. sig_atomic_t类型与volatile限定符 4.6. 竞态条件与sigsuspend函数 4.7. 关于SIGCHLD信号 34. 终端、作业控制与守护进程 1. 终端 1.1. 终端的基本概念 1.2. 终端登录过程 1.3. 网络登录过程 2. 作业控制 2.1. Session与进程组 2.2. 与作业控制有关的信号 3. 守护进程 35. 线程 1. 线程的概念 2. 线程控制 2.1. 创建线程 2.2. 终止线程 3. 线程间同步 3.1. mutex 3.2. Condition Variable 3.3. Semaphore 3.4. 其它线程间同步机制 4. 编程练习 36. TCP/IP协议基础 1. TCP/IP协议栈与数据包封装 2. 以太网(RFC 894)帧格式 3. ARP数据报格式 4. IP数据报格式 5. IP地址与路由 6. UDP段格式 7. TCP协议 7.1. 段格式 7.2. 通讯时序 7.3. 流量控制 37. socket编程 1. 预备知识 1.1. 网络字节序 1.2. socket地址的数据类型及相关函数 2. 基于TCP协议的网络程序 2.1. 最简单的TCP网络程序 2.2. 错误处理与读写控制 2.3. 把client改为交互式输入 2.4. 使用fork并发处理多client的请求 2.5. setsockopt 2.6. 使用select 3. 基于UDP协议的网络程序 4. UNIX Domain Socket IPC 5. 练习:实现简单的Web服务器 5.1. 基本HTTP协议 5.2. 执行CGI程序 A. 字符编码 1. ASCII码 2. Unicode和UTF-8 3. 在Linux C编程中使用Unicode和UTF-8 B. GNU Free Documentation License Version 1.3, 3 November 2008 参考书目 索引
评论 15 您还未登录,请先 登录 后发表或查看评论
©️2022 CSDN 皮肤主题:大白 设计师:CSDN官方博客 返回首页

打赏作者

akatope

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值