??xml version="1.0" encoding="utf-8" standalone="yes"?>
contextSwitch(PContext pOldContext, PContext pNewContext)
{
if(saveContext(pOldContext))
{
//
// Restore new context only on a nonzero exit from saveContext().
//
restoreContext(pNewContext);
// This line is never executed!
}
// Instead, the restored task continues to execute at this point.
}
例程 contextSwitch()实际上是被调度程序凋用,而调度程序又在那此终止中断的tbpȝ调用中被调用Q因此它不一定在q里l止中断。此外,׃调用调度E序的操作系l调用是用高U语a写的Q所以大部分q行d的寄存器已经被保存到它自己当地的栈中了。这减少了例EsaveContext()和restoreContext()需要做的工作。它们只需要关心指令指针,栈指针以及标志位的保存。例E?contextSwitch()的实际行为是很难仅仅通过看前面的代码来理解的。大部分的Y件开发者以q箋的方式思考问题,认ؓ每一行代码会紧接着上一条代码破执行。然而,q个代码实际为ƈ行地执行了两ơ。当一个Q务(CQ务)转变到运行状态,另一个(旧Q务)必须同时q回到就l状态。想一下新d当它在restoreContext()代码中被恢复的时候就会明白。无论新d以前做什么,它在saveContext 代码里L醒着?#8212;—因ؓq就是它的指令存攄地方。新d如何知道它是否是W一ơ(也就是,在准备休眠的q程Q或者是W二ơ(醒来的过E)从saveContext()中出来的呢?它确实需要知道这个差别,因此我不得不用一U有炚w蔽的Ҏ来实现saveContext()。例EsaveContext()不是保存了准的目前的指令指针。实际上是保存了一些指令前面的地址。那P当保存的讑֤场景恢复的时候,E序从saveContext 中另一个下同的点l。这也得saveContext 可能q回不同的|当Q务要休眠的时候ؓ非零Q当d唤v的时候ؓ零。例EcontextSwitch()利用q个q回的值来军_是否调用restoreContext()。如果不q行q个,那么与这个新d相关的代码永q不会执行?/p>
我知道这可能是一个复杂的事g序列Q因此我在图8-3 中说明了整个的过E?/p>
坦言之,一个操作系lƈ不是嵌入式或其它计算机系l的必需的组Ӟ它所能做的,也是像时用程序要实现的功能一栗本书中的所有例子都说明了这一炏V应用程序执行v来,都是从main 开始,然后q入pȝ调用、运行、结束。这与系l中只有一个Q务是一L。对于应用程序来_仅仅是实CLED q行闪烁Q这是操作pȝ的主要功用(屏蔽了很多复杂的操作Q?/p>
如果你以前没作过Ҏ作系l的研究Q那么,在这里得提醒一下,操作pȝ是非常复杂的?a style="color: #000000" >tb操作pȝ的厂商肯定是想你相信,他们是唯一能生产出功能强大又易用的操作pȝ的科学家。但是,我也要告诉你Q这q不是根困难的。实际上嵌入式操作系l要比桌面操作系l更Ҏ~写Q所需的模块和功能更ؓy、更易于实现。一旦明了要实C功能Qƈ有一定的实现技能,你将会发玎ͼ开发一个操作系lƈ不比开发嵌入式软g艰难多少?/p>
嵌入式操作系l很,因ؓ它可以缺很多桌面操作系l的功能。例如,嵌入式操什pȝ很少有硬盘或囑Ş界面Q因此,嵌入式操作系l可以下需要文件系l和囑Ş用户接口。而且Q一般来_是单用户pȝQ所以多用户操作pȝ的安全特性也可以省去了。上面所说的各种性能Q都可以作ؓ嵌入式操作系l的一部分Q但不是必须的?/p>
我们要讨论的这个应用程序不比其他大部分tb~程书籍中找到的“HelloQWorld”例子更复杂。它是对于嵌入式软g开发的一个实证,因此q个例子是出现在书的l尾而不是开始。我们不得不逐渐地徏立我们的道\通向大部分书c甚x高语言~译器认为是理所当然的计^台?/p>
一旦你能写“Hello, World”E序Q你的嵌入式q_开始着上去很像M其他~程环境。但是,嵌入式Y件开发过E中最困难的部?#8212;—使自q悉硬Ӟ为它建立一个Y件开发的q程Q连接到具体的硬件设?#8212;—q在后面呢。最后,你能够把你的力量集中于算法和用户界面Q这是由你要开发的产品来确定的。很多情况下Q这些程序的高斚w可以在其他的计算机^C开发,和我们一直在讨论的低U的嵌入式Y件开发同时进行,q且只要把高U部分导入嵌入式pȝ一ơ,两者就都完成了?/p>
?9-1 包含了一?#8220;Hello, World!”应用E序的高U的C意图。这个应用程序包括三个设备驱动程序,ADEOS 操作pȝ和两个ADEOS d。第一个Q务以每秒10Hz 的速度切换Arcom 板上的红色指C灯。第二个每隔10 U钟向主机或是连接到位子串口上的哑终端发送字W串“HelloQWOrldQ?#8221;。这两个d之外Q图中还有三个设备的驱动E序。这些驱动程序分别控制着Arcom 板子的指C灯、时钟以及串行端口。虽焉常把设备驱动画在操作系l的下面Q但是我把它们三个和操作pȝ攑֜同一个别,是ؓ了着重说明它们事实上依赖于ADEOS 比ADEOS 依赖于它们更多。实际上QADEOS 嵌入式操作系l甚至不知道Q或者说下关心)q些讑֤驱动是否存在于系l之中。这是嵌入式操作pȝ中设备驱动程序和其他g专用软g的共性?/p>
E序 main()的实现如下所C。这D代码简单地创造厂两个dQ?strong>tb启动了操作系l的日程表。在q样一个高的别上Q代码的含义是不a而喻的。事实上、我们已l在上一章中讨论了类似的代码?/p>
#include "adeos.h"
void flashRed(void);
void helloWorld(void);
/*
* Create the two tasks.
*/
Task taskA(flashRed, 150, 512);
Task taskB(helloWorld, 200, 512);
/****************************************************
*
* Function : main()
*
* Description : This function is responsible for starting the ADEOS scheduler
only.
*
* Notes :
*
* Returns : This function will never return!
*
****************************************************/
void
main(void)
{
os.start();
// This point will never be reached.
} /* main() */
一旦你使用了自动优化,q里有一些关于用手工的办法进一步减代码大的技巧?/p>
避免使用标准库例E?br />Z减少你的E序的大,你所能做的最好的一件事情就是避免用大的标准库例程。很多最大的库例E代h贵,只是因ؓ它们设法处理所有可能的情况。你自己有可能用更少的代码实C个子功能。比如,标准C 的库中的spintf例程是出了名的大。这个庞大代码中有相当一部分是位于它所依赖的QҎ处理例程。但是如果你不需要格式化昄点数?%f 或?d)Q那么你可以写你自己的sprintf 的整C用版本,q且可以节省几千字节的代码空间。实际上Q一些标准C 的库(q让我想起Cygnus 的newlib)里恰好包含了q样一个函敎ͼ叫作sprintf?/p>
本地字长
每一个处理器都有一个本地字长,q且ANSI C 和C++标准规定数据cdint必须L对应到那个字ѝ处理更或者更大的数据cd有时需要用附加的机器语言指o。在你的E序中通过可能的一致用int cdQ你也许能够从你的程序中削减宝贵的几癑֭节?/p>
goto 语句
像对待全局变量一P好的软g工程实践规定反对使用q项技术。但是危急的时候,goto 语句可以用来去除复杂的控制结构或者共享一块经帔R复的代码?br />除了q些技术以外,在前一部分介绍的几U方法可能也会有帮助Q特别是查询表、手工编写汇~、寄存器变最以及全局变量。在q些技术之中,利用手工~写汇编通常可以得到代码最大幅度的减少量?br />
虽然使Y件正的工作好像应该是一个工E合乎逻辑的最后一个步骤,但是在嵌入式的系l的开发中Q情况ƈ不Lq样的。出于对低hpd产品的需要,g的设计者需要提供刚好够的存储器和完成工作的处理能力。当Ӟ在工E的软g开发阶D,使程序正的工作是很重要的。ؓ此,通常需要一个或者更多的开发电路板Q有的有附加的存贮器Q有的有更快的处理器Q有的两者都有。这些电路板是用来使Y件正工作的。而工E的最后阶D则变成了对代码q行优化。最后一步的目标是得工作程序在一个廉Lgq_上运行?/p>
提高代码的效?br />所有现代的 C 和C++~译器都提供了一定程度上的代码优化。然而,大部分由~译器执行的优化技术仅涉及执行速度和代码大的一个^衡。你的程序能够变得更快或者更,但是不可能又变快又变。事实上Q在其中一个方面的提高׃对另一斚w产生负面的媄响。哪一斚w的提高对于程序更加的重要是由E序员来军_。知道这一点后Q无Z么时候遇到速度与大的矛盾Q编译器的优化阶D就会作出合适的选择?/p>
因ؓ你不可能让编译器Z同时做两U类型的优化Q我你让它尽其所能的减少E序的大。执行的速度通常只对于某些有旉限制或者是频繁执行的代码段是重要的。而且你可以通过手工的办法做很多事以提高q些代码D늚效率。然而,手工改变代码大小是一件很隄事情Q而且~译器处于一个更有利的位|,使得它可以在你所有的软g模块之间q行q种改变?/p>
直到你的E序工作hQ你可能已经知道或者是非常的清楚,哪一个子E序或者模块对于整体代码效率是最关键的。中断服务例E、高优先U的d、有实时限制的计、计密集型或者频J调用的函数都是候选对象。有一个叫作profiler 的工P它包括在一些Y件开发工L中,q个工具可以用来把你的视UK中到那些E序p大部分时?或者很多时?的例E上厅R?br />一旦你定了需要更高代码效率的例程Q可以运用下面的一U或者多U技术来减少它们的执行时间?/p>
inline 函数
?c++中,关键字inline 可以被加入到M函数的声明。这个关键字h~译器用函数内部的代码替换所有对于指出的函数的调用。这样做删去了和实际函数调用相关的时间开销Q这U做法在inline 函数频繁调用q且只包含几行代码的时候是最有效的?/p>
inline 函数提供了一个很好的例子Q它说明了有时执行的速度和代码的太小是如何反向关联的。重复的加入内联代码会增加你的程序的大小Q增加的大小和函数调用的ơ数成正比。而且Q很明显Q如果函数越大,E序大小增加得越明显。优化后的程序运行的更快了,但是现在需要更多的ROM?/p>
查询?br />switch 语句是一个普通的~程技术,使用旉要注意。每一个由tb机器语言实现的测试和跌{仅仅是ؓ了决定下一步要做什么工作,把宝贵的处理器旉耗尽了。ؓ了提高速度Q设法把具体的情冉|照它们发生的相对频率排序。换句话_把最可能发生的情冉|在第一Q最不可能的情况攑֜最后。这样会减少q_的执行时_但是在最差情况下Ҏ没有改善?/p>
如果每一个情况下都有许多的工作要做,那么也许把整个switch 语句用一个指向函数指针的表替换含更加有效。比如,下面的程序段是一个待改善的候选对象:
enum NodeType {NodeA, NodeB, NodeC}
switch(getNodeType())
{
case NodeA:
...
case NodeB:
...
case NodeC:
...
}
Z提高速度Q我们要用下面的代码替换q个switch 语句。这D代码的W一部分是准备工作:一个函数指针数l的创徏。第二部分是用更有效的一行语句替换switch 语句?/p>
int processNodeA(void);
int processNodeB(void);
int processNodeC(void);
/*
* Establishment of a table of pointers to functions.
*/
int (* nodeFunctions[])() = { processNodeA, processNodeB, processNodeC };
...
/*
* The entire switch statement is replaced by the next line.
*/
status = nodeFunctions[getNodeType()]();
手工~写汇编
一些Y件模块最好是用汇~语a来写。这使得E序员有Z把程序尽可能变得有效率。尽大部分的C/C++~译器生的机器代码比一个一般水q的E序员编写的机器代码要好的多Q但是对于一个给定的函数Q一个好的程序员仍然可能做得比一般水q的~译器要好。比如,在我职业生的早期,我用C 实现了一个数字o波器Q把它作为TI TMS320C30 数字信号处理器的输出目标。当时我们有?a style="color: #000000" >tb~译器也许是不知道,也许是不能利用一个特D的指oQ该指o准确地执行了我需要的那个数学操作。我用功能相同的内联汇编指o手工地替换了一DC 语言的@环,q样我就能够把整个计时间降低了十分之一以上?/p>
寄存器变?br />在声明局部变量的时候可以?register 关键字。这׃得编译器把变量放入一个多用选的寄存器,而不是堆栈里。合适地使用q种方珐Q它会ؓ~译器提供关于最l常讉K变量的提C,会稍微提高函数的执行速度。函数调用得是频繁Q这L改变p是可能提高代码的速度?/p>
全局变量
使用全局变量比向函数传递参数更加有效率。这样做去除了函数调用前参数入栈和函数完成后参数出栈的需要。实际上QQ何子E序最有效率的实现是根本没有参数。然而,军_使用全局变量对程序也可能有一些负作用。Y件工Eh士通常不鼓׃用全局变量Q努力促q模块化和重入目标,q些也是重要的考虑?/p>
轮询
中断服务例程l常用来提高E序的效率。然而,也有数例子׃q度和中断关联而造成实际上效率低下。在q些情况中,中断间的q_旉和中断的{待旉h相同量。这U情况下Q利用轮询与g讑֤通信可能会更好。当Ӟq也会软g的模块更?/p>
定点q算
除非你的目标q_包含一个Q点运的协处理器Q否则你会费很大的劲LU你E序中的点数据。编译器提供的Q点库包含了一l模仿Q点运协处理器指令组的子E序。很多这U函数要p比它们的整数q算函数更长的执行时_q且也可能是不可重入的?/p>
如果你只是利用QҎq行量的运,那么可能只利用定点运来实现它更好。虽然只是明白如何做到这一点就够困隄了,但是理论上用定点q算实现M点计算都是可能的?那就是所谓的点软g库?你最大的有利条g是,你可能不必只是ؓ了实C个或者两个计而实现整个IEEE 754 标准。如果真的需要那U类型的完整功能Q别d~译器的点库,d扑օ他加速你E序的方法吧?/p>
不论是C、C++对于内存寚w的问题在原理上是一致的Q对齐的原因和表玎ͼ单ȝ一下,以便朋友们共享?/p>
一、内存对齐的原因
大部分的参考资料都是如是说的:
1、^台原?UL原因)Q不是所有的gq_都能讉KL地址上的L数据的;某些gq_只能在某些地址处取某些特定cd的数据,否则抛出g异常?br />2、性能原因Q数据结?其是栈)应该可能地在自然边界上寚w。原因在于,Z讉K未对齐的内存Q处理器需要作两次内存讉KQ而对齐的内存讉K仅需要一ơ访问?/p>
也有的朋友说Q内存对齐出于对d的效率和数据的安全的考虑Q我觉得也有一定的道理?/p>
二、对齐规?br /> 每个特定q_上的~译器都有自q默认“寚wpL”(也叫寚w模数)。比?2位windowsq_下,VC默认是按?bytes寚w?VC->Project->settings->c/c++->Code Generation中的truct member alignment 值默认是8)Q程序员可以通过预编译命?pragma pack(n)Qn=1,2,4,8,16来改变这一pLQ其中的n是你要指定?#8220;寚wpL”?/p>
在嵌入式环境下,寚w往往与数据类型有养I特别是C~译器对~省的结构成员自然对届条件ؓ“N字节??#8221;QN卌成员数据cd的长度。如int型成员的自然对界条g?字节寚wQ而doublecd的结构成员的自然对界条g?字节寚w。若该成员的起始 偏移不位于该成员?#8220;默认自然对界条g”上,则在前一个节面后面添加适当个数的空字节。C~译器缺省的l构整体的自然对界条件ؓQ该l构所有成员中要求?最大自然对界条件。若l构体各成员长度之和不ؓ“l构整体自然对界条g的整数倍,则在最后一个成员后填充I字节?/p>
那么可以得到如下的小l?
cd 寚w方式Q变量存攄起始地址相对于结构的起始地址的偏U量Q?br />Char 偏移量必Mؓsizeof(char)?的倍数
Short 偏移量必Mؓsizeof(short)?的倍数
int 偏移量必Mؓsizeof(int)?的倍数
float 偏移量必Mؓsizeof(float)?的倍数
double 偏移量必Mؓsizeof(double)?的倍数
各成员变量在存放的时候根据在l构中出现的序依次甌I间Q同时按照上面的寚w方式调整位置Q空~的字节~译器会自动填充。同时ؓ了确保结构的大小为结 构的字节边界敎ͼ卌l构中占用最大空间的cd所占用的字节数Q的倍数Q所以在为最后一个成员变量申L间后Q还会根据需要自动填充空~的字节,也就?_l构体的dؓl构体最宽基本类型成员大的整数倍,如有需要编译器会在最末一个成员之后加上填充字节。对于char数组Q字节宽度仍然认Zؓ1?/p>
对于下述的一个结构体Q其寚w方式为:
struct Node1{
double m1;
char m2Q?br /> int m3;
};
对于W一个变量m1Qsizeof(double)=8个字节;接下来ؓW二个成员m2分配I间Q这时下一个可以分配的地址对于l构的v始地址的偏U量?Q是sizeof(char)的倍数Q所以把m2存放在偏U量?的地Ҏ_齐方式,该成员变量占?sizeof(char)=1个字节;接下来ؓW三个成员m3分配I间Q这时下一个可以分配的地址对于l构的v始地址的偏U量?Q不是sizeof (int)=4的倍数Qؓ了满_齐方式对偏移量的U束问题Q自动填?个字节(q三个字节没有放什么东西)Q这时下一个可以分配的地址对于l构的v始地址的偏U量?2Q刚好是sizeof(int), ׃8+4+4 = 16恰好是结构体中最大空间类型double(8)的倍数Q所以sizeof(Node1) =16.
typedef struct{
char a;
int b;
char c;
}Node2;
成员a占一个字节,所以a攑֜了第1位的位置Q由于第二个变量b?个字节,Z证v始位|是4(sizeof(b))的倍数Q所以需要在a后面填充3?字节Q也是b攑֜了从W?位到W?位的位置Q然后就是c攑֜?的位|,此时4+4+1=9。接下来考虑字节边界敎ͼ9q不是最大空间类型int(4) 的倍数Q应该取大于9且是4的的最整?2Q所以sizeof(Node2) = 12.
typedef struct{
char a;
char b;
int c;
}Node3;
明显圎ͼsizeof(Node3) = 8
对于l构体A中包含结构体B的情况,结构体A中的l构体成员B中的最宽的数据cd作ؓ该结构体成员B的数据宽度,同时l构体成员B必须满上述寚w的规定?/p>
要注意在VC中有一个对齐系数的概念Q若讄了对齐系敎ͼ那么上述描述的对齐方式,则不适合?/p>
例如Q?/p>
1字节寚w(#pragma pack(1))
输出l果Qsizeof(struct test_t) = 8 [两个~译器输Z致]
分析q程Q?br />成员数据寚w
#pragma pack(1)
struct test_t {
int a;
char b;
short c;
char d;
};
#pragma pack()
成员d?8Q?/p>
2字节寚w(#pragma pack(2))
输出l果Qsizeof(struct test_t) = 10 [两个~译器输Z致]
分析q程Q?br />成员数据寚w
#pragma pack(2)
struct test_t {
int a;
char b;
short c;
char d;
};
#pragma pack()
成员d?9Q?/p>
4字节寚w(#pragma pack(4))
输出l果Qsizeof(struct test_t) = 12 [两个~译器输Z致]
分析q程Q?br />1) 成员数据寚w
#pragma pack(4)
struct test_t { //按几寚wQ?偏移量ؓ后边W一个取模ؓ零的?br />int a;
char b;
short c;
char d;
};
#pragma pack()
成员d?9Q?/p>
8字节寚w(#pragma pack(8))
输出l果Qsizeof(struct test_t) = 12 [两个~译器输Z致]
分析q程Q?br />成员数据寚w
#pragma pack(8)
struct test_t {
int a;
char b;
short c;
char d;
};
#pragma pack()
成员d?9Q?/p>
16字节寚w(#pragma pack(16))
输出l果Qsizeof(struct test_t) = 12 [两个~译器输Z致]
分析q程Q?br />1) 成员数据寚w
#pragma pack(16)
struct test_t {
int a;
char b;
short c;
char d;
};
#pragma pack()
成员d?9Q?/p>
至于8字节寚w?6字节寚wQ我觉得q两个例子取得不好,没有太大的参考意义?/p>
(x666f)
理想上:如何客户企图使用某个接口~没有获得他所预期的行为,q个代码不该通过~译Q如果代码通过了编译,他的行ؓp是客h惌的?/p>
1. 导入外覆cdQwrapper typesQ?/p>
2. 让typesҎ被正用,不容易被误用。尽量领你的types行ؓ与内|types一致?/p>
3. 设计class犹如设计type
新type的对象应该如何被创徏和销毁?Q自p计operatornewQoperatornew[],operator delete和operator delete[])
对象的初始化和对象的复制该有什么样的差?对应于不同的函数调用
新type的对象如果被passed by value Q意味着什?/p>
什么是新type?#8220;合法?#8221;Q(Q?Q?/p>
你的新type需要配合某个承图pdQ?/p>
你的心type需要什么样的{?/p>
什么样的操作符合函数对此新type而言是合理的
什么样的标准函数应该驳?/p>
谁该取用新type的成?/p>
什么是新type的未声明接口
你的新type有多么一般化
你真的需要一个新type?/p>
一、宁以pass-by-reference-to-const 替换 pass-by-value
1.tbw效率高,没有M构造函数或析构函数被调用,因ؓ没有M新对象被创徏?/p>
2. by refrenece方式传递参数还可以避免对象slicing 问题
二、必返回对象时Q别妄想q回其reference
所有用上static对象的设计,会造成多线E安全性的怀疑?/p>
三、将成员变量声明为privateQ?/strong>
1. 可以实现?#8220;不准讉K”?#8220;只读讉K”?#8220;d讉K”?#8220;惟写讉K”
2. 装Q它使我们能够改变事物而只影响有限客户?/p>
成员变量隐藏在函数接口的背后,可以?#8220;所有可能的实现”提供Ҏ。如Q?strong>tb变量被读或被写时通知其他对象、可以验证class的约束条件以及函数的前提和事后{帖;以及在多U程环境执行同步控制。。。。等?/p>
四、宁以non-member、non-friend替换member函数
- namespace WebBrowserStuff{
- class WebBrowser{...};
- void clearBrowser(WebBrowser& wb);
- }
五、若所有参数皆需要类型{换,请ؓ此采用non-member函数
- class Rational {
- ...
- }
- const Rational operator*(const Rational& lhs,const Rational& rhs)
- {
- return Rational( lhs.numerator()* rhs.numerator()
- ,lhs.denominator()*rhs.denominator() );
- }
- Rational oneFourthQ?Q?Q;
- Rational resultQ?nbsp;
- result = oneFourth * 2;
- result = 2*oneFourth;
六、考虑写出一个不抛异常的swap函数
不幸的是Q如果按照这个标准来_嵌入式系l可能是E序员工作中到的最隄计算机^C。甚臛_某些嵌入式系l中Q根本无法实?#8220;HelloQWorld!”E序。即使在那些可以实现q个E序的嵌入式pȝ里面Q文本字W串的输Z更像是目标的一部分而不是开始的一部分?/p>
你看Q?#8220;HelloQWorld!”CZ隐含的假设,是有一个可以打印字W串的输备。通常使用的是用户昄器上的一个窗口来完成q个功能。但是大多数的嵌入式pȝq没有一个显C器或者类似的输出讑֤。即使是寚w些有昄器的pȝQ通常也需要用一段嵌入式程序,通过调用昄驱动E序来实现这个功能。这对一个嵌入式~程者来说绝Ҏ一个相当具有挑战性的开端?/p>
看v来我们还是最好以一个小的,Ҏ实现q且高度可移植的联h式程序来开始,q样?strong>tbE序也不太会有编E错误。归根到底,我这本书l箋选用“HelloQWorld!”。这个例子的原因是,实现q个E序实在太简单了。这L在读者的E序W一ơ就q行不v来的时候,会去掉一个可能的原因Q即Q错误不是因Z码里的缺P相反Q问题出在开发工h者创建可执行E序的过E里面?/p>
嵌h式程序员在很大程度上必须要依靠自q力量来工作。在开始一个新目的时候,除了他所熟悉的编E语a的语法,他必首先假定什么东襉K没有q{hQ甚臌标准库都没有Q就是类似printf()和scanf()的那些程序员常常依赖的辅助函数。实际上Q库例程常常作ؓ~程语言的基本语法出现。可是这部分标准很难支持所有可能的计算q_Qƈ且常常被嵌入式系l编译器的制造商们所忽略?/p>
所以在q一章里你实际上找不到一个真正的”HelloQWorld!”E序Q相反,我们假定在第一个例子中只可以用最基本的C 语言语法。随着本书的进一步深人,我们会逐步向我们的指opȝ里添加C++的语法、标准库例程和一个等效的字符输出讑֤。然后,在第九章“l合所学的知识”里面。我们才最l实C?#8220;HelloQWorld!”E序。到那时候你顺利地C成ؓ一个嵌入式pȝ~程专家的道路?/p>