1、十二五高等院校规划教材高级C语言编程凌明编著0%利里統夫大學北板BEIHANG UNIVERSITY PRESS“十二五”高等院校规划教材嵌入式系统高级C语言编程凌明编著嘘戊妙以熟燃彩内容简介本书主要介绍针对嵌入式系统基于 语言的软件项目开发流程 较为复杂的 语言编程知识与技巧 编 程风格及调试习惯 并通过对个具体的软件模块 的分析 介绍分析代码的方法以及设计软件系统需要考虑的各要素 本书以实际项目中的代码为例来进行介绍 详细分析在嵌入式系统开发中 程序员应该注意的方法 技巧和存在的陷阱本书适合用作学习嵌入式系统的高年级本科生或硕士研究生的教学用书也可作为从事嵌入式系统编 程的软 硬件工程师的
2、技术参考用书图书在版编目(CIP)数据嵌入式系统高级语言编程凌明编著 北京北京航空航天大学出版社嵌 凌 微型计算机语言程序设计中国版本图书馆 数据核字 第 号版权所有,侵权必究。嵌入式系统高级C语言编程凌明编著责任编辑冯颖北京航空航天大学出版社出版发行北京市海淀区学院路号邮编发行部电话 传真读者信箱 邮购电话印刷有限公司印装各地书店经销开本 印张 字数 千字年 月第版 年 月第次印刷印数 册-定价 兀序嵌入式系统是将先进的计算机技术半导体技术电子技术和各行各业的具体应用相结合 的产物 这就决定了它必然是个技术密集 资金密集 高度分散 不断创新的知识集成系统 然而 嵌入式系统是个非常综合的技术
3、在学科上涉及电子科学与技术 计算机科学与技术 微电子学等众多领域 在系统的架构上涉及数字电路 模拟电路 嵌入式微处理器 嵌入式操作 系统 底层驱动等技术 因此 虽然为了满足业界对人才培养的要求 越来越多的高校相关专业开始在专科 本科 硕士培养计划中开设嵌入式系统方面的课程 但是作为个新兴的课程 体系 关于嵌入式系统教学过程中相关先修课程与基础知识的准备 教学内容 包括硬件平台 与软件平台的选择实验教学与实践环节组织等问题依然处于争论和探索阶段通过对相关院校的嵌入式系统教学的调研以及在东南大学电子科学与工程学院集成电 路学院嵌入式系统教学实践的基础上我们发现现有电子类本科专业教学计划中存在与嵌入
4、 式系统教学要求相脱节的因素 其中一个比较突出的问题就是电子类学生软件基础比较弱 虽然电子类专业的学生都先修过 编程语言 计算机原理 等课程 但是缺乏大型软件项目的开发经验尤其缺乏操作系统方面的相关知识这些都为嵌入式系统课程的教学带来了一 定的困难因此在嵌入式系统课程体系中增加一些用于弥补学生软件知识的课程就非常有必 要了 凌明副教授 年开始在集成电路学院开设的 高级嵌入式系统 编程 硕士选修课 无疑是为解决这个问题而进行的有益尝试 而通过 届学生课程的讲解也取得了非常好的教 学效果 虽然关于嵌入式系统方面的专业书籍出版了很多 但是适合教学的教材可谓凤毛麟 角 因此在我的建议下凌明老师开始将课
5、程讲义的主要内容进行了系统地整理 编写成为面向 本科高年级和硕士阶段教学的这本教材全书分为 章 第 章简要回顾了 语言的发展历史并给出了作者对于学习 语言的 些建议和参考书目 第章和第章将 语言的主要语言要素作了提纲挈领式的总结和复 习 虽然不是一本 语言的入门教科书 但是出于对全书的系统性以及教学的考虑 作者用了 一定的篇幅将语言中的主要内容进行了总结其中第章重点介绍了 语言的关键字与运 算符 第 章则重点介绍了 语言的函数 标准 库以及相关内容 第 章详细介绍了嵌入式系统软件开发的基本流程和原理并针对 处理器作了比较详细的介绍第章是全书的重点和难点之一详细介绍了 语言中指针使用的高级技巧以
6、及程序员需要规避的内存陷阱 本章的后半部分还以实际的案例讲解了动态内存的分配与释放 然后以的实际案例进行了构建复杂数据结构的讲解 第 章则详细介绍了嵌入式系统中底层驱 动的编写技巧以及相关中断处理程序的编写技巧尤其是针对函数重人的问题进行了细致的 分析与讨论 本章的后半部分还以个实际的键盘驱动以及 为例进行了案例讲解在第 章中 作者介绍了嵌入式 语言编程需要遵循的编程规范和编码风格 本章的内容几乎在其他所有教科书中都没有涉及 但实际上对于工程项目的开发而言 本章的内容又是非常 重要和实用的只要是软件就离不开调试初学者往往在调试代码的过程中不知所措因此在 第 章中 作者介绍了嵌入式软件调试的基本
7、技巧和常用工具 本章的主要内容也是本书的 特色之一作者从工程的角度比较系统地介绍了嵌入式软件开发调试过程中常用的方法 这对 于初学者是非常有帮助的第 章则以东南大学国家专用集成电路系统工程技术研究中心自 主研发的 嵌入式图形用户界面 作为个综合案例 详细讲解了一个复杂软件系统的总体设计架构本书的特色之处是强调实际嵌入式软件项目中常用的技巧和方法 并融合了作者在所从 事的科研项目中总结出来的经验和心得 本书适合电子类专业本科高年级和相关专业硕士的 教学 可以作为相关选修课程的教材或主要参考用书 另外由于本书内容的实战性很强 因此 也非常适合作为广大嵌入式系统工程师的参考用书时龙兴2010年8月于
8、东南大学逸夫科技馆,_1刖 言最早接触编程语言是 年我在东南大学电子工程系 现在已经改名为东南大学电子科学与工程学院读本科一年级时学习的 编程语言当时我对计算机的基本组成原理以及计算机是如何工作的基本上是无所知我和同学们稀里糊涂地在计算中心的 主机上编写自己的 代码 最后稀里糊涂地通过考试 拿到学分 但确实没有留下什么深刻的印象 老师听到要生气了呵呵 大二的时候系里面组建了自己的 机房 我疯狂地迷上了在 上的编程 当时的主要编程工具是微软的 和 公司的当然这些都是作为自娱自乐和自我陶醉的小把戏 没有真正做出什么有用的东西 年我考入东南大学国家专用集成电路系统工程技术研究中心 以下简称 工程 中
9、心 攻读硕士学位 那时中心有一个基于 公司 内核 现在该公司的半导体部门已经独立成为 公司 龙珠处理器的 研发项目 年的时候这个项目的研发成果实现了产业化 深圳的一家公司后来推出了蓝火 随身 无线金融掌上电 脑 我和师兄郑凯东的研发任务是为这个 项目开发 协议栈 当时国内做这方面研发的大还比较少 可以参考的资料更是有限 郑凯东从互联网上下载了开源的写的个 是非常精致的基于 的网络操作系统它在 之上模拟了一个协作式多任务内核 并基于这个内核实现了非常完整的 协议栈 我们的任务就是将这个由上百个 文件构成的系统移植到 内核上 我们几乎没有任何文档 中的注释也非常精练 唯一的办法就是苦读这些源文件
10、然后一点一点将这些文件移植到 自带的 操作系统上 在协议完整地移植后 我们还为这个 编写了自己的 协议 协议以及与之相关的电子邮件收发应用程序现在看起来花了大半年的时间阅读高手编写的代码对于后来自己的成长是非常有帮助的这个经历让我真正理解了 编写大型软件的思维方式同时也对语言的博大精深有了深刻的体会在完成了 项目的 协议移植后 我的主要精力转移到了为 应用程序编写统软键盘模块在当时的软件负责大胡晨老师的指导下我将这个键盘模块设计成为采 用消息过滤和回调机制的软件架构 这为后来编写 工程中心自主的嵌入式图形用户界 面 打下了基础 后来我们又将 移植到 公司的处理器上 并且和自己编写的抢占式多任务
11、内核 融合到了一起 构成了一个比较完整的嵌入式软件开发平台 在 年与北京大学微处理器研究中心合作的众志微处理器项目中 我们为这个处理器开发了集成开发环境 并且也成功地将我们的移植到了该处理器 年 工程中心成为国内第一家 大学计划的授权用户成功研发了基于 的 嵌入式微处理器我们又将 移植到了平台 年 处理器进入量产阶段 作为芯片的设计单位 中心为处理器开发了大量的底层软件平台和相关的应用方案这些方案包括无线数据传输 终端 电子支付终端 解决方案 继电保护终端 电机保护器等 就在本书快要成稿时 处理器的升级版本 嵌入式微处理器也进入了量产阶段为移动导航终端 手持多媒体播放器 等应用设计的基于 处理
12、器内核的处理器也正式立项 并推出了第一个版本的测试芯片 我们为这两款处理器移植了 嵌入式 和 操作系统并开发了基于这些操作系统的底层软件包和相关驱动在上面所介绍的研发过程中 大量的研究生参与了其中的工作 由于考入 工程中心的研究生多是电子类专业背景因此他们的编程经验大多和我刚读硕士时样的贫乏 语言本质上 至少从它诞生之初起 不是门面向初学者的编程语言 其灵活多变 语法检查不 严格 对底层存储器的直接操作 主要是通过各种形式的指针 等特征使得 编程成为一次布满陷阱的冒险 我的这些师弟师妹们也和我当初一样成为这些陷阱的受害者 如何帮助他们 尽量避免或者少走当初我自己所遇到的弯路 使他们尽快地成为合
13、格的程序员 事实上 他们 作为初学者在比较大型的嵌入式软件系统开发过程中所遇到的这些问题具有一定的普遍性 至少我在刚刚接触的时候所遇到的挫折与麻烦与他们现在所遇到的问题是惊人的相似 这个 想法最终在 年底的时候变成了积极的行动 东南大学集成电路学院院长时龙兴教授 时老师同时也是国家专用集成电路系统工程技术研究中心的主任和我攻读硕士博士期间的指 导老师 建议我在学院开设关于嵌入式系统高级 语言编程的硕士选修课程 以帮助硕士研 究生弥补他们所学习的 语言知识与实际工程研发过程中所需要的能力之间的差距 在本书成稿的时候嵌入式系统高级语言编程这门选修课已成功开设了 届本书也是在该 课程讲义的基础上做了
14、进步的完善和补充关于本书首先 本书不是 语言的入门教材 在本书写作之初 我假设读者已经具备了初步的 语法知识并且至少有过使用编写程序的经验但从教材的完整性和系统性上考虑本书 在第 章和第 章给出了关于 语法提纲式的复习与总结 现在市面上有关 语言的基础 教材比比皆是 如果你对 还不是非常熟悉可以参阅第 章中给出的 语言基础教材 读者 也可以利用附录 所提供的一份测试样卷测试一下自己对 语言的掌握程度以便有针对性地阅读本书的相关章节其次本书也不是专门介绍 编程技巧的本书将主要介绍针对嵌入式系统基于 语言 的软件项目的开发流程 以及较为复杂的 语言编程知识与技巧 编程风格和调试习惯 并通 过对个具
15、体软件模块 的分析 介绍分析代码的方法以及设计软件系统需要考虑的各要素 本书将以实际项目中的代码作为实例来进行介绍 详细分析在嵌入式系 统开发中程序员应该注意的方法技巧和陷阱我试图将本书编写成为一本适合作为高校相 关专业高年级本科生和硕士研究生教学使用的教材 而不仅仅是一本参考书 虽然 显而易见 地 我希望本书同样也能适合工程师的需要 因此 在本书的写作过程中我尽可能地保证相 关知识的系统性同时我也注意适合教学的范例代码和课后习题的编写最后虽然嵌入式系统的语言编程在很大程度上与 编程甚至 或者 服务器编程没有太本质的区别它们都需要遵循基本的软件编程思想和编程规范但是嵌入式系 统还是有其自身特点
16、的 比如嵌入式软件的开发环境一般而言都比 编程的开发环境要复 杂得多 初学者在刚刚接触交叉编译的开发环境 仿真器 目标系统的时候往往会不知所措并 充满挫折感其实这些都是因为没有真正理解嵌入式软件开发环境的基本原理而造成的因 此第章专门介绍了嵌入式软件开发的基本流程和工具链以及这些流程工具所起到的作 用 另外 虽然嵌入式系统开发的概念远不仅局限于基于 处理器的嵌入式软件开发 但是由于 在消费类电子领域取得的巨大成功以及 位处理器在传统嵌入式系统的广泛采用本书在涉及具体 或者具体系统设计的时候往往以东南大学国家专用集成电路系统工程技术研究中心研发的 和 嵌入式微处理器 以 为内核为例进行介绍基于本
17、书的课程安排正如我在前面所介绍的 本书的主要内容来自于东南大学集成电路学院 嵌入式系统高级 语言编程这门硕士选修课程 作为 与嵌入式系统专业方向课程体系的一部分 嵌入式系统高级语言编程这门课程旨在帮助学生掌握针对嵌入式系统的基于语言的软件项 目开发流程 掌握较为复杂的 语言编程知识和技巧 培养良好的编程风格和调试习惯 并通过对个具体的软件模块 的分析使学生掌握分析代码的方法以及设计软件系统需要考虑的各要素 本课程一共 学时 其中课堂授课部分为 学时 学生实验和课外作业与讨论折合为学时实际我希望学生在课外所花的时间远多于这个数量下面是 我们在东南大学集成电路学院的课程安排 未与本书各章节内容严格
18、对应 仅供参考第讲概论学时第讲语言基本语法复习学时第讲编译 汇编 链接与调试学时第讲存储器与指针学时第讲中断与设备驱动学时第讲编码风格学时第讲代码的调试 学时第讲 设计详解一 学时可选第讲 设计详解二 学时可选课程项目 的控件开发 未定可选关于实验与第 讲的设计案例分析以及最后的课程项目教师可以根据所从事科研项 目的具体情况和学生的接受能力 选择适合本校的教学内容 本课程的多媒体课件和课程中 使用的部分教学代码,读者可以到http:/论坛下载。为了大家讨论的方 便我们在这个网站还将专门开设关于嵌入式的讨论区致谢这本书能够诞生 首先要感谢东南大学国家专用集成电路系统工程技术研究中心主任时 龙兴教
19、授和副主任胡晨教授如果没有时老师的鼓励和鞭策我很难在东南大学集成电路学 院开设嵌入式系统高级语言编程这门课程当然也就不会有这本书胡晨老师是我进入 嵌入式系统 编程的启蒙老师 在我攻读硕士学位以及以后的工作过程中 胡老师对我的指 导使我受益匪浅是胡老师使我真正认识到软件作为个系统的概念以及软件中的架构分 层封装与接口设计的重要性另外 我要感谢曾经在东南大学国家专用集成电路系统工程技术研究中心与我共事的研 究生同学 在与他们的项目合作中使我逐步积累了本书中所写的心得与体会 他们是郑凯东 浦汉来张宇戚隆宁金晶等北京航空航天大学出版社的胡晓柏老师的鼓励和支持使得本书最终得以出版 在此表示 由衷的感谢东
20、南大学集成电路学院的研究生张阳 徐继新以及南京邮电大学的冯海东同学参与了文 字的校稿和相关材料收集与整理工作东南大学国家 工程中心的张黎明同学和史先强同学分别编写了本书 节和 节的内容在本书的写作过程中这些同学还给出了很多 中肯的意见 在此表示特别的感谢书中引用了部分来自互联网的文章 博客的内容 在此对这些文章和博客的作者表示 感谢最后感谢我的妻子与儿子 是他们给了我本书写作过程中的鼓励与支持限于作者的水平书中错误和不妥之处敬请各位读者批评指正并提出宝贵意见读者也 可以通过 与我联系交流作者2010年9月目录语言的历史和特点.个小测验.如何学好嵌入式系统中的语言编程.真正深刻地认识存储器.认识
21、和理解嵌入式编程环境.认识和掌握 语言中的常见陷阱.掌握 语言程序设计过程中的调试方法.推荐的参考书目.语言的初级教材.语言进阶书籍.思考题.第2章C语言的关键字与运算符.语言的关键字.数据类型关键字.控制语句关键字与相关语句.存储类型关键字.其他类型关键字.语言的运算符.运算符中需要注意的问题.运算符的优先级.表达式求值.运算符的词法分析.语言的指针.指针的个要素.指针的类型.指针的初始化.指针的运算.指针与字符串.思考题.第3章C语言的函数.语言的函数.函数的声明原型与返回值.函数的参数.可变参数的函数.递归函数.标准库函数.输入与输出.字符类别测试.字符串函数.数学函数.实用函数.断言.
22、可变参数表.非局部跳转.标准库函数与系统调用.声明.作用域与链接属性.代码块作用域.文件作用域.函数作用域.原型作用域.链接属性.的预编译处理.思考题.第4章 编译、汇编与调试.嵌入式软件开发流程与工具.嵌入式软件开发的一般流程.编译器简介.链接器简介.嵌入式软件开发的调试环境.处理器的开发工具.基于 语言软件项目中的文件关系.语言项目中的文件依赖关系.文件.代码与汇编.与汇编的混合编程.编译器对局部变量和人口参数的处理.思考题.第5章存储器与指针.再论语言中的指针.指针与数组.函数指针.语言中的内存陷阱.局部变量.动态存储区.函数的指针参数.堆栈.堆栈的作用.函数调用栈帧与中断栈帧.堆栈的跟
23、踪与调试.动态内存分配.算法.函数.函数.利用链表构建复杂数据结构.的数据结构.的窗口创建函数.的窗口删除函数.思考题.第6章 中断与设备驱动.设备驱动简介.设备驱动 与.设备驱动程序的结构.中断与中断处理.中断的重要性.中断的分类与处理过程.语言中的中断处理.中断处理程序的编写.函数的可重人问题.什么是函数的重人.函数可重入的条件.不可重入函数的互斥保护.重人函数的伪问题.设备驱动案例 键盘驱动.键盘的硬件原理.键盘设备驱动实例.启动代码 分析.系统启动与.技术实现分析.思考题.第7章编码风格.简介及说明.语言规则.基础.数 据.说明与表达式.函 数.源文件.风格指导.程序的书写.命名.思考
24、题.第8章代码的调试.与.初学者的困惑.的手段和工具.的定位与修正.关注代码的层次与接口.关注内存的访问越界.关注边界情况.的修正.其他的方法和工具.利用断言.代码检查.编译器的警告与 工具.好的代码风格.思考题.第9章 ASIX Window GU!设计详解.概述.底层软件平台的实现.对 在系统调用上的支持.图形库的设计.和笔中断的设计.系统任务管理模块的设计.消息处理模块的设计.消息机制的设计.消息机制的应用流程.窗口类管理模块的设计.窗口及控件的实现.窗的实现 控件的实现思考题.附录A C+/C代码审查表(C语言部分).附录B部分课后思考题解答.附录C 嵌入式C语言测试样卷与参考答案.附
25、录D UB4020MBT开发板简介.参考文献.第1章概 述1.1 c语言的历史和特点在 语言诞生以前系统软件主要是用汇编语言编写的由于汇编语言程序依赖于计算 机硬件 其可读性和可移植性都很差 但一般的高级语言又难以实现对计算机硬件的直接操作 这正是汇编语言的优势于是人们盼望有一种兼有汇编语言和高级语言特性的新语言出现具有讽刺意味的是 的诞生是从失败开始的 年由通用电气 麻省理工 贝尔实验室联合研制的 操作系统几乎彻底失败 该操作系统实在是太庞大 太复杂了以至于超出了开发团队的控制程度从 项目撤出后贝尔实验室的工程师 和 开始利用业余时间将 写的一个小游戏 太空旅行 移植到小型机上 这个小游戏模
26、拟了太阳系的行星系统 游戏者可以驾驶飞船降落在某个行星上 与此同时 还为 小型机设计了一个比 更简单也更轻量级的操作系统 年 模仿 的名字将这个新操作系统戏称为 换成了 以示这个新操作系统较之原来要简单 单纯得多 与早期的操作系统样 最早的采用的 是用汇编语言编写的 但是汇编语言在处理复杂数据结构时难以编码同时也难以调试和理解 希望能够采用高级语言来编写 在尝试 失败后 他将一种研究性的高级语言 是由伦敦大学和剑桥大学合作研发的早期高级语言简化为一种他称之为 的高级语言以使得语言的解释器能够运行在 的存储器中然而由于硬件资源的限制而采用的解释执行使 的效率不高 因此 语言并不适合作为 系统的编
27、程语言 以至于 在年将 移植到 小型机的时候依然采用了汇编语言 利用更强大的硬件功能创立了 语言 这种新的语言支持多种数据类型 同时因为采用编译的运行方式而提高了性能很快人们将 称为 语言经过几年的演变和完善 到了 世纪 年代中期 语言已经和今天我们使用的 语言相差无几了虽然后续的完善一直持续不断比如增加了新的关键字 和 等年 编写了 编译器 可移植的 编译器 由于这个编译器的源码可以在贝尔实验室之外公开 故该编译器被广泛地移植到不同的处理器上 成为当时 编译器的共同基石 同年 语言的经典著作 编程语言出版 为了表示对该书两位作者 和 的敬意书中的 版本被称为 出版社当时估计能卖掉 本就不错了
28、然而截至年这本书共卖了 万册到 世纪 年代早期 语言已经被业界广泛采用了但是随之而来的是多种不同的实现和版本 比如为了适应 的特殊地址架构 微软公司的 语言版本增加了一些新的关键字 如 等 随着越来越多的非 基础的 语言版本出现 语言逐渐形成了类似于 语言样的松散语言家族 年美国国家标准化协会 根据语言问世以来各种版本对 语言的发展和扩充制定了 标准 年 月再次做了修订并最终确认了该标准国际标准化组织 随后接受了该标准作为国际标准 标准有 个主要部分分别是第 部分简介 第 部分环境 第 部分 语言 第 部分 运行库 该标准还有几个有用的附件 比如附件 一般警告信息 附件 可移植性问题等需要说明
29、的是 虽然 标准规范了 语言的实现 但是在实际情况中 各家 语言 提供商都会根据各平台的不同情况对 进行定的扩展比如我们上面提到的微软的语言实现中增加了关键字 又比如在嵌入式领域 的 编译器增加了关键字以支持 位整数 增加了关键字 以支持 语言编写的中断处理程序 注意 在有些编译器中有类似的关键字 如图 所示 我们可以将现实中的 语言实现看作是 的个超集这些厂商对 的扩展部分有可能彼此不兼容从而使得程序的移植需要对这些非标准的部分特别小心在这个问题上比较有代表性的例子是 的 编译器 由于该编译器对 进行了非常多的扩展 的内核源码基本上只能在 上进行编译 希望通过其他 编译器编译 内核几乎是不可
30、能的 另外一个需要注意的问题是 虽然 对 语言的规范进行了非常详细的约定 但是由于 语言的实 现平台纵跨了从 位单片机 到 位甚至 位 的硬件环境 因此在数据类型的约 定上标准 必须有足够的灵活性 比如 只规定了 数据类型是个位的数据但是并没有规定 类型应该是多少位 这就造成了不同 编译器对于这些数据类型的不同约定比如 公司的 规定 类型是 位整数但是 的编译器规定 类型是位整数 的 编译器关于 类型的数据宽度是可以配置的 因此 嵌入式软件程序员在编写 代码时或者从其他处理器平台移植 代码 时必须非常谨慎地处理这些与编译器相关的内容语言的特点主要有以下几点语言简洁 紧凑 使用方便 灵活 只有
31、个关键字 种控制语句 较之其他高级 语言 语言的关键字非常少方面是语言本身的设计使然 另个重要的原因是因为 语 言将所有与外围硬件设备相关的输入输出操作统统放在运行库中实现比如从键盘输入 向屏幕输出 文件的操作等都没有作为 语言关键字出现 而是以库函数的方法加以实现 这样做的好处一方面使得语言的实现变得比较简洁编译器的实现也会比较简单另一方面 由于与硬件设备相关的功能以函数的方法实现 使得 语言本身尽可能与硬件平台无关 这 也是 语言能够在如此众多的硬件平台上实现的重要原因某商用C编译器I/I ANSI C标准某商用编译器2 某商用编译器3图1-1 ANSI C与商用C编译器的关系运算符很丰富
32、 语言共有 种运算符 但关键字只有 个 语言中包含了一些特有的运算符 比如自增自减运算符 和针对指针运算的取内容运算符 和取地址 运算符 针对位运算的移位运算符 和 按位与 按位或按位异或按位取反等 这些运算符大大方便了程序员在进行底层代码编写的过程中对存储器 控制寄存器等 硬件资源进行操作数据结构丰富 语言的数据类型支持整型 实型 字型符 数组 指针 结构共用体 枚举类型 与其他高级语言不太一样的是语言没有字符串类型这也是我个人认为语言在处理字符串问题时比较不方便的原因事实上在很多需要对字 符串进行处理的应用中 比如脚本的解释程序 像早期 应用中的 脚本 往往更多地 采用非常适合字符串处理的
33、 语言进行编写而不经常采用语言具有结构化的控制语句在语言中支持这些结构化的控制语句我们后面会专门讨论这些控制语句虽然语言和绝大多数高级语 言样保留了 关键字而且 的语言结构也没有 那样严格和规范但是总的来说语言依然是非常好的结构化编程语言语言的语法限制不大严格程序设计自由度大这是个双刃剑 语法非常宽 容比如语言里面不检查数组越界它是语言里面很重要的个特点虽然这看起来是 个不好的特点 但是在些优秀的程序员手中 这个特点也可以变成一个非常灵活的 并且富有技巧性的方法 所以虽然说 很危险 很灵活 但是在高手手里面这些都是可以利用的也就是留给程序员的空间非常大 语言就像是种非常厉害的兵器比如流星锤他要
34、求玩这个兵器的人要很厉害 但如果个新手玩 就很可能被流星锤打中自己语言允许直接访问物理地址 能进行位 操作 可以直接对硬件操作 这是 语言非常重要的一个特点 对物理地址的访问主要是通过指针而指针又是里面最灵活的部分也是初学者最难掌握的内容但是如果没有指针的话实际上也就意味着不能够访 问硬件或者说访问硬件会变得很困难 那么对内存的操作也就变得很困难生成目标代码质量高程序执行效率高相对于其他高级语言 的编译器效率可能是最高的这一方面是因为对编译器优化的研究已经达到了非常成熟的程度这一点从 公司的 编译器在性能上每年仅仅提升小于 个百分点可以得到印证 另一方面是因为 语言本身的语言特性 使得在将 程
35、序转化成为汇编代码时 需要额外增加的检查代码要少得多 比如 语言不检查数组越界和内存缓冲区越界 也正因为 目标代码的高效性使得语言非常适合诸如操作系统编译器等系统软件的开发同时也使语言成为嵌入式 软件开发的首选高级语言 基于对成本和功耗的考虑 嵌入式系统的硬件性能往往受到严格的 限制可移植性好理论上讲任何个高级语言都应该具有很好的可移植性但是实际的 情况却不尽如人意 这是因为各个厂商推出的编译器往往会扩展一些自己的特性 语言的可移植性是比较好的从巨型机到单片机都可以使用语言这主要有两个原因第一语 言在 世纪 年代就制定了相关的标准 标准因此虽然各家编译器厂商推出的语言各不相同 但是都保证与 兼
36、容 第二 也是往往被大家忽略的原因 就是 语言本身的标准中并没有设计输入 输出的操作 的关键字中没有与计算机系统相关的输入 输出功能 所有的这些功能都是由 运行库中的库函数完成的 从这个意义上来说 语言本身是和硬件无关的 当然 的可移植性是相对的 实际的工程项目中移植依然是一个不容小视的问题语言在 年之间都是嵌入式软件开发使用最多的语言近五年来与语言更瓜分了大部分原属于汇编语言的版图 其中较高阶的 发展速度虽不如预期 但仍在嵌入式软件设计领域维持 左右的占有率整体看来 语言使用率在世纪 年代晩期加速上升 在 年达到高峰 然后稍微下滑 之后维持稳定 无论如何 嵌入式软件设计 师不会在短时间内放弃
37、使用 语言 原因有很多个 首先 语言编译器支持大多数的 位 位 与位 其次语言在处理器与驱动程序层级兼具高低级语言的特色请看图 所示的关于嵌入式软件工程师所使用编程语言的调查数据来自 年嵌入式系统市场研究70%图1-2 C语言在嵌入式软件开发中的比例1.2 个小测验语言的复杂和灵活主要体现在语言语法的灵活以及允许程序员对底层存储器的直 接操作上这两点又恰恰是 语言最优美最强大的地方 下面我们来做一个小小的测验请阅读以下的代码并找出其中的错误和潜在的危险因素注意这段代码本身并没有什么实 际意义 只是将人口参数 所指向的内容通过两个内部缓冲区 和 复制到局部数组中并将该数组的首地址作为返回值传递到
38、函数外我在这里只是希望通过这个例子说明 程序语法的正确性与功能或者逻辑的正确性之间有着本质的区别1 include stdlib.h23 char test char ptr45 uns igned char i6 char buf 8 10247 char p q8 将数组初始化为9 f or i=0 i=8 1024 i+10 buf i=0 x01112 p=malloc 102413 if p=NULL return NULL p申请失败 返回空指针14 q=malloc 204815 if q=NULL return NULL q申请失败 返回空指针17 memcpy p ptr 1
39、02418memcpy q ptr204819memcpy buf p102420buf=buf+102421memcpy buf q20482223free p24free q2526return buf27将ptr所指向的内容复制到q现在我们将P和q中的内容合并到buf数组中将数组buf的首地址返回出去怎么样你能在上面的代码中发现几处错误或者隐患呢还是让我们起来分析 下吧代码的第行即有问题在语言中包含文件的有两个符号或者 双引号的意思是告诉编译器首先在当前目录下搜索需要包含的文件 如果当前目录下没有该文件则 在编译选项指定的系统头文件目录中搜索该文件尖括号 的意思则是通知编译器首先在系统头
40、文件目录中搜索需要包含的文件 在这个例子中 是 标准库函数的头文件一般而言这个文件是存放在系统头文件目录中的 因此准确的用法应该是采用尖括 号 即 虽然在大多数情况下采用双引号的包含方式不会产生错误 但如果系统里比如 下的 有一个叫作 的头文件而你的源代码目录里也有一个自己写的 头文件那么此时系统就会默认使用你自己定义的头文件而 这可能并非你的本意第行的定义 是定义个无符号的位数但是请注意一个无符号位数的范围是 而第 行的 循环中却将 与 进行比较 如果 是个无符号 位数的话 那么这个数将永远小于 因为当 的值增长到 时再加 后 将重新变为这将使得这个 循环成为个死循环而永远不会结束第行定义了
41、一个 字节的数组 从语法上来说这个定义没有任何问题但是如果我们知道一个局部变量是如何在内存中表示的就会对这样的定义倒吸一口凉气了编译 器对局部变量有两种存储方法对于简单数据类型的变量比如 或者指针变量等 编译器会首先尽可能地采用 内部的通用寄存器来表示 因为寄存器的访问速度远远高于外部存储器的访问速度第二种方式是对于那些没有办法用寄存器表示的变量或者数组 结构体等变量采用当前的堆栈空间来存储 对于这段代码数组 显然是需要存放在堆栈中的然而 字节的空间对于大多数系统而言是很容易将堆栈空间耗尽的因此在局部数组 中开设大数组是需要仔细评估的程序员必须非常清楚自己的堆栈空间是否够用 如果算法 必须采用
42、大数组 可以采用 的方法来定义 虽然这同时会带来程序不可重入的问题 关于 关键字我们会在第 章中仔细讲解的第 行的 循环中 的表达式是错误的因为在 语言中数组的下标是从 开始的 因此对于 数组 合法的下标取值是从 到 所以上述的表达式的正确写法应该是 令人遗憾的是语言对于数组越界是不作任何检查的如果按照原来错误的写法在最后一次循环中程序执行了 的操作通常情况下程序在当时不会有任何异常但是其实紧邻最后个合法元素 存放的另外一个变量已经被错误地修改了程序只有在访问了该变量时可能出现不正常 而这 时可能已经离你修改它的第 行很远了第 行中的问题虽然不是错误 但却是个不好的编程风格 库函数的返回值是个
43、指向 类型的指针因此好的编程风格应该是在将这个返回值赋给其他类型的指 针变量前进行显式的强制类型转换所以比较合适的写法应该是第 行的 函数调用也存在同样的问题第行代码中有两个非常隐蔽的错误我们先来分析第一个 这个判断式的逻辑是错的正确的写法应该是 这是几乎所有语言使用者都会犯的错误令人真正害怕的是前面的表达式在语法上是完全正确的意思是将 赋值 给变量 然后判断 的取值是否不为 这个判断的取值永远是否 也就是说不管原来的是否真的为空 后面的 语句永远不会执行 更讨厌的是 如果 原来为非空指针则在经过第行代码后也会将值改为空第 行的第二个错误在这个语句的后半部分如果指针为空说明第 行的函数申请动态
44、内存失败调用 语句似乎没有任何问题但是当程序运行到第 行时实际上有一个潜在的条件 那就是 指针的动态内存申请一定是成功的 否则早在第 行程序就会 因此如果我们在 申请不到时直接返回就会将 指针申请的动态内存永远地丢失 这块内存空间永远也不会被释放回系统堆 这就是所谓的 内存泄漏 内存泄漏是个慢性错误 由于并不影响正常的程序运行所以通常情况下在内存泄漏的早期 程序的运行没有任何异常 直到系统堆中的内存空间已经 漏 光了其他程序调用 申请内存时总是失败 这时解决问题的唯一办法就是重新复位系统 这一行的正确写法应该是if q=NULL free p return NULL第行中包含了一个隐蔽的错误调
45、用函数将人口参数 所指向的内存复制 个字节到 指针所指向的内存空间 但程序在将 入口参数作为函数的参数时没有对 是否为空进行检查这是因为在通常情况下标准库函数为了效率往往不对入口参数进行合法性检查 如果 为空 那么 这个语句运行系统就崩溃了第 行应该是 因为前 个字节已经被第 行执行完毕后面的数据应该从 指针向后偏移 个字节开始第 行是个语法错误 但是在写代码时往往被程序员忽略 理解这个语法错误首 先要理解 编译器是如何处理数组的 在 语言程序编译的过程中 编译器要为数组所占用 的内存分配空间 因此在 中没有动态数组的概念数组在存储器中的位置也就是地址和 容量在编译时就已经确定了并且在程序运行
46、过程中不再发生改变编译器将数组的名字作 为个符号并将该符号与数组实际存放在内存中的地址对应起来 因此在 语言中数组名 就是数组的首地址 这个首地址已经在编译的时候确定 不能再改变了 所以这个语句是有语法错误的编译器会报错最后个错是第行的返回语句 正如前面所述 数组是通过堆栈存放的因此将堆栈中的地址作为指针传递到函数外部是非常危险的因为大家都知道在 我们出函数后 该函数的栈帧就已经无效了原来的这个堆栈空间随时都可能被用作其他用 途 返回这段内存的地址毫无意义 如果通过这个指针去修改其所指向的内容 将很容易地将 系统堆栈写 脏 将保留在新的栈帧中的数据覆盖在第 章安排这样的小测验的目的是想说明仅仅
47、掌握 语言的语法正确对于编写正确 无误的 程序是远远不够的要想写好程序还必须掌握更多的知识和技巧在下节将向 读者介绍除了掌握语法之外还有哪些知识是需要了解和掌握的1.3如何学好嵌入式系统中的C语言编程1.3.1 真正深刻地认识存储器冯 诺伊曼说过程序等于算法加数据结构 首先算法是什么 算法是通过存储在存 储器中的程序代码实现的 其次 数据结构又是什么 数据结构是存放在存储器中的各种类 型的数据 程序本质上就是处理器通过执行存放在存储器中的程序代码对存放在存储器中的 数据进行操作和变换的过程 在这个过程中除了处理器本身外 最核心的环节就是存储器 因为不管是程序的可执行代码还是数据都是存放在存储器
48、中的 撇开代码 变量 数组 指针 结构 堆栈等这些软件中的各个元素的表象 剩下的本质就是存储器 因此 理解 语言的关 键是真正理解存储器每个存储单元都有两个属性一是存储器里面存放的内容 是存储器的地址 这个内 容可以是代码也可以是数据甚至是另个存储单元的地址这个时候往往我们称这个存储单元里存放的是个指针 程序员需要时时刻刻将存储器的这两个属性牢记于心1.3.2 认识和理解嵌入式C编程环境嵌入式软件开发的个非常重要的特点就是交叉编译也就是开发工具运行的环境和被 调试的程序不是运行在同一个硬件平台处理器上的一般而言编译器汇编器链接器等 具链软件以及调试工具都运行在通用的 机平台上调试工具通过一定的
49、通信手段将链接 器输出的可执行文件下载到嵌入式系统开发板一般称为目标系统 的存储器中 并通过一定 的机制控制和观测目标系统的寄存器存储器等这个开发过程往往需要使用多种不同的 具对此初学者很容易感到困惑只有真正理解开发过程中各个环节的作用才能对嵌入式系 统编程有深入的认识另个问题是 虽然 语言是门高级语言 但是想真正用好 语言 程序员必须对编程 过程中所使用的工具非常了解清楚地知道每个工具的作用以及这些工具与硬件平台的相互 关系比如编译器是如何处理全局变量和全局数组的对于全局变量的处理与局部变量有 什么不同编译器是如何利用堆栈进行传递参数的又比如语言的编译器链接器是如何 处理一个项目中多个 文件
50、之间的相互依赖关系的 链接器最终是如何生成可执行文件的 可执行文件的内存映像又是如何安排的这些问题初看起来似乎与编程本身没有什么关 系但因为在嵌入式软件的开发过程中程序员要经常直接和底层的设备与工具打交道所以 个嵌入式软件的程序员应该对这些问题了如指掌1.3.3 认识和掌握C语言中的常见陷阱语言不是门面向初学者的编程语言 语言发明者的初衷是希望设计种面向编译 器和操作系统设计的高级语言因此语言中充满了各种各样对于初学者而言的陷阱这些 陷阱一方面来自于 语法本身的灵活性 另一方面来自于 对存储器边界的不检查 因此非 常容易在代码中造成存储器越界访问的问题在语言中最容易出错的地方是与存储器相 关的