查看文章

16位CRC校验原理与算法分析

2007-12-14 09:37

这里，不讨论CRC的纠错原理以及为什么要选下面提及的生成多项式，只是针对以下的生成多项式，如何获得CRC校验码，作一个比较详细的说明。

标准CRC生成多项式如下表：

名称 生成多项式 简记式* 标准引用
CRC-4 x4+x+1 3 ITU G.704
CRC-8 x8+x5+x4+1 0x31
CRC-8 x8+x2+x1+1 0x07
CRC-8 x8+x6+x4+x3+x2+x1 0x5E
CRC-12 x12+x11+x3+x+1 80F
CRC-16 x16+x15+x2+1 8005 IBM SDLC
CRC16-CCITT x16+x12+x5+1 1021 ISO HDLC, ITU X.25, V.34/V.41/V.42, PPP-FCS
CRC-32 x32+x26+x23+...+x2+x+1 04C11DB7 ZIP, RAR, IEEE 802 LAN/FDDI, IEEE 1394, PPP-FCS
CRC-32c x32+x28+x27+...+x8+x6+1 1EDC6F41 SCTP

生成多项式的最高位固定的1，故在简记式中忽略最高位1了，如0x1021实际是0x11021。
I、基本算法（人工笔算）：
以CRC16-CCITT为例进行说明，CRC校验码为16位，生成多项式17位。假如数据流为4字节：BYTE[3]、BYTE[2]、BYTE[1]、BYTE[0]；
数据流左移16位，相当于扩大256×256倍，再除以生成多项式0x11021，做不借位的除法运算（相当于按位异或），所得的余数就是CRC校验码。
发送时的数据流为6字节：BYTE[3]、BYTE[2]、BYTE[1]、BYTE[0]、CRC[1]、CRC[0]；

II、计算机算法1（比特型算法）：
1)将扩大后的数据流（6字节）高16位（BYTE[3]、BYTE[2]）放入一个长度为16的寄存器；
2)如果寄存器的首位为1，将寄存器左移1位(寄存器的最低位从下一个字节获得)，再与生成多项式的简记式异或；
否则仅将寄存器左移1位(寄存器的最低位从下一个字节获得)；
3)重复第2步，直到数据流（6字节）全部移入寄存器；
4)寄存器中的值则为CRC校验码CRC[1]、CRC[0]。

III、计算机算法2（字节型算法）：256^n表示256的n次方
把按字节排列的数据流表示成数学多项式，设数据流为BYTE[n]BYTE[n－1]BYTE[n－2]、、、BYTE[1]BYTE[0]，表示成数学表达式为BYTE[n]×256^n+BYTE[n-1]×256^(n-1)

+...+BYTE[1]*256+BYTE[0]，在这里+表示为异或运算。设生成多项式为G17（17bit），CRC码为CRC16。
则，CRC16＝(BYTE[n]×256^n+BYTE[n-1]×256^(n-1)+...+BYTE[1]×256+BYTE[0])×256^2/G17，即数据流左移16位，再除以生成多项式G17。
先变换BYTE[n-1]、BYTE[n-1]扩大后的形式，
CRC16＝BYTE[n]×256^n×256^2/G17+BYTE[n-1]×256^(n-1)×256^2/G17+...+BYTE[1]×256×256^2/G17+BYTE[0]×256^2/G17
＝(Z[n]+Y[n]/G17)×256^n+BYTE[n-1]×256^(n-1)×256^2/G17+...+BYTE[1]×256×256^2/G17+BYTE[0]×256^2/G17
＝Z[n]×256^n+{Y[n]×256/G17+BYTE[n-1]×256^2/G17}×256^(n-1)+...+BYTE[1]×256×256^2/G17+BYTE[0]×256^2/G17
＝Z[n]×256^n+{(YH8[n]×256+YHL[n])×256/G17+BYTE[n-1]×256^2/G17}×256^(n-1)+...+BYTE[1]×256×256^2/G17+BYTE[0]×256^2/G17
＝Z[n]×256^n+{YHL[n]×256/G17+(YH8[n]+BYTE[n-1])×256^2/G17}×256^(n-1)+...+BYTE[1]×256×256^2/G17+BYTE[0]×256^2/G17
这样就推导出，BYTE[n-1]字节的CRC校验码为{YHL[n]×256/G17+(YH8[n]+BYTE[n-1])×256^2/G17}，即上一字节CRC校验码Y[n]的高8位（YH8[n]）与本字节BYTE[n-1]异或，

该结果单独计算CRC校验码（即单字节的16位CRC校验码，对单字节可建立表格，预先生成对应的16位CRC校验码），所得的CRC校验码与上一字节CRC校验码Y[n]的低8位（YL8[n]）

乘以256（即左移8位）异或。然后依次逐个字节求出CRC，直到BYTE[0]。
字节型算法的一般描述为：本字节的CRC码，等于上一字节CRC码的低8位左移8位，与上一字节CRC右移8位同本字节异或后所得的CRC码异或。
字节型算法如下：
1)CRC寄存器组初始化为全"0"(0x0000)。（注意：CRC寄存器组初始化全为1时，最后CRC应取反。）
2)CRC寄存器组向左移8位,并保存到CRC寄存器组。
3)原CRC寄存器组高8位（右移8位）与数据字节进行异或运算，得出一个指向值表的索引。
4)索引所指的表值与CRC寄存器组做异或运算。
5)数据指针加1，如果数据没有全部处理完，则重复步骤2)。
6)得出CRC。

unsigned short GetCrc_16(unsigned char * pData, int nLength)
//函数功能：计算数据流* pData的16位CRC校验码，数据流长度为nLength
{
unsigned short cRc_16 = 0x0000; // 初始化

while(nLength>0)
{
cRc_16 = (cRc_16 << 8) ^ cRctable_16[((cRc_16>>8) ^ *pData) & 0xff]; //cRctable_16表由函数mK_cRctable生成
nLength--;
pData++;
}

return cRc_16;
}

void mK_cRctable(unsigned short gEnpoly)
//函数功能：生成0－255对应的16CRC校验码，其实就是计算机算法1（比特型算法）
//gEnpoly为生成多项式
//注意，低位先传送时，生成多项式应反转(低位与高位互换)。如CRC16-CCITT为0x1021，反转后为0x8408
{
unsigned short cRc_16=0;
unsigned short i,j,k;

for(i=0,k=0;i<256;i++,k++)
{
cRc_16 = i<<8;
for(j=8;j>0;j--)
{
if(cRc_16&0x8000) //反转时cRc_16&0x0001
cRc_16=(cRc_16<<=1)^gEnpoly; //反转时cRc_16=(cRc_16>>=1)^gEnpoly
else
cRc_16<<=1; //反转时cRc_16>>=1
}
cRctable_16[k] = cRc_16;
}
}


我的百科

窗体底端

64位

在计算机架构中，64位整数、内存地址或其他数据单元，是指它们最高达到64位（8字节）宽。此外，64位CPU和算术逻辑单元架构是以寄存器、内存总线或者数据总线的大小为基准。

    64 位CPU在1960年代，便已存在于超级计算机，且早在1990年代，就有以 RISC 为基础的工作站和服务器。2003年才以 x86-64 和 64 位 PowerPC 处理器架构的形式引入到（在此之前是 32 位）个人计算机领域的主流。

    一个 64 位的 CPU，内部可能有外部数据总线或不同大小的地址总线，可能比较大或比较小；术语“64位”也常用于描述这些总线的大小。例如，目前有许多机器有着使用 64 位总线的 32 位处理器（如最初的 Pentium 和之后的 CPU），因此有时会被称作“64位”。同样的，某些 16 位处理器（如 MC68000）指的是 16/32 位处理器具有 16 位的总线，不过内部也有一些 32 位的性能。这一术语也可能指计算机指令集的指令长度，或其它的数据项（如常见的 64 位双精度浮点数）。去掉进一步的条件，“64位”计算机架构一般具有 64 位宽的整数型寄存器，它可支持（内部和外部两者） 64 位“区块”（chunk）的整数型数据。

架构影响

    处理器中的寄存器通常可分为三种︰整数、浮点数、其它。在所有常见的主流处理器中，只有整数寄存器（integer register）才可存放指针值（内存数据的地址）。非整数寄存器不能存放指针来读写内存，因此不能用来避开任何受到整数寄存器大小所影响的内存限制。

    几乎所有常见的主流处理器（大部分的 ARM 和 32 位 MIPS 实作是明显的例外）集成了浮点数硬件，它有可能使用 64 位寄存器保存数据，以供处理。例如，x86 架构包含了 x87 浮点数指令，并使用 8 个 80 位寄存器构成堆栈结构。后来的 x86 修改版和 x86-64 架构，又加入 SSE 指令，它使用 8 个 128 位宽的寄存器（在 x86-64 中有 16 个寄存器）。与之相较，64 位 Alpha 系列处理器，除了 32 个 64 位宽整数寄存器以外，也定义了 32 个 64 位宽的浮点数寄存器。

内存限制

    目前大部分的 CPU（截至 2005 年），其单个寄存器可存放虚拟内存中任意数据的内存地址（本机）。因此，虚拟内存（计算机在程序的工作区域中所能保留的数据总量）中可用的地址取决于寄存器的宽度。自 1960 年的 IBM System/360 起，然后 1970年 的 DEC VAX 微型计算机，以及 1980年 中期的 Intel 80386，在事实上一致开发合用的 32 位大小的寄存器。32 位寄存器意味着 2^32 的地址，或可使用 4 GB 的内存。当时在设计这些架构时，4 GB 的内存远远超过一般所安装的可用量，而认为已足够用于寻址。认为 4 GB 地址为合适的大小，还有其它重要的理由︰在应用程序中，如数据库，42 亿多的整数已足够对大部分可计算的实例分配唯一的参考引用。

    然而在 1990年初，成本不断降低的内存，使安装的内存数量逼近 4 GB，且在处理某些类型的问题时，可以想像虚拟内存的使用空间将超过 4 GB 上限。为此，一些公司开始释出新的 64 位架构芯片家族，最初是提供给超级计算机、顶级工作站和服务器机器。64 位运算逐渐流向个人计算机，在 2003 年，某些型号的 Apple Macintosh 产生线转向 PowerPC 970 处理器（Apple 称为“G5”），并在 2006 年，转向 EM64T 处理器，且 x86-64 处理器在顶级的 PC 中遂渐普及。64 位架构的出现，有效的将内存上限提升至 2^64 地址，16 EB 的内存。从这个角度来看，在 4 MB 主内存很普遍时，最大的内存上限 2^32 的地址大约是一般安装内存的 1000 倍。如今，当 1 GB 的主内存很普遍时，2^64 的地址上限大约是 1 百亿倍。

    今天市面上大部分的消费级 PC 存在着人为的内存限制，因受限于实例上的限制，而几乎不太可能需要完整支持 16 EB 容量。举例来说，Apple 的 Mac Pro 最多可安装实例内存至 16 GB，而无必要支持超过的大小。最新的 Linux 内核（版本 2.6.16）可编译成最高支持 64 GB 的内存，Windows Vista Ultimate支持超过128G内存。

32 与 64 位

    从 32位到 64 位架构的改变是一个根本的改变，因为大多数操作系统必须进行全面性修改，以取得新架构的优点。其它软件也必须进行移植，以使用新的性能；较旧的软件一般可借由硬件兼容模式（新的处理器支持较旧的 32 位版本指令集）或软件模拟进行支持。或者直接在 64 位处理器里面实作 32 位处理器内核（如同 Intel 的 Itanium 处理器，其内含有 x86 处理器内核，用来执行 32 位 x86 应用程序）。支持 64 位架构的操作系统，一般同时支持 32 位和 64 位的应用程序。

    明显的例外是 AS/400，其软件执行在虚拟的指令集架构，称为 TIMI（技术独立机器界面），它会在执行之前，以低阶软件转换成本地机器码。低阶软件必须全部重写，以搬移整个 OS 以及所有的软件到新的平台。例如，当 IBM 转移较旧的 32/48 位“IMPI”指令集到 64 位 PowerPC（IMPI 完全不像 32 位 PowerPC，所以这比从 32 位版本的指令集转移到相同指令集的 64 位版本的规模还要庞大）。

    64 位架构无疑可应用在需要处理大量数据的应用程序，如数码视频、科学运算、和早期的大型数据库。在其它工作方面，其 32 位兼容模式是否会快过同等级的 32 位系统，这部分已有很多争论。在 x86-64 架构（AMD64 和 Intel 64）中，主要的 32 位操作系统和应用程序，可平滑的执行于 64 位硬件上。

    Sun 的 64 位 Java虚拟机的启动速度比 32 位虚拟机还慢，因为 Sun 仍假定所有的 64 位机器都是服务器，而且只有为 64 位平台实作“服务器”编译器（C2）。“客户端”编译器（C1）产生较慢的代码，不过编译较快速。所以尽管在 64 位 JVM 的 Java 程序在一段很长的周期会执行的较好（一般为长时间运作的“服务器”应用程序），它的启动时间可能更久。对于短生命期的应用程序（如 Java 编译器 javac）增加启动时间可控制执行时间，使 64 位的 JVM 整体变慢。

    应当指出，在比较 32 位和 64 位处理器时，速度并不是唯一的考量因素。应用程序，如多任务、应力测试（stress testing）、丛集（clustering）（用于HPC）可能更适合 64 位架构以正确部署。为了以上原因，64 位丛集已广泛部署于大型组织，如 IBM、Vodafone、HP、微软。

优缺点

    一个常见的误解是︰除非计算机安装的内存大于 4 GB，否则 64 位架构不会比 32 位架构好。这不完全正确︰

    部分操作系统保留了一部分进程地址空间供操作系统使用，造成使用者程序可用于映射内存的地址空间减少。例如，Windows XP DLL 以及 userland OS 组件映射到每一个进程的地址空间，即使计算机装有 4 GB 的内存，也仅留下 2 至 3.8 GB（端视其设置）的可用地址空间。这个限制在 64 位 Windows 中并不会出现。

    文件的内存映射对 32 位的架构而言不再合用，尤其是相对便宜的 DVD 烧录技术的引入。大于 4 GB 的文件不再罕见，且如此大的文件无法简单的映射到 32 位架构的内存；只能映射文件的一部分范围到地址空间上，并以内存映射存取文件，当有需要时，就必须将这些范围映射进或映射出地址空间。这是一个问题，因为充裕的内存映射仍是从磁盘至内存最有效率的存取方法，如果操作系统能适当实行的话。

    64 位架构主要的缺点是，相对于 32 位架构，占用相同的数据会消秏更多的内存空间（由于肿胀的指针，以及其它型态和对齐补白等可能）。这会增加进程对内存的需求，且可能会影响高效能处理器高速缓存的使用。维持一部分的 32 位模型是一个处理方法，且大致合理有效。实际上，高效能导向的 z/OS 操作系统便采取这个方法，要求程序代码存放在 32 位地址空间的任一数字，数据对象则可（选择性）存放在 64 位区域。

    目前主要的商业软件是建立在 32 位代码，而非 64 位代码，所以不能取得在 64 位处理器上较大的 64 位地址空间，或较宽的 64 位寄存器和数据路径的优点。然而，免费或自由软件操作系统的使用者已经可以使用专有的 64 位运算环境。并非所有的应用程序都需要大量的地址空间或操作 64 位数据项，所以这些程序不会享受到较大的地址空间或较宽的寄存器和数据路径的好处；主要受益于 64 位版本的应用程序，并不会享受到使用 x86 的版本，会有更多的寄存器可以使用。

软件的可用性

    64 位系统有时缺乏对应的软件，那些软件是写给 32 位架构。最严重的问题是不兼容的驱动程序。尽管大部分软件可执行于 32 位兼容模式（又称作模拟模式，即微软 WoW64 技术），其通常无法执行驱动程序（或类似软件），因为程序通常执行于操作系统和硬件之间，在此直接模拟无法使用。许多开放源始码软件封包可简单的从源始码编译为可执行于 64 位环境操作系统，如 Linux。所需的条件是供给 64 位机器的编译器（通常是 gcc）。目前大部分 64 位版本的驱动程序还不能用，由于缺少可用的驱动程序，所以使用 64 位操作系统会有挫折。

    因为设备的驱动程序通常执行于操作系统内核（kernel）的内部，有可能以 32 位进程执行内核，同时支持 64 位的使用者进程。以在内核里的额外消耗为代价，如此可为使用者提供受益于 64 位的内存和效能，且不破坏现存 32 位驱动程序的二进制兼容性。这个机制源于 Mac OS X 启用 64 位进程，同时支持 32 位的驱动程序。

64 位数据模型

    以高阶语言编写的应用软件，从 32 位架构转换到 64 位架构的各种困难。一个共同的问题是，部分程序员假定指针如同其它数据型态一样有相同的长度。程序员假定他们可以在数据型态之间传送数量而不遗失信息。这些假定只在一部分 32 位机器上如此（甚至是一部分 16 位机器），不过在 64 位机器上就不再如此。C 语言及其后代 C++ 尤其容易产生这种错误[1]。

    要在 C 和 C++ 中避免这种错误，如果确定原始类型的大小为所需的基础，sizeof操作符可用来确定原始类型的大小，无论是在编译以及执行时期。此外，在 C99 标准中的 <limits.h> 表头，以及在 C++ 标准中的 <limits> 表头的 numeric_limits 类，可提供更多有用的信息；sizeof只返回字符大小。这个用法使人产生误解，因为一个字符（CHAR_BITS）的大小是由自身决定，在所有的 C 或 C++ 实作中并未以相同方式定义。然而，除了这些编译器目标 DSP 以外，“64 位 = 8 字符（每一字符有 8 位）”已成标准。

    必须谨慎使用 ptrdiff_t 型态（在标准表头 <stddef.h> 中）两个指针相减的结果；太多代码宁可不正确的使用“int”或“long”。表示一个指针（而不是指针差异）为一个整数，在此可以使用 uintptr_t（它只定义在 C99 中，不过某些编译器另外集成较早版本的标准以提供之，作为一个扩充）。

    C 和 C++ 并未定义指针、整数型（int）、长型（long）为特定的位数目。

    在主要的 32 位机器程序设计环境中，指针、“int”变量、“long”变量全部都是 32 位长。

    然而，在 64 位机器下的许多程序设计环境，“int”变量仍然是 32 位宽，不过“long”和指针是 64 位宽，上述内容称为 LP64 数据模型。另一个选择是 ILP64 数据模型，三种数据型态都是 64 位宽，甚至 SILP64 连“short”变量也是 64 位宽。然而，大多数情况下所需的修改是相对次要且简单，而且许多编写良好的程序可以简单的重新编译给新的环境，而无须修改。另一个选择是 LLP64 模型，其维持 32 位代码的兼容性，使 int 和 long 为 32 位。“LL”指“long long”型态，其在所有平台下至少是 64 位，包括 32 位环境。

    今天有许多 64 位编译器使用 LP64 模型（包括 Solaris、AIX、HP、Linux、Mac OS X、IBM z/OS 本地编译器）。微软的 VC++ 编译器使用 LLP64 模型。其缺点是在 LP64 模型中将 long 存放到 int 可能会溢出。另一方面，还会使强制转型一个指针为 long 可以作用；在 LLP 模型下，情况则刚好相反。两者皆不应该出现在合乎 C99 的代码中。

    注意，程序设计模型是在预编译器底层选择的，且数个模型可共存于同一操作系统。然而一般由 OS API 选择程序设计模型作为原始模型。

    另一个考量是用于驱动程序的数据模式。在现代的操作系统中，驱动程序弥补了大多数的操作系统代码（尽管许多代码可能不会加载，当操作系统执行时）。许多驱动程序大量使用指针操控数据，且在某些情况下必须读入一定大小的指针进入支持 DMA 的硬件。举个例子，提供给 32 位 PCI 设备的驱动程序，请求设备的 DMA 数据进入 64 位机器内存的较高区域，可能无法满足来自操作系统从设备到大于 4 GB 内存读入数据的要求。因为对于这些地址的指针，将不适合设备的 DMA 寄存器。这个问题可如下解决，当向设备发出 DMA 请求时，OS 采用与设备相符的内存限制，或者使用 IOMMU。

64 位处理器时间表

    1961︰IBM 发表 IBM 7030 Stretch 超级计算机。它使用 64 位数据字组，以及 32 或 64 位的指令字组。

    1974︰Control Data Corporation 推出 CDC Star-100 向量超级计算机，它使用 64 位字组架构（先前的 CDC 系统是以 60 位架构为基础）。

    1976︰Cray Research 发表第一台 Cray-1 超级计算机。它以 64 位字组架构为基础，它成为后来的 Cray 向量超级计算机的基础。

    1983︰Elxsi推出 Elxsi 6400 平行微型超级计算机。Elxsi架构具有 64 位数据寄存器，不过地址空间仍是 32 位。

    1991︰MIPS科技公司生产第一台 64 位微处理器，作为 MIPS RISC 架构 R4000 的第三次修订版本。该款 CPU 使用于以 IRIS Crimson 启动的 SGI 图形工作站。然而，IRIX 操作系统并未包含对 R4000 的 64 位支持，直到 1996 年释出 IRIX 6.2 为止。Kendall Square Research 发表他们的第一台 KSR1 超级计算机，以专有的执行于 OSF/1 的 64 位 RISC 处理器架构为基础。

    1992︰Digital Equipment Corporation（DEC）引入纯 64 位 Alpha 架构，其诞生自 PRISM 项目。

    1993︰DEC 释出 64 位 OSF/1 AXP 类Unix 操作系统（后来改名为 Tru64 UNIX）和 OpenVMS 操作系统给 Alpha 系统。

    1994︰Intel 宣布 64 位 IA-64 架构的进度表（与 HP 共同开发）作为其 32 位 IA-32 处理器的继承者。以 1998–1999 推出时间为目标。SGI 释出 IRIX 6.0，即支持 64 位的 R8000 CPU。

    1995︰Sun 推出 64 位 SPARC 处理器 UltraSPARC。富士通所有的 HAL 计算机系统推出以 64 位 CPU 为基础的工作站，HAL 独立设计的第一代 SPARC64。IBM 释出 64 位 AS/400 系统，能够转换操作系统、数据库、应用程序的升级。DEC 释出 OpenVMS Alpha 7.0，第一个全 64 位版本的 OpenVMS for Alpha。

    1996︰HP 释出 PA-RISC 处理器架构的 64 位 2.0 版本的实作 PA-8000。任天堂引入 Nintendo 64 电视游戏主机，以低成本的 MIPS R4000 变体所打造。

    1997︰IBM 释出 RS64 全 64 位 PowerPC 处理器。

    1998︰IBM 释出 POWER3 全 64 位 PowerPC/POWER 处理器。Sun 释出 Solaris 7，以完整支持 64 位 UltraSPARC。

    1999︰Intel 释出 IA-64 架构的指令集。AMD 首次公开 64 位集以扩充给 IA-32，称为 x86-64（后来改名为 AMD64）。

    2000︰IBM 推出他自己的第一个兼容 ESA/390 的 64 位大型计算机 zSeries z900，以及新的 z/OS 操作系统。紧接着是 64 位 Linux on zSeries。

    2001︰Intel 终于推出他的 64 位处理器产品线，标记为 Itanium，主打顶级服务器。它无法满足人们的期待，因一再拖延 IA-64 市场而导致失败。Linux 是第一个可执行于该处理器的操作系统。

    2002︰Intel 引入 Itanium 2 作为 Itanium 的继承者。

    2003︰AMD 产出他的 AMD64 架构 Opteron 以及 Athlon 64 处理器产品线。Apple 也推出 64 位“G5”PowerPC 970 CPU courtesy of IBM，并连同升级他的 Mac OS X 操作系统，其增加对 64 位模式的部分支持。若干 Linux 发行版本释出对 AMD64 的支持。微软宣布将为 AMD 芯片建立新的 Windows 操作系统。Intel 坚持 Itanium 芯片仍维持只有 64 位的处理器。

    2004︰Intel 承认 AMD 在市场上的成功，并着手开发 AMD64 延伸的替代品，称为 IA-32e，稍后改名为 EM64T。升级版本的 Xeon 和 Pentium 4 处理器家族支持了新推出的指令。Freescale宣布 64 位 e700 core，以继承 PowerPC G4 系列。VIA Technologies 宣布 Isaiah 64 位处理器。

    2005︰Sun 于 1 月 31 日释出支持 AMD64 和 EM64T 处理器的 Solaris 10。3 月，Intel 宣布他的第一个双内核 EM64T 处理器 Pentium Extreme Edition 840 和新的 Pentium D 芯片将于 2005 第二季推出。4 月 30 日，微软公开释出提供给 AMD64 和 EM64T 处理器的 Windows XP Professional x64 Edition。5 月，AMD 引入他的第一个双内核 AMD64 Opteron 服务器 CPU，并宣布其桌上型版本，称为 Athlon 64 X2。将原本的 Athlon 64 X2 (Toledo) 处理器改为两个内核，并为每个内核的 L2 配上 1 MB 高速缓存内存，以大约 2.332 亿个晶体管组成。它有 199 mm&sup2; 那么大。7 月，IBM 宣布他最新的双内核 64 位 PowerPC 970MP (codenamed Antares)，由 IBM 和 Apple 使用。微软释出 Xbox 360 游戏主机，其使用由 IBM 生产的 64 位、三内核 Xenon PowerPC 处理器。

    2006︰双内核 Montecito Itanium 2 处理器进入生产。Sony、IBM、Toshiba 开始生产用于 PlayStation 3、服务器、工作站以及其它应用的 64 位 Cell 处理器。苹果公司在新的 Mac Pro 和 Intel Xserve 计算机中采用 64 位 EM64T Xeon 处理器，稍后更新 iMac、MacBook、MacBook Pro 使用 EM64T Core 2 处理器。

目前的 64 位微处理器架构

    属于 64 位的微处理器架构（2006年）有︰

    DEC Alpha 架构（查看 Digital Alpha timeline）

    Intel 的 IA-64 架构（用于 Intel 的 Itanium CPU）

    x86-64 架构，64 位版本的 x86 架构（又称作“x64”）

    AMD 的 AMD64（用于 AMD 的 Athlon 64、Opteron、Sempron、Turion 64 CPU）

    Intel 的 Intel64（用于 Intel 的新型 Pentium 4、Xeon、Core 2 CPU）

    SPARC 架构（从 SPARC V9 开始的 64 位）

    Sun 的 UltraSPARC 架构

    Fujitsu 的 SPARC64 架构

    IBM 的 POWER 架构（从 POWER3 和 RS64 变体开始的 64 位）

    IBM/Motorola 的 PowerPC 架构（从 PowerPC 620 和 PowerPC 970 变体开始的 64 位）

    IBM 的 z/Architecture，used by IBM zSeries 和 System z9 大型计算机，ESA/390 架构的 64 位版本

    MIPS 科技公司的 MIPS IV、MIPS V、MIPS64 架构

    HP 的 PA-RISC family（从 PA-RISC 2.0 开始的 64 位）

    大部分 64 位处理器架构可本地执行 32 位版本架构的代码，而无任何效能损失。这种支持通常称为双架构支持或更普遍的多架构支持。

超越 64 位

    直至 2007年，64 位字组似乎已满足大部分的运用。不过仍应提到，IBM 的 System/370 及后继者使用 128 位浮点数，且许多现代处理器也内含 128 位浮点数寄存器。System/370 及后继者尤其显著，在这方面，他们也使用多达 16 字节的可变长度十进制数（即128位）。

图像

    在数码图像中，64 位为附有 16 位 Alpha 通道的 48 位图像。

目前的 64 位AMD和Intel的处理器

    这里的64位技术是相对于32位而言的，这个位数指的是CPU GPRs（General-Purpose Registers，通用寄存器）的数据宽度为64位，64位指令集就是运行64位数据的指令，也就是说处理器一次可以运行64bit数据。64bit处理器并非现在才有的，在高端的RISC（Reduced Instruction Set Computing，精简指令集计算机）很早就有64bit处理器了，比如SUN公司的UltraSparcⅢ、IBM公司的POWER5、HP公司的Alpha等。

    64bit计算主要有两大优点：可以进行更大范围的整数运算；可以支持更大的内存。不能因为数字上的变化，而简单的认为64bit处理器的性能是32bit处理器性能的两倍。实际上在32bit应用下，32bit处理器的性能甚至会更强，即使是64bit处理器，目前情况下也是在32bit应用下性能更强。所以要认清64bit处理器的优势，但不可迷信64bit。

    目前主流CPU使用的64位技术主要有AMD公司的AMD64位技术、Intel公司的EM64T技术、和Intel公司的IA-64技术。其中IA-64是Intel独立开发，不兼容现在的传统的32位计算机，仅用于Itanium（安腾）以及后续产品Itanium 2，一般用户不会涉及到，因此这里仅对AMD64位技术和Intel的EM64T技术做一下简单介绍。

    AMD64位技术

    AMD64的位技术是在原始32位X86指令集的基础上加入了X86-64扩展64位X86指令集，使这款芯片在硬件上兼容原来的32位X86软件，并同时支持X86-64的扩展64位计算，使得这款芯片成为真正的64位X86芯片。这是一个真正的64位的标准，X86-64具有64位的寻址能力。

    X86-64新增的几组CPU寄存器将提供更快的执行效率。寄存器是CPU内部用来创建和储存CPU运算结果和其它运算结果的地方。标准的32-bit x86架构包括8个通用寄存器（GPR），AMD在X86-64中又增加了8组（R8-R9），将寄存器的数目提高到了16组。X86-64寄存器默认位64-bit。还增加了8组128-bit XMM寄存器（也叫SSE寄存器，XMM8-XMM15），将能给单指令多数据流技术（SIMD）运算提供更多的空间，这些128位的寄存器将提供在矢量和标量计算模式下进行128位双精度处理，为3D建模、矢量分析和虚拟现实的实现提供了硬件基础。通过提供了更多的寄存器，按照X86-64标准生产的CPU可以更有效的处理数据，可以在一个时钟周期中传输更多的信息。

    EM64T技术

    Intel官方是给EM64T这样定义的：EM64T全称Extended Memory 64 Technology，即扩展64bit内存技术。EM64T是Intel IA-32架构的扩展，即IA-32e（Intel Architectur-32 extension）。IA-32处理器通过附加EM64T技术，便可在兼容IA-32软件的情况下，允许软件利用更多的内存地址空间，并且允许软件进行32 bit线性地址写入。EM64T特别强调的是对32 bit和64 bit的兼容性。Intel为新核心增加了8个64 bit GPRs（R8-R15），并且把原有GRPs全部扩展为64 bit，如前文所述这样可以提高整数运算能力。增加8个128bit SSE寄存器（XMM8-XMM15），是为了增强多媒体性能，包括对SSE、SSE2和SSE3的支持。

    Intel为支持EM64T技术的处理器设计了两大模式：传统IA-32模式（legacy IA-32 mode）和IA-32e扩展模式（IA-32e mode）。在支持EM64T技术的处理器内有一个称之为扩展功能激活寄存器（extended feature enable register，IA32_EFER）的部件，其中的Bit10控制着EM64T是否激活。Bit10被称作IA-32e模式有效（IA-32e mode active）或长模式有效（long mode active，LMA)。当LMA＝0时，处理器便作为一颗标准的32 bit（IA32）处理器运行在传统IA-32模式；当LMA＝1时，EM64T便被激活，处理器会运行在IA-32e扩展模式下。

    目前AMD方面支持64位技术的CPU有Athlon 64系列、Athlon FX系列和Opteron系列。Intel方面支持64位技术的CPU有使用Nocona核心的Xeon系列、使用Prescott 2M核心的Pentium 4 6系列和使用Prescott 2M核心的P4 EE系列。

    64位技术就像一股不可阻挡的浪潮扑面而来，从今年开始，64位技术将逐步渗透进我们的生活，也许明年这个时候，我们已经习惯了在64位操作系统下运行64位程序。


BCD码

定义：用4位二进制数来表示1位十进制数中的0~9这10个数码，简称BCD码

编码本身都是为了在计算机中运算使用。实质上，在计算机中只有以高低电平来表示的二进制。至于，BCD码是一种8421编码，它将一个字节分两部分，高4位代表一个数字，低4位代表一个数字。也就是说，计算机怎么识别一个字节到底是二进制码还是BCD码，这是由程序代码来运算的。

我的理解可由16进制数简化理解：每2个BCD码构成一字节，接收到每字节二进制数据需处理成16进制的字符形式。仅是由00-99的范围。而非00-FF范围。同样发送的10进制数以每2个BCD码组成1个16进制数形成二进制的一个字节发送来达到目的。

    即BCD代码。Binary-Coded Decimal‎，简称BCD，称BCD码或二-十进制代码，亦称二进码十进数。是一种二进制的数字编码形式，用二进制编码的十进制代码。这种编码形式利用了四个位元来储存一个十进制的数码，使二进制和十进制之间的转换得以快捷的进行。这种编码技巧，最常用于会计系统的设计里，因为会计制度经常需要对很长的数字串作准确的计算。相对于一般的浮点式记数法，采用BCD码，既可保存数值的精确度，又可免却使电脑作浮点运算时所耗费的时间。此外，对于其他需要高精确度的计算，BCD编码亦很常用。

    由于十进制数共有0、1、2、……、9十个数码，因此，至少需要4位二进制码来表示1位十进制数。4位二进制码共有2^4=16种码组，在这16种代码中，可以任选10种来表示10个十进制数码，共有N=16！/（16-10）！约等于2.9乘以10的10次方种方案。常用的BCD代码列于末。

    常用 BCD编码方式

    最常用的BCD编码，就是使用"0"至"9"这十个数值的二进码来表示。这种编码方式，在中国大陆称之为“8421码”。除此以外，对应不同需求，各人亦开发了不同的编码方法，以适应不同的需求。这些编码，大致可以分成有权码和无权码两种：

    有权BCD码，如：8421(最常用)、2421、5421…

    无权BCD码，如：余3码、格雷码…

    以下为三种常见的BCD编码的比较。

    十进数 8421-BCD码余3-BCD码 2421-A码

    (M10) ＤＣＢＡ C3 C2 C1 C0 a3 a2 a1 a0

    ０００００００１１００００

    １０００１０１０００００１

    ２００１００１０１００１０

    ３００１１０１１０００１１

    ４０１０００１１１０１００

    ５０１０１１００００１０１

    ６０１１０１００１０１１０

    ７０１１１１０１００１１１

    ８１０００１０１１１１１０

    ９１００１１１００１１１１

    常用 BCD码

    十进制数 8421码 5421码 2421码余3码余3循环码

    0 0000 0000 0000 0011 0010

    1 0001 0001 0001 0100 0110

    2 0010 0010 0010 0101 0111

    3 0011 0011 0011 0110 0101

    4 0100 0100 0100 0111 0100

    5 0101 1000 1011 1000 1100

    6 0110 1001 1100 1001 1101

    7 0111 1010 1101 1010 1111

    8 1000 1011 1110 1011 1110

    9 1001 1100 1111 1100 1010

    -----------------------

    特点：

    8421编码直观，好理解。

    5421码和2421码中大于5的数字都是高位为1，5以下的高位为0。

    余3码是8421码加上3，有上溢出和下溢出的空间。

    格雷码相邻2个数有三位相同，只有一位不同。

    ————————————————————

    什么是BCD码2006-3-19 13:24:45

    bcd码也叫8421码就是将十进制的数以8421的形式展开成二进制，大家知道十进制是0～9十个数组成，着十个数每个数都有自己的8421码：

    0＝0000

    1＝0001

    2＝0010

    3＝0011

    4＝0100

    5＝0101

    6＝0110

    7＝0111

    8＝1000

    9＝1001

    举个例子：

    321的8421码就是

    3 2 1

    0011 0010 0001

    原因:0011=8x0+4x0+1x2+1x1=3 0010=8x0+4x0+2x1+1x0=2. 0001=8x0+4x0+2x0+1x1=1

    具体:

    bcd码是四位二进制码, 也就是将十进制的数字转化为二进制, 但是和普通的转化有一点不同, 每一个十进制的数字0-9都对应着一个四位的二进制码,对应关系如下: 十进制0 对应二进制0000 ;十进制1 对应二进制0001 ....... 9 1001 接下来的10就有两个上述的码来表示 10 表示为00010000 也就是BCD码是遇见1001就产生进位,不象普通的二进制码,到1111才产生进位10000

    举例:

    某二进制无符号数11101010，转换为三位非压缩BCD数，按百位、十位和个位的顺序表示，应为<U>__C</U>__。

    A.00000001 00000011 00000111 　　　B. 00000011 00000001 00000111

    C.00000010 00000011 00000100 　　　D. 00000011 00000001 00001001

    解:(1)11101010转换为十进制:234

    (2)按百位、十位和个位的顺序表示，应为<U>__C</U>__。

    附注：压缩BCD码与非压缩BCD码的区别—— 压缩BCD码的每一位用4位二进制表示，一个字节表示两位十进制数。例如10010110B表示十进制数96D；非压缩BCD码用1个字节表示一位十进制数，高四位总是0000，低4位的0000~1001表示0~9.例如00001000B表示十进制数8.


你所遇到的这几个宏是为了进行条件编译

你所遇到的这几个宏是为了进行条件编译。一般情况下，源程序中所有的行都参加编译。但是有时希望对其中一部分内容只在满足一定条件才进行编译，也就是对一部分内容指定编译的条件，这就是“条件编译”。有时，希望当满足某条件时对一组语句进行编译，而当条件不满足时则编译另一组语句。

条件编译命令最常见的形式为：

#ifdef 标识符

程序段1

#else

程序段2

#endif

它的作用是：当标识符已经被定义过(一般是用#define命令定义)，则对程序段1进行编译，否则编译程序段2。

其中#else部分也可以没有，即：

#ifdef

程序段1

#endif

这里的“程序段”可以是语句组，也可以是命令行。这种条件编译可以提高C源程序的通用性。如果一个C源程序在不同计算机系统上系统上运行，而不同的计算机又有一定的差异。例如，我们有一个数据类型，在Windows平台中，应该使用long类型表示，而在其他平台应该使用float表示，这样往往需要对源程序作必要的修改，这就降低了程序的通用性。可以用以下的条件编译：

#ifdef WINDOWS

#define MYTYPE long

#else

#define MYTYPE float

#endif

如果在Windows上编译程序，则可以在程序的开始加上

#define WINDOWS

这样则编译下面的命令行：

#define MYTYPE long

如果在这组条件编译命令之前曾出现以下命令行：

#define WINDOWS 0

则预编译后程序中的MYTYPE都用float代替。这样，源程序可以不必作任何修改就可以用于不同类型的计算机系统。当然以上介绍的只是一种简单的情况，可以根据此思路设计出其它的条件编译。

例如，在调试程序时，常常希望输出一些所需的信息，而在调试完成后不再输出这些信息。可以在源程序中插入以下的条件编译段：

#ifdef DEBUG

print (\"device_open(%p)\\n\", file);

#endif

如果在它的前面有以下命令行：

#define DEBUG

则在程序运行时输出file指针的值，以便调试分析。调试完成后只需将这个define命令行删除即可。有人可能觉得不用条件编译也可达此目的，即在调试时加一批printf语句，调试后一一将printf语句删除去。的确，这是可以的。但是，当调试时加的printf语句比较多时，修改的工作量是很大的。用条件编译，则不必一一删改printf语句，只需删除前面的一条“#define DEBUG”命令即可，这时所有的用DEBUG作标识符的条件编译段都使其中的printf语句不起作用，即起统一控制的作用，如同一个“开关”一样。 [Page]

有时也采用下面的形式：

#ifndef 标识符

程序段1

#else

程序段2

#endif

只是第一行与第一种形式不同：将“ifdef”改为“ifndef”。它的作用是：若标识符未被定义则编译程序段1，否则编译程序段2。这种形式与第一种形式的作用相反。

以上两种形式用法差不多，根据需要任选一种，视方便而定。

还有一种形式，就是#if后面的是一个表达式，而不是一个简单的标识符：

#if 表达式

程序段1

#else

程序段2

#endif

它的作用是：当指定的表达式值为真（非零）时就编译程序段1，否则编译程序段2。可以事先给定一定条件，使程序在不同的条件下执行不同的功能。

例如：输入一行字母字符，根据需要设置条件编译，使之能将字母全改为大写输出，或全改为小写字母输出。

#define LETTER 1

main()

{

char str[20]=\"C Language\",c;

int i=0;

while((c=str[i])!=’\\0’){

i++;

#if LETTER

if(c>=’a’&&c<=’z’) c=\"c-32\";

#else

if(c>=’A’&&c<=’Z’) c=\"c\"+32;

#endif

[NextPage]

printf(\"%c\",c);

}

}

运行结果为：C LANGUAGE

现在先定义LETTER为1，这样在预处理条件编译命令时，由于LETTER为真（非零），则对第一个if语句进行编译，运行时使小写字母变大写。如果将程序第一行改为：

#define LETTER 0

则在预处理时，对第二个if语句进行编译处理，使大写字母变成小写字母（大写字母与相应的小写字母的ASCII代码差32）。此时运行情况为：

c language

有人会问：不用条件编译命令而直接用if语句也能达到要求，用条件编译命令有什么好处呢？的确，此问题完全可以不用条件编译处理，但那样做目标程序长（因为所有语句都编译），而采用条件编译，可以减少被编译的语句，从而减少目标的长度。当条件编译段比较多时，目标程序长度可以大大减少。 [Page]

文件中的#ifndef

头件的中的#ifndef，这是一个很关键的东西。比如你有两个C文件，这两个C文件都include了同一个头文件。而编译时，这两个C文件要一同编译成一个可运行文件，于是问题来了，大量的声明冲突。

还是把头文件的内容都放在#ifndef和#endif中吧。不管你的头文件会不会被多个文件引用，你都要加上这个。一般格式是这样的：

#ifndef <标识>

:}G\"{d}Guest#define <标识>

......

}!Dg+t0il4rGuest......

#endif

<标识>在理论上来说可以是自由命名的，但每个头文件的这个“标识”都应该是唯一的。标识的命名规则一般是头文件名全大写，前后加下划线，并把文件名中的“.”也变成下划线，如：stdio.h

#ifndef _STDIO_H_

y6NW5GesGuest#define _STDIO_H_

......

#endif

2.在#ifndef中定义变量出现的问题（一般不定义在#ifndef中）。

#ifndef AAAEETOP专业博客---电子工程师自己的家园+_8RY&W)@ OC8WH

#define AAA

:G{#d:i)J-Ep XGuest...

)Ahzo N8`B9YGuestint i;EETOP专业博客---电子工程师自己的家园_;^T usl

...EETOP专业博客---电子工程师自己的家园S t0L$\\]REm9?

#endif

0o3u_s2kz7h0V8G[CsGuest里面有一个变量定义

+c%G~,n?ZlGuest在vc中链接时就出现了i重复定义的错误，而在c中成功编译。

结论：

(1).当你第一个使用这个头的.cpp文件生成.obj的时候，int i 在里面定义了当另外一个使用这个的.cpp再次[单独]生成.obj的时候，int i 又被定义然后两个obj被另外一个.cpp也include 这个头的，连接在一起，就会出现重复定义.

(2).把源程序文件扩展名改成.c后，VC按照C语言的语法对源程序进行编译，而不是C++。在C语言中，若是遇到多个int i，则自动认为其中一个是定义，其他的是声明。

(3).C语言和C++语言连接结果不同，可能（猜测）时在进行编译的时候，C++语言将全局

RFX’r/G\"JrGuest变量默认为强符号，所以连接出错。C语言则依照是否初始化进行强弱的判断的。（参考）

解决方法：

(1).把源程序文件扩展名改成.c。

(2).推荐解决方案：

5k[ En E4RGuest.h中只声明 extern int i;在.cpp中定义

<x.h>EETOP专业博客---电子工程师自己的家园 x3l~&^|BP

#ifndef __X_H__EETOP专业博客---电子工程师自己的家园Nv9H#\\Y._A:GL+Q&XI

#define __X_H__EETOP专业博客---电子工程师自己的家园}9\\3x7Ik9xIKnW

extern int i;EETOP专业博客---电子工程师自己的家园1}UxW(z M }1d&o

#endif //__X_H__

zGe b*ExmGuest<x.c>

ixc:t$X zGuestint i;

注意问题：

(1).变量一般不要定义在.h文件中。

一般情况下，源程序中所有的行都参加编译。但是有时希望对其中一部分内容只在满足一定条件才进行编译，也就是对一部分内容指定编译的条件，这就是“条件编译”。有时，希望当满足某条件时对一组语句进行编译，而当条件不满足时则编译另一组语句。

X1g2J QV3|+QnGuest条件编译命令最常见的形式为： EETOP专业博客---电子工程师自己的家园7e n7w[]8G3[ [Page]

#ifdef 标识符

}$?*xz TR?[JGuest 程序段1 EETOP专业博客---电子工程师自己的家园^t6D_ BJ

#else

X(|XDv G(@,SfGuest 程序段2 EETOP专业博客---电子工程师自己的家园.M%~:Yke1|Z;K8Q

#endif EETOP专业博客---电子工程师自己的家园1?%m%I@q5Y

BC6dG6I(OGuest 它的作用是：当标识符已经被定义过(一般是用#define命令定义)，则对程序段1进行编译，否则编译程序段2。 EETOP专业博客---电子工程师自己的家园9?2[tx8EKZ

其中#else部分也可以没有，即： EETOP专业博客---电子工程师自己的家园K%L#Z @h,Op[)Rn

#ifdef

q-A#G6sd%TGuest 程序段1

o*X1l6aa1mGuest #denif EETOP专业博客---电子工程师自己的家园{X)u+n3_A

EETOP专业博客---电子工程师自己的家园 y|.@KNS%\\tS

这里的“程序段”可以是语句组，也可以是命令行。这种条件编译可以提高C源程序的通用性。如果一个C源程序在不同计算机系统上系统上运行，而不同的计算机又有一定的差异。例如，我们有一个数据类型，在Windows平台中，应该使用long类型表示，而在其他平台应该使用float表示，这样往往需要对源程序作必要的修改，这就降低了程序的通用性。可以用以下的条件编译：

[NextPage]

Oo0?(^b’suGuest #ifdef WINDOWS

K[{1C%CD.U.i4OGuest #define MYTYPE long EETOP专业博客---电子工程师自己的家园 X-|#f2u*D+j }xG

#else EETOP专业博客---电子工程师自己的家园8c\"Y/[$n!QVb

#define MYTYPE float

H’Q/N@4vGuest #endif EETOP专业博客---电子工程师自己的家园;p6`jd3OP0Se8V

?JTIn’AYoGuest 如果在Windows上编译程序，则可以在程序的开始加上

0HOpZ0CH)hBGuest #define WINDOWS

Kj;qXO v8ykGuest EETOP专业博客---电子工程师自己的家园 ey J$r,U

这样则编译下面的命令行： EETOP专业博客---电子工程师自己的家园uK Amkx

#define MYTYPE long EETOP专业博客---电子工程师自己的家园bS3_)zREYv+l*?

EETOP专业博客---电子工程师自己的家园 cUe^.QCp;e

如果在这组条件编译命令之前曾出现以下命令行： EETOP专业博客---电子工程师自己的家园!Rb~n9Y&gA\"x+jB6\\&L [Page]

#define WINDOWS 0 EETOP专业博客---电子工程师自己的家园? f%hS#xsP

EETOP专业博客---电子工程师自己的家园1I%O\"HA.q&}/e%?

则预编译后程序中的MYTYPE都用float代替。这样，源程序可以不必作任何修改就可以用于不同类型的计算机系统。当然以上介绍的只是一种简单的情况，可以根据此思路设瞥?a href=\"javascript:;\" onClick=\"tagshow(event, ’%C6%E4%CB%FC’);return false;\">其它的条件编译?

tRX1w9G a{0{AGuest 例如，在调试程序时，常常希望输出一些所需的信息，而在调试完成后不再输出这些信息。可以在源程序中插入以下的条件编译段： EETOP专业博客---电子工程师自己的家园GRd*IR

#ifdef DEBUG

)k[?^7LGuest print (\"device_open(%p) \", file); EETOP专业博客---电子工程师自己的家园:k*P.U}\"d#YdZ9[

#endif

4LoC3DDn.]Guest

j\"Q5` q{Guest 如果在它的前面有以下命令行： EETOP专业博客---电子工程师自己的家园)t!Z1g~U4h3@$z

#define DEBUG

by#K y*nz9g0TyvGuest EETOP专业博客---电子工程师自己的家园wJu:h)a,q ^ Q

则在程序运行时输出file指针的值，以便调试分析。调试完成后只需将这个define命令行删除即可。有人可能觉得不用条件编译也可达此目的，即在调试时加一批printf语句，调试后一一将printf语句删除去。的确，这是可以的。但是，当调试时加的printf语句比较多时，修改的工作量是很大的。用条件编译，则不必一一删改printf语句，只需删除前面的一条“#define DEBUG”命令即可，这时所有的用DEBUG作标识符的条件编译段都使其中的printf语句不起作用，即起统一控制的作用，如同一个“开关”一样。 EETOP专业博客---电子工程师自己的家园0F4D@1PK(LGcI+L8C

有时也采用下面的形式：

l#U.@y8p*Wnw!NY jGuest #ifndef 标识符 EETOP专业博客---电子工程师自己的家园1Z)?3X hV j~

程序段1

{z DX/xq;kGuest #else EETOP专业博客---电子工程师自己的家园Sov@-J&NV

程序段2 EETOP专业博客---电子工程师自己的家园9b(~ ]GM }

#endif EETOP专业博客---电子工程师自己的家园*_%I`X,Rv\\6x

EETOP专业博客---电子工程师自己的家园:I9yU[B

只是第一行与第一种形式不同：将“ifdef”改为“ifndef”。它的作用是：若标识符未被定义则编译程序段1，否则编译程序段2。这种形式与第一种形式的作用相反。 EETOP专业博客---电子工程师自己的家园+z9f},mL:Z}P [Page]

以上两种形式用法差不多，根据需要任选一种，视方便而定。 EETOP专业博客---电子工程师自己的家园F&S0@3v _

还有一种形式，就是#if后面的是一个表达式，而不是一个简单的标识符：

4^$w+J Vd#\\mGuest #if 表达式 EETOP专业博客---电子工程师自己的家园.]-l,^]Y

程序段1 EETOP专业博客---电子工程师自己的家园)}IlI:B K)C

#else

&[(a$h/Q#CP,mnqGuest 程序段2 EETOP专业博客---电子工程师自己的家园%UJcX4[5D

#endif EETOP专业博客---电子工程师自己的家园(Ll/ydH1l bJG r

EETOP专业博客---电子工程师自己的家园 ? S0Wlb\\

它的作用是：当指定的表达式值为真（非零）时就编译程序段1，否则编译程序段2。可以事先给定一定条件，使程序在不同的条件下执行不同的功能。

作用范围就是当前文件啊。因为编译是以cpp或c文件位单位的嘛。还以这个为例：

[NextPage]

//正常代码EETOP专业博客---电子工程师自己的家园C Y_W ].Tu3O:V;A

#ifdef _DEBUG

.F,`\"CclGuest TRACE(\"Some infomation\");EETOP专业博客---电子工程师自己的家园+f/Z ^3g-Y~{/~Fg^

#elseEETOP专业博客---电子工程师自己的家园(RLkS2p0frb

//Now is release version,so do nothing

Q2{ht3G&S5`5_G.|Guest#endifEETOP专业博客---电子工程师自己的家园?MY(U/bI da/N

//正常代码

编译时是先把所有的预编译处理展开（比如宏）再编译，所以Debug模式下，编译时的代码是：EETOP专业博客---电子工程师自己的家园’F-Sv(B/O.M7B)J

//正常代码EETOP专业博客---电子工程师自己的家园m(T/Of)E8XT$]O’@

TRACE(\"Some infomation\");

kh|L at|Guest//正常代码

Release模式下的代码是：EETOP专业博客---电子工程师自己的家园d(s8?2Os{ f-m9A

//正常代码EETOP专业博客---电子工程师自己的家园u%xpN?bg}5{[

//正常代码

预处理就是在进行编译的第一遍词法扫描和语法分析之前所作的工作。说白了，就是对源文件进行编译前，先对预处理部分进行处理，然后对处理后的代码进行编译。这样做的好处是，经过处理后的代码，将会变的很精短。

关于预处理命令中的文件包含（#include），宏定义（#define），书上已经有了详细的说明，在这里就不详述了。这里主要是对条件编译（#ifdef,#else,#endif,#if等）进行说明。以下分3种情况：

1：情况1：

#ifdef _XXXX

...程序段1...

#else

...程序段2...

#endif

这表明如果标识符_XXXX已被#define命令定义过则对程序段1进行编译；否则对程序段2进行编译。

例： [Page]

#define NUM

.............

.............

.............

#ifdef NUM

printf(\"之前NUM有过定义啦！:) \\n\");

#else

printf(\"之前NUM没有过定义！:( \\n\");

#endif

}

如果程序开头有#define NUM这行，即NUM有定义，碰到下面#ifdef NUM的时候，当然执行第一个printf。否则第二个printf将被执行。

我认为，用这种，可以很方便的开启/关闭整个程序的某项特定功能。

2:情况2：

#ifndef _XXXX

...程序段1...

#else

...程序段2...

#endif

这里使用了#ifndef，表示的是if not def。当然是和#ifdef相反的状况（如果没有定义了标识符_XXXX，那么执行程序段1，否则执行程序段2）。例子就不举了。

3：情况3：

#if 常量

...程序段1...

#else

...程序段2...

#endif

这里表示，如果常量为真（非0，随便什么数字，只要不是0），就执行程序段1，否则执行程序段2。

我认为，这种方法可以将测试代码加进来。当需要开启测试的时候，只要将常量变1就好了。而不要测试的时候，只要将常量变0。

#if语句要判断是宏的值，宏的值只有通过#define或者在命令行-DXXX的形式定义。

用const int UP = 1；形式定义了一个整型常量，而不是一个宏。

对于宏的替换，是在预编译阶段就进行的，会把所有的宏替换为实际的值。而对const int UP的解析，却是在编译阶段。

在预编译阶段，只认#define定义的宏，不会理会const int 定义出来的常量，所以#if永远为假。

【fallinleave】:

#if寻找UP宏，你用const只是定义常量，并没有UP这个宏，而#define 是定义的宏，所以成立。


CVS系统

    CVS是一个C/S系统，是一个常用的代码版本控制软件。主要在开源软件管理中使用。与它相类似的代码版本控制软件有subversion。

    工作模式如下：

    CVS服务器（文件版本库）

    CVS（Concurrent Version System）版本控制系统是一种GNU软件包，主要用于在多人开发环境下的源码的维护。实际上CVS可以维护任意文档的开发和使用，例如共享文件的编辑修改，而不仅仅局限于程序设计。CVS维护的文件类型可以是文本类型也可以是二进制类型。CVS用Copy-Modify-Merge（拷贝、修改、合并）变化表支持对文件的同时访问和修改。它明确地将源文件的存储和用户的工作空间独立开来，并使其并行操作。CVS基于客户端/服务器的行为使其可容纳多个用户，构成网络也很方便。这一特性使得CVS成为位于不同地点的人同时处理数据文件（特别是程序的源代码）时的首选。

    所有重要的免费软件项目都使用CVS作为其程序员之间的中心点，以便能够综合各程序员的改进和更改。这些项目包括GNOME、KDE、THE GIMP和Wine等。

    CVS的基本工作思路是这样的：在一台服务器上建立一个源代码库，库里可以存放许多不同项目的源程序。由源代码库管理员统一管理这些源程序。每个用户在使用源代码库之前，首先要把源代码库里的项目文件下载到本地，然后用户可以在本地任意修改，最后用CVS命令进行提交，由CVS源代码库统一管理修改。这样，就好像只有一个人在修改文件一样，既避免了冲突，又可以做到跟踪文件变化等。

    CVS是并发版本系统（Concurrent Versions System）的意思，主流的开放源码网络透明的版本控制系统。CVS对于从个人开发者到大型、分布团队都是有用的。

    它的客户机/服务器存取方法使得开发者可以从任何因特网的接入点存取最新的代码。它的无限制的版本管理检出(check out：注1)的模式避免了通常的因为排它检出模式而引起的人工冲突。它的客户端工具可以在绝大多数的平台上使用。

    CVS被应用于流行的开放源码工程中，象Mozilla，GIMP，XEmacs，KDE，和GNOME等。那么它到底怎么样？

    你可能会说，它非常棒，但是对于"我"来说它能做什么？首先，基本的：一个版本控制系统保持了对一系列文件所作改变的历史记录。对于一个开发者来说，那就意味着在你对一个程序所进行开发的整个期间，能够跟踪对其所作的所有改动的痕迹。对你来说，有没有出现过由于在命令行上按错键而导致一天的工作都白费的情况呢？版本控制系统给了你一个安全的网络。

    版本控制系统对任何人都有用，真的。(毕竟，谁不愿意使用一个安全的网络呢？)它们经常被软件开发团队使用。在团队中工作的开发者需要能够调整他们的各自的修改；一个集中式版本控制系统允许那样做。

    代码集中的配置

    个人开发者希望一个版本控制系统的安全网络能够运行在他们的本地的一台机器上。然而，开发团队需要一个集中的服务器，所有的成员可以将服务器作为仓库来访问他们的代码。在一个办公室中，没有问题 --只是将仓库连到本地网络上的一台服务器上就行了。对于开放源码项目...噢，还是没有问题，这要感谢因特网。CVS内建了客户机/服务器存取方法，所以任何一个可以连到因特网上的开发者都可以存取在一台CVS服务器上的文件。

    调整代码

    在传统的版本控制系统中，一个开发者检出一个文件，修改它，然后将其登记回去。检出文件的开发者拥有对这个文件修改的排它权。没有其它的开发者可以检出这个文件 -- 并且只有检出那个文件的开发者可以登记(check in：注2)所做的修改。(当然对于管理员有很多方法可以超越这个限制。)

    想一下排它的检出可能会如何工作：Bob的兄弟检出 foo.java以便加入注释，写好代码后他什么也没做。然后他去吃午饭了。Bob吃完午饭后，发现他的老板所指给他的一个bug在 foo.java里。他试图检出 foo.java ... 但是版本控制系统不允许他这样做，因为他的兄弟已经把它检出了。Bob不得不等着他的兄弟吃完午饭回来(在这个"好"日子用了两个小时)，他才可以修正bug。

    在一个大型的开放源码工程中，因为开发者可能在任意的时区工作得很晚，给予一个开发者阻止任意地方的其它开发者继续处理任意文件的能力很明显无法运转。他们最终将因为不能够在他们想要的时候开展项目而感到厌烦。

    CVS通过它的无限制的检出模式解决了这个问题。检出一个文件并不给定开发者对那个文件的排它权。其它的开发者也可以对其检出，进行他们自己的修改，并且将其登记回去。

    "等一下！"你可能会说。"但是后面的登记不是会覆盖前面的吗？"回答是不会。详细地回答就是当多个开发者对同一个文件作了修改CVS会检测，并且自动合并那些改变。

    哇噢。自动的？不用担心 -- CVS 会很小心，并且将会自动合并那些只要不是对代码的同一行所作的改动。如果CVS不能安全的处理这些改动，开发者将不得不手工合并它们。从此去往何处？

    有大量的可用在许多平台上CVS 附加工具，它们给CVS增加了功能或使得CVS更容易使用。

    cvs是Concurrent Versions System的缩写，Concurrent有并发的，协作的，一致的等含义。CVS是一个版本控制系统，使用它，可以记录下源文件的历史。

    CVS维护的文件类型可以是文本类型也可以是二进制类型。CVS的基本工作思路是这样的：在一台服务器上建立一个源代码库，库里可以存放许多不同项目的源程序。每个用户在使用源代码库之前，首先要把源代码库里的项目文件下载到本地，然后用户可以在本地任意修改，最后用CVS命令进行提交，由CVS源代码库统一管理修改。

使用CVS的好处

    ·修改软件时可能会不知不觉混进一些 bug，而且可能过了很久你才会察觉到它们的存在。有了 cvs，你可以很容易地恢复旧版本，并从中看出到底是哪个修改导致了这个 bug。有时这是很有用的。

    ·cvs 用一种聪明的办法把一个文件的所有版本保存在一个文件里，仅仅保存不同版本之间的差异

    ·cvs 最初由 Dick Grune 在 1986 年 12 月以 shell 脚本的形式发布在 comp.sources.unix 的新闻组第 6 卷里;1989 年 4 月，Brian Berliner 设计了 cvs 并编写了代码。之后 Jeff Polk 帮助 Brian 设计了 cvs 模块和销售商分支支持。

    ·cvs 不能指导你如何构造什么。它只是将你所设计的一种树结构文件保存下来以备恢复之用。

    ·cvs 不能决定如何在一个检出工作目录使用磁盘空间。如果你在每一个目录中都写下 Makefile 或脚本，且必须知道其它一切的相对位置，有时不得不要检出整个仓库。

    ·如果你将你的工作模块化，并且建立了一个共享文件的 build 系统（通过links，mounts，Makefiles 里的 VPATH 等)，你就可以随意安排磁盘的使用。

    ·你应该在 cvs 下放一个工具来支持这样一个构造系统（脚本、Makefile 等等）。

    ·有些变化发生在 cvs 范围之外时，要想想什么文件需要重建。一个传统的方法是用 make 来构造，并用一些自动化的工具来产生 make 所用的相关文件。

cvs 不能替代管理

    你的经理和项目负责人应经常与你交流以确保你时时记得进度表、合并点、分支名和发布日期。如果他们不这样做，cvs 也没用。 cvs 只是一个用来使你的资源与你的步调一致的工具。但你是风笛手和作曲家。没有哪种乐器会自己演奏或是作曲。

    ·cvs 不能代替开发者之间的交流。

    在单个文件内遇到冲突时，大多数开发者不费多大力气就能解决它们。但更常见的"冲突(conflict)"，是那些难度较大、不在开发者之间进行交流就没法解决的问题。

    当在一个文件内或多个文件中同时发生变化时，cvs 并不知道何时它们会在逻辑上发生冲突。它的冲突(conflict)概念是纯粹文本意义上的，这种冲突会在同一个文件的两种变化十分接近以致于会破坏合并命令(如 diff3)。

    cvs 决不会指出程序逻辑上非文本或分布式的冲突。例如：假如你改变了在文件 A 中定义的函数 X 的参数。同时，别人在编辑文件 B，仍用旧参数调用 X 这个函数。此时产生的冲突 cvs 可就无能为力了。

cvs 没有变化控制

    变化控制可以指许多事情。首先它的意思可以是 BUG 跟踪bug-tracking，就是说它能维持一个数据库，其中包括已报告的 BUG 和每一个 BUG 状态 (是否已更正？在哪一个版本中？提交这个 BUG 的人是否认为已经更正？)。为了使 cvs 和一个外部的跟踪 BUG 系统协调一致，请参考 rcsinfo 和 verifymsg 文件 (参阅 Administrative files)。

    变化控制的另一个方面指跟踪这样的情况，即对好几个文件的改变实际上只是同一个逻辑变动。如果你在一次 cvs commit 操作中检入几个文件，cvs 会忘掉它们是一起检入的，它们共用一个 LOG 信息的事实只是把它们绑在一起而已。做一个 gnu 风格的 ChangeLog 可能会有点用。在一些系统中，变化控制的另一个方面是跟踪每个变化的状态的能力。一些变化由一个开发者写出，而另一些变化则由另一个开发者来作出评论，等等。一般来讲，用 cvs 来做，是产生一个 diff（用 cvs diff 或 diff），并且用电子邮件寄给某人，此人就可以用 patch 来应用它。这是非常灵活的，但依赖于 cvs 之外的机制以保证事情不会崩溃。

cvs 不是自动测试程序

    强制利用 commitinfo 文件测试套件应该是可能的。不过我没有听说过多少项目试图那样做或那里有微妙的陷阱。

cvs 没有内置的处理模型

    有些系统提供一些方法确保变更或发布通过不同的步骤，以及各种所需的批准过程。一般地，你可以用 cvs 来完成它，但是可能要多做点工作。有些情况下你想用 commitinfo、loginfo、rcsinfo 或 verifymsg 文件，要求在 CVS 提交之前完成某些操作。你也会考虑诸如 branches 和 tags 等特性是否能用在一个开发树中执行任务，然后仅当它们被证实就把某些修改合并到一棵稳定的树中

    CVS 还有一个更加重要的特性：能记下每个文件的每次修改，以及如何被修改...对于基于 Internet 的合作方式来说，这些特性太重要了。一个地域上分散的自愿者组织显然不可能投入很多的时间来训练其成员彼此合作。因为这样的话，当该组织有成员变更的时候，为此付出的投资将损失殆尽。所以需要指定一套基本的项目分配方案，以确保新成员能较容易的适应工作，同时也需要设置一个自动的系统来接受外来代码，并使每个成员能及时得到最新修改的代码。

CVS 中会经常提到的一些术语

    Revision (修订版本)－－文件历史记录中的被开发者提交的变化。一个修订版本就是一个时常变化的项目的 snapshot (瞬态图)。

    Repository (源代码库)－－CVS 存储所有修订版本历史记录的地方。每个项目都有自己的一个确定的源代码库。

    Working copy (工作拷贝)－－开发者对文件作出修改时文件所在的拷贝。

    Check out (检验)－－从源代码库中申请一份工作拷贝。该工作拷贝反映的是取出时项目的瞬时状态。当开发者对拷贝作出修改时，必须运用 commit (提交)和 update (更新) 命令来 “发布”变化和查看其他开发者所作的修改。

    Commit (提交)－－将工作拷贝中的变化输入中央源代码库。

    Log message (日志信息)－－提交修订版本的时候，附带描述变化的注解。通过查阅记录信息，人们可以获得一个当前项目进程的总结。

    Update (更新)－－从源代码库中取出别人的修改数据，将其输入自己的工作拷贝，并显示自己的工作拷贝是否有未提交的修改。注意，不要和 commit (提交)混淆，更新和提交是一对互补的指令。记住: Update 将使工作拷贝和源代码库拷贝保持同步更新。

    Conflicts (冲突)－－俩个开发者对同一个区域所作的变化都提交给主版本时出现的情况，在 CVS 觉察并指出这个冲突后，开发者必须解决该冲突。

[编辑本段]

CVS文件

    有些文件是以CVS作为作为扩展名的，例如导出使用SQL数据库查询所得结果时所保存的文件，这些文件可以使用EXCEL软件打开，打开时默认的间隔符是TAB键，所以我们很方便的读写这些文件的数据。


条件编译是指在编译源程序前，根据指定的条件确定编译的范围

条件编译是指在编译源程序前，根据指定的条件确定编译的范围。条件编译通过if系列预处理指令完成。预处理指令#if簇的用法在很多情况下与if控制语句类似，差别是if语句控制着某些语句是否被执行，某些语句是否被编译。

条件编译预处理指令有以下几种形式。

1．#ifdef标识符

程序段1

#else

程序段2

#end

当标识符已经被定义过，则对程序段1进行编译，否则对程序段2进行编译。

和if—else语句一样，#else子句可以缺省，缺省后的形式为：

#ifdef标识符

程序段1

#endif

另可增加#elif子句构成嵌套。

2．#ifndef标识符

程序段1

#else

程序段2

#endif

与第一种形式正好相反，当标识符未被定义过时，则对程序段1进行编译，否则，对程序段2进行编译。

3．#if表达式

程序段1

#else

程序段2

#endif

当表达式之值为真(非零)时，编译程序段1，否则编译程序段2。

例如：

#if DEBuG==1

调试代码

#endif

开发程序时，：DEBUG定义为1，插入调试代码帮助调试程序。调试完成，将DEBI．JG定义为o，再编译一次，即可去掉调试代码。

第一、 二种形式编译的控制条件为定义的标识符，第三种方式编译的控制条件为表达式，当然可用符号常量。

预处理过程扫描源代码，对其进行初步的转换，产生新的源代码提供给编译器。可见预处理过程先于编译器对源代码进行处理。

在C语言中，并没有任何内在的机制来完成如下一些功能：在编译时包含其他源文件、定义宏、根据条件决定编译时是否包含某些代码。要完成这些工作，就需要使用预处理程序。尽管在目前绝大多数编译器都包含了预处理程序，但通常认为它们是独立于编译器的。预处理过程读入源代码，检查包含预处理指令的语句和宏定义，并对源代码进行响应的转换。预处理过程还会删除程序中的注释和多余的空白字符。

预处理指令是以#号开头的代码行。#号必须是该行除了任何空白字符外的第一个字符。#后是指令关键字，在关键字和#号之间允许存在任意个数的空白字符。整行语句构成了一条预处理指令，该指令将在编译器进行编译之前对源代码做某些转换。下面是部分预处理指令：

指令 用途

# 空指令，无任何效果

#include 包含一个源代码文件

#define 定义宏

#undef 取消已定义的宏

#if 如果给定条件为真，则编译下面代码

#ifdef 如果宏已经定义，则编译下面代码

#ifndef 如果宏没有定义，则编译下面代码

#elif 如果前面的#if给定条件不为真，当前条件为真，则编译下面代码

#endif 结束一个#if……#else条件编译块

#error 停止编译并显示错误信息

一、文件包含

#include预处理指令的作用是在指令处展开被包含的文件。包含可以是多重的，也就是说一个被包含的文件中还可以包含其他文件。标准C编译器至少支持八重嵌套包含。

预处理过程不检查在转换单元中是否已经包含了某个文件并阻止对它的多次包含。这样就可以在多次包含同一个头文件时，通过给定编译时的条件来达到不同的效果。例如：

#define AAA

#include "t.c"

#undef AAA

#include "t.c"

为了避免那些只能包含一次的头文件被多次包含，可以在头文件中用编译时条件来进行控制。例如：

/*my.h*/

#ifndef MY_H

#define MY_H

……

#endif

在程序中包含头文件有两种格式：

#include <my.h>

#include "my.h"

第一种方法是用尖括号把头文件括起来。这种格式告诉预处理程序在编译器自带的或外部库的头文件中搜索被包含的头文件。第二种方法是用双引号把头文件括起来。这种格式告诉预处理程序在当前被编译的应用程序的源代码文件中搜索被包含的头文件，如果找不到，再搜索编译器自带的头文件。

采用两种不同包含格式的理由在于，编译器是安装在公共子目录下的，而被编译的应用程序是在它们自己的私有子目录下的。一个应用程序既包含编译器提供的公共头文件，也包含自定义的私有头文件。采用两种不同的包含格式使得编译器能够在很多头文件中区别出一组公共的头文件。

二、宏

宏定义了一个代表特定内容的标识符。预处理过程会把源代码中出现的宏标识符替换成宏定义时的值。宏最常见的用法是定义代表某个值的全局符号。宏的第二种用法是定义带参数的宏，这样的宏可以象函数一样被调用，但它是在调用语句处展开宏，并用调用时的实际参数来代替定义中的形式参数。

1.#define指令

#define预处理指令是用来定义宏的。该指令最简单的格式是：首先神明一个标识符，然后给出这个标识符代表的代码。在后面的源代码中，就用这些代码来替代该标识符。这种宏把程序中要用到的一些全局值提取出来，赋给一些记忆标识符。

#define MAX_NUM 10

int array[MAX_NUM];

for(i=0;i<MAX_NUM;i++) /*……*/

在这个例子中，对于阅读该程序的人来说，符号MAX_NUM就有特定的含义，它代表的值给出了数组所能容纳的最大元素数目。程序中可以多次使用这个值。作为一种约定，习惯上总是全部用大写字母来定义宏，这样易于把程序红的宏标识符和一般变量标识符区别开来。如果想要改变数组的大小，只需要更改宏定义并重新编译程序即可。

宏表示的值可以是一个常量表达式，其中允许包括前面已经定义的宏标识符。例如：

#define ONE 1

#define TWO 2

#define THREE (ONE+TWO)

注意上面的宏定义使用了括号。尽管它们并不是必须的。但出于谨慎考虑，还是应该加上括号的。例如：

six=THREE*TWO;

预处理过程把上面的一行代码转换成：

six=(ONE+TWO)*TWO;

如果没有那个括号，就转换成six=ONE+TWO*TWO;了。

宏还可以代表一个字符串常量，例如：

#define VERSION "Version 1.0 Copyright(c) 2003"

2.带参数的#define指令

带参数的宏和函数调用看起来有些相似。看一个例子：

#define Cube(x) (x)*(x)*(x)

可以时任何数字表达式甚至函数调用来代替参数x。这里再次提醒大家注意括号的使用。宏展开后完全包含在一对括号中，而且参数也包含在括号中，这样就保证了宏和参数的完整性。看一个用法：

int num=8+2;

volume=Cube(num);

展开后为(8+2)*(8+2)*(8+2);

如果没有那些括号就变为8+2*8+2*8+2了。

下面的用法是不安全的：

volume=Cube(num++);

如果Cube是一个函数，上面的写法是可以理解的。但是，因为Cube是一个宏，所以会产生副作用。这里的擦书不是简单的表达式，它们将产生意想不到的结果。它们展开后是这样的：

volume=(num++)*(num++)*(num++);

很显然，结果是10*11*12,而不是10*10*10;

那么怎样安全的使用Cube宏呢？必须把可能产生副作用的操作移到宏调用的外面进行：

int num=8+2;

volume=Cube(num);

num++;

3.#运算符

出现在宏定义中的#运算符把跟在其后的参数转换成一个字符串。有时把这种用法的#称为字符串化运算符。例如：

#define PASTE(n) "adhfkj"#n

main()

{

printf("%s\n",PASTE(15));

}

宏定义中的#运算符告诉预处理程序，把源代码中任何传递给该宏的参数转换成一个字符串。所以输出应该是adhfkj15。

4.##运算符

##运算符用于把参数连接到一起。预处理程序把出现在##两侧的参数合并成一个符号。看下面的例子：

#define NUM(a,b,c) a##b##c

#define STR(a,b,c) a##b##c

main()

{

printf("%d\n",NUM(1,2,3));

printf("%s\n",STR("aa","bb","cc"));

}

最后程序的输出为:

123

aabbcc

千万别担心，除非需要或者宏的用法恰好和手头的工作相关，否则很少有程序员会知道##运算符。绝大多数程序员从来没用过它。

三、条件编译指令

条件编译指令将决定那些代码被编译，而哪些是不被编译的。可以根据表达式的值或者某个特定的宏是否被定义来确定编译条件。

1.#if指令

#if指令检测跟在制造另关键字后的常量表达式。如果表达式为真，则编译后面的代码，知道出现#else、#elif或#endif为止；否则就不编译。

2.#endif指令

#endif用于终止#if预处理指令。

#define DEBUG 0

main()

{

#if DEBUG

printf("Debugging\n");

#endif

printf("Running\n");

}

由于程序定义DEBUG宏代表0，所以#if条件为假，不编译后面的代码直到#endif，所以程序直接输出Running。

如果去掉#define语句，效果是一样的。

3.#ifdef和#ifndef

#define DEBUG

main()

{

#ifdef DEBUG

printf("yes\n");

#endif

#ifndef DEBUG

printf("no\n");

#endif

}

#if defined等价于#ifdef; #if !defined等价于#ifndef

4.#else指令

#else指令用于某个#if指令之后，当前面的#if指令的条件不为真时，就编译#else后面的代码。#endif指令将中指上面的条件块。

#define DEBUG

main()

{

#ifdef DEBUG

printf("Debugging\n");

#else

printf("Not debugging\n");

#endif

printf("Running\n");

}

5.#elif指令

#elif预处理指令综合了#else和#if指令的作用。

#define TWO

main()

{

#ifdef ONE

printf("1\n");

#elif defined TWO

printf("2\n");

#else

printf("3\n");

#endif

}

程序很好理解，最后输出结果是2。

6.其他一些标准指令

#error指令将使编译器显示一条错误信息，然后停止编译。

#line指令可以改变编译器用来指出警告和错误信息的文件号和行号。

#pragma指令没有正式的定义。编译器可以自定义其用途。典型的用法是禁止或允许某些烦人的警告信息。


有些程序在调试、兼容性、平台移植等情况下可能想要通过简单地设置一些参数就生成一个不同的软件，这当然可以通过变量设置，把所有可能用到的代码都写进去，在初始化时配置，但在不同的情况下可能只用到一部分代码，就没必要把所有的代码都写进去，就可以用条件编译，通过预编译指令设置编译条件，在不同的需要时编译不同的代码。

（一）条件编译方法

条件编译是通过预编译指令来实现的，主要方法有：

1、#if， #elif， #else， #endif

#if　条件　1
  代码段　1
#elif 条件　2
代码段　2
...
#elif 条件　n
代码段　n
#else
代码段 n+1
#endif

即可以设置不同的条件，在编译时编译不同的代码，预编译指令中的表达式与C语言本身的表达式基本一至如逻辑运算、算术运算、位运算等均可以在预编译指令中使用。之所以能够实现条件编译是因为预编译指令是在编译之前进行处理的，通过预编译进行宏替换、条件选择代码段，然后生成最后的待编译代码，最后进行编译。

#if的一般含义是，如果#if后面的常量表达式为true，则编译它所控制的代码，如条件1成立时就代码段1，条件1不成立再看条件2是否成立，如果条件2成立则编译代码段2，否则再依次类推判断其它条件，如果条件1－N都不成力则会编译最后的代码段n+1.

2、#ifdef， #else， #endif或#ifndef， #else， #endif

条件编译的另一种方法是用#ifdef与#ifndef命令，它们分别表示“如果有定义”及“如果无定义”。有定义是指在编译此段代码时是否有某个宏通过 #define 指令定义的宏，#ifndef指令指找不到通过#define定义的某宏，该宏可以是在当前文件此条指令的关面定义的，也可以是在其它文件中，但在此指令之前包含到该文件中的。

#ifdef的一般形式是：

#ifdef macro_name
代码段　1
#else
代码段　2
#endif

或
#ifdef的一般形式是：

#ifndef macro_name
代码段　2
#else
代码段　1
#endif

这两段代码的效果是完全一样的。

3、通过宏函数defined（macro_name）

参数为宏名（无需加""），如果该macro_name定义过则返回真，否则返回假，用该函数则可以写比较复杂的条件编译指令如

#if defined(macro1) || (!defined(macro2) && defined(macro3))
...
#else
...
#endif

（二）条件编译技巧与示例

（1）#ifdef和#defined（）比较

首先比较一下这两种方法，第一种方法只能判断一个宏，如果条件比较复杂实现起来比较烦锁，用后者就比较方便。如有两个宏MACRO_1，MACRO_2只有两个宏都定义过才会编译代码段A，分别实现如下：

#ifdef MACRO_1
#ifdef MACRO_2
代码段　A
#endif
#endif

或者
#if defined(MACRO_1) && defined(MACRO_2)
#endif

同样，要实现更复杂的条件用#ifdef更麻烦，所以推荐使用后者，因为即使当前代码用的是简单的条件编译，以后在维护、升级时可能会增加，用后者可维护性较强。旧的编译器可能没有实现#defined（）指令，C99已经加为标准。要兼容老的编译器，还需用#ifdef指令。

2、#if与 #ifdef或#if　defined（）比较

比如自己写了一个printf函数，想通过一个宏MY_PRINTF_EN实现条件编译，用#if可实现如下

#define MY_PRINTF_EN 1

#if MYS_PRINTF_EN == 1
int printf(char* fmt, char* args, ...)
{
...
}
#endif

如果宏MY_PRINTF_EN定义为1则编译这段代码，如果宏定义不为1或者没有定义该宏，则不编译这段代码。同样也可以通过#ifdef或者#defined（）实现，如

#define MY_PRINTF_EN 1

#if defined(MY_PRINTF_EN)
int printf(char* fmt, char* args, ...)
{
...
}
#endif

在这种情况下两种方法具有异曲同工之妙，但试想如果你为了节约代码写了两个printf函数，在不同情况下使用不同的printf函数，一个是精简版一个是全功能标准版，如：

#define MY_PRINTF_SIMPLE

#ifdef MY_PRINTF_SIMPLE
void printf(*str) // 向终端简单地输出一个字符串
{...
}
#endif
#ifdef MY_PRINTF_STANDARD
int printf(char* fmt, char* args, ...)
{...
}
#endif

同样可以用#if defined()实现
#define MY_PRINTF_SIMPLE

#if defined(MY_PRINTF_SIMPLE)
void printf(*str) // 向终端简单地输出一个字符串
{
...
}
#elif defined(MY_PRINTF_STANDARD)
int printf(char* fmt, char* args, ...)
{
...
}
#endif

两种方法都可以实现，但可见后者更方便。但试想如果你有三个版本，用前者就更麻烦了，但方法相似，用后者就更方便，但仍需三个宏进行控制，你要住三个宏，改进一下就用#if可以用一个宏直接控制N种情况如：

#define MY_PRINTF_VERSION 1

#if MY_PRINTF_VERSION == 1
void printf(*str) // 向终端简单地输出一个字符串
{
...
}
#elif MY_PRINTF_VERSION == 2
int printf(char* fmt, char* args, ...)
{
...
}
#elif MY_PRINTF_VERSION == 3
int printf(unsigned char com_number, char* str)
{
...
}
#else
默认版本
#endif

这样，你只需修改一下数字就可以完成版本的选择了

看来好像用#if 比较好了，试想如下情况：你写了一个配置文件叫做config.h用来配置一些宏，通过这些宏来控制代码，如你在config.h的宏

#define MY_PRINTF_EN 1

来控制是否需要编译自己的printf函数，而在你的源代码文件printf.c中有如下指令

＃i nclude "config.h"
#if MY_PRINTF_EN == 1
int printf(char* fmt, char* args, ...)
{
...
}
#endif

但这样也会有一个问题，就是如果你忘了在config.h中添加宏MY_PRINTF_EN，那么自己写的printf函数也不会被编译，有些编译器会给出警告：MY_PRINTF_EN未定义。如果你有两个版本的想有一个默认版本，可以在printf.c中这样实现

#incldue "config.h"
#if !defined(MY_PRINTF_VERSION)
#define MY_PRINTF_VERSION 1
#endif

#if MY_PRINTF_VERSION == 1
void printf(*str) // 向终端简单地输出一个字符串
{
...
}
#elif MY_PRINTF_VERSION == 2
int printf(char* fmt, char* args, ...)
{
...
}
#elif MY_PRINTF_VERSION == 3
int printf(unsigned char com_number, char* str)
{
...
}
#endif

这种情况下还得用到#ifdef或#if　defined（），你可以不用动主体的任何代码，只需要修改printf.c文件中MY_RPINTF_VERSION宏的数字就可以改变了，如果用前面那种方法还得拖动代码，在拖动中就有可能造成错误。

再试想，如果软件升级了，或者有了大的改动，原来有三个版本，现在只剩下两个版本了，如

#if MY_PRINTF_VERSION == 2
int printf(char* fmt, char* args, ...)
{
...
}
#elif MY_PRINTF_VERSION == 3
int printf(unsigned char com_number, char* str)
{
...
}
#endif

因为这些核心代码不想让使用这些代码的人关心，他们只需要修改config.h文件，那就要在printf.c中实现兼容性。如果以前有人在config.h配置宏MY_PRINTF_VERSION为1，即有

#define MY_PRINTF_VERSION 1

而现在没有1版本了，要想兼容怎么办？那当然可以用更复杂的条件实现如：

#if MY_PRINTF_VERSION == 2 || MY_PRINTF_VERSION == 1
int printf(char* fmt, char* args, ...)
{
...
}
#elif MY_PRINTF_VERSION == 3
int printf(unsigned char com_number, char* str)
{
...
}
#endif

不过还有另外一种方法，即使用#undef命令

#if MY_PRINTF_VERSION == 1
#undef MY_PRINTF_VERSION
#define MY_PRINTF_VERSION 2
#endif
#if MY_PRINTF_VERSION == 2
int printf(char* fmt, char* args, ...)
{
...
}
#elif MY_PRINTF_VERSION == 3
int printf(unsigned char com_number, char* str)
{
...
}
#endif

用#if还有一个好处，如果你把宏名记错了，把MY_PRINTF_EN定义成了MY_PRINT_EN，那么你用#ifdef MY_PRINTF_EN或者#if defined（MY_PRINTF_EN）控制的代码就不能被编译，查起来又不好查，用#if MY_PRINTF_EN ==1控制就很好查，因为你把MY_PRINTF_EN定义成MY_PRINT_EN，则MY_PRINTF_EN实际上没有定义，那么编译器会给出警告#if MY_PRINTF_EN　== 1中的MY_PRINTF_EN没有定义，但错就比较快。


Modbus概念

    Modbus协议

    工业控制已从单机控制走向集中监控、集散控制，如今已进入网络时代，工业控制器连网也为网络管理提供了方便。Modbus就是工业控制器的网络协议中的一种。

    关键词：Modbus协议；串行通信；LRC校验；CRC校验；RS-232C

[编辑本段]

Modbus 协议简介

    Modbus 协议是应用于电子控制器上的一种通用语言。通过此协议，控制器相互之间、控制器经由网络（例如以太网）和其它设备之间可以通信。它已经成为一通用工业标准。有了它，不同厂商生产的控制设备可以连成工业网络，进行集中监控。

    此协议定义了一个控制器能认识使用的消息结构,而不管它们是经过何种网络进行通信的。它描述了一控制器请求访问其它设备的过程，如果回应来自其它设备的请求，以及怎样侦测错误并记录。它制定了消息域格局和内容的公共格式。

    当在一Modbus网络上通信时，此协议决定了每个控制器须要知道它们的设备地址，识别按地址发来的消息，决定要产生何种行动。如果需要回应，控制器将生成反馈信息并用Modbus协议发出。在其它网络上，包含了Modbus协议的消息转换为在此网络上使用的帧或包结构。这种转换也扩展了根据具体的网络解决节地址、路由路径及错误检测的方法。

[编辑本段]

路径及检测方法

    1、在Modbus网络上转输

    标准的Modbus口是使用一RS-232C兼容串行接口，它定义了连接口的针脚、电缆、信号位、传输波特率、奇偶校验。控制器能直接或经由Modem组网。

    控制器通信使用主—从技术，即仅一设备（主设备）能初始化传输（查询）。其它设备（从设备）根据主设备查询提供的数据作出相应反应。典型的主设备：主机和可编程仪表。典型的从设备：可编程控制器。

    主设备可单独和从设备通信，也能以广播方式和所有从设备通信。如果单独通信，从设备返回一消息作为回应，如果是以广播方式查询的，则不作任何回应。Modbus协议建立了主设备查询的格式：设备（或广播）地址、功能代码、所有要发送的数据、一错误检测域。

    从设备回应消息也由Modbus协议构成，包括确认要行动的域、任何要返回的数据、和一错误检测域。如果在消息接收过程中发生一错误，或从设备不能执行其命令，从设备将建立一错误消息并把它作为回应发送出去。

    2、在其它类型网络上转输

    在其它网络上，控制器使用对等技术通信，故任何控制都能初始和其它控制器的通信。这样在单独的通信过程中，控制器既可作为主设备也可作为从设备。提供的多个内部通道可允许同时发生的传输进程。

    在消息位，Modbus协议仍提供了主—从原则，尽管网络通信方法是“对等”。如果一控制器发送一消息，它只是作为主设备，并期望从从设备得到回应。同样，当控制器接收到一消息，它将建立一从设备回应格式并返回给发送的控制器。

    3、查询—回应周期

    （1）查询

    查询消息中的功能代码告之被选中的从设备要执行何种功能。数据段包含了从设备要执行功能的任何附加信息。例如功能代码03是要求从设备读保持寄存器并返回它们的内容。数据段必须包含要告之从设备的信息：从何寄存器开始读及要读的寄存器数量。错误检测域为从设备提供了一种验证消息内容是否正确的方法。

    （2）回应

    如果从设备产生一正常的回应，在回应消息中的功能代码是在查询消息中的功能代码的回应。数据段包括了从设备收集的数据：象寄存器值或状态。如果有错误发生，功能代码将被修改以用于指出回应消息是错误的，同时数据段包含了描述此错误信息的代码。错误检测域允许主设备确认消息内容是否可用。

[编辑本段]

传输方式

    控制器能设置为两种传输模式（ASCII或RTU）中的任何一种在标准的Modbus网络通信。用户选择想要的模式，包括串口通信参数（波特率、校验方式等），在配置每个控制器的时候，在一个Modbus网络上的所有设备都必须选择相同的传输模式和串口参数。

    ASCII模式

    : 地址功能代码数据数量数据1 ... 数据n LRC高字节 LRC低字节回车换行

    RTU模式

    地址功能代码数据数量数据1 ... 数据n CRC高字节 CRC低字节

    所选的ASCII或RTU方式仅适用于标准的Modbus网络，它定义了在这些网络上连续传输的消息段的每一位，以及决定怎样将信息打包成消息域和如何解码。

    在其它网络上（象MAP和Modbus Plus）Modbus消息被转成与串行传输无关的帧。

    1、ASCII模式

    当控制器设为在Modbus网络上以ASCII（美国标准信息交换代码）模式通信，在消息中的每个8Bit字节都作为两个ASCII字符发送。这种方式的主要优点是字符发送的时间间隔可达到1秒而不产生错误。

    代码系统

    · 十六进制，ASCII字符0...9，A...F

    · 消息中的每个ASCII字符都是一个十六进制字符组成

    每个字节的位

    · 1个起始位

    · 7个数据位，最小的有效位先发送

    · 1个奇偶校验位，无校验则无

    · 1个停止位（有校验时），2个Bit（无校验时）

    错误检测域

    · LRC(纵向冗长检测)

    2、RTU模式

    当控制器设为在Modbus网络上以RTU（远程终端单元）模式通信，在消息中的每个8Bit字节包含两个4Bit的十六进制字符。这种方式的主要优点是：在同样的波特率下，可比ASCII方式传送更多的数据。

    代码系统

    · 8位二进制，十六进制数0...9，A...F

    · 消息中的每个8位域都是一个两个十六进制字符组成

    每个字节的位

    · 1个起始位

    · 8个数据位，最小的有效位先发送

    · 1个奇偶校验位，无校验则无

    · 1个停止位（有校验时），2个Bit（无校验时）

[编辑本段]

错误检测域

    · CRC(循环冗长检测)

    三、Modbus消息帧

    两种传输模式中（ASCII或RTU），传输设备以将Modbus消息转为有起点和终点的帧，这就允许接收的设备在消息起始处开始工作，读地址分配信息，判断哪一个设备被选中（广播方式则传给所有设备），判知何时信息已完成。部分的消息也能侦测到并且错误能设置为返回结果。

    1、ASCII帧

    使用ASCII模式，消息以冒号（:）字符（ASCII码 3AH）开始，以回车换行符结束（ASCII码 0DH,0AH）。

    其它域可以使用的传输字符是十六进制的0...9,A...F。网络上的设备不断侦测“:”字符，当有一个冒号接收到时，每个设备都解码下个域（地址域）来判断是否发给自己的。

    消息中字符间发送的时间间隔最长不能超过1秒，否则接收的设备将认为传输错误。一个典型消息帧如下所示：

    起始位设备地址功能代码数据 LRC校验结束符

    1个字符 2个字符 2个字符 n个字符 2个字符 2个字符

    图2 ASCII消息帧

    2、RTU帧

    使用RTU模式，消息发送至少要以3.5个字符时间的停顿间隔开始。在网络波特率下多样的字符时间，这是最容易实现的(如下图的T1-T2-T3-T4所示)。传输的第一个域是设备地址。可以使用的传输字符是十六进制的0...9,A...F。网络设备不断侦测网络总线，包括停顿间隔时间内。当第一个域（地址域）接收到，每个设备都进行解码以判断是否发往自己的。在最后一个传输字符之后，一个至少3.5个字符时间的停顿标定了消息的结束。一个新的消息可在此停顿后开始。

    整个消息帧必须作为一连续的流转输。如果在帧完成之前有超过1.5个字符时间的停顿时间，接收设备将刷新不完整的消息并假定下一字节是一个新消息的地址域。同样地，如果一个新消息在小于3.5个字符时间内接着前个消息开始，接收的设备将认为它是前一消息的延续。这将导致一个错误，因为在最后的CRC域的值不可能是正确的。一典型的消息帧如下所示：

    起始位设备地址功能代码数据 CRC校验结束符

    T1-T2-T3-T4 8Bit 8Bit n个8Bit 16Bit T1-T2-T3-T4

    图3 RTU消息帧

    3、地址域

    消息帧的地址域包含两个字符（ASCII）或8Bit（RTU）。可能的从设备地址是0...247 (十进制)。单个设备的地址范围是1...247。主设备通过将要联络的从设备的地址放入消息中的地址域来选通从设备。当从设备发送回应消息时，它把自己的地址放入回应的地址域中，以便主设备知道是哪一个设备作出回应。

    地址0是用作广播地址，以使所有的从设备都能认识。当Modbus协议用于更高水准的网络，广播可能不允许或以其它方式代替。

    4、如何处理功能域

    消息帧中的功能代码域包含了两个字符（ASCII）或8Bits（RTU）。可能的代码范围是十进制的1...255。当然，有些代码是适用于所有控制器，有此是应用于某种控制器，还有些保留以备后用。

    当消息从主设备发往从设备时，功能代码域将告之从设备需要执行哪些行为。例如去读取输入的开关状态，读一组寄存器的数据内容，读从设备的诊断状态，允许调入、记录、校验在从设备中的程序等。

    当从设备回应时，它使用功能代码域来指示是正常回应(无误)还是有某种错误发生（称作异议回应）。对正常回应，从设备仅回应相应的功能代码。对异议回应，从设备返回一等同于正常代码的代码，但最重要的位置为逻辑1。

    例如：一从主设备发往从设备的消息要求读一组保持寄存器，将产生如下功能代码：

    0 0 0 0 0 0 1 1 （十六进制03H）

    对正常回应，从设备仅回应同样的功能代码。对异议回应，它返回：

    1 0 0 0 0 0 1 1 （十六进制83H）

    除功能代码因异议错误作了修改外，从设备将一独特的代码放到回应消息的数据域中，这能告诉主设备发生了什么错误。

    主设备应用程序得到异议的回应后，典型的处理过程是重发消息，或者诊断发给从设备的消息并报告给操作员。

    5、数据域

    数据域是由两个十六进制数集合构成的，范围00...FF。根据网络传输模式，这可以是由一对ASCII字符组成或由一RTU字符组成。

    从主设备发给从设备消息的数据域包含附加的信息：从设备必须用于进行执行由功能代码所定义的所为。这包括了象不连续的寄存器地址，要处理项的数目，域中实际数据字节数。

    例如，如果主设备需要从设备读取一组保持寄存器（功能代码03），数据域指定了起始寄存器以及要读的寄存器数量。如果主设备写一组从设备的寄存器（功能代码10十六进制），数据域则指明了要写的起始寄存器以及要写的寄存器数量，数据域的数据字节数，要写入寄存器的数据。

    如果没有错误发生，从从设备返回的数据域包含请求的数据。如果有错误发生，此域包含一异议代码，主设备应用程序可以用来判断采取下一步行动。

    在某种消息中数据域可以是不存在的（0长度）。例如，主设备要求从设备回应通信事件记录（功能代码0B十六进制），从设备不需任何附加的信息。

    6、错误检测域

    标准的Modbus网络有两种错误检测方法。错误检测域的内容视所选的检测方法而定。

    ASCII

    当选用ASCII模式作字符帧，错误检测域包含两个ASCII字符。这是使用LRC（纵向冗长检测）方法对消息内容计算得出的，不包括开始的冒号符及回车换行符。LRC字符附加在回车换行符前面。

    RTU

    当选用RTU模式作字符帧，错误检测域包含一16Bits值(用两个8位的字符来实现)。错误检测域的内容是通过对消息内容进行循环冗长检测方法得出的。CRC域附加在消息的最后，添加时先是低字节然后是高字节。故CRC的高位字节是发送消息的最后一个字节。

    7、字符的连续传输

    当消息在标准的Modbus系列网络传输时，每个字符或字节以如下方式发送（从左到右）：

    最低有效位...最高有效位

    使用ASCII字符帧时，位的序列是：

    有奇偶校验

    启始位 1 2 3 4 5 6 7 奇偶位停止位

    无奇偶校验

    启始位 1 2 3 4 5 6 7 停止位停止位

    图4. 位顺序（ASCII）

    使用RTU字符帧时，位的序列是：

    有奇偶校验

    启始位 1 2 3 4 5 6 7 8 奇偶位停止位

    无奇偶校验

    启始位 1 2 3 4 5 6 7 8 停止位停止位

    图4. 位顺序（RTU）

    四、错误检测方法

    标准的Modbus串行网络采用两种错误检测方法。奇偶校验对每个字符都可用，帧检测（LRC或CRC）应用于整个消息。它们都是在消息发送前由主设备产生的，从设备在接收过程中检测每个字符和整个消息帧。

    用户要给主设备配置一预先定义的超时时间间隔，这个时间间隔要足够长，以使任何从设备都能作为正常反应。如果从设备测到一传输错误，消息将不会接收，也不会向主设备作出回应。这样超时事件将触发主设备来处理错误。发往不存在的从设备的地址也会产生超时。

    1、奇偶校验

    用户可以配置控制器是奇或偶校验，或无校验。这将决定了每个字符中的奇偶校验位是如何设置的。

    如果指定了奇或偶校验，“1”的位数将算到每个字符的位数中（ASCII模式7个数据位，RTU中8个数据位）。例如RTU字符帧中包含以下8个数据位：

    1 1 0 0 0 1 0 1

    整个“1”的数目是4个。如果便用了偶校验，帧的奇偶校验位将是0，便得整个“1”的个数仍是4个。如果便用了奇校验，帧的奇偶校验位将是1，便得整个“1”的个数是5个。

    如果没有指定奇偶校验位，传输时就没有校验位，也不进行校验检测。代替一附加的停止位填充至要传输的字符帧中。

    2、LRC检测

    使用ASCII模式，消息包括了一基于LRC方法的错误检测域。LRC域检测了消息域中除开始的冒号及结束的回车换行号外的内容。

    LRC域是一个包含一个8位二进制值的字节。LRC值由传输设备来计算并放到消息帧中，接收设备在接收消息的过程中计算LRC，并将它和接收到消息中LRC域中的值比较，如果两值不等，说明有错误。

    LRC方法是将消息中的8Bit的字节连续累加，丢弃了进位。

    LRC简单函数如下：

    static unsigned char LRC(auchMsg,usDataLen)

    unsigned char *auchMsg ; /* 要进行计算的消息 */

    unsigned short usDataLen ; /* LRC 要处理的字节的数量*/

    { unsigned char uchLRC = 0 ; /* LRC 字节初始化 */

    while (usDataLen--) /* 传送消息 */

    uchLRC += *auchMsg++ ; /* 累加*/

    return ((unsigned char)(-((char)uchLRC))) ;

    }

    3、CRC检测

    使用RTU模式，消息包括了一基于CRC方法的错误检测域。CRC域检测了整个消息的内容。

    CRC域是两个字节，包含一16位的二进制值。它由传输设备计算后加入到消息中。接收设备重新计算收到消息的CRC，并与接收到的CRC域中的值比较，如果两值不同，则有误。

    CRC是先调入一值是全“1”的16位寄存器，然后调用一过程将消息中连续的8位字节各当前寄存器中的值进行处理。仅每个字符中的8Bit数据对CRC有效，起始位和停止位以及奇偶校验位均无效。

    CRC产生过程中，每个8位字符都单独和寄存器内容相或（OR），结果向最低有效位方向移动，最高有效位以0填充。LSB被提取出来检测，如果LSB为1，寄存器单独和预置的值或一下，如果LSB为0，则不进行。整个过程要重复8次。在最后一位（第8位）完成后，下一个8位字节又单独和寄存器的当前值相或。最终寄存器中的值，是消息中所有的字节都执行之后的CRC值。

    CRC添加到消息中时，低字节先加入，然后高字节。

    CRC简单函数如下：

    unsigned short CRC16(puchMsg, usDataLen)

    unsigned char *puchMsg ; /* 要进行CRC校验的消息 */

    unsigned short usDataLen ; /* 消息中字节数 */

    {

    unsigned char uchCRCHi = 0xFF ; /* 高CRC字节初始化 */

    unsigned char uchCRCLo = 0xFF ; /* 低CRC 字节初始化 */

    unsigned uIndex ; /* CRC循环中的索引 */

    while (usDataLen--) /* 传输消息缓冲区 */

    {

    uIndex = uchCRCHi ^ *puchMsg++ ; /* 计算CRC */

    uchCRCHi = uchCRCLo ^ auchCRCHi[uIndex} ;

    uchCRCLo = auchCRCLo][uIndex] ;

    }

    return (uchCRCHi << 8 | uchCRCLo) ;

    }

    /* CRC 高位字节值表 */

    static unsigned char auchCRCHi[] = {

    0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,

    0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,

    0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,

    0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,

    0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1,

    0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,

    0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1,

    0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,

    0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,

    0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40,

    0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1,

    0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,

    0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,

    0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40,

    0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,

    0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,

    0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,

    0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,

    0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,

    0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,

    0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,

    0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40,

    0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1,

    0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,

    0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,

    0x80, 0x41, 0x00, 0xC1, 0x81, 0x40

    } ;

    /* CRC低位字节值表*/

    static char auchCRCLo[] = {

    0x00, 0xC0, 0xC1, 0x01, 0xC3, 0x03, 0x02, 0xC2, 0xC6, 0x06,

    0x07, 0xC7, 0x05, 0xC5, 0xC4, 0x04, 0xCC, 0x0C, 0x0D, 0xCD,

    0x0F, 0xCF, 0xCE, 0x0E, 0x0A, 0xCA, 0xCB, 0x0B, 0xC9, 0x09,

    0x08, 0xC8, 0xD8, 0x18, 0x19, 0xD9, 0x1B, 0xDB, 0xDA, 0x1A,

    0x1E, 0xDE, 0xDF, 0x1F, 0xDD, 0x1D, 0x1C, 0xDC, 0x14, 0xD4,

    0xD5, 0x15, 0xD7, 0x17, 0x16, 0xD6, 0xD2, 0x12, 0x13, 0xD3,

    0x11, 0xD1, 0xD0, 0x10, 0xF0, 0x30, 0x31, 0xF1, 0x33, 0xF3,

    0xF2, 0x32, 0x36, 0xF6, 0xF7, 0x37, 0xF5, 0x35, 0x34, 0xF4,

    0x3C, 0xFC, 0xFD, 0x3D, 0xFF, 0x3F, 0x3E, 0xFE, 0xFA, 0x3A,

    0x3B, 0xFB, 0x39, 0xF9, 0xF8, 0x38, 0x28, 0xE8, 0xE9, 0x29,

    0xEB, 0x2B, 0x2A, 0xEA, 0xEE, 0x2E, 0x2F, 0xEF, 0x2D, 0xED,

    0xEC, 0x2C, 0xE4, 0x24, 0x25, 0xE5, 0x27, 0xE7, 0xE6, 0x26,

    0x22, 0xE2, 0xE3, 0x23, 0xE1, 0x21, 0x20, 0xE0, 0xA0, 0x60,

    0x61, 0xA1, 0x63, 0xA3, 0xA2, 0x62, 0x66, 0xA6, 0xA7, 0x67,

    0xA5, 0x65, 0x64, 0xA4, 0x6C, 0xAC, 0xAD, 0x6D, 0xAF, 0x6F,

    0x6E, 0xAE, 0xAA, 0x6A, 0x6B, 0xAB, 0x69, 0xA9, 0xA8, 0x68,

    0x78, 0xB8, 0xB9, 0x79, 0xBB, 0x7B, 0x7A, 0xBA, 0xBE, 0x7E,

    0x7F, 0xBF, 0x7D, 0xBD, 0xBC, 0x7C, 0xB4, 0x74, 0x75, 0xB5,

    0x77, 0xB7, 0xB6, 0x76, 0x72, 0xB2, 0xB3, 0x73, 0xB1, 0x71,

    0x70, 0xB0, 0x50, 0x90, 0x91, 0x51, 0x93, 0x53, 0x52, 0x92,

    0x96, 0x56, 0x57, 0x97, 0x55, 0x95, 0x94, 0x54, 0x9C, 0x5C,

    0x5D, 0x9D, 0x5F, 0x9F, 0x9E, 0x5E, 0x5A, 0x9A, 0x9B, 0x5B,

    0x99, 0x59, 0x58, 0x98, 0x88, 0x48, 0x49, 0x89, 0x4B, 0x8B,

    0x8A, 0x4A, 0x4E, 0x8E, 0x8F, 0x4F, 0x8D, 0x4D, 0x4C, 0x8C,

    0x44, 0x84, 0x85, 0x45, 0x87, 0x47, 0x46, 0x86, 0x82, 0x42,

    0x43, 0x83, 0x41, 0x81, 0x80, 0x40

    } ;

    ModBus网络是一个工业通信系统，由带智能终端的可编程序控制器和计算机通过公用线路或局部专用线路连接而成。其系统结构既包括硬件、亦包括软件。它可应用于各种数据采集和过程监控。下表1是ModBus的功能码定义。

    表1 ModBus功能码

    功能码名称作用

    01 读取线圈状态取得一组逻辑线圈的当前状态（ON/OFF)

    02 读取输入状态取得一组开关输入的当前状态（ON/OFF)

    03 读取保持寄存器在一个或多个保持寄存器中取得当前的二进制值

    04 读取输入寄存器在一个或多个输入寄存器中取得当前的二进制值

    05 强置单线圈强置一个逻辑线圈的通断状态

    06 预置单寄存器把具体二进值装入一个保持寄存器

    07 读取异常状态取得8个内部线圈的通断状态，这8个线圈的地址由控制器决定，用户逻辑可以将这些线圈定义，以说明从机状态，短报文适宜于迅速读取状态

    08 回送诊断校验把诊断校验报文送从机，以对通信处理进行评鉴

    09 编程（只用于484）使主机模拟编程器作用，修改PC从机逻辑

    10 控询（只用于484）可使主机与一台正在执行长程序任务从机通信，探询该从机是否已完成其操作任务，仅在含有功能码9的报文发送后，本功能码才发送

    11 读取事件计数可使主机发出单询问，并随即判定操作是否成功，尤其是该命令或其他应答产生通信错误时

    12 读取通信事件记录可是主机检索每台从机的ModBus事务处理通信事件记录。如果某项事务处理完成，记录会给出有关错误

    13 编程（184/384 484 584）可使主机模拟编程器功能修改PC从机逻辑

    14 探询（184/384 484 584）可使主机与正在执行任务的从机通信，定期控询该从机是否已完成其程序操作，仅在含有功能13的报文发送后，本功能码才得发送

    15 强置多线圈强置一串连续逻辑线圈的通断

    16 预置多寄存器把具体的二进制值装入一串连续的保持寄存器

    17 报告从机标识可使主机判断编址从机的类型及该从机运行指示灯的状态

    18 （884和MICRO 84）可使主机模拟编程功能，修改PC状态逻辑

    19 重置通信链路发生非可修改错误后，是从机复位于已知状态，可重置顺序字节

    20 读取通用参数（584L）显示扩展存储器文件中的数据信息

    21 写入通用参数（584L）把通用参数写入扩展存储文件，或修改之

    22～64 保留作扩展功能备用

    65～72 保留以备用户功能所用留作用户功能的扩展编码

    73～119 非法功能

    120～127 保留留作内部作用

    128～255 保留用于异常应答

    ModBus网络只是一个主机，所有通信都由他发出。网络可支持247个之多的远程从属控制器，但实际所支持的从机数要由所用通信设备决定。采用这个系统，各PC可以和中心主机交换信息而不影响各PC执行本身的控制任务。表2是ModBus各功能码对应的数据类型。

    表2 ModBus功能码与数据类型对应表

    代码功能数据类型

    01 读位

    02 读位

    03 读整型、字符型、状态字、浮点型

    04 读整型、状态字、浮点型

    05 写位

    06 写整型、字符型、状态字、浮点型

    08 N/A 重复“回路反馈”信息

    15 写位

    16 写整型、字符型、状态字、浮点型

    17 读字符型

    （1）ModBus的传输方式

    在ModBus系统中有2种传输模式可选择。这2种传输模式与从机PC通信的能力是同等的。选择时应视所用ModBus主机而定，每个ModBus系统只能使用一种模式，不允许2种模式混用。一种模式是ASCII（美国信息交换码），另一种模式是RTU（远程终端设备）这两种模式的定义见表3

    表3 ASCII和RTU传输模式的特性

    特性 ASCII(7位） RTU(8位）

    编码系统十六进制（使用ASCII可打印字符：0～9，A～F) 二进制

    每一个字符的位数开始位 1位 1位

    数据位（最低有效位第一位） 7位 8位

    奇偶校验（任选） 1位（此位用于奇偶校验，无校应则无该位） 1位（此位用于奇偶校验，无校应则无该位）

    停止位 1或2位 1或2位

    错误校验 LRC（即纵向冗余校验） CRC(即循环冗余校验）

    ASCII可打印字符便于故障检测，而且对于用高级语言（如Fortan）编程的主计算机及主PC很适宜。RTU则适用于机器语言编程的计算机和PC主机。

    用RTU模式传输的数据是8位二进制字符。如欲转换为ASCII模式，则每个RTU字符首先应分为高位和低位两部分，这两部分各含4位，然后转换成十六进制等量值。用以构成报文的ASCII字符都是十六进制字符。ASCII模式使用的字符虽是RTU模式的两倍，但ASCII数据的译玛和处理更为容易一些，此外，用RTU模式时报文字符必须以连续数据流的形式传送，用ASCII模式，字符之间可产生长达1s的间隔，以适应速度较快的机器。

    表4给出了以RTU方式读取整数据的例子

    以RTU方式读取整数据的例子

    主机请求

    地址功能码第一个寄存器的高位地址第一个寄存器的低位地址寄存器的数量的高位寄存器的数量的底位错误校验

    01 03 00 38 00 01 XX

    从机应答

    地址功能码字节数数据高字节数据低字节错误校验

    01 03 2 41 24 XX

    十六进制数4124表示的十进制整数为16676，错误校验值要根据传输方式而定。

    （2）ModBus的数据校验方式

    CRC-16（循环冗余错误校验）

    CRC-16错误校验程序如下：报文（此处只涉及数据位，不指起始位、停止位和任选的奇偶校验位）被看作是一个连续的二进制，其最高有效位（MSB）首选发送。报文先与X↑16相乘（左移16位），然后看X↑16+X↑15+X↑2+1除，X↑16+X↑15+X↑2+1可以表示为二进制数11000000000000101。整数商位忽略不记，16位余数加入该报文（MSB先发送），成为2个CRC校验字节。余数中的1全部初始化，以免所有的零成为一条报文被接收。经上述处理而含有CRC字节的报文，若无错误，到接收设备后再被同一多项式（X↑16+X↑15+X↑2+1）除，会得到一个零余数（接收设备核验这个CRC字节，并将其与被传送的CRC比较）。全部运算以2为模（无进位）。

    习惯于成串发送数据的设备会首选送出字符的最右位（LSB-最低有效位）。而在生成CRC情况下，发送首位应是被除数的最高有效位MSB。由于在运算中不用进位，为便于操作起见，计算CRC时设MSB在最右位。生成多项式的位序也必须反过来，以保持一致。多项式的MSB略去不记，因其只对商有影响而不影响余数。

    生成CRC-16校验字节的步骤如下：

    ①装如一个16位寄存器，所有数位均为1。

    ②该16位寄存器的高位字节与开始8位字节进行“异或”运算。运算结果放入这个16位寄存器。

    ③把这个16寄存器向右移一位。

    ④若向右（标记位）移出的数位是1，则生成多项式1010000000000001和这个寄存器进行“异或”运算；若向右移出的数位是0，则返回③。

    ⑤重复③和④，直至移出8位。

    ⑥另外8位与该十六位寄存器进行“异或”运算。

    ⑦重复③~⑥，直至该报文所有字节均与16位寄存器进行“异或”运算，并移位8次。

    ⑧这个16位寄存器的内容即2字节CRC错误校验，被加到报文的最高有效位。

    另外，在某些非ModBus通信协议中也经常使用CRC16作为校验手段，而且产生了一些CRC16的变种，他们是使用CRC16多项式X↑16+X↑15+X↑2+1，单首次装入的16位寄存器为0000；使用CRC16的反序X↑16+X↑14+X↑1+1，首次装入寄存器值为0000或FFFFH。

    LRC（纵向冗余错误校验）

    LRC错误校验用于ASCII模式。这个错误校验是一个8位二进制数，可作为2个ASCII十六进制字节传送。把十六进制字符转换成二进制，加上无循环进位的二进制字符和二进制补码结果生成LRC错误校验（参见图）。这个LRC在接收设备进行核验，并与被传送的LRC进行比较，冒号（：）、回车符号（CR）、换行字符（LF）和置入的其他任何非ASCII十六进制字符在运算时忽略不计。

    表5 LRC生成范例－－读取02号从机的前8个线圈

    十六进制

    二进制

    地址 0 2 0000 0010

    功能码 0 1 0000 0001

    起始地址高位 0 0 0000 0000

    起始地址低位 0 0 0000 0000

    单元数量 0 0 0000 0000

    0 8 + 0000 1000

    0000 1011

    变成补码 1111 0101

    错误校验 F 5 F 5

    接受PC把所有收到的数据字节（包括最后的LRC）加在一起，8位应全部为0（注意：和可能超过8位，应略去最低位） 0000 0010

    0000 0001

    0000 0000

    0000 0000

    0000 0000

    0000 1000

    错误校验 1111 0101

    和 0000 0000

    四、另外介绍一种在MODBUS设计、调试、安装过程中必备的软件——MODSCAN32，它是针对于MODBUS在rtu模式下做的一个通用测试程序，通过它可以用pda对工业现场中的modbus 地址中的数据进行测试。

    的modbus 地址中的数据进行测试。


Modbus通讯协议

  Modbus协议

Modbus协议最初由Modicon公司开发出来，在1979年末该公司成为施耐德自动化(Schneider Automation)部门的一部分，现在Modbus已经是工业领域全球最流行的协议。此协议支持传统的RS-232、RS-422、RS-485和以太网设备。许多工业设备，包括PLC，DCS，智能仪表等都在使用Modbus协议作为他们之间的通讯标准。有了它，不同厂商生产的控制设备可以连成工业网络，进行集中监控。

当在网络上通信时，Modbus协议决定了每个控制器须要知道它们的设备地址，识别按地址发来的消息，决定要产生何种行动。如果需要回应，控制器将生成应答并使用Modbus协议发送给询问方。

Modbus协议包括ASCII、RTU、TCP等，并没有规定物理层。此协议定义了控制器能够认识和使用的消息结构，而不管它们是经过何种网络进行通信的。标准的Modicon控制器使用RS232C实现串行的Modbus。Modbus的ASCII、RTU协议规定了消息、数据的结构、命令和就答的方式，数据通讯采用Maser/Slave方式，Master端发出数据请求消息，Slave端接收到正确消息后就可以发送数据到Master端以响应请求；Master端也可以直接发消息修改Slave端的数据，实现双向读写。

Modbus协议需要对数据进行校验，串行协议中除有奇偶校验外，ASCII模式采用LRC校验，RTU模式采用16位CRC校验，但TCP模式没有额外规定校验，因为TCP协议是一个面向连接的可靠协议。另外，Modbus采用主从方式定时收发数据，在实际使用中如果某Slave站点断开后（如故障或关机），Master端可以诊断出来，而当故障修复后，网络又可自动接通。因此，Modbus协议的可靠性较好。

下面我来简单的给大家介绍一下，对于Modbus的ASCII、RTU和TCP协议来说，其中TCP和RTU协议非常类似，我们只要把RTU协议的两个字节的校验码去掉，然后在RTU协议的开始加上5个0和一个6并通过TCP/IP网络协议发送出去即可。所以在这里我仅介绍一下Modbus的ASCII和RTU协议。

下表是ASCII协议和RTU协议进行的比较：

协议 | 开始标记 | 结束标记 | 校验 | 传输效率 | 程序处理
ASCII | :（冒号） | CR,LF | LRC | 低 | 直观，简单，易调试
RTU | 无 | 无 | CRC | 高 | 不直观，稍复杂

通过比较可以看到，ASCII协议和RTU协议相比拥有开始和结束标记，因此在进行程序处理时能更加方便，而且由于传输的都是可见的ASCII字符，所以进行调试时就更加的直观，另外它的LRC校验也比较容易。但是因为它传输的都是可见的ASCII字符，RTU传输的数据每一个字节ASCII都要用两个字节来传输，比如RTU传输一个十六进制数0xF9,ASCII就需要传输’F’’9’的ASCII码0x39和0x46两个字节，这样它的传输的效率就比较低。所以一般来说，如果所需要传输的数据量较小可以考虑使用ASCII协议，如果所需传输的数据量比较大，最好能使用RTU协议。

下面对两种协议的校验进行一下介绍。

1、LRC校验

LRC域是一个包含一个8位二进制值的字节。LRC值由传输设备来计算并放到消息帧中，接收设备在接收消息的过程中计算LRC，并将它和接收到消息中LRC域中的值比较，如果两值不等，说明有错误。

LRC校验比较简单，它在ASCII协议中使用，检测了消息域中除开始的冒号及结束的回车换行号外的内容。它仅仅是把每一个需要传输的数据按字节叠加后取反加1即可。下面是它的VC代码：

BYTE GetCheckCode(const char * pSendBuf, int nEnd)//获得校验码

{

BYTE byLrc = 0;

char pBuf[4];

int nData = 0;

for(i=1; i<end; i+=2) //i初始为1，避开“开始标记”冒号

{

//每两个需要发送的ASCII码转化为一个十六进制数

pBuf [0] = pSendBuf [i];

pBuf [1] = pSendBuf [i+1];

pBuf [2] = '\0';

sscanf(pBuf,"%x",& nData);

byLrc += nData;

}

byLrc = ~ byLrc;

byLrc ++;

return byLrc;

}

2、CRC校验

CRC域是两个字节，包含一16位的二进制值。它由传输设备计算后加入到消息中。接收设备重新计算收到消息的CRC，并与接收到的CRC域中的值比较，如果两值不同，则有误。

CRC是先调入一值是全“1”的16位寄存器，然后调用一过程将消息中连续的8位字节各当前寄存器中的值进行处理。仅每个字符中的8Bit数据对CRC有效，起始位和停止位以及奇偶校验位均无效。

CRC产生过程中，每个8位字符都单独和寄存器内容相或（OR），结果向最低有效位方向移动，最高有效位以0填充。LSB被提取出来检测，如果LSB为1，寄存器单独和预置的值或一下，如果LSB为0，则不进行。整个过程要重复8次。在最后一位（第8位）完成后，下一个8位字节又单独和寄存器的当前值相或。最终寄存器中的值，是消息中所有的字节都执行之后的CRC值。

CRC添加到消息中时，低字节先加入，然后高字节。下面是它的VC代码：

WORD GetCheckCode(const char * pSendBuf, int nEnd)//获得校验码

{

WORD wCrc = WORD(0xFFFF);

for(int i=0; i<nEnd; i++)

{

wCrc ^= WORD(BYTE(pSendBuf[i]));

for(int j=0; j<8; j++)

{

if(wCrc & 1)

{

wCrc >>= 1;

wCrc ^= 0xA001;

}

else

{

wCrc >>= 1;

}

}

}

return wCrc;

}

对于一条RTU协议的命令可以简单的通过以下的步骤转化为ASCII协议的命令：

1、 把命令的CRC校验去掉，并且计算出LRC校验取代。

2、 把生成的命令串的每一个字节转化成对应的两个字节的ASCII码，比如0x03转化成0x30,0x33（0的ASCII码和3的ASCII码）。

3、 在命令的开头加上起始标记“:”，它的ASCII码为0x3A。

4、 在命令的尾部加上结束标记CR,LF（0xD,0xA），此处的CR,LF表示回车和换行的ASCII码。

所以以下我们仅介绍RTU协议即可，对应的ASCII协议可以使用以上的步骤来生成。

下表是Modbus支持的功能码：

功能码 | 名称 | 作用
01 | 读取线圈状态 | 取得一组逻辑线圈的当前状态（ON/OFF)
02 | 读取输入状态 | 取得一组开关输入的当前状态（ON/OFF)
03 | 读取保持寄存器 | 在一个或多个保持寄存器中取得当前的二进制值
04 | 读取输入寄存器 | 在一个或多个输入寄存器中取得当前的二进制值
05 | 强置单线圈 | 强置一个逻辑线圈的通断状态
06 | 预置单寄存器 | 把具体二进值装入一个保持寄存器
07 | 读取异常状态 | 取得8个内部线圈的通断状态，这8个线圈的地址由控制器决定
08 | 回送诊断校验 | 把诊断校验报文送从机，以对通信处理进行评鉴
09 | 编程（只用于484） | 使主机模拟编程器作用，修改PC从机逻辑
10 | 控询（只用于484） | 可使主机与一台正在执行长程序任务从机通信，探询该从机是否已完成其操作任务，仅在含有功能码9的报文发送后，本功能码才发送
11 | 读取事件计数 | 可使主机发出单询问，并随即判定操作是否成功，尤其是该命令或其他应答产生通信错误时
12 | 读取通信事件记录 | 可是主机检索每台从机的ModBus事务处理通信事件记录。如果某项事务处理完成，记录会给出有关错误
13 | 编程（184/384 484 584） | 可使主机模拟编程器功能修改PC从机逻辑
14 | 探询（184/384 484 584） | 可使主机与正在执行任务的从机通信，定期控询该从机是否已完成其程序操作，仅在含有功能13的报文发送后，本功能码才得发送
15 | 强置多线圈 | 强置一串连续逻辑线圈的通断
16 | 预置多寄存器 | 把具体的二进制值装入一串连续的保持寄存器
17 | 报告从机标识 | 可使主机判断编址从机的类型及该从机运行指示灯的状态
18 | （884和MICRO 84） | 可使主机模拟编程功能，修改PC状态逻辑
19 | 重置通信链路 | 发生非可修改错误后，是从机复位于已知状态，可重置顺序字节
20 | 读取通用参数（584L） | 显示扩展存储器文件中的数据信息
21 | 写入通用参数（584L） | 把通用参数写入扩展存储文件，或修改之
22～64 | 保留作扩展功能备用 |
65～72 | 保留以备用户功能所用 | 留作用户功能的扩展编码
73～119 | 非法功能 |
120～127 | 保留 | 留作内部作用
128～255 | 保留 | 用于异常应答

在这些功能码中较长使用的是1、2、3、4、5、6号功能码，使用它们即可实现对下位机的数字量和模拟量的读写操作。

1、读可读写数字量寄存器（线圈状态）：

计算机发送命令：[设备地址] [命令号01] [起始寄存器地址高8位] [低8位] [读取的寄存器数高8位] [低8位] [CRC校验的低8位] [CRC校验的高8位]

例：[11][01][00][13][00][25][CRC低][CRC高]

意义如下：

<1>设备地址：在一个485总线上可以挂接多个设备，此处的设备地址表示想和哪一个设备通讯。例子中为想和17号(十进制的17是十六进制的11)通讯。

<2>命令号01：读取数字量的命令号固定为01。

<3>起始地址高8位、低8位：表示想读取的开关量的起始地址(起始地址为0)。比如例子中的起始地址为19。

<4>寄存器数高8位、低8位：表示从起始地址开始读多少个开关量。例子中为37个开关量。

<5>CRC校验：是从开头一直校验到此之前。在此协议的最后再作介绍。此处需要注意，CRC校验在命令中的高低字节的顺序和其他的相反。

设备响应：[设备地址] [命令号01] [返回的字节个数][数据1][数据2]...[数据n][CRC校验的低8位] [CRC校验的高8位]

例：[11][01][05][CD][6B][B2][0E][1B][CRC低][CRC高]

意义如下：

<1>设备地址和命令号和上面的相同。

<2>返回的字节个数：表示数据的字节个数，也就是数据1，2...n中的n的值。

<3>数据1...n：由于每一个数据是一个8位的数，所以每一个数据表示8个开关量的值，每一位为0表示对应的开关断开，为1表示闭合。比如例子中，表示20号(索引号为19)开关闭合，21号断开，22闭合，23闭合，24断开，25断开，26闭合，27闭合...如果询问的开关量不是8的整倍数，那么最后一个字节的高位部分无意义，置为0。

<4>CRC校验同上。

2、读只可读数字量寄存器（输入状态）：

和读取线圈状态类似，只是第二个字节的命令号不再是1而是2。

3、写数字量（线圈状态）：

计算机发送命令：[设备地址] [命令号05] [需下置的寄存器地址高8位] [低8位] [下置的数据高8位] [低8位] [CRC校验的低8位] [CRC校验的高8位]

例：[11][05][00][AC][FF][00][CRC低][CRC高]

意义如下：

<1>设备地址和上面的相同。

<2>命令号:写数字量的命令号固定为05。

<3>需下置的寄存器地址高8位，低8位：表明了需要下置的开关的地址。

<4>下置的数据高8位，低8位：表明需要下置的开关量的状态。例子中为把该开关闭合。注意，此处只可以是[FF][00]表示闭合[00][00]表示断开，其他数值非法。

<5>注意此命令一条只能下置一个开关量的状态。

设备响应：如果成功把计算机发送的命令原样返回，否则不响应。

4、读可读写模拟量寄存器（保持寄存器）：

计算机发送命令：[设备地址] [命令号03] [起始寄存器地址高8位] [低8位] [读取的寄存器数高8位] [低8位] [CRC校验的低8位] [CRC校验的高8位]

例：[11][03][00][6B][00][03][CRC低][CRC高]

意义如下：

<1>设备地址和上面的相同。

<2>命令号:读模拟量的命令号固定为03。

<3>起始地址高8位、低8位：表示想读取的模拟量的起始地址(起始地址为0)。比如例子中的起始地址为107。

<4>寄存器数高8位、低8位：表示从起始地址开始读多少个模拟量。例子中为3个模拟量。注意，在返回的信息中一个模拟量需要返回两个字节。

设备响应：[设备地址] [命令号03] [返回的字节个数][数据1][数据2]...[数据n][CRC校验的低8位] [CRC校验的高8位]

例：[11][03][06][02][2B][00][00][00][64][CRC低][CRC高]

意义如下：

<1>设备地址和命令号和上面的相同。

<2>返回的字节个数：表示数据的字节个数，也就是数据1，2...n中的n的值。例子中返回了3个模拟量的数据，因为一个模拟量需要2个字节所以共6个字节。

<3>数据1...n：其中[数据1][数据2]分别是第1个模拟量的高8位和低8位，[数据3][数据4]是第2个模拟量的高8位和低8位，以此类推。例子中返回的值分别是555，0，100。

<4>CRC校验同上。

5、读只可读模拟量寄存器（输入寄存器）：

和读取保存寄存器类似，只是第二个字节的命令号不再是2而是4。

6、写单个模拟量寄存器（保持寄存器）：

计算机发送命令：[设备地址] [命令号06] [需下置的寄存器地址高8位] [低8位] [下置的数据高8位] [低8位] [CRC校验的低8位] [CRC校验的高8位]

例：[11][06][00][01][00][03][CRC低][CRC高]

意义如下：

<1>设备地址和上面的相同。

<2>命令号:写模拟量的命令号固定为06。

<3>需下置的寄存器地址高8位，低8位：表明了需要下置的模拟量寄存器的地址。

<4>下置的数据高8位，低8位：表明需要下置的模拟量数据。比如例子中就把1号寄存器的值设为3。

<5>注意此命令一条只能下置一个模拟量的状态。

设备响应：如果成功把计算机发送的命令原样返回，否则不响应。


TTS的全称为Text To Speech，即“从文本到语音”

TTS 的全称为Text To Speech，即“从文本到语音”。它是同时运用语言学和心理学的杰出之作，在内置芯片的支持之下，通过神经网络的设计，把文字智能地转化为自然语音流。

TTS技术对文本文件进行实时转换，转换时间之短可以秒计算。在其特有智能语音控制器作用下，文本输出的语音音律流畅，使得听者在听取信息时感觉自然，毫无机器语音输出的冷漠与生涩感。TTS语音合成技术即将覆盖国标一、二级汉字，具有英文接口，自动识别中、英文，支持中英文混读。所有声音采用真人普通话为标准发音，实现了120-150个汉字/秒的快速语音合成，朗读速度达3-4个汉字/秒，使用户可以听到清晰悦耳的音质和连贯流畅的语调。

TTS概述

    TTS是Text To Speech的缩写，即“从文本到语音”。它是同时运用语言学和心理学的杰出之作，在内置芯片的支持之下，通过神经网络的设计，把文字智能地转化为自然语音流。TTS技术对文本文件进行实时转换，转换时间之短可以秒计算。在其特有智能语音控制器作用下，文本输出的语音音律流畅，使得听者在听取信息时感觉自然，毫无机器语音输出的冷漠与生涩感。TTS语音合成技术即将覆盖国标一、二级汉字，具有英文接口，自动识别中、英文，支持中英文混读。所有声音采用真人普通话为标准发音，实现了120-150个汉字/秒的快速语音合成，朗读速度达3-4个汉字/秒，使用户可以听到清晰悦耳的音质和连贯流畅的语调。现在有少部分MP3随身听具有了TTS功能。

    TTS是语音合成应用的一种，它将储存于电脑中的文件，如帮助文件或者网页，转换成自然语音输出。TTS可以帮助有视觉障碍的人阅读计算机上的信息，或者只是简单的用来增加文本文档的可读性。现在的TTL应用包括语音驱动的邮件以及声音敏感系统。TTS经常与声音识别程序一起使用。现在有很多TTS的产品，包括Read Please 2000， Proverbe Speech Unit，以及Next Up Technology的TextAloud。朗讯、 Elan、以及 AT&T都有自己的语音合成产品。

    除了TTS软件之外，很多商家还提供硬件产品，其中包括以色列WizCom Technologies公司的 Quick Link Pen，它是一个笔状的可以扫描也可以阅读文字的设备；还有Ostrich Software公司的Road Runner，一个手持的可以阅读ASCII文本的设备；另外还有美国DEC公司的DecTalk TTS，它是可以替代声卡的外部硬件设备，它包含一个内部软件设备，可以与个人电脑自己的声卡协同工作。

TTS解析

    TTS文语转换用途很广，包括电子邮件的阅读、IVR系统的语音提示等等，目前IVR系统已广泛应用于各个行业（如电信、交通运输等）。

    TTS所用的关键技术就是语音合成(SpeechSynthesis)。早期的TTS一般采用专用的芯片实现，如德州仪器公司的TMS50C10/TMS50C57、飞利浦的PH84H36等，但主要用在家用电器或儿童玩具中。

    而基于微机应用的TTS一般用纯软件实现，主要包括以下几部分：

    ●文本分析-对输入文本进行语言学分析，逐句进行词汇的、语法的和语义的分析，以确定句子的低层结构和每个字的音素的组成，包括文本的断句、字词切分、多音字的处理、数字的处理、缩略语的处理等。

    ●语音合成-把处理好的文本所对应的单字或短语从语音合成库中提取，把语言学描述转化成言语波形。

    ●韵律处理-合成音质(Qualityof Synthetic Speech)是指语音合成系统所输出的语音的质量，一般从清晰度(或可懂度)、自然度和连贯性等方面进行主观评价。清晰度是正确听辨有意义词语的百分率；自然度用来评价合成语音音质是否接近人说话的声音，合成词语的语调是否自然；连贯性用来评价合成语句是否流畅。

    要合成出高质量的语音，所采用的算法是极为复杂的，因此对机器的要求也非常高。算法的复杂度决定了目前微机并发进行多通道TTS的系统容量。

    TTS在CTI的应用中的基本构架

    在一般的CTI应用系统中，都会有IVR（交互式语音应答系统）。IVR系统是呼叫中心的重要组成部分，通过IVR系统，用户可以利用音频按健电话输入信息，从系统中获得预先录制的数字或合成语音信息。具有TTS功能的IVR可以加快服务速度，节约服务成本，使IVR为呼叫者提供7*24小时的服务。

    目前常见的IVR系统大都是通用的工控机平台上插入语音板卡组成，并支持中文语音合成TTS等技术。

    一个典型的包含TTS服务的电话服务流程可分为：

    用户电话拨入，系统IVR响应，获得用户按键等信息。

    IVR根据用户的按键信息，向数据库服务器申请相关数据。

    数据库服务器返回文本数据给IVR。

    IVR通过其TCP通讯接口，将需要合成的文本信息发送给TTS服务器。

    TTS服务器将用户文本合成的语音数据分段通过TCP通讯接口发送给IVR服务器。

    IVR服务器把分段语音数据组装成为独立的语音文件。

    IVR播放相应的语音文件给电话用户。

    一般的公网接入（IVR）大都采用工控机+语音板卡，而合成的语音数据则通过局域网传给IVR。这种结构只适用于简单的应用场合。

中文TTS系统

    包括中文语音处理和语音合成，利用中文韵律等相关知识对中文语句进行分词、词性判断、注音、数字符号转换，语音合成通过查询中文语音库得到语音。目前中文TTS系统，比较著名的有：IBM，Microsoft，Fujitsu，科大讯飞，捷通华声等研究的系统。目前比较关键的就是中文韵律处理、符号数字、多音字、构词方面有较多的问题，需要不断研究，使得中文语音合成的自然化程度较高。

TTS与CTI

    CTI技术使电信和计算机相互融合，克服了传统电信和计算机服务相对单一的缺点，将两者完美结合了起来。其应用领域非常广泛，任何需要语音、数据通信，特别是那些希望把计算机网与通信网结合起来完成语音数据信息交换的系统都会用到CTI技术。

    TTS即语音合成技术(Text To Speech)，它涉及声学、语言学、数学信号处理技术、多媒体技术等多个学科技术，是中文信息处理领域的一项前沿技术，实现把计算机中任意出现的文字转换成自然流畅的语音输出。

    TTS在CTI系统中可以应用在IVR（交互式语音应答）服务器上，以提供语音交互式平台，为用户电话来访提供语音提示，引导用户选择服务内容和输入电话事务所需的数据，并接受用户在电话拨号键盘上输入的信息，实现对计算机数据库等信息资料的交互式访问。

    在IVR中应用TTS可以自动将文本信息转换为语音文件，或者实时地将文本信息合成语音并通过电话发布。实现文本与语音自动双向转换，以达到人与系统的自动交互，随时随地为客户服务。维护人员不必再人工录音，只须将电子文档引入系统中，系统可以自动将电子文档转换为语音信息播放给客户。数据库中存放的大量数据，无需事先进行录音，能够随时根据查询条件查出并合成语音进行播报，从而大大减少了座席人员的工作负担。

    那么应如何将TTS功能附加到CTI应用中呢？某些比较先进的交换平台，已经在交换机的内部实现了TTS的功能，并作为标准接口的一部分对外提供，业务开发商只需要简单的调用他们即可以在业务中使用该功能。

    对于未实现TTS功能的PBX，就需要业务开发商自己去选择合适的平台，在此基础上进行二次开发，即调用所选TTS平台提供的标准接口，实现语音合成功能。

    目前CTI已经成为全球发展最为迅猛的产业之一，每年以50%的速度增长，CTI如同计算机产业一样是一个金字塔形的产业链，从上到下会以至少20倍的幅度增值。TTS作为一种诱人的新技术，如果能很好的嵌入到增值业务的应用中去，必将形成一个更好的应用前景。

    杭州音通软件有限公司是由国家教育部和浙江省人民政府联办并依托浙江大学而成立的高新技术公司，音通公司主要致力于计算机语音技术的研发并逐步开拓语音识别、语音流媒体传输等其它语音领域的研究。其核心技术（Intone＿TTS）是具有自主知识产权的中文语音合成技术，在由浙江省科技厅组织的鉴定中被专家一致鉴定为国内领先地位，并已申请多项国家专利。

    Intone_TTS是一套把文本信息转换为语音信息的开发工具包，为系统集成商、软件开发商提供了完备的接口函数和编程示例，使用户能够灵活的进行调用，并集成到其它应用系统中。接口需要语音合成运行库的支持，适合多种开发环境。开发者可以根据具体的应用场合进行选择。

    它能够对所有的汉字、英文、阿拉伯数字进行语音合成；

    支持繁体字及多音字的编辑；

    合成效果：自然、平滑；

    规范的函数调用接口，同时支持微软SAPI的调用；支持同步调用和异步调用方式；

    支持PCM Wave，uLaw/aLaw Wave，ADPCM，Dialogic Vox等多种语音格式；

    支持GB2312码（简体中文）、BIG5码（繁体）、UNICODE码；

    支持多路通道同时合成；

    支持Dialogic、东进、三汇等主流语音板卡；

TTS漫谈

    TTS就是Text To Speech，文本转语音，文本朗读，差不多是一个意思。在语音系统开发中经常要用到。

    目前市场上的TTS很多，实现方式也各式各样，有的很昂贵，如科大讯飞，据说当初得到863计划的资助，有很高的技术；有的相对便宜，如捷通华声, InfoTalk；也有免费的，如微软的TTS产品。

    相对于ASR（Automatic Speech Recognition，自动语音识别）来说，实现一个TTS产品所需要的技术难度不算大，在我看来也就是个力气活。

    要是让我们来做一个能够把汉语句子朗读出来的TTS，我们会怎么做呢？

    有一种最简单的TTS，就是把每个字都念出来，你会问，岂不要录制6千多个汉字的语音？幸运的是，汉语的音节很少，很多同音字。我们最多只是需要录制：声母数×韵母数×4，（其实不是每个读音都有4声），这样算来，最多只需要录制几百个语音就可以了。

    在合成的时候需要一张汉字对应拼音的对照表，汉字拼音输入法也依赖这张表，可以在网上找到，不过通常没有4声音调，大不了自己加上，呵呵，要不怎么说是力气活呢。

    这样做出来的TTS效果也还可以，特别是朗读一些没有特别含义的如姓名，家庭住址，股票代码等汉语句子，听起来足够清晰。这要归功于我们伟大的母语通常都是单音节，从古代的时候开始，每个汉字就有一个词，表达一个意思。而且汉字不同于英语，英语里面很多连读，音调节奏变化很大，汉字就简单多了。

    当然，你仍然要处理一些细节，比如多音字，把“银行”读成“yin xing”就不对了；再比如，标点符号的处理，数字、字母的处理，这些问题对于写过很多程序的你，当然不难了。

    国内的一些语音板卡带的TTS，不管是卖钱的还是免费的，大体都是这样做出来的，也就是这样的效果。

    如果要把TTS的效果弄好一点，再来点力气活，把基本的词录制成语音，如常见的两字词，四字成语等，再做个词库和语音库的对照表，每次需要合成时到词库里面找。这样以词为单位，比以字为单位，效果自然是好多了。当然，这里面还是有个技术，就是分词的技术，要把复杂的句子断成合理的词序列，也有点技术。这也要怪新文化那些先驱们，当初倡导白话文，引进西文的横排格式、标点符号的时候，没有引进西文中的空格分词。不过即使分词算法那么不高效，不那么准确，也问题不大，如前面所说，汉字是单音节词，把声音合起来，大体上不会有错。

    当然，科大讯飞的力气活又干的多了些，据说已经进化到以常用句子为单位来录音了，大家可以想像，这要耗费更多的力气，换来更好的效果。

    至于增加一些衔接处的“词料”，弄一些修饰性的音调，我认为是无关紧要的，对整体的效果改进不是太大。

    市面上商品化TTS一般还支持粤语，请个粤语播音员录音，把上面的力气活重做一遍就是了。

    再说句题外话，很多人觉得录音最好找电台、电视台的播音员，其实找个你周围的女同事来录制，只要吐字清晰就可以了。在某种情况下，寻常声音比字正腔圆的新闻联播来得可爱。

    再来说说文本的标识，对于复杂文本，某些内容程序没有办法处理，需要标识出来。比如，单纯的数字“128”，是应该念成“一百二十八”还是“一二八”？解决办法通常是加入XML标注，如微软的TTS："<context ID = "number_cardinal">128</context>"念成“一百二十八”，"<context ID = "number_digit">128</context>"将念成“一二八”。TTS引擎可以去解释这些标注。遗憾的是，语音XML标注并没有形成大家都完全认可的标准，基本上是各自一套。

    再说说TTS应用编程，微软的TTS编程接口叫SAPI，是COM接口，开发起来还是有点麻烦，还好MSDN的网站上资料很全面。微软的TTS虽然免费，但其中文角色目前是个男声，声音略嫌混浊，感觉不爽。

    国内一般的厂家提供API调用接口，相对比较简单，可以方便地嵌入应用程序中去。

    商品化的TTS还有个并发许可限制，就是限制同时合成的并发线程数，我觉得这个限制用处不大。无论哪种TTS，都可以将文本文件转换成语音文件，供语音卡播放。大部分应用句子比较短小，一般不会超过100个汉字，合成的时间是非常短的，弄个线程专门负责合成，其它应用向该线程请求就是了，万一句子很长，把它分解成多个短句子就是了，播放的速度总是比合成的速度慢。

    也很多应用是脱机合成，没有实时性要求，就更不必买多个许可了。

    更多情况下，我们甚至没有必要购买TTS，比如语音开发中常见的费用催缴，拨通后播放：“尊敬的客户，您本月的费用是：212元”，前面部分对所有客户都一样，录一个语音文件就是了，而数字的合成是很简单的，你只要录制好10个数字语音，再加上十，百，千，万，再加上金钱的单位“元”。

    TTS(Training+Tool+Scheme)超越计划

    针对目前成长型企业遇到的人力资源问题，立体化解决人力资源瓶颈、通过企业与专家共建、实现人才强企的人力资源方向的重大智业项目。为企业培养人力资源高级管理人才，提供先进人力资源管理工具，并协助企业建立现代人力资源战略规划。通过“培训（Training）+工具（Tool）+方案（Scheme）”的办法，为企业系统解决人力资源难点问题，进而搭建科学、完善的人力资源管理体系。


介绍

这篇文章的目的是为了介绍std::vector，如何恰当地使用它们的成员函数等操作。本文中还讨论了条件函数和函数指针在迭代算法中使用，如在remove_if()和for_each()中的使用。通过阅读这篇文章读者应该能够有效地使用vector容器，而且应该不会再去使用C类型的动态数组了。

Vector总览

vector是C++标准模板库中的部分内容，它是一个多功能的，能够操作多种数据结构和算法的模板类和函数库。vector之所以被认为是一个容器，是因为它能够像容器一样存放各种类型的对象，简单地说，vector是一个能够存放任意类型的动态数组，能够增加和压缩数据。

为了可以使用vector，必须在你的头文件中包含下面的代码：

#include <vector>

vector属于std命名域的，因此需要通过命名限定，如下完成你的代码：

using std::vector;

vector<int> vInts;

或者连在一起，使用全名：

std::vector<int> vInts;

建议使用全局的命名域方式：

usingnamespace std;

在后面的操作中全局的命名域方式会造成一些问题。vector容器提供了很多接口，在下面的表中列出vector的成员函数和操作。

Vector成员函数

函数 | 表述
c.assign(beg,end) c.assign(n,elem) | 将[beg; end)区间中的数据赋值给c。 将n个elem的拷贝赋值给c。
c.at(idx) | 传回索引idx所指的数据，如果idx越界，抛出out_of_range。
c.back() | 传回最后一个数据，不检查这个数据是否存在。
c.begin() | 传回迭代器重的可一个数据。
c.capacity() | 返回容器中数据个数。
c.clear() | 移除容器中所有数据。
c.empty() | 判断容器是否为空。
c.end() | 指向迭代器中的最后一个数据地址。
c.erase(pos) c.erase(beg,end) | 删除pos位置的数据，传回下一个数据的位置。 删除[beg,end)区间的数据，传回下一个数据的位置。
c.front() | 传回地一个数据。
get_allocator | 使用构造函数返回一个拷贝。
c.insert(pos,elem) c.insert(pos,n,elem) c.insert(pos,beg,end) | 在pos位置插入一个elem拷贝，传回新数据位置。 在pos位置插入n个elem数据。无返回值。 在pos位置插入在[beg,end)区间的数据。无返回值。
c.max_size() | 返回容器中最大数据的数量。
c.pop_back() | 删除最后一个数据。
c.push_back(elem) | 在尾部加入一个数据。
c.rbegin() | 传回一个逆向队列的第一个数据。
c.rend() | 传回一个逆向队列的最后一个数据的下一个位置。
c.resize(num) | 重新指定队列的长度。
c.reserve() | 保留适当的容量。
c.size() | 返回容器中实际数据的个数。
c1.swap(c2) swap(c1,c2) | 将c1和c2元素互换。 同上操作。
vector<Elem> c vector <Elem> c1(c2) vector <Elem> c(n) vector <Elem> c(n, elem) vector <Elem> c(beg,end) c.~ vector <Elem>() | 创建一个空的vector。 复制一个vector。 创建一个vector，含有n个数据，数据均已缺省构造产生。 创建一个含有n个elem拷贝的vector。 创建一个以[beg;end)区间的vector。 销毁所有数据，释放内存。

Vector操作

函数 | 描述
operator[] | 返回容器中指定位置的一个引用。

创建一个vector

vector容器提供了多种创建方法，下面介绍几种常用的。

创建一个Widget类型的空的vector对象：

vector<Widget> vWidgets;

// ------

// |

// |- Since vector is a container, its member functions

// operate on iterators and the container itself so

// it can hold objects of any type.

创建一个包含500个Widget类型数据的vector：

vector<Widget> vWidgets(500);

创建一个包含500个Widget类型数据的vector，并且都初始化为0：

vector<Widget> vWidgets(500, Widget(0));

创建一个Widget的拷贝：

vector<Widget> vWidgetsFromAnother(vWidgets);

向vector添加一个数据

vector添加数据的缺省方法是push_back()。push_back()函数表示将数据添加到vector的尾部，并按需要来分配内存。例如：向vector<Widget>中添加10个数据，需要如下编写代码：

for(int i= 0;i<10; i++)

vWidgets.push_back(Widget(i));

获取vector中制定位置的数据

很多时候我们不必要知道vector里面有多少数据，vector里面的数据是动态分配的，使用push_back()的一系列分配空间常常决定于文件或一些数据源。如果你想知道vector存放了多少数据，你可以使用empty()。获取vector的大小，可以使用size()。例如，如果你想获取一个vector v的大小，但不知道它是否为空，或者已经包含了数据，如果为空想设置为-1，你可以使用下面的代码实现：

int nSize = v.empty() ? -1 : static_cast<int>(v.size());

访问vector中的数据

使用两种方法来访问vector。

1、 vector::at()

2、 vector::operator[]

operator[]主要是为了与C语言进行兼容。它可以像C语言数组一样操作。但at()是我们的首选，因为at()进行了边界检查，如果访问超过了vector的范围，将抛出一个例外。由于operator[]容易造成一些错误，所有我们很少用它，下面进行验证一下：

分析下面的代码：

vector<int> v;

v.reserve(10);

for(int i=0; i<7; i++)

v.push_back(i);

try

{

int iVal1 = v[7]; // not bounds checked - will not throw

int iVal2 = v.at(7); // bounds checked - will throw if out of range

}

catch(const exception& e)

{

cout << e.what();

}

我们使用reserve()分配了10个int型的空间，但并不没有初始化。如下图所示：

你可以在这个代码中尝试不同条件，观察它的结果，但是无论何时使用at()，都是正确的。

删除vector中的数据

vector能够非常容易地添加数据，也能很方便地取出数据，同样vector提供了erase()，pop_back()，clear()来删除数据，当你删除数据的时候，你应该知道要删除尾部的数据，或者是删除所有数据，还是个别的数据。在考虑删除等操作之前让我们静下来考虑一下在STL中的一些应用。

Remove_if()算法

现在我们考虑操作里面的数据。如果要使用remove_if()，我们需要在头文件中包含如下代码：：

#include <algorithm>

Remove_if()有三个参数：

1、 iterator _First：指向第一个数据的迭代指针。

2、 iterator _Last：指向最后一个数据的迭代指针。

3、 predicate _Pred：一个可以对迭代操作的条件函数。

条件函数

条件函数是一个按照用户定义的条件返回是或否的结果，是最基本的函数指针，或者是一个函数对象。这个函数对象需要支持所有的函数调用操作，重载operator()()操作。remove_if()是通过unary_function继承下来的，允许传递数据作为条件。

例如，假如你想从一个vector<CString>中删除匹配的数据，如果字串中包含了一个值，从这个值开始，从这个值结束。首先你应该建立一个数据结构来包含这些数据，类似代码如下：

#include <functional>

enum findmodes

{

FM_INVALID = 0,

FM_IS,

FM_STARTSWITH,

FM_ENDSWITH,

FM_CONTAINS

};

typedefstruct tagFindStr

{

UINT iMode;

CString szMatchStr;

} FindStr;

typedef FindStr* LPFINDSTR;

然后处理条件判断：

class FindMatchingString

: public std::unary_function<CString, bool>

{

public:

FindMatchingString(const LPFINDSTR lpFS) : m_lpFS(lpFS) {}

bool operator()(CString& szStringToCompare) const

{

bool retVal = false;

switch(m_lpFS->iMode)

{

case FM_IS:

{

retVal = (szStringToCompare == m_lpFDD->szMatchStr);

break;

}

case FM_STARTSWITH:

{

retVal = (szStringToCompare.Left(m_lpFDD->szMatchStr.GetLength())

== m_lpFDD->szWindowTitle);

break;

}

case FM_ENDSWITH:

{

retVal = (szStringToCompare.Right(m_lpFDD->szMatchStr.GetLength())

== m_lpFDD->szMatchStr);

break;

}

case FM_CONTAINS:

{

retVal = (szStringToCompare.Find(m_lpFDD->szMatchStr) != -1);

break;

}

}

return retVal;

}

private:

LPFINDSTR m_lpFS;

};

通过这个操作你可以从vector中有效地删除数据：

// remove all strings containing the value of

// szRemove from vector<CString> vs.

FindStr fs;

fs.iMode = FM_CONTAINS;

fs.szMatchStr = szRemove;

vs.erase(std::remove_if(vs.begin(), vs.end(), FindMatchingString(&fs)), vs.end());

Remove_if()能做什么？

你可能会疑惑，对于上面那个例子在调用remove_if()的时候还要使用erase()呢？这是因为大家并不熟悉STL中的算法。Remove(),remove_if()等所有的移出操作都是建立在一个迭代范围上的，那么不能操作容器中的数据。所以在使用remove_if()，实际上操作的时容器里数据的上面的。思考上面的例子：

1、 szRemove = “o”.

2、 vs见下面图表中的显示。

观察这个结果，我们可以看到remove_if()实际上是根据条件对迭代地址进行了修改，在数据的后面存在一些残余的数据，那些需要删除的数据。剩下的数据的位置可能不是原来的数据，但他们是不知道的。

调用erase()来删除那些残余的数据。注意上面例子中通过erase()删除remove_if()的结果和vs.enc()范围的数据。

压缩一个臃肿的vector

很多时候大量的删除数据，或者通过使用reserve()，结果vector的空间远远大于实际需要的。所有需要压缩vector到它实际的大小。resize()能够增加vector的大小。Clear()仅仅能够改变缓存的大小，所有的这些对于vector释放内存等九非常重要了。如何来解决这些问题呢，让我们来操作一下。

我们可以通过一个vector创建另一个vector。让我们看看这将发生什么。假定我们已经有一个vector v，它的内存大小为1000，当我们调用size()的时候，它的大小仅为7。我们浪费了大量的内存。让我们在它的基础上创建一个vector。

std::vector<CString> vNew(v);

cout << vNew.capacity();

vNew.capacity()返回的是7。这说明新创建的只是根据实际大小来分配的空间。现在我们不想释放v，因为我们要在其它地方用到它，我们可以使用swap()将v和vNew互相交换一下？

vNew.swap(v);

cout << vNew.capacity();

cout << v.capacity();

有趣的是：vNew.capacity()是1000，而v.capacity()是7。

现在是达到我的目的了，但是并不是很好的解决方法，我们可以像下面这么写：

std::vector<CString>(v).swap(v);

你可以看到我们做了什么？我们创建了一个临时变量代替那个命名的，然后使用swap(),这样我们就去掉了不必要的空间，得到实际大小的v。

结论

我希望这个文档可以给那些使用STL vector容器的开发者很有价值的参考。我也希望通过阅读这篇文章你可以放心地使用vector来代替C语言中的数据了。

参考

Plauger, P.J. Standard C++ Library Reference. February, 2003. MSDN.

Schildt, Herbert. C++ from the Ground Up, Second Edition. Berkeley: 1998.

Sutter, Herb. More Exceptional C++. Indianapolis: 2002.


vss

VSS相关解释

    1.VSS 即 Var Sect Sweep，就是可变剖面扫描，也就是常说的变截面扫描，是PROE的一种建模工具。

    2.VSS----Visual Source Safe

    VSS 的全称为 Visual Source Safe 。作为 Microsoft Visual Studio 的一名成员，它主要任务就是负责项目文件的管理，几乎可以适用任何软件项目。

    源代码版本控制机制是现代软件开发中必不可少的管理机制之一，通常借助版本控制软件即Source Code Management(SCM) systems或者Version Control systems来实现。版本控制在软件开发中的重要作用如下：

    1）作为代码仓库有效的管理软件开发中各个不同版本的源代码和文档，占用空间小并且方便各个版本代码和文档的获取。

    2）对开发小组中对源代码的访问进行有效的协调（不同的版本控制软件采取不同的协调策略）。

    常用的版本控制软件有Clearcase, CVS, PVCS和Microsoft公司的Visual SourceSafe (VSS)。下面对Windows平台开发最常用的Visual SourceSafe 6.0进行简单介绍。

    3. Cisco VSS

    既虚拟交换系统是一种网络系统虚拟化技术，将两台Cisco&reg; Catalyst&reg;6500系列交换机组合为单一虚拟交换机，从而提高运营效率、增强不间断通信，并将系统带宽容量扩展到1.4Tbps。在初始阶段，VSS将使两台物理Cisco Catalyst 6500系列交换机作为单一逻辑虚拟交换机运行，称为虚拟交换系统 1440（VSS1440）。

    4.VSS ------Volume Shadow copy Service

    VSS (Volume Shadow copy Service, 卷映射拷贝服务)，最初是在Windows Server 2003 SP1中引入的存储技术。它通过在卷管理模块上加入快照功能，在此基础上就可以创建基于时间点(Point-In-Time)的映像(Image)，从而实现数据的快速备份和恢复，用户也无需害怕因无意删除数据而造成的困扰，这就是VSS的关键所在。由于VSS所表现的优良特性，因此在Microsfot最新的Vista操作系统中，也加入了该功能。

    VSS能够让存储基础设备(比如磁盘、阵列)创建高保真的时间点映像，就是映射拷贝(Shadow Copy)。它可以在多卷、或者单个卷上创建映射拷贝，同时不会影响到系统性能。VSS通过协调业务应用软件、备份程序，以及存储硬件，从而达到创建高保真映射拷贝的能力。它可以应用到以下领域：

    A、快速数据备份和恢复；

    B、针对制定文件的恢复；

    C、磁带备份、测试，以及数据挖掘方面；

    由于Microsoft在OS中集成了VSS技术，它将各个处理功能模块化，并针对存储软硬件厂商提供相应的开发接口；开发商只需要关注自己的模块，根据需求设计相关的应用，从而减少了整体设计的难度。就笔者了解，不少软硬件存储公司就采用VSS技术来设计NAS，并以此为基础构建数据保护服务器。

    VSS整体框架包含了VSS核心模块、请求者(Requestor)、写入者(Writer)，以及提供者(Provider)。

    5.VSS ----电路一种符号

    在电子电路中，VSS / VDD /VCC这些为电子电路符号。VSS的V还是电压，S是:Series，表示公共连接（一般就是接地）或电源负极。VSS的意思是接地电压（接地点电位视为0）。在“场效应”即COMS元件中，VDD乃CMOS的漏极引脚，VSS乃CMOS的源极引脚。

    另外，VCC：C=circuit 表示电路的意思, 即接入电路的电压（电路的供电电压）， D=device 表示器件的意思, 即器件内部的工作电压（芯片的工作电压），在普通的电子电路中，一般Vcc>Vdd !。

运行环境

    Windows平台下使用VSS开发的典型环境是基于C/S架构的，即开发小组的每个开发者在各自的Windows平台下利用开发工具（比如VC）开发项目中的各个模块，而配有专门的服务器集中控制开发过程中的文档和代码。服务器和开发人员的客户机分别装有VSS的服务器和客户端程序。

安装步骤

    VSS6.0服务器的安装

    第一步，将Visual Studio6.0的CD1放入CD-ROM，自动进入安装程序，选择第二项“Server Applications and Tools(Add Only)”；

    第二步，然后在“Server Setups”的“Server Components”中选择“Visua l Source Safe Server”,按“Install”按钮后，根据提示放入CD2；

    第三步，根据Installation Wizard的提示，继续安装过程，选择安装路径，直至提示你重启以使新安装的程序生效，重启计算机。

    VSS6.0客户端的安装

    在VSS6.0服务器安装完毕后，就可以在VSS管理员的指导下安装客户端的软件。由于我们是在一个Windows环境的子网内，每台PC客户机均可通过网上邻居来访问VSS服务器。在VSS管理员为用户指明VSS客户端软件的安装程序“Netsetup.exe”的位置（VSS服务器安装目录内）后，用户可以直接通过资源管理器“Explorer”在网上邻居中找到该程序，并双击以运行它。 Netsetup.exe被启动后进入VSS客户端的安装，在提示框中输入用户名和工作组名，选择VSS的安装路径后，点击安装图标，进入实际安装步骤，以后的工作将由系统自动完成。

使用说明

    VSS使用过程中要遵循的是lock-modify-unlock流程而不是 copy-modify-merge流程（比如CVS），即开发人员首先将自己要修改源代码和文档从VSS服务器主备份文件上checkout到本地同时锁定服务器上的源代码和文档（multi - checkout情况除外），修改完成后checkout到服务器上同时解除服务器上文件的锁定。服务器集中控制所有的源程序和文档。

    VSS使用中涉及到的概念

    1）项目（project），所谓的项目是一组存在VSS中的文件（任何类型），可以在项目中或是项目之间进行文件的添加、删除、编辑和共享。一个项目与操作系统的文件夹有很多的相似之处，但它更好地支持文件合并、历史和版本控制。所有的文件存在VSS数据库的项目中，开发组成员不能在VSS中的服务器主备份文件上工作（除了检查和版本比对等特殊情况外）而是VSS为每个成员在各自的工作目录（workfolder）下提供一个拷贝以供工作。尽管在没有工作目录的情况下也可以查看某个文件，但如要真正在VSS管理下工作，就必须要创建一个工作目录。 2）工作目录（workfolder）是用户真正对项目文件进行调试修改的地方，当用户checkout文件时，VSS将就将文件拷贝到用户的工作目录下，当用户修改了该文件并将其checkin时，VSS再将它从用户的工作目录拷回到VSS 的数据库中。在用户作checkout时，VSS将会自动管理他的工作目录，诸如创建必要的子目录。而且工作目录可以随时创建或修改。

    3）版本跟踪。VSS提供版本控制和历史服务，而且老版本程序和文档能够被重新得到以便进行bug跟踪或其他目的。VSS用日期/时间戳来记录文件是何时被checkout或是何时被修改的，它主要有三种方法来跟踪文件和项目的版本：

    版本号：这是由VSS维护的内部数码，用户对它没有控制权。每个文件和项目的每个版本都有一个版本号，这些版本号总是一个整数且是递增的。

    标签（label）：这些是用户赋给某个项目或文件的某个版本的一个字符串，可以是任何格式的长度不超过31字符的字符串。推荐使用。

    日期/时间戳：它给出了一个文件何时最后被修改的信息，或者是一个文件何时被checkin。VSS同时支持 12小时和24小时的时间格式。

VSS6.0服务器的配置和管理

    在VSS6.0服务器安装完毕后，就可以针对开发项目进行VSS服务器的配置和管理，这些工作均需由VSS管理员来完成。

    第一步，为整个项目创建一个VSS数据库（在VSS服务器安装时，系统已经创建了一个缺省数据库Common），启动Visual SourceSafe6.0 Admin（Start/Programs/Microsoft Visual Studio6.0/Microsoft Visual SourceSafe/Visual Sou rceSafe6.0 Admin），跳出对话窗口，点击下拉菜单Tools中的Create Database ...菜单项，将出现对话窗口，选择新数据库的路径，例如创建MyApp数据库，然后点击OK按钮，系统将完成创建工作。

    第二步，为新创建的数据库（例如MyApp）建立用户，首先需要打开该数据库，点击下拉菜单Users/Open SourceSafe Database...，然后选择数据库MyApp并打开。再选择菜单项Add User...，输入用户名和口令。然后依次创建其他的用户。可以为不同的用户设置针对不同项目文件夹的不同存取权限，存在两种方法：1）使用VSS提供的功能。选中Tools/Options…对话框中Porject Security属性页的Enable Project Security复选框并选择Tools/Rights by Project…或者Tools/Rights Assignments for User…进行设置；2）使用windows操作系统的cacls命令。

    第三步，在该新建的数据库中创建项目Project。启动Microsoft Visual SourceSafe6.0（Start/Programs/Microsoft Visual Studio6.0/Microsoft Visual SourceSafe/Microsoft Visual SourceSafe6.0），出现对话窗口，点击下拉菜单File中Open SourceSafe Database...，跳出对话框，选择一个数据库（例如MyApp），双击它或按Open按钮打开该数据库；一个项目Project是一组相关的文档或者是一个文件的集合，VSS允许你以任何的层次结构来存贮和组织你的项目。在VSS数据库中，你可以创建一个或者多个项目。点击菜单File中的命令Create Project...，创建一个项目，例如MyProject；创建完项目MyProject后，需要向MyProject中添加文件，点击File中Add Files命令，将跳出对话框，选择相应文件或目录，点击Add按钮，将它们添加到MyProject中去。

    至此VSS服务器的配置到此基本上已经完成了，创建了数据库和项目，并为它们建立了相应的用户，这样用户在客户端就可以直接登录到VSS服务器上，进行在VSS控制管理下的开发工作。

    注意：关于服务器主备份文件的目录结构应该在项目开发前确定并征得全体开发人员得同意。实际使用过程中，如果发生服务器主备份文件目录结构不合理或者需要临时增加删除一些文件的情况，VSS管理员有责任集中进行处理。

VSS6.0客户端的使用

    下面以实验室数据仓库项目具体实例说明VSS客户端使用步骤。

    第一步，登录到VSS服务器。启动Microsoft Visual SourceSafe6.0客户端（Start/Programs/Microsoft Visual Studio6.0/Microsoft Visual SourceSafe/Microsoft Visual SourceSafe6.0），出现如下登录窗口；

    点击Browse…按钮，出现下面对话框；

    再点击Browse…按钮，选择下图所示的目录（DBGROUP服务器上的共享目录，如提示输入DBGROUP服务器帐号密码则分别填写vss/vss）；

    单击打开按钮并在随后出现的两个对话框上单击OK和Open；

    重新回到登录对话框；

    输入管理员指定的用户名和密码，即登录到了VSS服务器上。

    注意：不同得用户和密码设置对不同目录（project）有不同的访问权限。

    第二步，如果是第一次登录到VSS服务器，还需要先设定工作目录，否则无法进行checkout操作。

    工作目录的设定如下图所示，选中自己相关模块的文件夹，单击右键，选择Set Working Folder…；

    在弹出的对话框中选择自己所设定的工作目录并单击OK；注意：工作目录设定完成后，服务器上的文件checkout后均放到该工作目录中，所以本地机器上的工作目录应收到保护不能随意删除移动。

    第三步，checkout文件到本地工作目录中。选中相关的文件夹，单击右键并选择Check Out…；

    系统弹出checkout对话框如下，单击OK后该目录下所有文件和子文件夹即checkout到了本地工作目录同时服务器上的文件被锁定呈红色，其他用户不可以再checkout该文件夹（服务器设置为不允许multicheckout）。

    注意：一定要选中Recursive复选框以保证所有子文件夹被checkout。

    第四步，对本地工作目录当中的文件进行修改调试。

    第五步，将工作目录中的文件checkin。如果不将修改后的文件checkin则第四步中对文件的修改无法反映到服务器上项目文件的主备份中，而且极有可能导致下次checkout后本地文件被旧版本文件覆盖的情况。在VSS客户端窗口中选中要checkin的文件夹单击右键选择Check in…，弹出checkin对话框，单击OK则本地工作目录中的文件checkin到了服务器上同时服务器上的文件解除锁定（变黑）。

注意事项

    一定要选中Recursive复选框以保证所有子文件夹被checkin。

    建议1

    在实际使用过程中，开发人员或者管理员有责任对服务器上的源程序和文档定期或不定期的做相应Label，以方便源程序和文档历史版本的管理。VSS可以方便的根据Label获取各个历史版本（有其他获取历史版本的方式，本文推荐使用Label方法）。

    添加Label。选中要添加Label的目录（project）单击右键选中Label…；

    设置Label对话框中填入Label名称和相关Comment，如下图所示；

    如果要获取某一历史版本，选中相应的目录（project）并选择Show History…，如下图所示选择相应复选框并单击OK；

    VSS弹出下面对话框，其中列出了所有管理员所作的Label，选中相应的Label，然后单击Get按钮则做Label的相应版本就会checkout到本地工作目录中。其他按钮的使用参见帮助；建议2

    开发人员请不要随意变更VSS服务器上的目录（project）结构，不要在目录（project）中随便增加删除文件，不要随便访问他人目录（project）及文件。如服务器上目录（project）结构确实需要变化请与管理员联系。

    建议3

    严格遵守VSS的使用规程，防止因误操作导致程序或者文档的丢失。如遇两人需同时修改统一目录（project）或文件的情况，请串行处理而非multicheckout方式处理以减少出错几率。VSS概述VSS概述

    版本控制是工作组软件开发中的重要方面，它能防止意外的文件丢失、允许反追踪到早期版本、并能

    对版本进[1][2]行分支、合并和管理。在软件开发和您需要比较两种版本的文件或找回早期版本的文件时，源代

    码的控制是非常有用的。

    Visual SourceSafe 是一种源代码控制系统，它提供了完善的版本和配置管理功能，以及安全保护和跟踪

    检查功能。VSS通过将有关项目文档（包括文本文件、图象文件、二进制文件、声音文件、视屏文件）存

    入数据库进行项目研发管理工作。用户可以根据需要随时快速有效地共享文件。文件一旦被添加进VSS，

    它的每次改动都会被记录下来，用户可以恢复文件的早期版本，项目组的其他成员也可以看到有关文档的

    最新版本，并对它们进行修改，VSS也同样会将新的改动记录下来。你还会发现，用 VSS来组织管理项目,

    使得项目组间的沟通与合作更简易而且直观。

VSS “Vintorez”微声狙击步枪

VSS “Vintorez”微声狙击步枪

    VSS“Vintorez”（俄文为ВСС“Винторез”，中文为“绞丝机”）其实就是AS的狙击型（虽然AS实际上也被常常当作狙击步枪使用），也是中央精密机械工程研究院的P. Serdjukov小组的同时研制的。VSS是Vinovka Snaiperskaja Spetsialnaya的缩写，即“特种狙击步枪。VSS也可以发射SP-5普通弹，但主要是发射SP-6穿甲弹。和AS一样，这种步枪也是为特种部队研制的，已经装备了俄罗斯的特种部队及执法机构的行动单位，也已经在独联体各地的武装冲突中广泛应用。

    AS与VSS的结构原理是完全一样的，在外形上的区别只是VSS取消了独立小握握，改为框架式的木制运动型枪托，枪托底部有橡胶底板。VSS与AS的弹匣通用，但在VSS上标配的是10发弹匣。


串口通信协议

什么是串口

    串口是计算机上一种非常通用设备通信的协议（不要与通用串行总线Universal SerialBus或者USB混淆）。大多数计算机包含两个基于RS232的串口。串口同时也是仪器仪表设备通用的通信协议；很多GPIB兼容的设备也带有RS-232口。同时，串口通信协议也可以用于获取远程采集设备的数据。

    串口通信的概念非常简单，串口按位（bit）发送和接收字节。尽管比按字节（byte）的并行通信慢，但是串口可以在使用一根线发送数据的同时用另一根线接收数据。它很简单并且能够实现远距离通信。比如IEEE488定义并行通行状态时，规定设备线总常不得超过20米，并且任意两个设备间的长度不得超过2米；而对于串口而言，长度可达1200米。

    典型地，串口用于ASCII码字符的传输。通信使用3根线完成：（1）地线，（2）发送，（3）接收。由于串口通信是异步的，端口能够在一根线上发送数据同时在另一根线上接收数据。其他线用于握手，但是不是必须的。串口通信最重要的参数是波特率、数据位、停止位和奇偶校验。对于两个进行通行的端口，这些参数必须匹配：

    a，波特率：这是一个衡量通信速度的参数。它表示每秒钟传送的bit的个数。例如300波特表示每秒钟发送300个bit。当我们提到时钟周期时，我们就是指波特率例如如果协议需要4800波特率，那么时钟是4800Hz。这意味着串口通信在数据线上的采样率为4800Hz。通常电话线的波特率为14400，28800和36600。波特率可以远远大于这些值，但是波特率和距离成反比。高波特率常常用于放置的很近的仪器间的通信，典型的例子就是GPIB设备的通信。

    b，数据位：这是衡量通信中实际数据位的参数。当计算机发送一个信息包，实际的数据不会是8位的，标准的值是5、7和8位。如何设置取决于你想传送的信息。比如，标准的ASCII码是0～127（7位）。扩展的ASCII码是0～255（8位）。如果数据使用简单的文本（标准ASCII码），那么每个数据包使用7位数据。每个包是指一个字节，包括开始/停止位，数据位和奇偶校验位。由于实际数据位取决于通信协议的选取，术语“包”指任何通信的情况。

    c，停止位：用于表示单个包的最后一位。典型的值为1，1.5和2位。由于数据是在传输线上定时的，并且每一个设备有其自己的时钟，很可能在通信中两台设备间出现了小小的不同步。因此停止位不仅仅是表示传输的结束，并且提供计算机校正时钟同步的机会。适用于停止位的位数越多，不同时钟同步的容忍程度越大，但是数据传输率同时也越慢。

    d，奇偶校验位：在串口通信中一种简单的检错方式。有四种检错方式：偶、奇、高和低。当然没有校验位也是可以的。对于偶和奇校验的情况，串口会设置校验位（数据位后面的一位），用一个值确保传输的数据有偶个或者奇个逻辑高位。例如，如果数据是011，那么对于偶校验，校验位为0，保证逻辑高的位数是偶数个。如果是奇校验，校验位位1，这样就有3个逻辑高位。高位和低位不真正的检查数据，简单置位逻辑高或者逻辑低校验。这样使得接收设备能够知道一个位的状态，有机会判断是否有噪声干扰了通信或者是否传输和接收数据是否不同步。

什么是RS-232

    RS-232（ANSI/EIA-232标准）是IBM-PC及其兼容机上的串行连接标准。可用于许多用途，比如连接鼠标、打印机或者Modem，同时也可以接工业仪器仪表。用于驱动和连线的改进，实际应用中RS-232的传输长度或者速度常常超过标准的值。RS-232只限于PC串口和设备间点对点的通信。RS-232串口通信最远距离是50英尺。

    DB-9针连接头

    -------------

    \ 1 2 3 4 5 /

    \ 6 7 8 9 /

    -------

    从计算机连出的线的截面。

    RS-232针脚的功能：

    数据：

    TXD（pin 3）：串口数据输出(Transmit Data)

    RXD（pin 2）：串口数据输入(Receive Data)

    握手：

    RTS（pin 7）：发送数据请求(Request to Send)

    CTS（pin 8）：清除发送(Clear to Send)

    DSR（pin 6）：数据发送就绪(Data Send Ready)

    DCD（pin 1）：数据载波检测(Data Carrier Detect)

    DTR（pin 4）：数据终端就绪(Data Terminal Ready)

    地线：

    GND（pin 5）：地线

    其他

    RI（pin 9）：铃声指示

什么是RS-422

    RS-422（EIA RS-422-AStandard）是Apple的Macintosh计算机的串口连接标准。RS-422使用差分信号，RS-232使用非平衡参考地的信号。差分传输使用两根线发送和接收信号，对比RS-232，它能更好的抗噪声和有更远的传输距离。在工业环境中更好的抗噪性和更远的传输距离是一个很大的优点。

什么是RS-485

    RS-485（EIA-485标准）是RS-422的改进，因为它增加了设备的个数，从10个增加到32个，同时定义了在最大设备个数情况下的电气特性，以保证足够的信号电压。有了多个设备的能力，你可以使用一个单个RS-422口建立设备网络。出色抗噪和多设备能力，在工业应用中建立连向PC机的分布式设备网络、其他数据收集控制器、HMI或者其他操作时，串行连接会选择RS-485。RS-485是RS-422的超集，因此所有的RS-422设备可以被RS-485控制。RS-485可以用超过4000英尺的线进行串行通行。

    DB-9 引脚连接

    -------------

    \ 1 2 3 4 5 /

    \ 6 7 8 9 /

    -------

    从计算机连出的线的截面。

    RS-485的引脚的功能

    数据：1（DATA-) 2(DATA+)

    地线：5

什么是握手

    RS-232通行方式允许简单连接三线：Tx、Rx和地线。但是对于数据传输，双方必须对数据定时采用使用相同的波特率。尽管这种方法对于大多数应用已经足够，但是对于接收方过载的情况这种使用受到限制。这时需要串口的握手功能。在这一部分，我们讨论三种最常用的RS-232握手形式：软件握手、硬件握手和 Xmodem。

    a，软件握手：我们讨论的第一种握手是软件握手。通常用在实际数据是控制字符的情况，类似于GPIB使用命令字符串的方式。必须的线仍然是三根：Tx，Rx和地线，因为控制字符在传输线上和普通字符没有区别，函数SetXModem允许用户使能或者禁止用户使用两个控制字符XON和OXFF。这些字符在通信中由接收方发送，使发送方暂停。

    例如：假设发送方以高波特率发送数据。在传输中，接收方发现由于CPU忙于其他工作，输入buffer已经满了。为了暂时停止传输，接收方发送XOFF，典型的值是十进制19，即十六进制13，直到输入buffer空了。一旦接收方准备好接收，它发送XON，典型的值是十进制17，即十六进制11，继续通信。输入buffer半满时，LabWindows发送XOFF。此外，如果XOFF传输被打断，LabWindows会在buffer达到75％和90％时发送XOFF。显然，发送方必须遵循此守则以保证传输继续。

    b，硬件握手：第二种是使用硬件线握手。和Tx和Rx线一样，RTS/CTS和DTR/DSR一起工作，一个作为输出，另一个作为输入。第一组线是RTS（Request to Send）和CTS（Clear toSend）。当接收方准备好接收数据，它置高RTS线表示它准备好了，如果发送方也就绪，它置高CTS，表示它即将发送数据。另一组线是DTR（DataTerminal Ready）和DSR（Data SetReady）。这些现主要用于Modem通信。使得串口和Modem通信他们的状态。例如：当Modem已经准备好接收来自PC的数据，它置高DTR线，表示和电话线的连接已经建立。读取DSR线置高，PC机开始发送数据。一个简单的规则是DTR/DSR用于表示系统通信就绪，而RTS/CTS用于单个数据包的传输。

    在LabWindows，函数SetCTSMode使能或者禁止使用硬件握手。如果CTS模式使能，LabWindows使用如下规则：

    当PC发送数据：

    RS-232库必须检测CTS线高后才能发送数据。

    当PC接收数据：

    如果端口打开，且输入队列有空接收数据，库函数置高RTS和DTR。

    如果输入队列90％满，库函数置低RTS，但使DTR维持高电平。

    如果端口队列近乎空了，库函数置高RTS，但使DRT维持高电平。

    如果端口关闭，库函数置低RTS和DTR。

    c，XModem握手：最后讨论的握手叫做XModem文件传输协议。这个协议在Modem通信中非常通用。尽管它通常使用在Modem通信中，XModem协议能够直接在其他遵循这个协议的设备通信中使用。在LabWindows中，实际的XModem应用对用户隐藏了。只要PC和其他设备使用XModem协议，在文件传输中就使用LabWindows的XModem函数。函数是XModemConfig，XModemSend和XModemReceive。

    XModem使用介于如下参数的协议：start_of_data、end_of_data、neg_ack、wait_delay、start_delay、max_tries、packet_size。这些参数需要通信双方认定，标准的XModem有一个标准的定义：然而，可以通过XModemConfig函数修改，以满足具体需要。这些参数的使用方法由接收方发送的字符neg_ack确定。这通知发送方其准备接收数据。它开始尝试发送，有一个超时参数start_delay；当超时的尝试超过max_ties次数，或者收到接收方发送的start_of_data，发送方停止尝试。如果从发送方收到start_of_data，接收方将读取后继信息数据包。包中含有包的数目、包数目的补码作为错误校验、packet_size字节大小的实际数据包，和进一步错误检查的求和校验值。在读取数据后，接收方会调用wait_delay，然后想发送方发送响应。如果发送方没有收到响应，它会重新发送数据包，直到收到响应或者超过重发次数的最大值max_tries。如果一直没有收到响应，发送方通知用户传输数据失败。

    由于数据必须以pack_size个字节按包发送，当最后一个数据包发送时，如果数据不够放满一个数据包，后面会填充ASCII码NULL（0）字节。这导致接收的数据比原数据多。在XModem情况下一定不要使用XON/XOFF，因为XModem发送方发出包的数目很可能增加到XON/OFF控制字符的值，从而导致通信故障。

串口

百科名片

串口叫做串行接口，也称串行通信接口，按电气标准及协议来分包括RS-232-C、RS-422、RS485、USB等。 RS-232-C、RS-422与RS-485标准只对接口的电气特性做出规定，不涉及接插件、电缆或协议。USB是近几年发展起来的新型接口标准，主要应用于高速数据传输领域。

简介

    RS-232-C：也称标准串口，是目前最常用的一种串行通讯接口。它是在1970年由美国电子工业协会（EIA）联合贝尔系统、 调制解调器厂家及计算机终端生产厂家共同制定的用于串行通讯的标准。它的全名

是“数据终端设备（DTE）和数据通讯设备（DCE）之间串行二进制数据交换接口技术标准”。传统的RS-232-C接口标准有22根线，采用标准25芯D型插头座。后来的PC上使用简化了的9芯D型插座。现在应用中25芯插头座已很少采用。现在的台式电脑一般有两个串行口：COM1和COM2，从设备管理器的端口列表中就可以看到。硬件表现为计算机后面的9针D形接口，由于其形状和针脚数量的原因，其接头又被称为DB9接头。现在有很多手机数据线或者物流接收器都采用COM口与计算机相连，很多投影机，液晶电视等设备都具有了此接口，厂家也常常会提供控制协议，便于在控制方面实现编程受控，现在越来越多的智能会议室和家居建设都采用了中央控制设备对多种受控设备的串口控制方式。

    RS-422：为改进RS-232通信距离短、速率低的缺点，RS-422定义了一种平衡通信接口，将传输速率提高到10Mb/s，传输距离延长到4000英尺（速率低于100kb/s时），并允许在一条平衡总线上连接最多10个接收器。RS-422是一种单机发送、多机接收的单向、平衡传输规范，被命名为TIA/EIA-422-A标准。

    RS-485：为扩展应用范围，EIA又于1983年在RS-422基础上制定了RS-485标准，增加了多点、双向通信能力，即允许多个发送器连接到同一条总线上，同时增加了发送器的驱动能力和冲突保护特性，扩展了总线共模范围，后命名为TIA/EIA-485-A标准。

串口的优点

    串口形容一下就是一条车道，而并口就是有8个车道同一时刻能传送8位（一个字节）数据。

    但是并不是并口快，由于8位通道之间的互相干扰。传输时速度就受到了限制。而且当传输出错时，要同时重新传8个位的数据。串口没有干扰，传输出错后重发一位就可以了。所以要比并口快。串口硬盘就是这样被人们重视的。

扩展阅读

    交换机串口

        交换机的串口的英文就是trunk;是用来做下一跳路由转换用的.每个VLAN只有通过与TRUNK的路由指向后才能上外网

    RS232C串口通信接线方法（三线制）

    首先，串口传输数据只要有接收数据针脚和发送针脚就能实现：同一个串口的接收脚和发送脚直接用线相连，两个串口相连或一个串口和多个串口相连

    • 同一个串口的接收脚和发送脚直接用线相连对9针串口和25针串口，均是2与3直接相连；

    • 两个不同串口（不论是同一台计算机的两个串口或分别是不同计算机的串口）

    DB9-DB9

    2-3,3-2,5-5

    DB25-DB25

    2-3,3-2,7-7

    DB9-DB25

    2-3,3-2,5-7

    上面是对微机标准串行口而言的，还有许多非标准设备，如接收GPS数据或电子罗盘数据，只要记住一个原则：接收数据针脚（或线）与发送数据针脚（或线）相连，彼些交叉，信号地对应相接。 [1][2]　引脚说明：　Pin Name Dir Description　1DCD 载波检测　2 RXD Receive Data 接收数据　3 TXD Transmit Data 发送数据　4 DTR Data Terminal Ready 数据终端准备　5 GND System Ground 接地　6 DSR Data Set Ready 数据准备完成　7 RTS Request to Send 请求发送　8 CTS Clear to Send 清除发送 9 RI Ring Indicator 振铃提示电脑主板上的串口：进行串行传输的接口，它一次只能传输1Bit。串行端口可以用于连接外置调制解调器、绘图仪或串行打印机。它也可以控制台连接的方式连接网络设备，例如路由器和交换机，主要用来配置它们。

Universal Serial Bus

    （通用串行总线)简称USB，是目前电脑上应用较广泛的接口规范，由Intel、Microsoft、Compaq、IBM、NEC、Northern Telcom等几家大厂商发起的新型外设接口标准。USB接口是电脑主板上的一种四针接口，其中中间两个针传输数据，两边两个针给外设供电。USB接口速度快、连接简单、不需要外接电源，传输速度12Mbps，最新USB2.0可达480Mbps；电缆最大长度 5米，USB电缆有4条线，2条信号线，2条电源线，可提供5伏特电源，USB电缆还分屏蔽和非屏蔽两种，屏蔽电缆传输速度可达12Mbps，价格较贵，非屏蔽电缆速度为1.5Mbps，但价格便宜；USB通过串联方式最多可串接127个设备；支持热插拔。

RJ-45接口

    RJ-45接口是以太网最为常用的接口，RJ45是一个常用名称，指的是由IEC (60)603-7标准化，使用由国际性的接插件标准定义的8个位置(8针)的模块化插孔或者插头。


OEM一词在电脑硬件产品中简直实在是太普遍了，像OEM光驱、OEM显示器、OEM鼠标什么的

OEM一词在电脑硬件产品中简直实在是太普遍了，像OEM光驱、OEM显示器、OEM鼠标什么的。有些人甚至以为它是一个有质量保证的品牌。那么，你对OEM又有多少认识呢？说一件产品是OEM产品是否就代表信得过呢？

其实，OEM与现代工业社会有着密切的关系。一些著名的品牌商品制造商，常常因为自己的厂房不能达到大批量生产的要求，又或者需要某些特定的零件，因此向其他厂商求助。

这些伸出援手的厂商就被称为OEM(Original Equipment Manufacturer，原始设备生产商)。如将之引申到IT领域的话，则表示那些进行代工的生产商。例如CPU风扇，Intel或AMD公司本身并不生产，它们通常会找像日本三洋公司这样的专业电机制造企业作风扇OEM生产。

ODM又是怎么一回事呢？原来，某制造商设计出一种产品后，在某些情况下可能会被另外一些品牌的制造商看中，要求配上后者的品牌名称来进行生产，又或者稍微修改一些设计(如按键位置)来生产。这样做的最大好处是其他厂商减少了自己研制的时间。有些人也习惯性称这些产品是OEM，实际上应该称之为ODM(Original Design Manufacturer，原始设计制造商)。例如一些日本品牌的笔记本电脑实际上就是由台湾厂商代工生产的。事后，台湾笔记本电脑制造商只要修改某些设计细节或配件便可以以自己的品牌名称进行批量生产。原因在于它们为这些日本品牌作的是ODM而非OEM。当然，我们可以说它们都是从同一条生产线生产出来。

OEM和ODM两者最大的区别不单单是名称而已。OEM产品是为品牌厂商度身订造的，生产后也只能使用该品牌名称，绝对不能冠上生产者自己的名称再进行生产。而ODM则要看品牌企业有没有买断该产品的版权。如果没有的话，制造商有权自己组织生产，只要没有企业公司的设计识别即可。

在工业社会中，OEM和ODM可谓司空见惯。因为出于制造成本、运输方便性、节省开发时间等方面的考虑，知名品牌企业一般都愿意找其他厂商OEM或ODM。在找别的企业进行OEM或ODM时，知名品牌企业也要承担不少责任。毕竟产品冠的是自己的牌子，如果产品质量不佳的话，少则有顾客找上门投诉，重则可能要上法庭。所以，品牌企业在委托加工期间肯定会进行严格的质量控制。但代工结束后，质量不敢保证。故此，当有的商家告诉你某件产品的生产商是某大品牌的OEM或ODM产品时，绝不要相信其质量就等同于该品牌。你唯一能够相信的，是这家制造商有一定的生产能力。

另外，不知大家是否遇到过这样的情形：到电脑城购买产品(如LCD显示器)时，商家向你介绍某个台湾品牌的LCD显示器的液晶面板是某某日本大厂OEM的。其实这种说法是一个颇为错误的概念。首先，为保障品牌的质量和信誉，ODM或OEM公司的名称是保密的。如果说穿了A牌子笔记本电脑是B牌子生产的话，那人家还会买价格更贵的A牌子产品吗？因此上述LCD显示器的例子，实际上只是该台湾品牌购买了日本品牌的液晶面板配件来进行生产。质量好或不好，并不能完全相信。那我们该如何识别是否代工厂商采用了知名厂商的配件呢？以LCD显示器为例，目前其核心技术仍然控制在日本和韩国几家大厂商手中。消费者可以从技术指标来进行鉴别。如对比度为400:1的液晶显示器只有日本的日立(Hitachi)和富士通(Fujitsu)等公司生产，而对比度为350:1的产品则主要由韩国三星公司和日本NEC公司生产。

其实对于消费者来说，最怕听到的就是一些似是而非的简称，什么OEM、ODM之类，有时真的听得人一头雾水。不过，只要我们多看多学一些新知识，遇到有销售商家说OEM或ODM的问题，不要怕烦，先回家查清楚，那无论是OEM或ODM，问题都不大了。

OEM原设备制造商
参考资料：http://tech.sina.com.cn/c/2001-09-26/6123.html


内存

百科名片

内存是计算机中重要的部件之一，它是与CPU进行沟通的桥梁。计算机中所有程序的运行都是在内存中进行的，因此内存的性能对计算机的影响非常大。内存（Memory）也被称为内存储器，其作用是用于暂时存放CPU中的运算数据，以及与硬盘等外部存储器交换的数据。只要计算机在运行中，CPU就会把需要运算的数据调到内存中进行运算，当运算完成后CPU再将结果传送出来，内存的运行也决定了计算机的稳定运行。内存是由内存芯片、电路板、金手指等部分组成的。

英文名称：Memory

    拼音：nèi cún

【内存简介】

    在计算机的组成结构中，有一个很重要的部分，就是存储器。存储器是用来存储程序和数据的部件，对于计算机来说，有了存储器，才有记忆功能，才能保证正常工作。存储器的种类很多，按其用途可分为主存储器和辅助存储器，主存储器又称内存储器（简称内存，港台称之为记忆体）。

    内存是电脑中的主要部件，它是相对于外存而言的。我们平常使用的程序，如Windows操作系统、打字软件、游戏软件等，一般都是安装在硬盘等外存上的，但仅此是不能使用其功能的，必须把它们调入内存中运行，才能真正使用其功能，我们平时输入一段文字，或玩一个游戏，其实都是在内存中进行的。就好比在一个书房里，存放书籍的书架和书柜相当于电脑的外存，而我们工作的办公桌就是内存。通常我们把要永久保存的、大量的数据存储在外存上，而把一些临时的或少量的数据和程序放在内存上，当然内存的好坏会直接影响电脑的运行速度。

【内存概述】

    内存就是存储程序以及数据的地方，比如当我们在使用WPS处理文稿时，当你在键盘上敲入字符时，它就被存入内存中，当你选择存盘时，内存中的数据才会被存入硬（磁）盘。在进一步理解它之前，还应认识一下它的物理概念。

    内存一般采用半导体存储单元，包括随机存储器（RAM），只读存储器（ROM），以及高速缓存（CACHE）。只不过因为RAM是其中最重要的存储器。S（synchronous）DRAM 同步动态随机存取存储器：SDRAM为168脚，这是目前PENTIUM及以上机型使用的内存。SDRAM将CPU与RAM通过一个相同的时钟锁在一起，使CPU和RAM能够共享一个时钟周期，以相同的速度同步工作，每一个时钟脉冲的上升沿便开始传递数据，速度比EDO内存提高50%。DDR（DOUBLE DATA RATE）RAM ：SDRAM的更新换代产品，他允许在时钟脉冲的上升沿和下降沿传输数据，这样不需要提高时钟的频率就能加倍提高SDRAM的速度。

    ●只读存储器（ROM）

    ROM表示只读存储器（Read Only Memory），在制造ROM的时候，信息（数据或程序）就被存入并永久保存。这些信息只能读出，一般不能写入，即使机器停电，这些数据也不会丢失。ROM一般用于存放计算机的基本程序和数据，如BIOS ROM。其物理外形一般是双列直插式（DIP）的集成块。

    ●随机存储器（RAM）

    随机存储器（Random Access Memory）表示既可以从中读取数据，也可以写入数据。当机器电源关闭时，存于其中的数据就会丢失。我们通常购买或升级的内存条就是用作电脑的内存，内存条（SIMM）就是将RAM集成块集中在一起的一小块电路板，它插在计算机中的内存插槽上，以减少RAM集成块占用的空间。目前市场上常见的内存条有1G／条,2G/条,4G/条等。

    ●高速缓冲存储器（Cache）

    Cache也是我们经常遇到的概念，也就是平常看到的一级缓存(L1 Cache)、二级缓存(L2 Cache)、三级缓存(L3 Cache)这些数据，它位于CPU与内存之间，是一个读写速度比内存更快的存储器。当CPU向内存中写入或读出数据时，这个数据也被存储进高速缓冲存储器中。当CPU再次需要这些数据时，CPU就从高速缓冲存储器读取数据，而不是访问较慢的内存，当然，如需要的数据在Cache中没有，CPU会再去读取内存中的数据。

    ●物理存储器和地址空间

    物理存储器和存储地址空间是两个不同的概念。但是由于这两者有十分密切的关系，而且两者都用B、KB、MB、GB来度量其容量大小，因此容易产生认识上的混淆。初学者弄清这两个不同的概念，有助于进一步认识内存储器和用好内存储器。

    物理存储器是指实际存在的具体存储器芯片。如主板上装插的内存条和装载有系统的BIOS的ROM芯片，显示卡上的显示RAM芯片和装载显示BIOS的ROM芯片，以及各种适配卡上的RAM芯片和ROM芯片都是物理存储器。

    存储地址空间是指对存储器编码（编码地址）的范围。所谓编码就是对每一个物理存储单元（一个字节）分配一个号码，通常叫作“编址”。分配一个号码给一个存储单元的目的是为了便于找到它，完成数据的读写，这就是所谓的“寻址”（所以，有人也把地址空间称为寻址空间）。

    地址空间的大小和物理存储器的大小并不一定相等。举个例子来说明这个问题：某层楼共有17个房间，其编号为801～817。这17个房间是物理的，而其地址空间采用了三位编码，其范围是800～899共100个地址，可见地址空间是大于实际房间数量的。

    对于386以上档次的微机，其地址总线为32位，因此地址空间可达2的32次方,即4GB。（虽然如此，但是我们一般使用的一些操作系统例如windows xp、却最多只能识别或者使用3.25G的内存，64位的操作系统能识别并使用4G和4G以上的的内存，

    好了，现在可以解释为什么会产生诸如：常规内存、保留内存、上位内存、高端内存、扩充内存和扩展内存等不同内存类型。

【内存概念】

    各种内存概念

    这里需要明确的是，我们讨论的不同内存的概念是建立在寻址空间上的。

IBM推出的第一台PC机采用的CPU是8088芯片，它只有20根地址线，也就是说，它的地址空间是1MB。

    PC机的设计师将1MB中的低端640KB用作RAM，供DOS及应用程序使用，高端的384KB则保留给ROM、视频适配卡等系统使用。从此，这个界限便被确定了下来并且沿用至今。低端的640KB就被称为常规内存即PC机的基本RAM区。保留内存中的低128KB是显示缓冲区，高64KB是系统BIOS（基本输入／输出系统）空间，其余192KB空间留用。从对应的物理存储器来看，基本内存区只使用了512KB芯片，占用0000至7FFFF这512KB地址。显示内存区虽有128KB空间，但对单色显示器（MDA卡）只需4KB就足够了，因此只安装4KB的物理存储器芯片，占用了B0000至B0FFF这4KB的空间，如果使用彩色显示器（CGA卡）需要安装16KB的物理存储器，占用B8000至BBFFF这16KB的空间，可见实际使用的地址范围都小于允许使用的地址空间。

    在当时（1980年末至1981年初）这么“大”容量的内存对PC机使用者来说似乎已经足够了，但是随着程序的不断增大，图象和声音的不断丰富，以及能访问更大内存空间的新型CPU相继出现，最初的PC机和MS－DOS设计的局限性变得越来越明显。

    ●1.什么是扩充内存？

    到1984年，即286被普遍接受不久，人们越来越认识到640KB的限制已成为大型程序的障碍，这时，Intel和Lotus，这两家硬、软件的杰出代表，联手制定了一个由硬件和软件相结合的方案，此方法使所有PC机存取640KB以上RAM成为可能。而Microsoft刚推出Windows不久，对内存空间的要求也很高，因此它也及时加入了该行列。

    在1985年初，Lotus、Intel和Microsoft三家共同定义了LIM－EMS，即扩充内存规范，通常称EMS为扩充内存。当时，EMS需要一个安装在I／O槽口的内存扩充卡和一个称为EMS的扩充内存管理程序方可使用。但是I／O插槽的地址线只有24位（ISA总线），这对于386以上档次的32位机是不能适应的。所以，现在已很少使用内存扩充卡。现在微机中的扩充内存通常是用软件如DOS中的EMM386把扩展内存模拟或扩充内存来使用。所以，扩充内存和扩展内存的区别并不在于其物理存储器的位置，而在于使用什么方法来读写它。下面将作进一步介绍。

    前面已经说过扩充存储器也可以由扩展存储器模拟转换而成。EMS的原理和XMS不同，它采用了页帧方式。页帧是在1MB空间中指定一块64KB空间（通常在保留内存区内，但其物理存储器来自扩展存储器），分为4页，每页16KB。EMS存储器也按16KB分页，每次可交换4页内容，以此方式可访问全部EMS存储器。符合EMS的驱动程序很多，常用的有EMM386.EXE、QEMM、TurboEMS、386MAX等。DOS和Windows中都提供了EMM386.EXE。

    ●2.什么是扩展内存？

    我们知道，286有24位地址线，它可寻址16MB的地址空间，而386有32位地址线，它可寻址高达4GB的地址空间，为了区别起见，我们把1MB以上的地址空间称为扩展内存XMS（eXtend memory）。

    在386以上档次的微机中，有两种存储器工作方式，一种称为实地址方式或实方式，另一种称为保护方式。在实方式下，物理地址仍使用20位，所以最大寻址空间为1MB，以便与8086兼容。保护方式采用32位物理地址，寻址范围可达4GB。DOS系统在实方式下工作，它管理的内存空间仍为1MB，因此它不能直接使用扩展存储器。为此，Lotus、Intel、AST及Microsoft公司建立了MS－DOS下扩展内存的使用标准，即扩展内存规范XMS。我们常在Config.sys文件中看到的Himem.sys就是管理扩展内存的驱动程序。

    扩展内存管理规范的出现迟于扩充内存管理规范。

    ●3.什么是高端内存区？

    在实方式下，内存单元的地址可记为：

    段地址：段内偏移

    通常用十六进制写为XXXX：XXXX。实际的物理地址由段地址左移4位再和段内偏移相加而成。若地址各位均为1时，即为FFFF：FFFF。其实际物理地址为：FFF0＋FFFF=10FFEF，约为1088KB（少16字节），这已超过1MB范围进入扩展内存了。这个进入扩展内存的区域约为64KB，是1MB以上空间的第一个64KB。我们把它称为高端内存区HMA（High Memory Area）。HMA的物理存储器是由扩展存储器取得的。因此要使用HMA，必须要有物理的扩展存储器存在。此外HMA的建立和使用还需要XMS驱动程序HIMEM.SYS的支持，因此只有装入了HIMEM.SYS之后才能使用HMA。

    ●4.什么是上位内存？

    为了解释上位内存的概念，我们还得回过头看看保留内存区。保留内存区是指640KB～1024KB（共384KB）区域。这部分区域在PC诞生之初就明确是保留给系统使用的，用户程序无法插足。但这部分空间并没有充分使用，因此大家都想对剩余的部分打主意，分一块地址空间（注意：是地址空间，而不是物理存储器）来使用。于是就得到了又一块内存区域UMB。

    UMB（Upper Memory Blocks）称为上位内存或上位内存块。它是由挤占保留内存中剩余未用的空间而产生的，它的物理存储器仍然取自物理的扩展存储器，它的管理驱动程序是EMS驱动程序。

    ●5.什么是SHADOW（影子）内存？

    对于细心的读者，可能还会发现一个问题：即是对于装有1MB或1MB以上物理存储器的机器，其640KB～1024KB这部分物理存储器如何使用的问题。由于这部分地址空间已分配为系统使用，所以不能再重复使用。为了利用这部分物理存储器，在某些386系统中，提供了一个重定位功能，即把这部分物理存储器的地址重定位为1024KB～1408KB。这样，这部分物理存储器就变成了扩展存储器，当然可以使用了。但这种重定位功能在当今高档机器中不再使用，而把这部分物理存储器保留作为Shadow存储器。Shadow存储器可以占据的地址空间与对应的ROM是相同的。Shadow由RAM组成，其速度大大高于ROM。当把ROM中的内容（各种BIOS程序）装入相同地址的Shadow RAM中，就可以从RAM中访问BIOS，而不必再访问ROM。这样将大大提高系统性能。因此在设置CMOS参数时，应将相应的Shadow区设为允许使用（Enabled）。

    ●6、什么是奇 / 偶校验？

    奇/偶校验（ECC）是数据传送时采用的一种校正数据错误的一种方式，分为奇校验和偶校验两种。

    如果是采用奇校验，在传送每一个字节的时候另外附加一位作为校验位，当实际数据中“1”的个数为偶数的时候，这个校验位就是“1”，否则这个校验位就是“0”，这样就可以保证传送数据满足奇校验的要求。在接收方收到数据时，将按照奇校验的要求检测数据中“1”的个数，如果是奇数，表示传送正确，否则表示传送错误。

    同理偶校验的过程和奇校验的过程一样，只是检测数据中“1”的个数为偶数。

    ●1.什么是CL延迟？

    CL反应时间是衡定内存的另一个标志。CL是CAS Latency的缩写，指的是内存存取数据所需的延迟时间，简单的说，就是内存接到CPU的指令后的反应速度。一般的参数值是2和3两种。数字越小，代表反应所需的时间越短。在早期的PC133内存标准中，这个数值规定为3，而在Intel重新制订的新规范中，强制要求CL的反应时间必须为2，这样在一定程度上，对于内存厂商的芯片及PCB的组装工艺要求相对较高，同时也保证了更优秀的品质。因此在选购品牌内存时，这是一个不可不察的因素。

    还有另的诠释：内存延迟基本上可以解释成是系统进入数据进行存取操作就绪状态前等待内存响应的时间。

    打个形象的比喻，就像你在餐馆里用餐的过程一样。你首先要点菜，然后就等待服务员给你上菜。同样的道理，内存延迟时间设置的越短，电脑从内存中读取数据的速度也就越快，进而电脑其他的性能也就越高。这条规则双双适用于基于英特尔以及AMD处理器的系统中。由于没有比 2-2-2-5更低的延迟，因此国际内存标准组织认为以现在的动态内存技术还无法实现0或者1的延迟。

    通常情况下，我们用4个连着的阿拉伯数字来表示一个内存延迟，例如 2-2-2-5。其中，第一个数字最为重要，它表示的是CAS Latency,也就是内存存取数据所需的延迟时间。第二个数字表示的是RAS-CAS延迟，接下来的两个数字分别表示的是RAS预充电时间和Act-to-Precharge延迟。而第四个数字一般而言是它们中间最大的一个。

    总结

    经过上面分析，内存储器的划分可归纳如下：

    ●基本内存占据0～640KB地址空间。

    ●保留内存占据640KB～1024KB地址空间。分配给显示缓冲存储器、各适配卡上的ROM和系统ROM BIOS，剩余空间可作上位内存UMB。UMB的物理存储器取自物理扩展存储器。此范围的物理RAM可作为Shadow RAM使用。

    ●上位内存（UMB）利用保留内存中未分配使用的地址空间建立，其物理存储器由物理扩展存储器取得。UMB由EMS管理，其大小可由EMS驱动程序设定。

    ●高端内存（HMA）扩展内存中的第一个64KB区域（1024KB～1088KB）。由HIMEM.SYS建立和管理。

    ●XMS内存符合XMS规范管理的扩展内存区。其驱动程序为HIMEM.SYS。

    ●EMS内存符合EMS规范管理的扩充内存区。其驱动程序为EMM386.EXE等。

    内存：随机存储器（RAM），主要存储正在运行的程序和要处理的数据。

【内存频率】

    内存主频和CPU主频一样，习惯上被用来表示内存的速度，它代表着该内存所能达到的最高工作频率。内存主频是以MHz（兆赫）为单位来计量的。内存主频越高在一定程度上代表着内存所能达到的速度越快。内存主频决定着该内存最高能在什么样的频率正常工作。目前较为主流的内存频率是800MHz的DDR2内存，以及一些内存频率更高的DDR3内存。

    大家知道，计算机系统的时钟速度是以频率来衡量的。晶体振荡器控制着时钟速度，在石英晶片上加上电压，其就以正弦波的形式震动起来，这一震动可以通过晶片的形变和大小记录下来。晶体的震动以正弦调和变化的电流的形式表现出来，这一变化的电流就是时钟信号。而内存本身并不具备晶体振荡器，因此内存工作时的时钟信号是由主板芯片组的北桥或直接由主板的时钟发生器提供的，也就是说内存无法决定自身的工作频率，其实际工作频率是由主板来决定的。

    DDR内存和DDR2内存的频率可以用工作频率和等效频率两种方式表示，工作频率是内存颗粒实际的工作频率，但是由于DDR内存可以在脉冲的上升和下降沿都传输数据，因此传输数据的等效频率是工作频率的两倍；而DDR2内存每个时钟能够以四倍于工作频率的速度读/写数据，因此传输数据的等效频率是工作频率的四倍。例如DDR 200/266/333/400的工作频率分别是100/133/166/200MHz，而等效频率分别是200/266/333/400MHz；DDR2 400/533/667/800的工作频率分别是100/133/166/200MHz，而等效频率分别是400/533/667/800MHz。

【内存发展】

    在计算机诞生初期并不存在内存条的概念，最早的内存是以磁芯的形式排列在线路上，每个磁芯与晶体管组成的一个双稳态电路作为一比特（BIT)的存储器,每一比特都要有玉米粒大小，可以想象一间的机房只能装下不超过百k字节左右的容量。后来才出线现了焊接在主板上集成内存芯片，以内存芯片的形式为计算机的运算提供直接支持。那时的内存芯片容量都特别小，最常见的莫过于256K×1bit、1M×4bit，虽然如此，但这相对于那时的运算任务来说却已经绰绰有余了。

    内存条的诞生

    内存芯片的状态一直沿用到286初期，鉴于它存在着无法拆卸更换的弊病，这对于计算机的发展造成了现实的阻碍。有鉴于此，内存条便应运而生了。将内存芯片焊接到事先设计好的印刷线路板上，而电脑主板上也改用内存插槽。这样就把内存难以安装和更换的问题彻底解决了。

    在80286主板发布之前，内存并没有被世人所重视，这个时候的内存是直接固化在主板上，而且容量只有64 ～256KB，对于当时PC所运行的工作程序来说，这种内存的性能以及容量足以满足当时软件程序的处理需要。不过随着软件程序和新一代80286硬件平台的出现，程序和硬件对内存性能提出了更高要求，为了提高速度并扩大容量，内存必须以独立的封装形式出现，因而诞生了“内存条”概念。

    在80286主板刚推出的时候，内存条采用了SIMM（Single In-lineMemory Modules，单边接触内存模组）接口，容量为30pin、256kb，必须是由8 片数据位和1 片校验位组成1 个bank，正因如此，我们见到的30pin SIMM一般是四条一起使用。自1982年PC进入民用市场一直到现在，搭配80286处理器的30pin SIMM 内存是内存领域的开山鼻祖。

    随后，在1988 ～1990 年当中，PC 技术迎来另一个发展高峰，也就是386和486时代，此时CPU 已经向16bit 发展，所以30pin SIMM 内存再也无法满足需求，其较低的内存带宽已经成为急待解决的瓶颈，所以此时72pin SIMM 内存出现了，72pin SIMM支持32bit快速页模式内存，内存带宽得以大幅度提升。72pin SIMM内存单条容量一般为512KB ～2MB，而且仅要求两条同时使用，由于其与30pin SIMM 内存无法兼容，因此这个时候PC业界毅然将30pin SIMM 内存淘汰出局了。

    EDO DRAM（Extended Date Out RAM 外扩充数据模式存储器）内存，这是1991 年到1995 年之间盛行的内存条，EDO DRAM同FPM DRAM（Fast Page Mode RAM 快速页面模式存储器）极其相似，它取消了扩展数据输出内存与传输内存两个存储周期之间的时间间隔，在把数据发送给CPU的同时去访问下一个页面，故而速度要比普通DRAM快15~30%。工作电压为一般为5V，带宽32bit，速度在40ns以上，其主要应用在当时的486及早期的Pentium电脑上。

    在1991 年到1995 年中，让我们看到一个尴尬的情况，那就是这几年内存技术发展比较缓慢，几乎停滞不前，所以我们看到此时EDO DRAM有72 pin和168 pin并存的情况，事实上EDO 内存也属于72pin SIMM 内存的范畴，不过它采用了全新的寻址方式。EDO 在成本和容量上有所突破，凭借着制作工艺的飞速发展，此时单条EDO 内存的容量已经达到4 ～16MB 。由于Pentium及更高级别的CPU数据总线宽度都是64bit甚至更高，所以EDO DRAM与FPM DRAM都必须成对使用。

    SDRAM时代

    自Intel Celeron系列以及AMD K6处理器以及相关的主板芯片组推出后，EDO DRAM内存性能再也无法满足需要了，内存技术必须彻底得到个革新才能满足新一代CPU架构的需求，此时内存开始进入比较经典的SDRAM时代。

    第一代SDRAM 内存为PC66 规范，但很快由于Intel 和AMD的频率之争将CPU外频提升到了100MHz，所以PC66内存很快就被PC100内存取代，接着133MHz 外频的PIII以及K7时代的来临，PC133规范也以相同的方式进一步提升SDRAM 的整体性能，带宽提高到1GB/sec以上。由于SDRAM 的带宽为64bit，正好对应CPU 的64bit 数据总线宽度，因此它只需要一条内存便可工作，便捷性进一步提高。在性能方面，由于其输入输出信号保持与系统外频同步，因此速度明显超越EDO 内存。

    不可否认的是，SDRAM 内存由早期的66MHz，发展后来的100MHz、133MHz，尽管没能彻底解决内存带宽的瓶颈问题，但此时CPU超频已经成为DIY用户永恒的话题，所以不少用户将品牌好的PC100品牌内存超频到133MHz使用以获得CPU超频成功，值得一提的是，为了方便一些超频用户需求，市场上出现了一些PC150、PC166规范的内存。

    尽管SDRAM PC133内存的带宽可提高带宽到1064MB/S，加上Intel已经开始着手最新的Pentium 4计划，所以SDRAM PC133内存不能满足日后的发展需求，此时，Intel为了达到独占市场的目的，与Rambus联合在PC市场推广Rambus DRAM内存（称为RDRAM内存）。与SDRAM不同的是，其采用了新一代高速简单内存架构，基于一种类RISC(Reduced Instruction Set Computing，精简指令集计算机)理论，这个理论可以减少数据的复杂性，使得整个系统性能得到提高。

    在AMD与Intel的竞争中，这个时候是属于频率竞备时代，所以这个时候CPU的主频在不断提升，Intel为了盖过AMD，推出高频PentiumⅢ以及Pentium 4 处理器，因此Rambus DRAM内存是被Intel看着是未来自己的竞争杀手锏，Rambus DRAM内存以高时钟频率来简化每个时钟周期的数据量，因此内存带宽相当出色，如PC 1066 1066 MHz 32 bits带宽可达到4.2G Byte/sec，Rambus DRAM曾一度被认为是Pentium 4 的绝配。

    尽管如此，Rambus RDRAM 内存生不逢时，后来依然要被更高速度的DDR“掠夺”其宝座地位，在当时，PC600、PC700的Rambus RDRAM 内存因出现Intel820 芯片组“失误事件”、PC800 Rambus RDRAM因成本过高而让Pentium 4平台高高在上，无法获得大众用户拥戴，种种问题让Rambus RDRAM胎死腹中，Rambus曾希望具有更高频率的PC1066 规范RDRAM来力挽狂澜，但最终也是拜倒在DDR 内存面前。

    DDR时代

    DDR SDRAM(Double Data Rate SDRAM)简称DDR，也就是“双倍速率SDRAM”的意思。DDR可以说是SDRAM的升级版本， DDR在时钟信号上升沿与下降沿各传输一次数据，这使得DDR的数据传输速度为传统SDRAM的两倍。由于仅多采用了下降缘信号，因此并不会造成能耗增加。至于定址与控制信号则与传统SDRAM相同，仅在时钟上升缘传输。

    DDR 内存是作为一种在性能与成本之间折中的解决方案，其目的是迅速建立起牢固的市场空间，继而一步步在频率上高歌猛进，最终弥补内存带宽上的不足。第一代DDR200 规范并没有得到普及，第二代PC266 DDR SRAM（133MHz时钟×2倍数据传输＝266MHz带宽）是由PC133 SDRAM内存所衍生出的，它将DDR 内存带向第一个高潮，目前还有不少赛扬和AMD K7处理器都在采用DDR266规格的内存，其后来的DDR333内存也属于一种过度，而DDR400内存成为目前的主流平台选配，双通道DDR400内存已经成为800FSB处理器搭配的基本标准，随后的DDR533 规范则成为超频用户的选择对象。

    DDR2时代

    随着CPU 性能不断提高，我们对内存性能的要求也逐步升级。不可否认，紧紧依高频率提升带宽的DDR迟早会力不从心，因此JEDEC 组织很早就开始酝酿DDR2 标准，加上LGA775接口的915/925以及最新的945等新平台开始对DDR2内存的支持，所以DDR2内存将开始演义内存领域的今天。

    DDR2 能够在100MHz 的发信频率基础上提供每插脚最少400MB/s 的带宽，而且其接口将运行于1.8V 电压上，从而进一步降低发热量，以便提高频率。此外，DDR2 将融入CAS、OCD、ODT 等新性能指标和中断指令，提升内存带宽的利用率。从JEDEC组织者阐述的DDR2标准来看，针对PC等市场的DDR2内存将拥有400、533、667MHz等不同的时钟频率。高端的DDR2内存将拥有800、1000MHz两种频率。DDR-II内存将采用200-、220-、240-针脚的FBGA封装形式。最初的DDR2内存将采用0.13微米的生产工艺，内存颗粒的电压为1.8V，容量密度为512MB。

    内存技术在2005年将会毫无悬念，SDRAM为代表的静态内存在五年内不会普及。QBM与RDRAM内存也难以挽回颓势，因此DDR与DDR2共存时代将是铁定的事实。

    PC-100的“接班人”除了PC一133以外，VCM(VirXual Channel Memory)也是很重

    要的一员。VCM即“虚拟通道存储器”，这也是目前大多数较新的芯片组支持的一种内存标准，VCM内存主要根据由NEC公司开发的一种“缓存式DRAM”技术制造而成，它集成了“通道缓存”，由高速寄存器进行配置和控制。在实现高速数据传输的同时，VCM还维持着对传统SDRAM的高度兼容性，所以通常也把VCM内存称为VCM SDRAM。VCM与SDRAM的差别在于不论是否经过CPU处理的数据，都可先交于VCM进行处理，而普通的SDRAM就只能处理经CPU处理以后的数据，所以VCM要比SDRAM处理数据的速度快20％以上。目前可以支持VCM SDRAM的芯片组很多，包括：Intel的815E、VIA的694X等。

    3．RDRAM

    Intel在推出：PC-100后，由于技术的发展，PC-100内存的800MB／s带宽已经不能满足需求，而PC-133的带宽提高并不大(1064MB／s)，同样不能满足日后的发展需求。Intel为了达到独占市场的目的，与Rambus公司联合在PC市场推广Rambus DRAM(DirectRambus DRAM)。

    Rambus DRAM是：Rambus公司最早提出的一种内存规格，采用了新一代高速简单内存架构，基于一种RISC(Reduced Instruction Set Computing，精简指令集计算机)理论，从而可以减少数据的复杂性，使得整个系统性能得到提高。Rambus使用400MHz的16bit总线，在一个时钟周期内，可以在上升沿和下降沿的同时传输数据，这样它的实际速度就为400MHz×2＝800MHz，理论带宽为(16bit×2×400MHz／8)1.6GB／s，相当于PC-100的两倍。另外，Rambus也可以储存9bit字节，额外的一比特是属于保留比特，可能以后会作为：ECC(ErroI·Checking and Correction，错误检查修正)校验位。Rambus的时钟可以高达400MHz，而且仅使用了30条铜线连接内存控制器和RIMM(Rambus In-line MemoryModules，Rambus内嵌式内存模块)，减少铜线的长度和数量就可以降低数据传输中的电磁干扰，从而快速地提高内存的工作频率。不过在高频率下，其发出的热量肯定会增加，因此第一款Rambus内存甚至需要自带散热风扇。

    DDR3时代

    DDR3相比起DDR2有更低的工作电压，从DDR2的1.8V降落到1.5V，性能更好更为省电；DDR2的4bit预读升级为8bit预读。DDR3目前最高能够达到2000Mhz的速度，尽管目前最为快速的DDR2内存速度已经提升到800Mhz/1066Mhz的速度，但是DDR3内存模组仍会从1066Mhz起跳。

    一、DDR3在DDR2基础上采用的新型设计：

    1．8bit预取设计，而DDR2为4bit预取，这样DRAM内核的频率只有接口频率的1/8，DDR3-800的核心工作频率只有100MHz。

    2．采用点对点的拓朴架构，以减轻地址/命令与控制总线的负担。

    3．采用100nm以下的生产工艺，将工作电压从1.8V降至1.5V，增加异步重置（Reset）与ZQ校准功能。

【内存区别】

    DDR2与DDR的区别

    与DDR相比，DDR2最主要的改进是在内存模块速度相同的情况下，可以提供相当于DDR内存两倍的带宽。这主要是通过在每个设备上高效率使用两个DRAM核心来实现的。作为对比，在每个设备上DDR内存只能够使用一个DRAM核心。技术上讲，DDR2内存上仍然只有一个DRAM核心，但是它可以并行存取，在每次存取中处理4个数据而不是两个数据。

    与双倍速运行的数据缓冲相结合，DDR2内存实现了在每个时钟周期处理多达4bit的数据，比传统DDR内存可以处理的2bit数据高了一倍。DDR2内存另一个改进之处在于，它采用FBGA封装方式替代了传统的TSOP方式。

    然而，尽管DDR2内存采用的DRAM核心速度和DDR的一样，但是我们仍然要使用新主板才能搭配DDR2内存，因为DDR2的物理规格和DDR是不兼容的。首先是接口不一样，DDR2的针脚数量为240针，而DDR内存为184针；其次，DDR2内存的VDIMM电压为1.8V，也和DDR内存的2.5V不同。

    DDR2的定义：

    DDR2（Double Data Rate 2） SDRAM是由JEDEC（电子设备工程联合委员会）进行开发的新生代内存技术标准，它与上一代DDR内存技术标准最大的不同就是，虽然同是采用了在时钟的上升/下降延同时进行数据传输的基本方式，但DDR2内存却拥有两倍于上一代DDR内存预读取能力（即：4bit数据读预取）。换句话说，DDR2内存每个时钟能够以4倍外部总线的速度读/写数据，并且能够以内部控制总线4倍的速度运行。

    此外，由于DDR2标准规定所有DDR2内存均采用FBGA封装形式，而不同于目前广泛应用的TSOP/TSOP-II封装形式，FBGA封装可以提供了更为良好的电气性能与散热性，为DDR2内存的稳定工作与未来频率的发展提供了坚实的基础。回想起DDR的发展历程，从第一代应用到个人电脑的DDR200经过DDR266、DDR333到今天的双通道DDR400技术，第一代DDR的发展也走到了技术的极限，已经很难通过常规办法提高内存的工作速度；随着Intel最新处理器技术的发展，前端总线对内存带宽的要求是越来越高，拥有更高更稳定运行频率的DDR2内存将是大势所趋。

    DDR2与DDR的区别：

    在了解DDR2内存诸多新技术前，先让我们看一组DDR和DDR2技术对比的数据。

    1、延迟问题：

    从上表可以看出，在同等核心频率下，DDR2的实际工作频率是DDR的两倍。这得益于DDR2内存拥有两倍于标准DDR内存的4BIT预读取能力。换句话说，虽然DDR2和DDR一样，都采用了在时钟的上升延和下降延同时进行数据传输的基本方式，但DDR2拥有两倍于DDR的预读取系统命令数据的能力。也就是说，在同样100MHz的工作频率下，DDR的实际频率为200MHz，而DDR2则可以达到400MHz。

    这样也就出现了另一个问题：在同等工作频率的DDR和DDR2内存中，后者的内存延时要慢于前者。举例来说，DDR 200和DDR2-400具有相同的延迟，而后者具有高一倍的带宽。实际上，DDR2-400和DDR 400具有相同的带宽，它们都是3.2GB/s，但是DDR400的核心工作频率是200MHz，而DDR2-400的核心工作频率是100MHz，也就是说DDR2-400的延迟要高于DDR400。

    2、封装和发热量：

    DDR2内存技术最大的突破点其实不在于用户们所认为的两倍于DDR的传输能力，而是在采用更低发热量、更低功耗的情况下，DDR2可以获得更快的频率提升，突破标准DDR的400MHZ限制。

    DDR内存通常采用TSOP芯片封装形式，这种封装形式可以很好的工作在200MHz上，当频率更高时，它过长的管脚就会产生很高的阻抗和寄生电容，这会影响它的稳定性和频率提升的难度。这也就是DDR的核心频率很难突破275MHZ的原因。而DDR2内存均采用FBGA封装形式。不同于目前广泛应用的TSOP封装形式，FBGA封装提供了更好的电气性能与散热性，为DDR2内存的稳定工作与未来频率的发展提供了良好的保障。

    DDR2内存采用1.8V电压，相对于DDR标准的2.5V，降低了不少，从而提供了明显的更小的功耗与更小的发热量，这一点的变化是意义重大的。

    DDR2采用的新技术：

    除了以上所说的区别外，DDR2还引入了三项新的技术，它们是OCD、ODT和Post CAS。

    OCD（Off-Chip Driver）：也就是所谓的离线驱动调整，DDR II通过OCD可以提高信号的完整性。DDR II通过调整上拉（pull-up）/下拉（pull-down）的电阻值使两者电压相等。使用OCD通过减少DQ-DQS的倾斜来提高信号的完整性；通过控制电压来提高信号品质。

    ODT：ODT是内建核心的终结电阻器。我们知道使用DDR SDRAM的主板上面为了防止数据线终端反射信号需要大量的终结电阻。它大大增加了主板的制造成本。实际上，不同的内存模组对终结电路的要求是不一样的，终结电阻的大小决定了数据线的信号比和反射率，终结电阻小则数据线信号反射低但是信噪比也较低；终结电阻高，则数据线的信噪比高，但是信号反射也会增加。因此主板上的终结电阻并不能非常好的匹配内存模组，还会在一定程度上影响信号品质。DDR2可以根据自己的特点内建合适的终结电阻，这样可以保证最佳的信号波形。使用DDR2不但可以降低主板成本，还得到了最佳的信号品质，这是DDR不能比拟的。

    Post CAS：它是为了提高DDR II内存的利用效率而设定的。在Post CAS操作中，CAS信号（读写/命令）能够被插到RAS信号后面的一个时钟周期，CAS命令可以在附加延迟（Additive Latency）后面保持有效。原来的tRCD（RAS到CAS和延迟）被AL（Additive Latency）所取代，AL可以在0，1，2，3，4中进行设置。由于CAS信号放在了RAS信号后面一个时钟周期，因此ACT和CAS信号永远也不会产生碰撞冲突。

    总的来说，DDR2采用了诸多的新技术，改善了DDR的诸多不足，虽然它目前有成本高、延迟慢能诸多不足，但相信随着技术的不断提高和完善，这些问题终将得到解决。

    DDR3与DDR2几个主要的不同之处：

    1.突发长度（Burst Length，BL）

    由于DDR3的预取为8bit，所以突发传输周期（Burst Length，BL）也固定为8，而对于DDR2和早期的DDR架构系统，BL=4也是常用的，DDR3为此增加了一个4bit Burst Chop（突发突变）模式，即由一个BL=4的读取操作加上一个BL=4的写入操作来合成一个BL=8的数据突发传输，届时可通过A12地址线来控制这一突发模式。而且需要指出的是，任何突发中断操作都将在DDR3内存中予以禁止，且不予支持，取而代之的是更灵活的突发传输控制（如4bit顺序突发）。

    2.寻址时序（Timing）

    就像DDR2从DDR转变而来后延迟周期数增加一样，DDR3的CL周期也将比DDR2有所提高。DDR2的CL范围一般在2～5之间，而DDR3则在5～11之间，且附加延迟（AL）的设计也有所变化。DDR2时AL的范围是0～4，而DDR3时AL有三种选项，分别是0、CL-1和CL-2。另外，DDR3还新增加了一个时序参数——写入延迟（CWD），这一参数将根据具体的工作频率而定。

    3.DDR3新增的重置（Reset）功能

    重置是DDR3新增的一项重要功能，并为此专门准备了一个引脚。DRAM业界很早以前就要求增加这一功能，如今终于在DDR3上实现了。这一引脚将使DDR3的初始化处理变得简单。当Reset命令有效时，DDR3内存将停止所有操作，并切换至最少量活动状态，以节约电力。

    在Reset期间，DDR3内存将关闭内在的大部分功能，所有数据接收与发送器都将关闭，所有内部的程序装置将复位，DLL（延迟锁相环路）与时钟电路将停止工作，而且不理睬数据总线上的任何动静。这样一来，将使DDR3达到最节省电力的目的。

    4.DDR3新增ZQ校准功能

    ZQ也是一个新增的脚，在这个引脚上接有一个240欧姆的低公差参考电阻。这个引脚通过一个命令集，通过片上校准引擎（On-Die Calibration Engine，ODCE）来自动校验数据输出驱动器导通电阻与ODT的终结电阻值。当系统发出这一指令后，将用相应的时钟周期（在加电与初始化之后用512个时钟周期，在退出自刷新操作后用256个时钟周期、在其他情况下用64个时钟周期）对导通电阻和ODT电阻进行重新校准。

    5.参考电压分成两个

    在DDR3系统中，对于内存系统工作非常重要的参考电压信号VREF将分为两个信号，即为命令与地址信号服务的VREFCA和为数据总线服务的VREFDQ，这将有效地提高系统数据总线的信噪等级。

    6.点对点连接（Point-to-Point，P2P）

    这是为了提高系统性能而进行的重要改动，也是DDR3与DDR2的一个关键区别。在DDR3系统中，一个内存控制器只与一个内存通道打交道，而且这个内存通道只能有一个插槽，因此，内存控制器与DDR3内存模组之间是点对点（P2P）的关系（单物理Bank的模组），或者是点对双点（Point-to-two-Point，P22P）的关系（双物理Bank的模组），从而大大地减轻了地址/命令/控制与数据总线的负载。而在内存模组方面，与DDR2的类别相类似，也有标准DIMM（台式PC）、SO-DIMM/Micro-DIMM（笔记本电脑）、FB-DIMM2（服务器）之分，其中第二代FB-DIMM将采用规格更高的AMB2（高级内存缓冲器）。

    面向64位构架的DDR3显然在频率和速度上拥有更多的优势，此外，由于DDR3所采用的根据温度自动自刷新、局部自刷新等其它一些功能，在功耗方面DDR3也要出色得多，因此，它可能首先受到移动设备的欢迎，就像最先迎接DDR2内存的不是台式机而是服务器一样。在CPU外频提升最迅速的PC台式机领域，DDR3未来也是一片光明。目前Intel预计在明年第二季所推出的新芯片-熊湖(Bear Lake)，其将支持DDR3规格，而AMD也预计同时在K9平台上支持DDR2及DDR3两种规格。

    内存异步工作模式包含多种意义，在广义上凡是内存工作频率与CPU的外频不一致时都可以称为内存异步工作模式。首先，最早的内存异步工作模式出现在早期的主板芯片组中，可以使内存工作在比CPU外频高33MHz或者低33MHz的模式下(注意只是简单相差33MHz)，从而可以提高系统内存性能或者使老内存继续发挥余热。其次，在正常的工作模式(CPU不超频)下，目前不少主板芯片组也支持内存异步工作模式，例如Intel 910GL芯片组，仅仅只支持533MHz FSB即133MHz的CPU外频，但却可以搭配工作频率为133MHz的DDR 266、工作频率为166MHz的DDR 333和工作频率为200MHz的DDR 400正常工作(注意此时其CPU外频133MHz与DDR 400的工作频率200MHz已经相差66MHz了)，只不过搭配不同的内存其性能有差异罢了。再次，在CPU超频的情况下，为了不使内存拖CPU超频能力的后腿，此时可以调低内存的工作频率以便于超频，例如AMD的Socket 939接口的Opteron 144非常容易超频，不少产品的外频都可以轻松超上300MHz，而此如果在内存同步的工作模式下，此时内存的等效频率将高达DDR 600，这显然是不可能的，为了顺利超上300MHz外频，我们可以在超频前在主板BIOS中把内存设置为DDR 333或DDR 266，在超上300MHz外频之后，前者也不过才DDR 500(某些极品内存可以达到)，而后者更是只有DDR 400(完全是正常的标准频率)，由此可见，正确设置内存异步模式有助于超频成功。

    目前的主板芯片组几乎都支持内存异步，英特尔公司从810系列到目前较新的875系列都支持，而威盛公司则从693芯片组以后全部都提供了此功能。

三代内存的区别

【内存品牌】

    为了让大家更好的了解市场上有哪些发烧内存条可供选用，下面我们以厂商为列做个概括介绍。

    现代（HY）

    个人认为，原厂现代和三星内存是目前兼容性和稳定性最好的内存条，其比许多广告吹得生猛的内存条要来得实在得多，此外，现代"Hynix（更专业的称呼是海力士半导体Hynix Semiconductor Inc.）"的D43等颗粒也是目前很多高频内存所普遍采用的内存芯片。目前，市场上超值的现代高频条有现代原厂DDR500内存，采用了TSOP封装的HY5DU56822CT-D5内存芯片，其性价比很不错。

    金士顿（Kingston）

    作为世界第一大内存生产厂商的Kingston，其金士顿内存产品在进入中国市场以来，就凭借优秀的产品质量和一流的售后服务，赢得了众多中国消费者的心。

    不过Kingston虽然作为世界第一大内存生产厂商，然而Kingston品牌的内存产品，其使用的内存颗粒确是五花八门，既有Kingston自己颗粒的产品，更多的则是现代（Hynix）、三星（Samsung）、南亚（Nanya）、华邦（Winbond）、英飞凌（Infinoen）、美光（Micron）等等众多厂商的内存颗粒。

    Kingston的高频内存有采用"Hynix"D43颗粒和Winbond的内存颗粒的金士顿DDR400、DDR433-DDR500内存等，其分属ValueRam系列（经济型）和HyperX系列。

    Kingston的ValueRam系列，价格与普通的DDR400一样，但其可以超频到DDR500使用。而Kingston的HyperX系列其超频性也不错，Kingston 500MHz的HyperX超频内存（HyperX PC4000）有容量256MB、512MB单片包装与容量512MB与1GB双片的包装上市，其电压为2.6伏特，采用铝制散热片加强散热，使用三星K4H560838E-TCCC芯片，在DDR400下的CAS值为2.5，DDR500下的CAS值为3，所以性能也一般。

    利屏

    利屏是进来新近崛起的一个内存新秀。利屏科技（深圳）有限公司总部设在美国西部风景如画的世界高科技重镇旧金山。公司致力于研发、生产和销售利屏LPT极限高端内存条产品。公司拥有一支技术过硬的产品研发团队和足迹遍及中、外的专业销售队伍。产品深受广大游戏玩家和超频爱好者的喜爱。同时被冠以“超频之神”的美誉。

    “利屏”眼镜蛇DDR400系列内存也是专门为追求性能的玩家所设计，它采用的也是D43的颗粒，但是时序更高，为了加强散热更是加上了金属散热片，其超频能力相当强劲，在加0.1V左右的电压下可以超频到DDR520。而利屏的DDR466内存，它采用的是编号为K4H560838E-TCCC的三星颗粒，运行在DDR466的时候内存时序为 3-4-4-8，但其256MB容量接近500元的报价就显得太高了。

    而利屏的高端极限内存DDR560，提供单片256MB和512MB包装，同时双片装的512MB和1024MB支持双通道架构，每条内存的表面均有铜质散热片进行散热及确保运行的稳定性，其CAS值均为3，只能说刚好能用而已。

    勤茂（TwinMOS）

    勤茂（TwinMOS）CAS为2的DDR433内存，采用CSP技术封装—这款，勤茂DDR433内存的CAS Latency控制在2，Burst Length控制在2、4、8，性能指数不错。此外，内存外面包裹着金黄色内存罩，能起到散热和屏蔽的作用，内存颗粒与散热片之间则填充了导热的垫片。价格在350元左右，其可超性也不含糊，性价比不错。

    胜创（Kingmax）

    成立于1989年的胜创科技有限公司是一家名列中国台湾省前200强的生产企业（Commonwealth Magazine，May 2000），同时也是内存模组的引领生产厂商。

    通过严格的质量控制和完善的研发实力，胜创科技获得了ISO-9001证书，同时和IT行业中最优秀的企业建立了合作伙伴关系。公司以不断创新的设计工艺和追求完美的信念生产出了高性能的尖端科技产品，不断向移动计算领域提供价廉物美的最出色的内存模组。

    在SDRAM时期，Kingmax就曾成功的建造了PC150帝国，开启了内存产品的高速时代，也奠定了Kingmax在内存领域领先的地位。而今DDR来了，从266到300，再到现在的500，Kingmax始终保持着领先的位置，继续引领着内存发展的方向。说到KingMax内存，就不能不说到它独特的 “TinyBGA”封装技术专利——作为全球领先的DRAM生产厂商，胜创科技在1997年宣布了第一款基于TinyBGA封装技术的内存模组，这项屡获殊荣的封装技术能以同样的体积大小封装3倍于普通技术所达到的内存容量。同时，胜创科技还研制了为高端服务器和工作站应用设计的1GB StackBGA模组、为DDR应用设计的FBGA模组以及为Rambus RIMM应用设计的速度高达1.6GB/秒的flip-chip BGA/DCA模组。

    Kingmax胜创推出的低价版的DDR433内存产品，该产品采用传统的TSOP封装内存芯片，工作频率433MHz。Kingmax推出的这个SuperRam PC3500系列的售价和PC3200处于同一档次，这为那些热衷超频又手头不宽裕的用户提供了一个不错的选择。此外，Kingmax也推出了CL-3的DDR500内存产品，其性能和其它厂家的同类产品大同小异。

    海盗船（Corsair）

    Corsair（海盗船）是一家较有特点的内存品牌，其内存条都包裹着一层黑色金属外壳，这层金属壳紧贴在内存颗粒上，一方面可以屏蔽其他的电磁干扰。其代表产品如Corsair TwinX PC3200（CMX512-3200XL）内存，其在DDR400下，可以稳定运行在CL2-2-2-5-T1下，将潜伏期和寻址时间缩短为原来的一半，这款内存并不比一些DDR500产品差，而且Corsair为这种内存提供终身保修。

    而Corsair DDR500内存采用Hynix芯片，这款XMS4000能稳定运行在DDR500，并且可以超频到DDR530，在DDR500下其CAS值为2.5，性能还算不错。

    宇瞻（Apacer）

    在内存市场，Apacer一直以来都有着较好的声誉，其SDRAM时代的WBGA封装也响彻一时，在DDR内存上也树立了良好形象。宇瞻科技隶属宏基集团，实力非常雄厚。初期专注于内存模组行销，并已经成为全球前四大内存模组供应商之一。据权威人士透露，在国际上，宇瞻的品牌知名度以及产品销量与目在前国内排名第一的品牌持平甚至超过，之所以在国内目前没有坐到龙头位置，是因为宇瞻对于品牌宣传一直比较低调，精力更多投入到产品研发生产而不是品牌推广当中。

    最近，宇瞻相应推出的"宇瞻金牌内存"系列。宇瞻金牌内存产品线特别为追求高稳定性、高兼容性的内存用户而设计。宇瞻金牌内存坚持使用100％原厂测试颗粒（决不使用OEM颗粒）是基于现有最新的DDR内存技术标准设计而成，经过ISO 9002认证之工厂完整流程生产制造。采用20微米金手指高品质6层PCB板，每条内存都覆盖有美观精质的黄金色金属铭牌，而且通过了最高端的Advantest测试系统检测后，采用高速SMT机台打造，经过高低压、高低温、长时间的密封式空间严苛测试，并经过全球知名系统及主板大厂完全兼容性测试，品质与兼容性都得到最大限度的保证。

    宇瞻的DDR500内存（PC4000内存）采用金黄色的散热片和绿色的PCB板搭配。金属散热片的材质相当不错，在手中有种沉甸甸的感觉，为了防止氧化，其表面被镀成了金色。内存颗粒方面，这款内存采用了三星的内存颗粒，具体型号为：K4H560838E-TCC5，为32Mx8规格DDR466@CL=3的TSOPII封装颗粒，标准工作电压2.6V+-0.1V，标准运行时序CL-tRCD-tRP为 3-4-4。在DDR500下其CL值为3，性能将就。

    金邦（Geil）

    金邦科技股份有限公司是世界上专业的内存模块制造商之一。全球第一家也是唯一家以汉字注册的内存品牌，并以中文命名的产品"金邦金条"、"千禧条GL2000"迅速进入国内市场，在极短的时间内达到行业销量遥遥领先。第一支"量身订做，终身保固"记忆体模组的内存品牌，首推"量身订做"系列产品，使计算机进入最优化状态。在联合电子设备工程委员会JEDEC尚未通过DDR400标准的情况下，率先推出第一支"DDR400"并成功于美国上市。

    金邦高性能、高品质和高可靠性的内存产品，引起业界和传媒的广泛关注。在过去几年中，金邦内存多次荣获国内权威杂志评为读者首选品牌和编辑选择奖，稳夺国内存储器市场占有率三强。

    金邦的Geil Platinum系列的DDR500内存（PC4000），采用TSOPII封装，使用了纯铜内存散热片，可较妥善的解决内存的散热问题。采用六层低电磁干扰PCB板设计；单条容量256MB，在内存芯片上做了整体打磨并上打上了Geil的印记，"号称"使用了4ns的内存芯片，但仍可以看出其是采用Hynix的内存颗粒。额定工作频率可达500MHz，在内存参数方面这是默认为CL3，可达CL2.5。除此而外，其还有金条PC4200（DDR533）等款产品。

    威刚（ADATA）

    威刚的高频内存有DDR450、460、500等。Adata DDR500是一款价格适宜的DDR500产品，CAS值为3，其没有使用散热片，在芯片上的标签显示了Adata ID号，但是，建议不用，因为毕竟没用散热片，所以使用寿命较短。

【内存容量】

    内存容量同硬盘、软盘等存储器容量单位都是相同的，它们的基本单位都是字节（B），并且：

    1024B=1KB=1024字节=2^10字节（^代表次方）

    1024KB=1MB=1048576字节=2^20字节

    1024MB=1GB=1073741824字节=2^30字节

    1024GB=1TB=1099511627776字节=2^40字节

    1024TB=1PB=1125899906842624字节=2^50字节

    1024PB=1EB=1152921504606846976字节=2^60字节

    1024EB=1ZB=1180591620717411303424字节=2^70字节

    1024ZB=1YB=1208925819614629174706176字节=2^80字节

【内存辨别】

    金士顿：在新的“数码凹版”防伪标中，采用了人民币防伪技术的其中两种，即“凹版防伪”和“纹理防伪”，以提高对产品防伪能力，把最先进的防伪技术用于内存上，通过最简单的辨别方法就可识别真假。凹凸版防伪技术是人民币纸币上采用的特有印刷技术，使用者通过触摸印刷图案有明显的凹凸手感。全新的“纳米折光”防伪标的最大好处就是无需任何第三方鉴别工具，不用电话不用短信，仅仅通过用户目测，就可以在第一时间立刻准确的辨别金士顿产品真伪。“纳米折光”防伪标采用了业界独一无二的纳米玻璃微晶折射光线的技术，在不同的光线角度下展现不同的文字及图案，有些类似传统的激光全息防伪标。但与传统激光标不同的是，“纳米折光”标绝无伪造的可能。

    三星：1. 产品标签：三星金条的型号标签位于内存产品的左起第一、二颗粒位置，为白色特殊纸张的贴纸，无法轻易完整揭下，印有产品的型号、容量、工作频率。

    2. 防伪标签：三星金条的防伪标签位于内存产品的右起第二颗粒位置上，金色的雷射标签上印有“三星金条”的LOGO。

    3. 芯片光泽：原装原厂的三星内存---三星金条芯片表面一般都很有质感，要么有光泽或荧光感，要么经过了哑光。

    4. 看PCB板：三星金条的PCB表面应光洁、色泽发灰、元件焊接整齐、焊点均匀而有光泽，边缘整齐而无毛边，同时还印有“Samsung”LOGO。

    5.光亮的金手指：三星金条的金手指要光亮，不能有发白或发黑的现象，发白是镀层质量差的表现，发黑是磨损和氧化的结果。

    6.原厂三星金条采用透明大盒包装，包装内附有三星金条说明书和全国首家推出的电子保修卡！而劣质的三星内存则没有！

    当然，还有一样少不了的就是800电话了，还是老话，不要嫌麻烦，一般卖假货的奸商会用各种理由不让你在他那里打800电话，一些粗心的用户贪图方便就中了他们的圈套了。

现代：首先真的现代内存条上贴的是原厂标签，而假的是普通贴纸；真的现代内存芯片在整条电路上的排列是无间隙排列，假的则有明显间隔；真的现代内存芯片从背面可以看到圆形凹槽，假的现代内存芯片背面则是三个呈直线排列的圆形标记。真的现代内存芯片真芯片左下角有一个圆形凹槽，右下角也有一排稍暗的印字，这些特征假芯片都没有。真的现代内存芯片上的字迹清楚，而假内存芯片上的字迹边缘不清晰，有的有毛刺。购卖时只要仔细观察，应该可以辨别出来的。

内存故障判断过程

    1. 读不到内存

    &Oslash; 槽坏

    a) 弹片接触不好：氧化、弹力失控、开路

    b) 槽短路烧坏：两针短路、损坏

    &Oslash; 内存相关线路：其中一个槽短路；供电；时钟；行列选通；线路有开路；数据线；地址线；控制线；北桥坏（多数与北桥线路有关）

    2. 数码跳不完整

    C0—C1—C3—C5（没有C3和C5，则相关线路有问题）

    3. 内存容量报错

    &Oslash; 内存条坏

    &Oslash; 接触不好

    &Oslash; 插槽与北桥之间线路有问题

    4. 进98后缺少字符：内存条坏

    5. 进98后死机：内存条坏

    6. AGP槽短路—影响北桥、内存

    7. BIOS错或资料丢失

    &Oslash; 病毒感染

    &Oslash; 升级失败

    &Oslash; 供电不对

    注：时钟IC有时也会引起不读内存

    不同主板的诊断卡走数会有不同，常见的有下面3种：

    1：00—C0—C1—C3—0b—0d—3d—42—6F—7F—FF

    2：FF—C1—1d—2b—3d—42—6F—7F—FF

    3：FF—d3—d4—0b—2A—31—3d—4E


英文名称：Register

寄存器定义

    寄存器是中央处理器内的组成部分。寄存器是有限存贮容量的高速存贮部件，它们可用来暂存指令、数据和位址。在中央处理器的控制部件中，包含的寄存器有指令寄存器(IR)和程序计数器(PC)。在中央处理器的算术及逻辑部件中，包含的寄存器有累加器(ACC)。

    寄存器是内存阶层中的最顶端，也是系统获得操作资料的最快速途径。寄存器通常都是以他们可以保存的位元数量来估量，举例来说，一个 “8 位元寄存器”或 “32 位元寄存器”。寄存器现在都以寄存器档案的方式来实作，但是他们也可能使用单独的正反器、高速的核心内存、薄膜内存以及在数种机器上的其他方式来实作出来。

    寄存器通常都用来意指由一个指令之输出或输入可以直接索引到的暂存器群组。更适当的是称他们为 “架构寄存器”。

    例如，x86 指令集定义八个 32 位元寄存器的集合，但一个实作 x86 指令集的 CPU 可以包含比八个更多的寄存器。

    寄存器是CPU内部的元件，寄存器拥有非常高的读写速度，所以在寄存器之间的数据传送非常快。

特点及原理

    寄存器又分为内部寄存器与外部寄存器，所谓内部寄存器，其实也是一些小的存储单元，也能存储数据。但同存储器相比，寄存器又有自己独有的特点：

    ①寄存器位于CPU内部，数量很少，仅十四个；

    ②寄存器所能存储的数据不一定是8bit，有一些寄存器可以存储16bit数据，对于386/486处理器中的一些寄存器则能存储32bit数据；

    ③每个内部寄存器都有一个名字，而没有类似存储器的地址编号。

    寄存器的功能十分重要，CPU对存储器中的数据进行处理时，往往先把数据取到内部寄存器中，而后再作处理。外部寄存器是计算机中其它一些部件上用于暂存数据的寄存器，它与CPU之间通过“端口”交换数据，外部寄存器具有寄存器和内存储器双重特点。有些时候我们常把外部寄存器就称为“端口”，这种说法不太严格，但经常这样说。

    外部寄存器虽然也用于存放数据，但是它保存的数据具有特殊的用途。某些寄存器中各个位的0、1状态反映了外部设备的工作状态或方式；还有一些寄存器中的各个位可对外部设备进行控制；也有一些端口作为CPU同外部设备交换数据的通路。所以说，端口是CPU和外设间的联系桥梁。CPU对端口的访问也是依据端口的“编号”（地址），这一点又和访问存储器一样。不过考虑到机器所联接的外设数量并不多，所以在设计机器的时候仅安排了1024个端口地址，端口地址范围为0--3FFH。

寄存器用途

    1.可将寄存器内的数据执行算术及逻辑运算；

    2.存于寄存器内的地址可用来指向内存的某个位置，即寻址；

    3.可以用来读写数据到电脑的周边设备。

数据寄存器

    8086 有14个16位寄存器，这14个寄存器按其用途可分为(1)通用寄存器、(2)指令指针、(3)标志寄存器和(4)段寄存器等4类。

    (1)通用寄存器有8个, 又可以分成2组,一组是数据寄存器(4个),另一组是指针寄存器及变址寄存器(4个).

    顾名思义，通用寄存器是那些你可以根据自己的意愿使用的寄存器，修改他们的值通常不会对计算机的运行造成很大的影响。

    数据寄存器分为:

    AH&AL＝AX(accumulator)：累加寄存器，常用于运算;在乘除等指令中指定用来存放操作数,另外,所有的I/O指令都使用这一寄存器与外界设备传送数据.

    BH&BL＝BX(base)：基址寄存器，常用于地址索引；

    CH&CL＝CX(count)：计数寄存器，常用于计数；常用于保存计算值,如在移位指令,循环(loop)和串处理指令中用作隐含的计数器.

    DH&DL＝DX(data)：数据寄存器，常用于数据传递。

    他们的特点是,这4个16位的寄存器可以分为高8位: AH, BH, CH, DH.以及低八位：AL,BL,CL,DL。这2组8位寄存器可以分别寻址，并单独使用。

    另一组是指针寄存器和变址寄存器，包括：

    SP（Stack Pointer）：堆栈指针，与SS配合使用，可指向目前的堆栈位置；

    BP（Base Pointer）：基址指针寄存器，可用作SS的一个相对基址位置；

    SI（Source Index）：源变址寄存器可用来存放相对于DS段之源变址指针；

    DI（Destination Index）：目的变址寄存器，可用来存放相对于 ES 段之目的变址指针。

    这4个16位寄存器只能按16位进行存取操作，主要用来形成操作数的地址，用于堆栈操作和变址运算中计算操作数的有效地址。

    (2) 指令指针IP(Instruction Pointer)

    指令指针IP是一个16位专用寄存器，它指向当前需要取出的指令字节，当BIU从内存中取出一个指令字节后，IP就自动加1，指向下一个指令字节。注意，IP指向的是指令地址的段内地址偏移量，又称偏移地址(Offset Address)或有效地址(EA，Effective Address)。

    (3)标志寄存器FR(Flag Register)

    8086有一个18位的标志寄存器FR，在FR中有意义的有9位，其中6位是状态位，3位是控制位。

    OF： 溢出标志位OF用于反映有符号数加减运算所得结果是否溢出。如果运算结果超过当前运算位数所能表示的范围，则称为溢出，OF的值被置为1，否则，OF的值被清为0。

    DF：方向标志DF位用来决定在串操作指令执行时有关指针寄存器发生调整的方向。

    IF：中断允许标志IF位用来决定CPU是否响应CPU外部的可屏蔽中断发出的中断请求。但不管该标志为何值，CPU都必须响应CPU外部的不可屏蔽中断所发出的中断请求，以及CPU内部产生的中断请求。具体规定如下：

    (1)、当IF=1时，CPU可以响应CPU外部的可屏蔽中断发出的中断请求；

    (2)、当IF=0时，CPU不响应CPU外部的可屏蔽中断发出的中断请求。

    TF：跟踪标志TF。该标志可用于程序调试。TF标志没有专门的指令来设置或清楚。

    （1）如果TF=1，则CPU处于单步执行指令的工作方式，此时每执行完一条指令，就显示CPU内各个寄存器的当前值及CPU将要执行的下一条指令。

    （2）如果TF=0，则处于连续工作模式。

    SF：符号标志SF用来反映运算结果的符号位，它与运算结果的最高位相同。在微机系统中，有符号数采用补码表示法，所以，SF也就反映运算结果的正负号。运算结果为正数时，SF的值为0，否则其值为1。当运算结果没有产生溢出时，运算结果等于逻辑结果（即因该得到的正确的结果），此时SF表示的是逻辑结果的正负，当运算结果产生溢出时，运算结果不等于逻辑结果，此时的SF值所表示的正负情况与逻辑结果相反，即：SF=0时，逻辑结果为负，SF=1时，逻辑结果为正。

    ZF： 零标志ZF用来反映运算结果是否为0。如果运算结果为0，则其值为1，否则其值为0。在判断运算结果是否为0时，可使用此标志位。

    AF：下列情况下，辅助进位标志AF的值被置为1，否则其值为0：

    (1)、在字操作时，发生低字节向高字节进位或借位时；

    (2)、在字节操作时，发生低4位向高4位进位或借位时。

    PF：奇偶标志PF用于反映运算结果中“1”的个数的奇偶性。如果“1”的个数为偶数，则PF的值为1，否则其值为0。

    CF：进位标志CF主要用来反映无符号数运算是否产生进位或借位。如果运算结果的最高位产生了一个进位或借位，那么，其值为1，否则其值为0。

    (4)段寄存器(Segment Register)

    为了运用所有的内存空间，8086设定了四个段寄存器，专门用来保存段地址：

    CS（Code Segment）：代码段寄存器；

    DS（Data Segment）：数据段寄存器；

    SS（Stack Segment）：堆栈段寄存器；

    ES（Extra Segment）：附加段寄存器。

    当一个程序要执行时，就要决定程序代码、数据和堆栈各要用到内存的哪些位置，通过设定段寄存器 CS，DS，SS 来指向这些起始位置。通常是将DS固定，而根据需要修改CS。所以，程序可以在可寻址空间小于64K的情况下被写成任意大小。所以，程序和其数据组合起来的大小，限制在DS 所指的64K内，这就是COM文件不得大于64K的原因。8086以内存做为战场，用寄存器做为军事基地，以加速工作。

    以上是8086寄存器的整体概况, 自80386开始，PC进入32bit时代，其寻址方式，寄存器大小，功能等都发生了变化。

    =============================以下是80386的寄存器的一些资料======================================

    寄存器都是32-bits宽。

    A、通用寄存器

    下面介绍通用寄存器及其习惯用法。顾名思义，通用寄存器是那些你可以根据自己的意愿使用的寄存器，修改他们的值通常不会对计算机的运行造成很大的影响。通用寄存器最多的用途是计算。

    EAX：通用寄存器。相对其他寄存器，在进行运算方面比较常用。在保护模式中，也可以作为内存偏移指针（此时，DS作为段寄存器或选择器）

    EBX：通用寄存器。通常作为内存偏移指针使用（相对于EAX、ECX、EDX），DS是默认的段寄存器或选择器。在保护模式中，同样可以起这个作用。

    ECX：通用寄存器。通常用于特定指令的计数。在保护模式中，也可以作为内存偏移指针（此时，DS作为寄存器或段选择器）。

    EDX：通用寄存器。在某些运算中作为EAX的溢出寄存器（例如乘、除）。在保护模式中，也可以作为内存偏移指针（此时，DS作为段寄存器或选择器）。

    同AX分为AH&AL一样，上述寄存器包括对应的16-bit分组和8-bit分组。

    B、用作内存指针的特殊寄存器

    ESI：通常在内存操作指令中作为“源地址指针”使用。当然，ESI可以被装入任意的数值，但通常没有人把它当作通用寄存器来用。DS是默认段寄存器或选择器。

    EDI：通常在内存操作指令中作为“目的地址指针”使用。当然，EDI也可以被装入任意的数值，但通常没有人把它当作通用寄存器来用。DS是默认段寄存器或选择器。

    EBP和ESP：作为指针的寄存器，也可作为16位寄存器BP, SP使用，常用于椎栈操作。通常，它被高级语言编译器用以建造‘堆栈帧'来保存函数或过程的局部变量，不过，还是那句话，你可以在其中保存你希望的任何数据。SS是它的默认段寄存器或选择器。

    注意，这四个寄存器没有对应的8-bit分组。换言之，你可以通过SI、DI、BP、SP作为别名访问他们的低16位，却没有办法直接访问他们的低8位。

    C、段选择器：

    实模式下的段寄存器到保护模式下摇身一变就成了选择器。不同的是，实模式下的“段寄存器”是16-bit的，而保护模式下的选择器是32-bit的。

    CS 代码段，或代码选择器。同IP寄存器(稍后介绍)一同指向当前正在执行的那个地址。处理器执行时从这个寄存器指向的段（实模式）或内存（保护模式）中获取指令。除了跳转或其他分支指令之外，你无法修改这个寄存器的内容。

    DS 数据段，或数据选择器。这个寄存器的低16 bit连同ESI一同指向的指令将要处理的内存。同时，所有的内存操作指令默认情况下都用它指定操作段(实模式)或内存(作为选择器，在保护模式。这个寄存器可以被装入任意数值，然而在这么做的时候需要小心一些。方法是，首先把数据送给AX，然后再把它从AX传送给DS(当然，也可以通过堆栈来做).

    ES 附加段，或附加选择器。这个寄存器的低16 bit连同EDI一同指向的指令将要处理的内存。同样的，这个寄存器可以被装入任意数值，方法和DS类似。

    FS F段或F选择器(推测F可能是Free?)。可以用这个寄存器作为默认段寄存器或选择器的一个替代品。它可以被装入任何数值，方法和DS类似。

    GS G段或G选择器(G的意义和F一样，没有在Intel的文档中解释)。它和FS几乎完全一样。

    SS 堆栈段或堆栈选择器。这个寄存器的低16 bit连同ESP一同指向下一次堆栈操作(push和pop)所要使用的堆栈地址。这个寄存器也可以被装入任意数值，你可以通过入栈和出栈操作来给他赋值，不过由于堆栈对于很多操作有很重要的意义，因此，不正确的修改有可能造成对堆栈的破坏。

    * 注意一定不要在初学汇编的阶段把这些寄存器弄混。他们非常重要，而一旦你掌握了他们，你就可以对他们做任意的操作了。段寄存器，或选择器，在没有指定的情况下都是使用默认的那个。这句话在现在看来可能有点稀里糊涂，不过你很快就会在后面知道如何去做。

    D、指令指针寄存器：

    EIP 这个寄存器非常的重要。这是一个32位宽的寄存器，同CS一同指向即将执行的那条指令的地址，存放指令的偏移地址。微处理器工作于实模式下，EIP是IP（16位）寄存器。不能够直接修改这个寄存器的值，修改它的唯一方法是跳转或分支指令。(CS是默认的段或选择器)

    E、标志寄存器EFR

    EFR（extra flags register）包括状态位、控制位和系统标志位，用于指示微处理器的状态并控制微处理器的操作。80486 CPU标志寄存器如图2.12所示。

    ①状态标志位：包括进位标志CF、奇偶标志PF、辅助进位标志AF、零标志ZF 、符号标志SF和溢出标志OF。

    ②控制标志位：包括陷阱标志（单步操作标志）TF、中断标志IF和方向标志DF。80486 CPU标志寄存器中的状态标志位和控制标志位与8086 CPU标志寄存器中的状态标志位和控制标志位的功能完全一样，这里就不再赘述。

    ③系统标志位和IOPL字段：在EFR寄存器中的系统标志和IOPL字段，用于控制操作系统或执行某种操作。它们不能被应用程序修改。

    IOPL（I/O privilege level field）：输入/输出特权级标志位。它规定了能使用I/O敏感指令的特权级。在保护模式下，利用这两位编码可以分别表示0, 1, 2, 3这四种特权级，0级特权最高，3级特权最低。在80286以上的处理器中有一些I/O敏感指令，如CLI（关中断指令）、STI（开中断指令）、IN（输入）、OUT（输出）。IOPL的值规定了能执行这些指令的特权级。只有特权高于IOPL的程序才能执行I/O敏感指令，而特权低于IOPL的程序，若企图执行敏感指令，则会引起异常中断。

    NT（nested task flag）：任务嵌套标志。在保护模式下，指示当前执行的任务嵌套于另一任务中。当任务被嵌套时，NT＝1，否则NT＝0。

    RF（resume flag）：恢复标志。与调试寄存器一起使用，用于保证不重复处理断点。当RF＝1时，即使遇到断点或故障，也不产生异常中断。

    VM（virtual 8086 mode flag）：虚拟8086模式标志。用于在保护模式系统中选择虚拟操作模式。VM＝1，启用虚拟8086模式；VM＝0，返回保护模式。

    AC（alignment check flag）：队列检查标志。如果在不是字或双字的边界上寻址一个字或双字，队列检查标志将被激活。

    上面是最基本的寄存器。下面是一些其他的寄存器，你甚至可能没有听说过它们。(都是32位宽)：

    CR0, CR2, CR3(控制寄存器)。举一个例子，CR0的作用是切换实模式和保护模式。

    还有其他一些寄存器，D0, D1, D2, D3, D6和D7(调试寄存器)。他们可以作为调试器的硬件支持来设置条件断点。

    TR3, TR4, TR5, TR6 和 TR? 寄存器(测试寄存器)用于某些条件测试。

寄存器分类

    数据寄存器 - 用来储存整数数字（参考以下的浮点寄存器）。在某些简单/旧的 CPU，特别的数据寄存器是累加器，作为数学计算之用。

    地址寄存器 - 持有存储器地址，以及用来访问存储器。在某些简单/旧的CPU里，特别的地址寄存器是索引寄存器（可能出现一个或多个）。

    通用目的寄存器（GPRs） - 可以保存数据或地址两者，也就是说他们是结合数据/地址寄存器的功用。

    浮点寄存器（FPRs） - 用来储存浮点数字。

    常数寄存器 - 用来持有只读的数值（例如 0、1、圆周率等等）。

    向量寄存器 - 用来储存由向量处理器运行SIMD（Single Instruction, Multiple Data）指令所得到的数据。

    特殊目的寄存器 - 储存CPU内部的数据，像是程序计数器（或称为指令指针），堆栈寄存器，以及状态寄存器（或称微处理器状态字组）。

    指令寄存器（instruction register） - 储存现在正在被运行的指令

    索引寄存器（index register） - 是在程序运行实用来更改运算对象地址之用。

    在某些架构下，模式指示寄存器（也称为“机器指示寄存器”）储存和设置跟处理器自己有关的数据。由于他们的意图目的是附加到特定处理器的设计，因此他们并不被预期会成微处理器世代之间保留的标准。

    有关从随机存取存储器提取信息的寄存器与CPU（位于不同芯片的储存寄存器集合）

    存储器缓冲寄存器（Memory buffer register）

    存储器数据寄存器（Memory data register）

    存储器地址寄存器（Memory address register）

    存储器型态范围寄存器（Memory Type Range Registers）[1][2]

段寄存器在寻址过程中的作用

    寄存器（Register）是CPU内部的元件，所以在寄存器之间的数据传送非常快。用途：1.可将寄存器内的数据执行算术及逻辑运算。2.存于寄存器内的地址可用来指向内存的某个位置，即寻址。3.可以用来读写数据到电脑的周边设备。8086 有8个8位数据寄存器，这些8位寄存器可分别组成16位寄存器：AH&AL＝AX：累加寄存器，常用于运算；BH&BL＝BX：基址寄存器，常用于地址索引；CH&CL＝CX：计数寄存器，常用于计数；DH&DL＝DX：数据寄存器，常用于数据传递。

参考资料：

  • 1.

CPU寄存器概述

http://article.wxiu.com/yjwx/cpu/200907/09-5901.html

  • 2.

ARM寄存器简介

http://article.wxiu.com/yjwx/other/200907/09-5902.html

扩展阅读：

1.http://wiki.ccw.com.cn/index.php/%E5%AF%84%E5%AD%98%E5%99%A8

2.<汇编语言程序设计》

3.http://www.3800hk.com/Article/cxsj/hbyy/jcjchbyy/2005-08-06/Article_51498.html


一般情况下，源程序中所有的行都参加编译

一般情况下，源程序中所有的行都参加编译。但是有时希望对其中一部分内容只在满足一定条件下才进行编译，即对一部分内容指定编译条件，这就是“条件编译”。（conditional compile）

    条件编译语句排版时，需考虑以下三种位置：

    （1）条件编译语句块与函数定义体之间不存在相互嵌套（主要在（.h）文件中）

    ◆条件编译关键字语句顶格左对齐；

    ◆所含的#include语句(块) #define语句(块)甚至是被嵌套下级条件编译语句块，按照语句块嵌套的排版方式进行缩进排版。

    （2）条件编译语句块嵌套在函数体之外（主要在（.c）文件中）

    这种情况下，条件编译语句块不影响函数体

    ◆条件编译关键字语句顶格左对齐；

    ◆所含的函数体定义无需缩进，依旧按照单个函数体定义的排版方式进行。

    （3）条件编译语句嵌套在函数体内（主要在（.c）文件中）

    a）当条件编译语句块与被包语句所属的语句块之间没有逻辑路径交叉时，以下两种方式均可

    ◆按照语句块嵌套方式进行缩进排版 (推荐)；

    ◆条件编译语句不影响原先语句块排版，条件编译语句与所包含的关键字语句块左对齐。

    b）当条件编译语句块与被包语句所属的语句块之间存在逻辑路径交叉时

    ◆条件编译语句顶格左对齐，其它语句按照正常顺序排版。

    条件编译的形式如下所示（NNN、MMM等都是在某处已经定义为 1 或者 0 的）：

    #if NNN

    statement1;

    #elif MMM

    statement2;

    #else

    statement3;

#endif

#endif

是预编译处理指令中的条件编译。

    预编译处理是指在编译系统对文件进行编译---词法分析、语法分析、代码生成及优化之前，对一些特殊的编译语句先进行处理，然后将处理结果与源程序一起编译，生成目标文件。

    预编译处理语句都是以#开头，其结尾不带分号，与普通程序语句相区别。

    #endif用于结束条件编译，编译时与前面最近的#if作为一对，编译两者之间的部分程序段。

14.4 条件编译

C语言的预处理器可以识别大量用于支持条件编译的指令。条件编译是指根据预处理器所执行的测试结果来包含或排除程序的片断。

14.4.1 #if指令和#endif指令

假如我们正在调试一个程序。我们想要程序显示出特定变量的值，因此将printf函数调用添加到程序中重要的部分。一旦找到错误，经常需要保留这些printf函数调用，以备以后使用。条件编译允许我们保留这些调用，但是让编译器忽略它们。

下面是我们需要采取的方式。首先定义一个宏，并给它一个非0的值：

#define DEBUG 1

宏的名字并不重要。接下来，我们要在每组printf函数调用的前后加上#if和#endif：

#if DEBUG

printf("Value of i : %d\n", i);

printf("Value of j : %d\n", j);

#endif

在于处理过程中，#if指令会测试DEBUG的值。由于DEBUG的值非0，因此预处理器会将这两个printf函数调用保留在程序中（但#if和#endif行会消失）。如果我们将DEBUG的值改为0并重新编译程序，预处理器则会将这4行代码都消失。编译器将不会看到这些printf函数调用，所以这些调用就不会在目标代码中占用空间，也不会在程序运行时浪费时间。我们可以将#if-#endif保留在最终的程序中，这样如果程序在运行时出错，可以继续产生这些诊断信息（将DEBUG改为1并重新编译）。

一般来说，#if指令的格式如下：

[#if指令]　　#if 常量表达式

#endif指令则更简单：

[#endif指令]　　#endif

当预处理器遇到#if指令时，会计算常量表达式。如果表达式的值为0，那么在#if与#endif之间的行将在预处理过程中从程序中删除。否则，这些在#if和#endif之间的行会被保留在程序中，并继续被编译器处理——这时#if和#endif对程序没有任何影响。

对于没有定义过的标识符，#if指令会把它当作是值为0的宏对待。因此，如果省略DEBUG的定义，测试

#if DEBUG

会失败（但不会产生出错消息），而测试

#if !DEBUG

会成功。

14.4.2 defined运算符

14.3节中介绍过运算符#和##，还有另外一个运算符——defined，它仅用于预处理器。当defined应用于标识符时，如果标识符是一个定义过的宏返回1，否则返回0。#defined运算符通常与#if指令结合使用，允许写成

#if defined(DEBUG)

...

#endif

仅当DEBUG被定义成宏时，#if和#endif之间的代码会被保留在程序中。DEBUG两侧的括号不是必需的，因此可以简单写成

#if defined DEBUG

由于defined运算符仅检测DEBUG是否被定义为宏，所以不需要给DEBUG一个值：

#define DEBUG

14.4.3 #ifdef指令和#ifndef指令

#ifdef指令测试一个标识符是否已经定义为宏：

[#ifdef指令]　　#ifdef 标识符

#ifdef指令的使用与#if指令类似：

#ifdef 标识符

当标识符被定义为宏时需要包含代码

#endif

严格地说， [image] 并不需要#ifdef，因为我们可以组合#if指令和defined运算符来达到相同的效果。换而言之，指令

#ifdef 标识符

等价于

#if defined(标识符)

#ifndef指令与#ifdef指令类似，但是测试的是标识符是否没有被定义为宏：

[#ifndef指令]　　#ifndef 标识符

写成

#ifndef 标识符

等价于写成

#if !defined(标识符)

14.4.4 #elif指令和#else指令

#if指令、#ifdef指令和#ifndef指令可以像普通的if语句那样嵌套使用。当发生嵌套时，最好随着嵌套层次的增加而增加缩进。一些程序员对每一个#endif都加注释，来指明是对应于哪个条件测试的#if指令：

#if DEBUG

...

#endif /* DEBUG */

这种方法可以帮助读者更方便地找到起始的#if指令。

为了提供更多的便利，预处理器提供了#elif和#else指令：

[#elif指令]　　#elif 表达式

[#else指令]　　#else

#elif指令和#else指令可以与#if指令、#ifdef指令和#ifndef指令组合使用，来测试一系列条件：

#if 表达式1

当表达式1非0时需要包含的代码

#if 表达式2

当表达式1为0但表达式2非0时需要包含的代码

#else

其他情况下需要包含的代码

#endif

虽然上面的例子使用了#if指令，但#ifdef指令或#ifndef指令也可以替代使用。在#if指令和#endif指令之间可以有多个#elif指令，但最多只能有一个#else指令。

14.4.5 使用条件编译

条件编译对于调试是非常方便的，但它并不仅限于此。下面是其他一些常见的应用：

l 编写在多台机器或多种操作系统之间可移植的程序。下面的例子中会根据WINDOWS、DOS或OS2是否被定义为宏，而将三组代码之一包含到程序中：

#if defined(WINDOWS)

...

#elif defined(DOS)

...

#elif defined(OS2)

...

#endif

一个程序中可能包含许多这样的#if指令。在程序的开头会定义这些宏之一（而且只有一个），由此选择了一个特定的操作系统。例如，定义OS2宏可以指明程序将运行在OS/2操作系统下。

l 编写可以使用不同的编译器进行编译的程序。不同的编译器经常用于不同的C语言版本，这些版本之间会有一些差异。一些会接受标准C，另外一些则不会。一些版本会包含针对特定机器的扩展，而其他的则或没有，或提供不同的扩展集。条件编译可以使程序适应于不同的编译器。思考编写这样一个程序，当使用标准C的编译器进行编译时，可不可能被编译成功。__STDC__宏允许预处理器检测编译器是否支持标准C，如果不支持，我们可能必须修改程序的某些方面，尤其是有可能必须使用经典C的函数声明替代标准C的函数原型。对于每一处函数的声明，我们都可以使用下面的代码：

#if __STDC__

标准C函数原型

#else

经典C函数声明

#endif

l 为宏提供默认定义。条件编译使我们可以检测一个宏当前是否已经被定义了，如果没有，则提供一个默认的定义。例如，如果它还没有被定义的话，下面的代码会定义宏BUFFER_SIZE：

#ifndef BUFFER_SIZE

#define BUFFER_ZISE 256

#endif

l 临时屏蔽包含注释的代码。由于在标准C中注释不能嵌套，因此不能直接“注释掉”包含注释的代码。然而，可以使用#if指令替代：

#if 0

包含注释的代码行

#endif

将代码以这种方式屏蔽掉经常称为“条件屏蔽”。

15.2节会讨论另外一种条件编译的常用用途：保护头文件以避免重复包含。

14.5 其他指令

在本章的最后，我们将简要地了解一下#error指令、#line指令和#pragma指令。这些指令有一个共同点：C语言的初学者不会经常使用它们。实际上，本书中的所有程序都不会使用它们，因此你可以放心地跳过本节。以后，当你准备成为一个C语言专家时，你会需要熟悉这些指令。

14.5.1 #error指令

#error指令有如下格式：

[#error 指令]　　#error 消息

其中，消息是一个C语言标记序列。如果预处理器遇到一个#error指令，它会显示一个出错消息，这个出错消息一定会包含消息。对于不同的编译器，出错消息的具体形式也可能会不一样。下面是一个典型的示例：

Error directive: 记号序列

碰到#error指令预示着一个严重的程序错误，大多数编译器会立即终止编译而不去找出其他错误。

#error指令通常与条件编译指令一起用于检测正常编译过程中不应出现的情况。例如，假定我们需要确保，一个程序在一台无法使用int类型来存储大于100 000的数的机器上不能编译。最大允许的int值用INT_MAX宏（ [image] 23.2节）表示，所以我们需要做的就是当INT_MAX宏不超过100 000时调用#error指令：

#if INT_MAX < 100000

#error int type is too small

#endif

如果试图在一台整型以16位存储的机器上编译这个程序，将产生一条出错消息：

Error directive: int type is too small

#error指令通常会出现在#if-#elif-#else序列中的#else部分：

#if defined(WINDOWS)

...

#elif defined(DOS)

...

#elif defined(OS2)

...

#else

#error No operating system specified

#endif

14.5.2 #line指令

#line指令是用来改变给程序行编号的方式的。（程序行通常是按1，2，3，…来编号的。）我们也可以使用这条指令使编译器认为它正在从一个有不同名字的文件中读取一个程序。

#line指令有两种形式。在一种形式中，指定一个行号：

[#line 指令 (形式1)]　　#line n

n 必须是介于1到32 767之间的整数。这条指令导致程序后面的行被编号为n、n+1、n+2等。

在#line的第二种形式中，需要同时指定行号和文件名：

[#line指令（形式2）]　　#line n "文件名"

指令后面的行会被认为是来自文件，行号由n开始。

#line指令的一种作用是改变__LINE__宏（可能还有__FILE__宏）的值。更重要的是，大多数编译器会使用来自#line指令的信息产生出错消息。例如，假设下列指令出现在文件foo.c的开头：

#line 10 "bar.c"

现在，假设编译器在foo.c的第5行监测到一个错误。出错消息会指向bar.c的第13行，而不是foo.c的第5行。（为什么是第13行呢？因为指令占据了foo.c的第1行，因此对foo.c的重新编号从第2行开始，并将这一行作为bar.c的第10行。）

乍一看，#line指令使人迷惑。为什么要使出错消息指向另一行，甚至是另一个文件呢？这样不是会使程序变得难以调试吗？

实际上，程序员并不经常使用#line指令。然而，它主要用于那些产生C代码作为输出的程序。程序yacc（Yet Another Compiler-Compiler）是其中著名的程序之一。yacc是一个UNIX工具，用于自动生成部分的编译器。在使用yacc之前，程序员需要准备一个包含yacc所需要的信息以及C代码段的文件。通过这个文件，yacc生成了一个C程序y.tab.c，并合并了程序员所提供的代码。程序员接着按照正常方法编译y.tab.c。通过在y.tab.c中插入#line指令，yacc会使编译器认为代码来自原始文件——也就是程序员写的那个文件。于是，任何编译y.tab.c时产生的出错消息会指向原始文件中的行，而不是y.tab.c中的行。其最终结果是：调试变得更容易，因为出错消息都指向程序员编写的文件，而不是（更复杂的）由yacc生成的文件。

14.5.3 #pragma指令

#pragma指令为要求编译器执行某些特殊操作提供了一种方法。这条指令对非常大的程序或需要使用特定编译器的特殊功能的程序非常有用。

#pragma指令有如下形式：

[#pragma指令]　　#pragma 记号

其中，记号是一般C语言的记号。#pragma指令通常只跟着一个记号，这个记号表示了一条编译器需要服从的命令。

一些编译器允许#pragma指令所包含的不仅是简单的命令。特别是有些编译器允许#pragam指令带参数：

#pragma data(heap_size => 1000, stack_size => 2000)

#pragma指令中出现的命令集在不同的编译器上是不一样的。你必须通过查阅你所使用的编译器的文档来了解哪些命令是可以使用的，以及这些命令的功能。顺便提一下，如果#pragma指令包含了无法识别的命令，编译器必须忽略这些#pragma指令，不允许产生出错消息。

问与答

*问：我看到在有些程序中#单独占一行。这样是合法的吗？

*答：是合法的。这就是所谓的空指令，它没有任何作用。一些程序员用空指令作为条件编译模块之间的间隔：

#if INT_MAX < 100000

#

#error int type is too small

#

#endif

当然，空行也可以。不过#可以帮助读者看出模块的范围。

*问：我不清楚程序中哪些常量需要定义成宏。有没有一些可以参照的规则？（p.195）

*答：一条首要的规则是每一个数字常量，如果不是0和1，就需要定义成宏。字符常量和字符串常量有一点复杂，因为使用宏来替换字符或字符串常量并不总是提高程序的可读性。我个人建议在下面的条件下使用宏来替代字符或字符串常量：（1）常量被不止一次地使用；（2）存在常量某天被修改的可能。根据第二条规则，我不会像这样使用宏：

#define NUL '\0'

虽然有些程序员会使用。

*问：如果要被“字符串化”的参数包含 " 或 \ 字符，#运算符会如何处理？（p.197）

*答：它会将 " 转换为 \"，\ 转换为 \\。考虑下面的宏：

#define STRINGIZE(x) #x

预处理器会将STRINGIZE("foo")替换为"\"foo\""。

*问：我无法使下面的宏正常工作：

#define CONCAT(x,y) x##y

*问：如期望那样，CANCAT(a,b)会给出ab，但CONCAT(a,CONCAT(b,c))会给出一个怪异的结果。这是为什么？

*答：感谢那些连Kernighan和Ritchie都承认“怪异”的规则，替换列表中依赖##的宏通常不能嵌套调用。这里的问题在于CONCAT(a,CONCAT(b,c))不会按照“正常”的方式扩展——CONCAT(b,c)首先得出bc，然后CONCAT(a,bc)给出abc。C标准指明，在替换列表中，位于##运算符之前和之后的宏参数在替换时不被扩展，结果，CONCAT(a,CONCAT(b,c))扩展成aCONCAT(b,c)，而不会进一步扩展，因为没有名为aCONCAT的宏。
有一种办法可以解决这个问题，但不太好看。技巧是定义第二个宏，只是简单地调用第一个宏：

#define CONCAT2(x,y) CONCAT(x,y)

写成CONCAT2(a,CONCAT2(b,c))就会得到我们希望的结果。在扩展外面的CONCAT2调用时，预处理器将会同时扩展CONCAT2(b,c)。这里的区别在于CONCAT2的扩展列表不包含##。如果这个也不行，那也不用担心，这种问题并不是经常遇到的。

顺便提一下，#运算符也有同样的问题。如果#x出现在替换列表中，其中x是一个宏参数，其对应的实际参数也不会被扩展。因此，假设N是一个代表10的宏，且STR(x)包含替换列表#x，STR(N)扩展的结果为"N"，而不是"10"。解决的方法与我们在CONCAT例子中类似：定义第二个宏来调用STR。

*问：如果预处理器再重新扫描时又发现了最初的宏名会如何处理呢？例如下面的例子：

#define N (2*M)

#define M (N+!)

i = N; /* infinite loop?*/

*问：预处理器会将N替换为(2*M)，接着将M替换为(N+1)。预处理器还会再次替换N，成为一个无限循环吗？（p.199）

*答：一些早期的预处理器确实会进入无限循环，但符合标准C的预处理器则不会。按照C语言标准，如果在扩展宏的过程中原先的宏名重复出现的话，宏名不会再次被替换。下面是预处理后i的赋值语句的形式：

i = (2*(N+1));

一些大胆的程序员会通过编写其名字与保留字或标准库中的函数名匹配的宏来利用这一行为。以库函数sqrt为例。sqrt函数（ [image] 23.3.5节）计算参数的平方根，如果参数为负数则返回一个由实现定义的值，我们可能希望当参数为负数时返回0。由于sqrt是标准库函数，我们无法很容易地修改它。但是我们可以定义一个宏，使它在参数为负数时返回0：

#define sqrt(x) ((x)>0 ? sqrt(x) : 0)

预处理器会截获sqrt的调用，并将它替换成上面的条件表达式。在扫描宏的过程中条件表达式中的sqrt调用不会被替换，因此会被保留由编译器处理。

*问：我觉得预处理器就是一个编辑器。它如何计算常量表达式呢？（p.202）

*答：预处理器比你想的要复杂。它足够“了解”C语言，所以能够计算常量表达式。虽然它不会完全按照编译器的方式去做。（例如，预处理器认为所有未定义的名字的值为0。其他的差异太深奥，就不再深入了。）在实际使用中，预处理器常量表达式中的操作数通常为常量、表示常量的宏或defined运算符的应用。

*问：为什么C提供#ifdef指令和#ifndef指令，既然我们可以使用#if指令和define运算符达到同样效果？（p.203）

*答：#ifdef指令和#ifndef指令从20世纪70年代就在C语言中存在了，而defined运算符则是在80年代的标准化过程中加到C语言中的。因此，实际的问题是：为什么将defined运算符加到C语言中？答案就是defined增加了灵活性。我们现在可以使用#if和defined运算符来测试任意数量的宏，而不再是只能使用#ifdef和#ifndef对一个宏进行测试。例如，下面的指令检查是否FOO和BAR被定义了而BAZ没有被定义：

#if defined(FOO) && defined(BAR) && !defined(BAZ)

*问：我想编译一个还没有写完的程序，因此我“条件屏蔽”未完成的部分。我在开始的地方加了一条信息来提示自己以后将程序写完：

#if 0

Haven’t finished this part yet.

...

#endif

*问：为什么我在编译时会得到一条出错消息呢？预处理器不是简单地忽略#if指令和#endif指令之间的所有行吗？（p.205）

*答：在#if指令和#endif指令之间的行必须由预处理记号（ [image] 附录A）组成。预处理记号类似于C语言记号（标识符、运算符、数等）。当预处理器试图将第一行分解为记号时，会遇到Haven（一个合法的标识符），接着是’t（一个非法的字符常量）。一些预处理器会跳过#if指令和#endif指令之间所有的行而不会检查预处理记号，但这些预处理器并没有严格遵守C语言标准的规则。

练习

14.3节

1. 编写宏来计算下面的值。

(a) x的立方。

(b) x除以4的余数。

(c) 如果x与y的乘积小于100值为1，否则值为0。

你写的宏始终正常工作吗？如果不是，哪些参数会失败呢？

2. 编写一个宏NELEMS(a)来计算一个一维数组a中元素的个数。提示：使用sizeof运算符。

3. 假定DOUBLE是如下宏：

#define DOUBLE(x) 2*x

(a) DOUBLE(1+2)的值是多少？

(b) 4/DOUBLE(2)的值是多少？

(c) 改正DOUBLE的定义。

4. 针对下面每一个宏，举例说明宏的问题，并提出修改方法。

(a) #define AVG(x,y) (x+y)/2

(b) #define AREA(x,y) (x)*(y)

*5. 下面的宏定义有问题：

#define ABS(a) ((a)<0?-(a):a)

举例说明为什么ABS不能正常工作，并提出修改方法。你可以假定ABS的参数没有副作用。

6. 假定TOUPPER定义成下面的宏：

#define TOUPPER(c) ('a'<=(c)&&(c)<='z'?(c)-'a'+'A':(c))

假设s是一个字符串，i是一个int型变量。给出下面每个代码段所产生的输出。

(a) strcpy(s, "abcd");

i = 0;

putchar(TOUPPER(s[++i]));

(b) strcpy(s, "0123");

i = 0;

putchar(TOUPPER(s[++i]));

7. (a) 编写宏DISP(f,x)，使其扩展后调用printf函数来显示函数f的参数为x时的值。例如：

DISP(sqrt, 3.0);

应该扩展为

printf("sqrt(%g) = %g\n", 3.0, sqrt(3.0));

(b) 编写宏DISP2(f,x,y)，类似DISP但应用于有两个参数的函数。

*8. 假定GENERIC_MAX是如下宏：

#define GENERIC_MAX(type) \

type type##_max(type x, type y) \

{ \

return x > y ? x : y; \

}

(a) 写出GENERIC_MAX(long)被预处理器扩展后的形式。

(b) 解释为什么GENERIC_MAX不能应用在像unsigned long这样的基本类型上？

(c) 如何使GENERIC_MAX对任何基本类型都可以正常工作？提示：不要改变GENERIC_MAX的定义。

*9. 如果需要一个宏，使它展开后包含当前行号和文件名。换而言之，我们会写

const char *str = LINE_FILE;

扩展后为

const char *str = "Line 10 of file foo.c";

其中foo.c是包含程序的文件，10是调用LINE_FILE行的行号。警告：这个练习仅针对高级程序员。尝试编写前请认真阅读“问与答”小节的内容！

14.4节

10. 假定宏M有如下定义：

#define M 10

下面哪项测试会失败？

(a) #if M

(b) #ifdef M

(c) #ifndef M

(d) #if defined(M)

(e) #if !defined(M)

11. (a) 指出下面的程序预处理后的形式。

#define N 100

void f(void);

main()

{

f();

#ifdef N

#unde f N

#endif

return 0;

}

void f(void)

{

#if defined(N)

printf("N is %d\n", N);

#else

printf("N is undefined\n");

#endif

}

(b) 这个程序的输出是什么？

12. 指出下面的程序预处理后的形式。其中有几行可能会导致编译错误，请找出这些错误。

#define N = 10

#define INC(x) x+l

#define SUB (x,y) x - y

#define SQR(x) ((x)*(x))

#define CUBE(x) (SQR(x)*(x))

#deflne Ml(x,y) x##y

#define M2(x,y) #x #y

main ( )

{

int a[N], i, j, k, m;

#ifdef N

i = j;

#else

j = i;

#endif

i = 10 * INC(j);

i = SUB(j, k);

i = SQR(SQR(j++)) ;

i = CUBE(j);

i = M1(j, k);

puts(M2(i, j)) ;

#undef SQR

i = SQR(j);

#define SQR

i = SQR(j);

return 0 ;

)


  有些程序在调试、兼容性、平台移植等情况下可能想要通过简单地设置一些参数就生成一个不同的软件，这当然可以通过变量设置，把所有可能用到的代码都写进去，在初始化时配置，但在不同的情况下可能只用到一部分代码，就没必

    有些程序在调试、兼容性、平台移植等情况下可能想要通过简单地设置一些参数就生成一个不同的软件，这当然可以通过变量设置，把所有可能用到的代码都写进去，在初始化时配置，但在不同的情况下可能只用到一部分代码，就没必要把所有的代码都写进去，就可以用条件编译，通过预编译指令设置编译条件，在不同的需要时编译不同的代码。

     （一）条件编译方法

     条件编译是通过预编译指令来实现的，主要方法有：

     1、#if， #elif， #else， #endif

  #if　条件　1
  代码段　1
#elif 条件　2
     代码段　2
...
#elif 条件　n
  代码段　n
#else
  代码段 n+1
#endif

     即可以设置不同的条件，在编译时编译不同的代码，预编译指令中的表达式与C语言本身的表达式基本一至如逻辑运算、算术运算、位运算等均可以在预编译指令中使用。之所以能够实现条件编译是因为预编译指令是在编译之前进行处理的，通过预编译进行宏替换、条件选择代码段，然后生成最后的待编译代码，最后进行编译。

     #if的一般含义是，如果#if后面的常量表达式为true，则编译它所控制的代码，如条件1成立时就代码段1，条件1不成立再看条件2是否成立，如果条件2成立则编译代码段2，否则再依次类推判断其它条件，如果条件1－N都不成力则会编译最后的代码段n+1.

     2、#ifdef， #else， #endif或#ifndef， #else， #endif

     条件编译的另一种方法是用#ifdef与#ifndef命令，它们分别表示“如果有定义”及“如果无定义”。有定义是指在编译此段代码时是否有某个宏通过 #define 指令定义的宏，#ifndef指令指找不到通过#define定义的某宏，该宏可以是在当前文件此条指令的关面定义的，也可以是在其它文件中，但在此指令之前包含到该文件中的。
     #ifdef的一般形式是：

  #ifdef macro_name
       代码段　1
#else
       代码段　2
#endif

或
#ifdef的一般形式是：

#ifndef macro_name
       代码段　2
#else
       代码段　1
#endif

     这两段代码的效果是完全一样的。

     3、通过宏函数defined（macro_name）

     参数为宏名（无需加""），如果该macro_name定义过则返回真，否则返回假，用该函数则可以写比较复杂的条件编译指令如

  #if defined(macro1) || (!defined(macro2) && defined(macro3))
...
#else
...
#endif

     （二）条件编译技巧与示例

     （1）#ifdef和#defined（）比较

     首先比较一下这两种方法，第一种方法只能判断一个宏，如果条件比较复杂实现起来比较烦锁，用后者就比较方便。如有两个宏MACRO_1，MACRO_2只有两个宏都定义过才会编译代码段A，分别实现如下：

  #ifdef MACRO_1
#ifdef MACRO_2
       代码段　A
#endif
#endif

或者
#if defined(MACRO_1) && defined(MACRO_2)
#endif

     同样，要实现更复杂的条件用#ifdef更麻烦，所以推荐使用后者，因为即使当前代码用的是简单的条件编译，以后在维护、升级时可能会增加，用后者可维护性较强。旧的编译器可能没有实现#defined（）指令，C99已经加为标准。要兼容老的编译器，还需用#ifdef指令。

     2、#if与 #ifdef或#if　defined（）比较

     比如自己写了一个printf函数，想通过一个宏MY_PRINTF_EN实现条件编译，用#if可实现如下

  #define MY_PRINTF_EN 1

#if MYS_PRINTF_EN == 1
  int printf(char* fmt, char* args, ...)
{
       ...
}
#endif

     如果宏MY_PRINTF_EN定义为1则编译这段代码，如果宏定义不为1或者没有定义该宏，则不编译这段代码。同样也可以通过#ifdef或者#defined（）实现，如

  #define MY_PRINTF_EN 1

#if defined(MY_PRINTF_EN)
  int printf(char* fmt, char* args, ...)
{
       ...
}
#endif

     在这种情况下两种方法具有异曲同工之妙，但试想如果你为了节约代码写了两个printf函数，在不同情况下使用不同的printf函数，一个是精简版一个是全功能标准版，如：

  #define MY_PRINTF_SIMPLE

#ifdef MY_PRINTF_SIMPLE
     void printf(*str) // 向终端简单地输出一个字符串
{...
}
#endif
#ifdef MY_PRINTF_STANDARD
  int printf(char* fmt, char* args, ...)
{...
}
#endif

同样可以用#if defined()实现
#define MY_PRINTF_SIMPLE

#if defined(MY_PRINTF_SIMPLE)
     void printf(*str) // 向终端简单地输出一个字符串
{
       ...
}
#elif defined(MY_PRINTF_STANDARD)
  int printf(char* fmt, char* args, ...)
{
       ...
}
#endif

     两种方法都可以实现，但可见后者更方便。但试想如果你有三个版本，用前者就更麻烦了，但方法相似，用后者就更方便，但仍需三个宏进行控制，你要住三个宏，改进一下就用#if可以用一个宏直接控制N种情况如：

  #define MY_PRINTF_VERSION　　　　 1

#if MY_PRINTF_VERSION == 1
     void printf(*str) // 向终端简单地输出一个字符串
{
       ...
}
#elif MY_PRINTF_VERSION == 2
  int printf(char* fmt, char* args, ...)
{
       ...
}
#elif MY_PRINTF_VERSION == 3
int printf(unsigned char com_number, char* str)
{
       ...
}
#else
       默认版本
#endif

     这样，你只需修改一下数字就可以完成版本的选择了

     看来好像用#if 比较好了，试想如下情况：你写了一个配置文件叫做config.h用来配置一些宏，通过这些宏来控制代码，如你在config.h的宏

     #define MY_PRINTF_EN 1

     来控制是否需要编译自己的printf函数，而在你的源代码文件printf.c中有如下指令

  ＃i nclude "config.h"
#if MY_PRINTF_EN == 1
  int printf(char* fmt, char* args, ...)
{
       ...
}
#endif

     但这样也会有一个问题，就是如果你忘了在config.h中添加宏MY_PRINTF_EN，那么自己写的printf函数也不会被编译，有些编译器会给出警告：MY_PRINTF_EN未定义。如果你有两个版本的想有一个默认版本，可以在printf.c中这样实现

  #incldue "config.h"
#if !defined(MY_PRINTF_VERSION)
   #define MY_PRINTF_VERSION　　 1
#endif

#if MY_PRINTF_VERSION == 1
     void printf(*str) // 向终端简单地输出一个字符串
{
       ...
}
#elif MY_PRINTF_VERSION == 2
  int printf(char* fmt, char* args, ...)
{
       ...
}
#elif MY_PRINTF_VERSION == 3
int printf(unsigned char com_number, char* str)
{
       ...
}
#endif

     再试想，如果软件升级了，或者有了大的改动，原来有三个版本，现在只剩下两个版本了，如

  #if MY_PRINTF_VERSION == 2
  int printf(char* fmt, char* args, ...)
{
       ...
}
#elif MY_PRINTF_VERSION == 3
int printf(unsigned char com_number, char* str)
{
       ...
}
#endif

     因为这些核心代码不想让使用这些代码的人关心，他们只需要修改config.h文件，那就要在printf.c中实现兼容性。如果以前有人在config.h配置宏MY_PRINTF_VERSION为1，即有

     #define MY_PRINTF_VERSION　　 1

     而现在没有1版本了，要想兼容怎么办？那当然可以用更复杂的条件实现如：

  #if MY_PRINTF_VERSION == 2 || MY_PRINTF_VERSION == 1
  int printf(char* fmt, char* args, ...)
{
       ...
}
#elif MY_PRINTF_VERSION == 3
int printf(unsigned char com_number, char* str)
{
       ...
}
#endif

     不过还有另外一种方法，即使用#undef命令

  #if MY_PRINTF_VERSION == 1
   #undef MY_PRINTF_VERSION
   #define MY_PRINTF_VERSION　 2
#endif
#if MY_PRINTF_VERSION == 2
  int printf(char* fmt, char* args, ...)
{
       ...
}
#elif MY_PRINTF_VERSION == 3
int printf(unsigned char com_number, char* str)
{
       ...
}
#endif

     用#if还有一个好处，如果你把宏名记错了，把MY_PRINTF_EN定义成了MY_PRINT_EN，那么你用#ifdef MY_PRINTF_EN或者#if defined（MY_PRINTF_EN）控制的代码就不能被编译，查起来又不好查，用#if MY_PRINTF_EN ==1控制就很好查，因为你把MY_PRINTF_EN定义成MY_PRINT_EN，则MY_PRINTF_EN实际上没有定义，那么编译器会给出警告#if MY_PRINTF_EN　== 1中的MY_PRINTF_EN没有定义，但错就比较快。


监控组态软件

监控组态软件

    俗称组态软件，译自英文SCADA,即 Supervision,Control and Data Aquisition(数据采集与监视控制)。

    组态软件的应用领域很广，它可以应用于电力系统、给水系统、石油、化工等领域的数据采集与监视控制以及过程控制等诸多领域。在电力系统以及电气化铁道上又称远动系统(RTU System,Remote Terminal Unit)。

    组态软件是指一些数据采集与过程控制的专用软件，它们是在自动控制系统监控层一级的软件平台和开发环境，使用灵活的组态方式，为用户提供快速构建工业自动控制系统监控功能的、通用层次的软件工具。

    组态软件应该能支持各种工控设备和常见的通信协议，并且通常应提供分布式数据管理和网络功能。对应于原有的HMI（人机接口软件，Human Machine Interface）的概念，组态软件应该是一个使用户能快速建立自己的HMI的软件工具，或开发环境。在组态软件出现之前，工控领域的用户通过手工或委托第三方编写HMI应用，开发时间长，效率低，可靠性差；或者购买专用的工控系统，通常是封闭的系统，选择余地小，往往不能满足需求，很难与外界进行数据交互，升级和增加功能都受到严重的限制。组态软件的出现，把用户从这些困境中解脱出来，可以利用组态软件的功能，构建一套最适合自己的应用系统。随着它的快速发展，实时数据库、实时控制、SCADA、通讯及联网、开放数据接口、对I/O设备的广泛支持已经成为它的主要内容，随着技术的发展，监控组态软件将会不断被赋予新的内容。

    国外组态软件：

    1、InTouch：Wonderware是 Invensys plc“生产管理”部的一个运营单位，是全球工业自动化软件的领先供应商。

    网址：http://www.wonderware.com.cn

    Wonderware的InTouch软件是最早进入我国的组态软件。在80年代末、90年代初，基于Windows3.1的InTouch软件曾让我们耳目一新，并且InTouch提供了丰富的图库。但是，早期的InTouch软件采用ＤＤＥ方式与驱动程序通信，性能较差，最新的InTouch7.0版已经完全基于32位的Windows平台，并且提供了OPC支持。

    2、IFix：GE Fanuc 智能设备公司由美国通用电气公司（GE）和日本Fanuc公司合资组建，提供自动化硬件和软件解决方案，帮助用户降低成本，提高效率并增强其盈利能力。

    网址：http://www.gefanuc.com.cn

    Intellution公司以Fix组态软件起家，1995年被爱默生收购，现在是爱默生集团的全资子公司，Fix6.ｘ软件提供工控人员熟悉的概念和操作界面，并提供完备的驱动程序（需单独购买）。Intellution将自己最新的产品系列命名为iFiX，在iFiX中，Intellution提供了强大的组态功能，但新版本与以往的6.ｘ版本并不完全兼容。原有的Script语言改为VBA（Visual Basic For Application），并且在内部集成了微软的VBA开发环境。遗憾的是，Intellution并没有提供6.1版脚本语言到VBA的转换工具。在iFiX中，Intellution的产品与Microsoft的操作系统、网络进行了紧密的集成。Intellution也是OPC（OLE for Process Control）组织的发起成员之一。iFiX的OPC组件和驱动程序同样需要单独购买。

    3、Citech：悉雅特集团（Citect）是世界领先的提供工业自动化系统、设施自动化系统、实时智能信息和新一代 MES 的独立供应商。

    网址：http://www.citect.com.cn

    CiT公司的Citech也是较早进入中国市场的产品。Citech具有简洁的操作方式，但其操作方式更多的是面向程序员，而不是工控用户。Citech提供了类似Ｃ语言的脚本语言进行二次开发，但与iFix不同的是，Citech的脚本语言并非是面向对象的，而是类似于Ｃ语言，这无疑为用户进行二次开发增加了难度。

    4、WinCC：西门子自动化与驱动集团(A&D)是西门子股份公司中最大的集团之一，是西门子工业领域的重要组成部分。

    网址：http://www.ad.siemens.com.cn

    Simens的WinCC也是一套完备的组态开发环境，Simens提供类Ｃ语言的脚本，包括一个调试环境。WinCC内嵌OPC支持，并可对分布式系统进行组态。但WinCC的结构较复杂，用户最好经过Simens的培训以掌握WinCC的应用。

    国内组态软件：

    1.力控ForceControl（北京三维力控科技有限公司）

    网址：http://www.sunwayland.com.cn

    北京三维力控科技有限公司是专业从事监控组态软件研发与服务的高新技术企业，核心软件产品初创于1992年，公司以自主创新为动力，逐渐奠定了在国内市场的领先地位。

    国产监控组态软件唯一的完整的冗余与热备体系设计，完整的分布式网络结构；

    支持控制设备冗余、多重网络冗余，多客户端冗余、冗余的主从站都可以操作；

    人机监控图形界面与过程数据处理分离，内置独立的实时历史数据库；

    实时历史数据库支持Windows/Unix/Linux 操作系统，数据库开放接口支持远程访问；软件具备独立的Web Server，支持 Web Service 接口，支持PDA 终端访问方式；和设备支持多种通讯方式，上千种的驱动程序，支持多协议设备共用一条通讯总线，支持不同通讯链路切换。

    产品发展历程：

    1992年：诞生了力控的DOS版本；

    1994年：基于16位的Windows(3.1)的力控版本形成；

    1996年：基于32位的Windows(95)的力控1.0形成；

    1999年：力控1.2版本推出,并在中国石油大庆天然气公司广泛应用；

    2000年：力控2.0推出，同时出版了《监控组态软件及应用》一书；

    2001年：力控“软”策略PC控制软件推出；

    2002年：力控2.6推出，北京三维力控正式成立；

    2004年：力控3系列软件推出；

    2005年：力控pFieldcomm&reg;网关软件推出；

    2006年：力控5.0版本软件推出；

    2007年：力控6.0版本软件推出；

    2008年：力控6.1版本软件推出；

    产品特点：

    方便、灵活的开发环境，提供各种工程、画面模板、可嵌入各种格式（BMP、GIF、JPG、JPEG、CAD等）的图片，方便画面制作，大大降低了组态开发的工作量；

    高性能实时、历史数据库，快速访问接口在数据库4万点数据负荷时，访问吞吐量可达到20000次/秒；

    强大的分布式报警、事件处理，支持报警、事件网络数据断线存储，恢复功能；

    支持操作图元对象的多个图层，通过脚本可灵活控制各图层的显示与隐藏；

    强大的ACTIVEX控件对象容器，定义了全新的容器接口集，增加了通过脚本对容器对象的直接操作功能，通过脚本可调用对象的方法、属性；

    全新的、灵活的报表设计工具：提供丰富的报表操作函数集、支持复杂脚本控制，包括：脚本调用和事件脚本，可以提供报表设计器，可以设计多套报表模板；

    提供在Internet/Intranet上通过IE浏览器以“瘦”客户端方式来监控工业现场的解决方案；

    支持通过PDA掌上终端在Internet实时监控现场的生产数据，支持通过移动GPRS、CDMA网络与控制设备或其它远程力控节点通讯

    支持控制设备冗余、控制网络冗余、监控服务器冗余、监控网络冗余、监控客户端冗余等多种系统冗余方式。

    全新的高性能实时、历史数据库

    力控6.0重新设计了数据库内核部分。除了采用旋转门算法对历史数据进行压缩外，重新优化设计了磁盘存贮算法以实现对长年形成的海量历史数据的快速查询。支持的数据类型除原有的浮点型、布尔型、字符串型外，新增了对二进制型数据的支持。单台服务器容量可达100,000点，吞吐量支持每秒写入/查询20,000个数据点。历史数据库可在线备份。支持多服务器处理。

    当数据库构成双机冗余系统时增加了从站操作功能，即处于备用状态的从站可作为普通操作员站使用。

    新增数据库快速访问接口DBI

    DBI是一套全新设计开发的实时数据库访问接口。它除了兼容原有接口DbCom的所有接口功能外，还增加了获取数据库结构信息的接口，并增加了动态控制变化数据集功能。由于DBI口采用了快速数据访问机制，数据访问吞吐量可达到20000次/秒，可以适合过程仿真、优化控制、专家诊断等多种行业应用。

    进程管理

    新增的进程管理器可按照配置安全启动、停止各个程序进程，支持进程异常自动处理功能。可远程监测各进程状态，远程启、停进程，远程上传、下载工程应用或更新程序组件。

    报警、事件处理

    强大的分布式报警、事件处理，支持报警、事件网络数据断线存储，恢复功能。

    功能强大的、开放的过程可视化监控平台

    全面提升的HMI

    1）支持HMI图层操作，可灵活控制各图层的显示与隐藏。

    2）重新设计的HMI的对象容器，定义了全新的容器接口集，增加了通过脚本对容器对象的直接操作功能，通过脚本可调用对象的方法、属性。

    3）完善了力控OCX容器的事件型脚本功能，支持更多类型的OCX控件。

    4）增强的SQL脚本。可根据SQL脚本函数返回值判断执行结果、支持模糊查询、支持时间类型字段的处理等。

    灵活的报表生成器

    1）提供丰富的报表操作函数集、支持复杂脚本控制，包括：脚本调用和事件脚本。

    2）兼容Excel工作表文件，提供类Excel的绝大部分功能，包括：编辑功能、计算功能、图表功能等。

    3）支持图表显示自动刷新，可输出多种文件格式： Excel、TXT、PDF、HTML、CSV等。

    4）可实现多层表头，可以实现报表嵌套，可以制作复杂的报表格式。

    5）具备打印、打印预览、页眉页脚打印功能。

    6）报表数据源支持实时数据库和各种关系数据库，可显示、处理实时和历史数据。

    7）提供报表设计器，可以设计多套报表模板。

    对标准组件和图库进行了大量更新与扩充

    1）重新设计、开发实时和历史趋势、XY曲线、多功能报警、历史报表、总貌画面、温控曲线、ADO历史曲线等标准组件。

    2）新增多功能图片显示、GIF透明动画、CAD图形组件等实用图形工具。

    3）优化、改造了文本输入、下拉框、列表框、复选框、多选按钮、起始时间、时间范围、历史追忆等Windows控件。

    4）优化、改造了多媒体播放器、Flash播放器、浏览器、幻灯片等子图。

    5）增加了大批如：PID调节器、手操器等实用子图。

    新一代的网络功能

    1）Web发布不但支持自有的Web服务器，同时支持IIS服务器，开放的Web控件方便用户建立门户网站。

    2）支持掌上PDA终端访问网络服务器，浏览画面。

    3）丰富了XML数据传送接口。

    4）加强了GPRS/CDMA移动网络的并发数据功能，适合大型移动网络数据处理。

    全面升级的I/O调度

    6.0的I/O调度在兼容原有全部驱动程序的前提下，进行了多项功能改进。

    1）通过简单组态配置，即可支持控制设备的的串口、以太网通道冗余以及控制器冗余。

    2）每个驱动可加载独立进程完成采集，彻底消除单进程容易产生的线程安全隐患。

    3)增加驱动管理器，可查看信道、设备的状态信息，信道的通信报文，对信道进行启、停控制。

    4）支持异种通信协议共享同一信道（如：采用RS485协议同时在一个链路上采集多种厂家、不同协议的设备）。

    5）增加了监控数据读写的优先级控制功能。

    2.组态王KingView（北京亚控科技发展有限公司）

    网址：http://www.kingview.com

    性能特点：

    （1）组态王对大容量工程具有高度的可靠性和实时性：

数据采集性能l

网络通讯性能l

画面刷新性能l

脚本执行性能l

    （2）组态王具有易用性特点：操作简单、直观、易理解，符合工控行业使用习惯，简单实用

    （3）组态王具有扩展性：易于进行功能扩展

    （4）组态王具有良好开放性：提供完善的api接口，利于与其他系统对kingview系统的整合，接口内容包括：

实时数据的访问接口l

历史数据的访问接口l

报警数据的访问接口l

安全用户的访问接口l

变量对象的配置接口l

设备对象的配置接口l

用户对象的配置接口l

    （5）组态王7.0具有良好可维护性：具有诊断、监视、记录工具，能够进行快速的故障定位和排除

    （6）组态王7.0具有可定制性：定制OEM版本快速方便

    技术参数：

    （1）实时数据：报警事件冗余切换小于2秒

    （2）历史数据：

存储速度：10000点/sl

存储时间范围：可由客户订制，精确到毫秒级l

查询：历史库查询数据时间小于3秒l

    同一数据可按照年、月、日、时查询，时间相差在秒级以内

历史冗余切换时间小于2秒l

    （3）冗余切换：

IOserver之间切换小于2秒l

IOserver可进行最长7天的数据缓存l

    （4）画面系统

画面最大尺寸可达到10240x7680（或更大）l

画面数目没有限制l

    （5）曲线趋势图：

最多同时显示4个绘图区l

最多同时显示64条曲线l

最多可以同时具有16条数据轴l

可同时具有2条时间轴l

    （6）门户：

可以支持200个以上的客户端同时访问l

    3.realinfo(紫金桥软件技术有限公司)

    网址：http://www.realinfo.com.cn

    紫金桥监控组态软件是紫金桥公司在长期的科研和工程实践中开发的通用工业组态软件。紫金桥组态软件在实际应用中，以其可靠性、方便性和强大的功能得到用户的高度评价，用户已经广泛应用于石化、炼油、汽车、化工、冶金、制药、建材、轻工、造纸、采矿、环保、电力、交通、智能楼宇、仓储、物流、水利等多个行业和领域的过程控制、管理监测、现场监视、远程监视、故障诊断、企业管理、资源计划等系统。

    紫金桥组态软件主要特点：

    客户/服务器体系结构\强大的数据库处理核心\可靠的冗余系统\丰富的IO驱动\逼真的图形系统\功能强大的脚本系统\报表系统\丰富的组件对象\周密的安全管理系统等。

    4.MCGS(北京昆仑通态自动化软件科技有限公司):

    网址：http://www.mcgs.com.cn

    ·全中文可视化组态软件，简洁、大方，使用方便灵活

    ·完善的中文在线帮助系统和多媒体教程

    ·真正的32位程序，支持多任务、多线程，运行于Win95/98/NT/2000平台

    ·提供近百种绘图工具和基本图符，快速构造图形界面

    ·支持数据采集板卡、智能模块、智能仪表、PLC、变频器、网络设备等700多种国内外众多常用设备

    ·支持温控曲线、计划曲线、实时曲线、历史曲线、XY曲线等多种工控曲线

    ·支持ODBC接口，可与SQL Server、Oracle、Access等关系型数据库互联

    ·支持OPC接口、DDE接口和OLE技术，可方便的与其他各种程序和设备互联

    ·提供渐进色、旋转动画、透明位图、流动块等多种动画方式，可以达到良好的动画效果

    ·上千个精美的图库元件，保证快速的构建精美的动画效果

    ·功能强大的网络数据同步、网络数据库同步构建，保证多个系统完美结合

    ·完善的网络体系结构，可以支持最新流行的各种通讯方式，包括电话通讯网，宽带通讯网，ISDN通讯网，GPRS通讯网和无线通讯网

    5.还有Controx（开物）,易控等

    组态软件特点

    随着工业自动化水平的迅速提高，计算机在工业领域的广泛应用，人们对工业自动化的要求越来越高，种类繁多的控制设备和过程监控装置在工业领域的应用，使得传统的工业控制软件已无法满足用户的各种需求。在开发传统的工业控制软件时，当工业被控对象一旦有变动，就必须修改其控制系统的源程序，导致其开发周期长；已开发成功的工控软件又由于每个控制项目的不同而使其重复使用率很低，导致它的价格非常昂贵；在修改工控软件的源程序时，倘若原来的编程人员因工作变动而离去时，则必须同其他人员或新手进行源程序的修改，因而更是相当困难。通用工业自动化组态软件的出现为解决上述实际工程问题提供了一种崭新的方法，因为它能够很好地解决传统工业控制软件存在的种种问题，使用户能根据自己的控制对象和控制目的的任意组态，完成最终的自动化控制工程。

    组态（Configuration）为模块化任意组合。通用组态软件主要特点：

    （1）延续性和可扩充性。用通用组态软件开发的应用程序，当现场（包括硬件设备或系统结构）或用户需求发生改变时，不需作很多修改而方便地完成软件的更新和升级；

    （2）封装性（易学易用），通用组态软件所能完成的功能都用一种方便用户使用的方法包装起来，对于用户，不需掌握太多的编程语言技术（甚至不需要编程技术），就能很好地完成一个复杂工程所要求的所有功能；

    （3）通用性，每个用户根据工程实际情况，利用通用组态软件提供的底层设备（PLC、智能仪表、智能模块、板卡、变频器等）的I/O Driver、开放式的数据库和画面制作工具，就能完成一个具有动画效果、实时数据处理、历史数据和曲线并存、具有多媒体功能和网络功能的工程，不受行业限制。

    监控组态软件2008年最新发展及趋势

    一、背景

    自2000年以来，国内监控组态软件产品、技术、市场都取得了飞快的发展，应用领域日益拓展，用户和应用工程师数量不断增多。充分体现了“工业技术民用化”的发展趋势。

    监控组态软件是工业应用软件的重要组成部分，其发展受到很多因素的制约，归根结底，是应用的带动对其发展起着最为关键的推动作用。

    关于新技术的不断涌现和快速发展对监控组态软件会产生何种影响，有人认为随着技术的发展，通用组态软件会退出市场，例如有的自动化装置直接内嵌“Web Server”实时画面供中控室操作人员访问。

    作者并不这样认为。用户要求的多样化，决定了不可能有哪一种产品囊括全部用户的所有的画面要求，最终用户对监控系统人机界面的需求不可能固定为单一的模式，因此最终用户的监控系统是始终需要“组态”和“定制”的。这就是监控组态软件不可能退出市场的主要原因，因为需求是存在且不断增长的。

    监控组态软件是在信息化社会的大背景下，随着工业IT技术的不断发展而诞生、发展起来的。在整个工业自动化软件大家庭中，监控组态软件属于基础型工具平台。监控组态软件给工业自动化、信息化、及社会信息化带来的影响是深远的，它带动着整个社会生产、生活方式的变化，这种变化仍在继续发展。因此组态软件作为新生事物尚处于高速发展时期，目前还没有专门的研究机构就它的理论与实践进行研究、总结和探讨，更没有形成独立、专门的理论研究机构。

    近5年来，一些与监控组态软件密切相关的技术如OPC、OPC-XML、现场总线等技术也取得了飞速的发展，是监控组态软件发展的有力支撑。

    二、监控组态软件的最新发展情况

    2．1 监控组态软件日益成为自动化硬件厂商争夺的重点

    整个自动化系统中，软件所占比重逐渐提高，虽然组态软件只是其中一部分，但因其渗透能力强、扩展性强，近年来蚕食了很多专用软件的市场。因此，监控组态软件具有很高的产业关联度，是自动化系统进入高端应用、扩大市场占有率的重要桥梁。在这种思路的驱使下，西门子的WinCC在市场上取得巨大成功。目前，国际知名的工业自动化厂商如Rockwell、GE Fanuc、Honeywell、西门子、ABB、施耐德、英维思等均开发了自己的组态软件。

    监控组态软件在DCS操作站软件中所占比重日益提高

    继FOXBORO之后，Euro therm(欧陆)、Delta V、PCS7等DCS系统纷纷使用通用监控组态软件作为操作站。同时，国内的DCS厂家也开始尝试使用监控组态软件作为操作站。以力控科技为代表的ForceControl国产组态软件在这方面也取得了良好的业绩。

    在大学和科研机构，越来越多的人开始从事监控组态软件的相关技术研究

    从国内自动化行业学术期刊来看，以组态软件及与其密切相关的新技术为核心的研究课题呈上升趋势，众多研究人员的存在，是组态软件技术发展及创新的重要活跃因素，也一定能够积累很多技术成果。无论是技术成果还是研究人员，都会遵循金字塔的规律，由基础向高端形成过渡。这些研究人员和他们的研究成果为监控组态软件厂商开发新产品提供了有益的经验借鉴，并开拓他们的思路。

    基于Linux 的监控组态软件及相关技术正在迅速发展之中，以力控科技为代表的厂商相继推出成熟的商品，对组态软件业的格局将产生深远的影响。

    2．2 集成化、定制化

    从软件规模上看，大多数监控组态软件的代码规模超过100万行，已经不属于小型软件的范畴了。从其功能来看，数据的加工与处理、数据管理、统计分析等功能越来越强。

    监控组态软件作为通用软件平台，具有很大的使用灵活性。但实际上很多用户需要“傻瓜”式的应用软件，即需要很少的定制工作量即可完成工程应用。为了既照顾“通用”又兼顾“专用”，监控组态软件拓展了大量的组件，用于完成特定的功能，如批次管理、事故追忆、温控曲线、油井示功图组件、协议转发组件、ODBCRouter、ADO曲线、专家报表、万能报表组件、事件管理、GPRS透明传输组件等。

    2．3 纵向：功能向上、向下延伸

    组态软件处于监控系统的中间位置，向上、向下均具有比较完整的接口，因此对上、下应用系统的渗透能力也是组态软件的一种本能，具体表现为：

    向上

    其管理功能日渐强大，在实时数据库及其管理系统的配合下，具有部分MIS、MES或调度功能。尤以报警管理与检索、历史数据检索、操作日志管理、复杂报表等功能较为常见。

    向下

    日益具备网络管理（或节点管理）功能：在安装有同一种组态软件的不同节点上，在设定完地址或计算机名称后，互相间能够自动访问对方的数据库。组态软件的这一功能，与OPC规范以及IEC61850规约、BACNet等现场总线的功能类似，反映出其网络管理能力日趋完善的发展趋势。

    软PLC、嵌入式控制等功能：除组态软件直接配备软PLC组件外，软PLC组件还作为单独产品与硬件一起配套销售，构成PAC控制器。这类软PLC组件一般都可运行于嵌入式Linux操作系统。

    OPC服务软件：OPC标准简化了不同工业自动化设备之间的互联通讯，无论在国际上还是国外，都已成为广泛认可的互联标准。而组态软件同时具备OPC Server和OPC Client功能，如果将组态软件丰富的设备驱动程序根据用户需要打包为OPCServe单独销售，则既丰富了软件产品种类又满足了用户的这方面需求，加拿大的Matrikon公司即以开发、销售各种OPCServer软件为主要业务，已经成为该领域的领导者。监控组态软件厂商拥有大量的设备驱动程序，因此开展OPCSever软件的定制开发具有得天独厚的优势，三维力控的Multi-OPCServer 产品即属此类。

    工业通信协议网关：它是一种特殊的Gateway，属工业自动化领域的数据链产品。OPC标准适合计算机与工业I/O设备或桌面软件之间的数据通讯，而工业通信协议网关适合在不同的工业I/O设备之间、计算机与I/O设备之间需要进行网段隔离、无人值守、数据保密性强等应用场合的协议转换。市场上有专门从事工业通讯协议网关产品开发、销售的厂商，如Woodhead、prolinx等，但是组态软件厂商将其丰富的I/O驱动程序扩展一个协议转发模块就变成了通讯网关，开发工作的风险和成本极小。Multi_OPCServer和通讯网关pFieldComm都是力控ForceControl组态软件的衍生产品。

    2．4 横向：监控、管理范围及应用领域扩大

    只要同时涉及实时数据通讯（无论是双向还是单向）、实时动态图形界面显示、必要的数据处理、历史数据存储及显示，就存在对组态软件的潜在需求。

    除了大家熟知的工业自动化领域，近几年以下领域已经成为监控组态软件的新增长点：

    设备管理或资产管理（PAM，Plant Asset Management）。此类软件的代表是艾默生公司的设备管理软件AMS。据ARC机构预测，到2009年全球PAM的业务量将达到19亿美元。PAM所包含的范围很广，其共同点是实时采集设备的运行状态，累积设备的各种参数（如运行时间、检修次数、负荷曲线等），及时发现设备隐患、预测设备寿命，提供设备检修建议，对设备进行实时综合诊断。

    先进控制或优化控制系统。在工业自动化系统获得普及以后，为提高控制质量和控制精度，很多用户开始引进先进控制或优化控制系统。这些系统包括自适应控制、（多变量）预估控制、无模型控制器、鲁棒控制、智能控制（专家系统、模糊控制、神经网络等）、其他依据新控制理论而编写的控制软件等。这些控制软件的常项是控制算法，使用监控组态软件主要解决控制软件的人机界面、与控制设备的实时数据通讯等问题。

    工业仿真系统。仿真软件为用户操作模拟对象提供了与实物几乎相同的环境。仿真软件不但节省了巨大的培训成本开销，还提供了实物系统所不具备的智能特性。仿真系统的开发商专长于仿真模块的算法，在实时动态图形显示、实时数据通讯方面不一定有优势，力控&reg;监控组态软件与仿真软件间通过高速数据接口联为一体，在教学、科研仿真应用中应用越来越广泛。

    电网系统信息化建设。电力自动化是监控组态软件的一个重要应用领域，电力是国家的基础行业，其信息化建设是多层次的，由此决定了对组态软件的多层次需求。

    智能建筑：物业管理的主要需求是能源管理（节能）和安全管理，这一管理模式要求建筑物智能设备必须联网，首先有效地解决信息孤岛问题，减少人力消耗，提高应急反应速度和设备预期寿命，智能建筑行业在能源计量、变配电、安防&门禁、消防系统系统联入IBMS服务器方面需求旺盛。

    公共安全监控与管理：公共安全的隐患可造成突发事件应急失当，容易造成城市公共设施瘫痪、人员群死群伤等恶性灾难。公共安全监控包括：

    人防（车站、广场）等市政工程有毒气体浓度监控及火灾报警。

    水文监测：包括水位、雨量、闸位、大坝的实时监控。

    重大建筑物（如桥梁等）健康状态监控：及时发现隐患，预报事故的发生。

    机房动力环境监控：在电信、铁路、银行、证券、海关等行业以及国家重要的机关部门，计算机服务器的正常工作是业务和行政正常进行的必要条件，因此存放计算机服务器的机房重地已经成为监控的重点，监控的内容包括：UPS工作参数及状态、电池组的工作参数及状态、空调机组的运行状态及参数、漏水监测、发电机组监测、环境温湿度监测、环境可燃气体浓度监测、门禁系统监测等。

    城市危险源实时监测：对存放危险源的场所、危险源行踪的监测。避免放射性物质和剧毒物质失控地流通。

    国土资源立体污染监控：对土壤、大气中与农业生产有关的污染物含量进行实时监测，建立立体式实时监测网络。

    城市管网系统实时监控及调度：包括供水管网、燃气管网、供热管网等的监控。

    三、与组态软件密切相关的应用、市场环境变化情况

    组态软件已经成为工业自动化系统的必要组成部分，即“基本单元”或“基本元件”，因此吸引了大型自动化公司纷纷投资开发自有知识产权的组态软件，以期依靠强大的市场产生大批量的销售，从中获取利润。

    目前在国内外市场占有率较高的监控组态软件分别是GE Fanuc的iFix、Wonderware的Intouch、西门子WinCC、Citech和LabView等。中国大陆厂商以力控、亚控等为主，除此外尚有5-10个厂商从事监控组态软件业务。

    在国内市场上，高端市场仍被国外产品垄断。国内产品已经开始抢占一些高端市场，并且所占比例在逐渐增长。

    (1) 组态软件产品本身的变化

    作为通用型工具软件，组态软件在自动化系统中始终处于“承上启下”的地位。用户在涉及工业信息化的项目中，如果涉及到实时数据采集，首先会考虑试用组态软件。正因如此，组态软件几乎应用于所有的工业信息化项目当中。应用的多样性，给组态软件的性能指标、使用方式、接口方式都提出了很多新的要求，也存在一些挑战。这些需求对组态软件系统结构带来的冲击是巨大的，对组态软件的发展起到关键的促进作用。

    功能变迁：仍以人机界面为主，数据采集、历史数据库、报警管理、操作日志管理、权限管理、数据通讯转发成为其基础功能；功能组件呈分化、集成化、功能细分的发展趋势，以适应不同行业、不同用户层次的多方面需求。

    新技术的采用：组态软件的IT化趋势明显，大量的最新计算技术、通讯技术、多媒体技术被用来提高其性能，扩充其功能。

    注重效率：实际上，有的“组态”工作非常繁琐，用户希望通过模板快速生成自己的项目应用。图形模板、数据库模板、设备模板可以让用户以“复制”方式快速生成目标程序。

    组态软件注重数据处理能力和数据吞吐能力的提高：组态软件除了常规的实时数据通讯、人机界面功能外，1万点以上的实时数据历史存储与检索、100个以上C/S或B/S客户端对历史数据库系统的并发访问，对组态软件的性能都是严峻的考验。随着应用深度的提高，这种要求会变得越来越普遍。

    与控制系统硬件捆绑：组态软件与自动控制设备实现无缝集成，为硬件“量身定做”。这表明组态软件的渗透能力逐渐加强，自动化系统从来就离不开软件的支持，而整体解决方案利于硬件产品的销售，也利于厂商控制销售价格。

    例如力控&reg;的DCS专用版，与国内外很多DCS系统实现了捆绑。

    (2)组态软件其他应用环境的变化

    造成组态软件需求增长的另外一个原因是，传感器、数据采集装置、控制器的智能化程度越来越高，实时数据浏览和管理的需求日益高涨，有的用户甚至要求在自己的办公室里监督定货的制造过程。

    类似OPC这样的组织的出现，以及现场总线、尤其是工业以太网的快速发展，大大简化了异种设备间互连、开发I/O设备驱动软件的工作量。I/O驱动软件也逐渐会朝标准化的方向发展。

    通过近十年的发展，以力控科技等为代表的国内监控组态软件，在技术、市场、服务方面已趋于成熟，形成了比较雄厚的市场和技术积累，具备了与国外对手抗衡的本钱。

    新技术的出现，会淘汰一批墨守成规、不思进取的厂商。那些以用户需求为为中心、勇于创新，采用新技术不断满足用户日益增长的潜在需求的厂商会逐渐在市场上取得主动，成为组态软件及相关工业IT产品市场的主导者。

    发展潜力：虽然组态软件的市场潜力巨大，但是要想得到这个市场却并非容易。一方面，用户对组态软件的要求越来越高，用户的应用水平也在同步提高，相应地对软件的品质要求也越来越高；另一方面，组态软件厂商应该前瞻性地研发具有潜在需求的新功能、新产品。因此市场巨大并不代表所有从事组态软件开发的厂商都有均等的机会，机会永远属于少数优秀厂商。

    1.2 为适应新需求未来监控组态软件的分布式体系结构

    前面已经介绍，监控组态软件的规模都在100万行以上，这样庞大的软件系统在结构设计上必氩捎梅植际浇峁埂７植际较低巢⒉皇羌嗫刈樘砑淖ɡ壳昂芏啻笮腿砑低扯际欠植际较低场?

    在组态软件中，重新提起“分布式”这个老话题是必要的，因为规模大于5000点的应用几乎离不开分布式应用的需求。还需要强调，不是因为组态软件缺少分布式结构的产品，而是缺少真正经得起分布式应用考验的产品。

    1.3 目前国内监控组态软件产业发展中存在的问题

    软件是自动化系统的核心与灵魂，组态软件又具有很高的渗透能力和产业关联度。不管从横向还是纵向看，一个自动化系统中，组态软件日益渗透到每个角落，占据越来越多的份额。组态软件越来越多地体现着自动化系统的价值。

    虽然软件是自动化系统的核心与灵，但是组态软件还远未承担起这一角色。组态软件的内涵和外延在不断变化，其在自动化系统中所扮演的角色会逐渐接近这一标准。

    力控ForceControl软件正在努力覆盖自动化软件更多的范畴。

    所以，在自动化系统中国内监控组态软件厂商承载着民族工业自动化产业的未来希望与核心竞争力。监控组态软件厂商要想承担起这样的重任，必须在上图所示各个层次的软件上拥有自己的核心竞争能力，确立在市场上的足够发言权和主动地位。中国的华为公司为我们树立了榜样，只要在后续技术创新、延长软件产品线上能够满足用户日益增长的各种需求，并保持原创性创新的长盛不衰，中国的工业自动化软件产业也一定会创造出工业IT界的华为奇迹。