关于作者

姓名:陈杰

性别:男

出生日期:1982-07-17

地区:陕西-西安

联系电话:

QQ:16226203婚否:未婚
用户名:blueter717
笔名:忧树
地区: 陕西-西安
行业:其他

日历  

快速登录

+ 用户名:
+ 密 码:

在线留言



常用链接

访问统计:
文章个数:26
评论个数:7
留言条数:0




Powered by BlogDriver 2.1

blueter的博客

 

欢迎你来到我的心灵家园! 让我们用心感悟生活,体味别样精彩的人生......

文章

亲爱的,我爱你......  (作者置顶)

- 作者: 忧树 2005年09月17日, 星期六 08:54  回复(0) |  引用(0) 加入博采

关于C语言的一点笔记-2-sizeof

sizeof是一个操作符,而不是一个函数。

sizeof的结果:
  sizeof操作符的结果类型是size_t,它在头文件中typedef为unsigned int类型。该类型保证能容纳实现所建立的最大对象的字节大小。

  1、若操作数具有类型char、unsigned char或signed char,其结果等于1。

  ANSI C正式规定字符类型为1字节。

  2、int、unsigned int 、short int、unsigned short 、long int 、unsigned long 、float、double、long double类型的sizeof 在ANSI C中没有具体规定,大小依赖于实现,一般可能分别为2、2、2、2、4、4、4、8、10。

  3、当操作数是指针时,sizeof依赖于编译器。例如Microsoft C/C++7.0中,near类指针字节数为2,far、huge类指针字节数为4。一般Unix的指针字节数为4。

  4、当操作数具有数组类型时,其结果是数组的总字节数。

  5、联合类型操作数的sizeof是其最大字节成员的字节数。结构类型操作数的sizeof是这种类型对象的总字节数,包括任何垫补在内。

  让我们看如下结构:

  struct {char b; double x;} a;

  在某些机器上sizeof(a)=12,而一般sizeof(char)+ sizeof(double)=9。

  这是因为编译器在考虑对齐问题时,在结构中插入空位以控制各成员对象的地址对齐。如double类型的结构成员x要放在被4整除的地址。

  6、如果操作数是函数中的数组形参或函数类型的形参,sizeof给出其指针的大小。

以下是MSDN中对于sizeof的描述:

sizeof Operator

sizeof expression

The sizeof keyword gives the amount of storage, in bytes, associated with a variable or a type (including aggregate types). This keyword returns a value of type size_t.The expression is either an identifier or a type-cast expression (a type specifier enclosed in parentheses).When applied to a structure type or variable, sizeof returns the actual size, which may include padding bytes inserted for alignment. When applied to a statically dimensioned array, sizeof returns the size of the entire array. The sizeof operator cannot return the size of dynamically allocated arrays or external arrays.

- 作者: 忧树 2006年11月9日, 星期四 20:34  回复(0) |  引用(0) 加入博采

关于C语言的一点笔记-1-typedef

   typedef 用法小结
   来源一:Using typedef to Curb Miscreant Code
            Typedef 声明有助于创建平台无关类型,甚至能隐藏复杂和难以理解的语法。不管怎样,使用 typedef
            能为代码带来意想不到的好处,通过本文你可以学习用 typedef 避免缺欠,从而使代码更健壮。
            typedef 声明,简称 typedef,为现有类型创建一个新的名字。比如人们常常使用 typedef
            来编写更美观和可读的代码。所谓美观,意指 typedef
            能隐藏笨拙的语法构造以及平台相关的数据类型,从而增强可移植性和以及未来的可维护性。本文下面将竭尽全力来揭示 typedef
            强大功能以及如何避免一些常见的陷阱。
            如何创建平台无关的数据类型,隐藏笨拙且难以理解的语法?

            使用 typedefs 为现有类型创建同义字。

            定义易于记忆的类型名
              typedef 使用最多的地方是创建易于记忆的类型名,用它来归档程序员的意图。类型出现在所声明的变量名字中,位于
            ''typedef'' 关键字右边。例如:
            typedef int size;
              此声明定义了一个 int 的同义字,名字为 size。注意 typedef
            并不创建新的类型。它仅仅为现有类型添加一个同义字。你可以在任何需要 int 的上下文中使用 size:
            void measure(size * psz);
            size array[4];
            size len = file.getlength();
            std::vector vs;
              typedef 还可以掩饰符合类型,如指针和数组。例如,你不用象下面这样重复定义有 81 个字符元素的数组:
            char line[81];
            char text[81];
            定义一个 typedef,每当要用到相同类型和大小的数组时,可以这样:
            typedef char Line[81];
            Line text, secondline;
            getline(text);
            同样,可以象下面这样隐藏指针语法:
            typedef char * pstr;
            int mystrcmp(pstr, pstr);
              这里将带我们到达第一个 typedef 陷阱。标准函数 strcmp()有两个‘const char
            *'类型的参数。因此,它可能会误导人们象下面这样声明 mystrcmp():
            int mystrcmp(const pstr, const pstr);
              这是错误的,按照顺序,‘const pstr'被解释为‘char * const'(一个指向 char
            的常量指针),而不是‘const char *'(指向常量 char 的指针)。这个问题很容易解决:
            typedef const char * cpstr;
            int mystrcmp(cpstr, cpstr); // 现在是正确的
            记住:不管什么时候,只要为指针声明 typedef,那么都要在最终的 typedef 名称中加一个
            const,以使得该指针本身是常量,而不是对象。

            代码简化
              上面讨论的 typedef 行为有点像 #define 宏,用其实际类型替代同义字。不同点是 typedef
            在编译时被解释,因此让编译器来应付超越预处理器能力的文本替换。例如:
            typedef int (*PF) (const char *, const char *);
              这个声明引入了 PF 类型作为函数指针的同义字,该函数有两个 const char * 类型的参数以及一个 int
            类型的返回值。如果要使用下列形式的函数声明,那么上述这个 typedef 是不可或缺的:
            PF Register(PF pf);
              Register() 的参数是一个 PF
            类型的回调函数,返回某个函数的地址,其署名与先前注册的名字相同。做一次深呼吸。下面我展示一下如果不用
            typedef,我们是如何实现这个声明的:
            int (*Register (int (*pf)(const char *, const char *)))
            (const char *, const char *);
              很少有程序员理解它是什么意思,更不用说这种费解的代码所带来的出错风险了。显然,这里使用 typedef
            不是一种特权,而是一种必需。持怀疑态度的人可能会问:"OK,有人还会写这样的代码吗?",快速浏览一下揭示 signal()函数的头文件
            ,一个有同样接口的函数。

            typedef 和存储类关键字(storage class specifier)
              这种说法是不是有点令人惊讶,typedef 就像 auto,extern,mutable,static,和 register
            一样,是一个存储类关键字。这并是说 typedef 会真正影响对象的存储特性;它只是说在语句构成上,typedef 声明看起来象
            static,extern 等类型的变量声明。下面将带到第二个陷阱:
            typedef register int FAST_COUNTER; // 错误
              编译通不过。问题出在你不能在声明中有多个存储类关键字。因为符号 typedef 已经占据了存储类关键字的位置,在 typedef
            声明中不能用 register(或任何其它存储类关键字)。

            促进跨平台开发
              typedef 有另外一个重要的用途,那就是定义机器无关的类型,例如,你可以定义一个叫 REAL
            的浮点类型,在目标机器上它可以i获得最高的精度:
            typedef long double REAL;
            在不支持 long double 的机器上,该 typedef 看起来会是下面这样:
            typedef double REAL;
            并且,在连 double 都不支持的机器上,该 typedef 看起来会是这样: 、
            typedef float REAL;
              你不用对源代码做任何修改,便可以在每一种平台上编译这个使用 REAL 类型的应用程序。唯一要改的是 typedef
            本身。在大多数情况下,甚至这个微小的变动完全都可以通过奇妙的条件编译来自动实现。不是吗? 标准库广泛地使用 typedef
            来创建这样的平台无关类型:size_t,ptrdiff 和 fpos_t 就是其中的例子。此外,象 std::string 和
            std::ofstream 这样的 typedef
            还隐藏了长长的,难以理解的模板特化语法,例如:basic_string,allocator> 和 basic_ofstream>。

            作者简介
              Danny Kalev 是一名通过认证的系统分析师,专攻 C++ 和形式语言理论的软件工程师。1997 年到 2000 年期间,他是
            C++ 标准委员会成员。最近他以优异成绩完成了他在普通语言学研究方面的硕士论文。
            业余时间他喜欢听古典音乐,阅读维多利亚时期的文学作品,研究 Hittite、Basque 和 Irish Gaelic
            这样的自然语言。其它兴趣包括考古和地理。Danny 时常到一些 C++ 论坛并定期为不同的 C++
            网站和杂志撰写文章。他还在教育机构讲授程序设计语言和应用语言课程。
            来源二:(http://www.ccfans.net/bbs/dispbbs.asp?boardid=30&id=4455)
            C语言中typedef用法
            1. 基本解释

              typedef为C语言的关键字,作用是为一种数据类型定义一个新名字。这里的数据类型包括内部数据类型(int,char等)和自定义的数据类型(struct等)。


              在编程中使用typedef目的一般有两个,一个是给变量一个易记且意义明确的新名字,另一个是简化一些比较复杂的类型声明。

              至于typedef有什么微妙之处,请你接着看下面对几个问题的具体阐述。
             2. typedef & 结构的问题

              当用下面的代码定义一个结构时,编译器报了一个错误,为什么呢?莫非C语言不允许在结构中包含指向它自己的指针吗?请你先猜想一下,然后看下文说明:

            typedef struct tagNode
            {
             char *pItem;
             pNode pNext;
            } *pNode;
              答案与分析:

              1、typedef的最简单使用
            typedef long byte_4;
              给已知数据类型long起个新名字,叫byte_4。

              2、 typedef与结构结合使用
            typedef struct tagMyStruct
            {
             int iNum;
             long lLength;
            } MyStruct;
              这语句实际上完成两个操作:

              1) 定义一个新的结构类型
            struct tagMyStruct
            {
             int iNum;
             long lLength;
            };
              分析:tagMyStruct称为“tag”,即“标签”,实际上是一个临时名字,struct
            关键字和tagMyStruct一起,构成了这个结构类型,不论是否有typedef,这个结构都存在。
              我们可以用struct tagMyStruct varName来定义变量,但要注意,使用tagMyStruct
            varName来定义变量是不对的,因为struct 和tagMyStruct合在一起才能表示一个结构类型。
              2) typedef为这个新的结构起了一个名字,叫MyStruct。
            typedef struct tagMyStruct MyStruct;
              因此,MyStruct实际上相当于struct tagMyStruct,我们可以使用MyStruct varName来定义变量。
              答案与分析
              C语言当然允许在结构中包含指向它自己的指针,我们可以在建立链表等数据结构的实现上看到无数这样的例子,上述代码的根本问题在于typedef的应用。

              根据我们上面的阐述可以知道:新结构建立的过程中遇到了pNext域的声明,类型是pNode,要知道pNode表示的是类型的新名字,那么在类型本身还没有建立完成的时候,这个类型的新名字也还不存在,也就是说这个时候编译器根本不认识pNode。


              解决这个问题的方法有多种:

              1)、
            typedef struct tagNode
            {
             char *pItem;
             struct tagNode *pNext;
            } *pNode;

              2)、
            typedef struct tagNode *pNode;
            struct tagNode
            {
             char *pItem;
             pNode pNext;
            };
              注意:在这个例子中,你用typedef给一个还未完全声明的类型起新名字。C语言编译器支持这种做法。

              3)、规范做法:
            struct tagNode
            {
             char *pItem;
             struct tagNode *pNext;
            };
            typedef struct tagNode *pNode;
             3. typedef & #define的问题

              有下面两种定义pStr数据类型的方法,两者有什么不同?哪一种更好一点?
            typedef char *pStr;
            #define pStr char *;
              答案与分析:
              通常讲,typedef要比#define要好,特别是在有指针的场合。请看例子:
            typedef char *pStr1;
            #define pStr2 char *;
            pStr1 s1, s2;
            pStr2 s3, s4;
              在上述的变量定义中,s1、s2、s3都被定义为char
            *,而s4则定义成了char,不是我们所预期的指针变量,根本原因就在于#define只是简单的字符串替换而typedef则是为一个类型起新名字。


              #define用法例子:
            #define f(x) x*x
            main( )
            {
             int a=6,b=2,c;
             c=f(a) / f(b);
             printf("%d \\n",c);
            }
              以下程序的输出结果是: 36。
              因为如此原因,在许多C语言编程规范中提到使用#define定义时,如果定义中包含表达式,必须使用括号,则上述定义应该如下定义才对:
            #define f(x) (x*x)

              当然,如果你使用typedef就没有这样的问题。
              4. typedef & #define的另一例

              下面的代码中编译器会报一个错误,你知道是哪个语句错了吗?
            typedef char * pStr;
            char string[4] = "abc";
            const char *p1 = string;
            const pStr p2 = string;
            p1++;
            p2++;
              答案与分析:
              是p2++出错了。这个问题再一次提醒我们:typedef和#define不同,它不是简单的文本替换。上述代码中const pStr
            p2并不等于const char * p2。const pStr p2和const long
            x本质上没有区别,都是对变量进行只读限制,只不过此处变量p2的数据类型是我们自己定义的而不是系统固有类型而已。因此,const pStr
            p2的含义是:限定数据类型为char *的变量p2为只读,因此p2++错误。

              #define与typedef引申谈
              1) #define宏定义有一个特别的长处:可以使用 #ifdef
            ,#ifndef等来进行逻辑判断,还可以使用#undef来取消定义。
              2)
            typedef也有一个特别的长处:它符合范围规则,使用typedef定义的变量类型其作用范围限制在所定义的函数或者文件内(取决于此变量定义的位置),而宏定义则没有这种特性。

              5. typedef & 复杂的变量声明
              在编程实践中,尤其是看别人代码的时候,常常会遇到比较复杂的变量声明,使用typedef作简化自有其价值,比如:
              下面是三个变量的声明,我想使用typdef分别给它们定义一个别名,请问该如何做?
            >1:int *(*a[5])(int, char*);
            >2:void (*b[10]) (void (*)());
            >3. doube(*)() (*pa)[9];
              答案与分析:

              对复杂变量建立一个类型别名的方法很简单,你只要在传统的变量声明表达式里用类型名替代变量名,然后把关键字typedef加在该语句的开头就行了。

            >1:int *(*a[5])(int, char*);
            //pFun是我们建的一个类型别名
            typedef int *(*pFun)(int, char*);
            //使用定义的新类型来声明对象,等价于int* (*a[5])(int, char*);
            pFun a[5];

            >2:void (*b[10]) (void (*)());
            //首先为上面表达式蓝色部分声明一个新类型
            typedef void (*pFunParam)();
            //整体声明一个新类型
            typedef void (*pFun)(pFunParam);
            //使用定义的新类型来声明对象,等价于void (*b[10]) (void (*)());
            pFun b[10];

            >3. doube(*)() (*pa)[9];
            //首先为上面表达式蓝色部分声明一个新类型
            typedef double(*pFun)();
            //整体声明一个新类型
            typedef pFun (*pFunParam)[9];
            //使用定义的新类型来声明对象,等价于doube(*)() (*pa)[9];
            pFunParam pa;

- 作者: 忧树 2006年11月9日, 星期四 20:30  回复(0) |  引用(0) 加入博采

CRC算法与实现

CRC算法与实现     bhw98 

摘要: 本文首先讨论了CRC的代数学算法,然后以常见的CRC-ITU为例,通过硬件电路的实现,引出了比特型算法,最后重点介绍了字节型快速查表算法,给出了相应的C语言实现。

关键词: CRC, FCS, 生成多项式, 检错重传

 

 
引言

CRC的全称为Cyclic Redundancy Check,中文名称为循环冗余校验。它是一类重要的线性分组码,编码和解码方法简单,检错和纠错能力强,在通信领域广泛地用于实现差错控制。实际上,除数据通信外,CRC在其它很多领域也是大有用武之地的。例如我们读软盘上的文件,以及解压一个ZIP文件时,偶尔会碰到“Bad CRC”错误,由此它在数据存储方面的应用可略见一斑。

差错控制理论是在代数理论基础上建立起来的。这里我们着眼于介绍CRC的算法与实现,对原理只能捎带说明一下。若需要进一步了解线性码、分组码、循环码、纠错编码等方面的原理,可以阅读有关资料。

利用CRC进行检错的过程可简单描述为:在发送端根据要传送的k位二进制码序列,以一定的规则产生一个校验用的r位监督码(CRC码),附在原始信息后边,构成一个新的二进制码序列数共k+r位,然后发送出去。在接收端,根据信息码和CRC码之间所遵循的规则进行检验,以确定传送中是否出错。这个规则,在差错控制理论中称为“生成多项式”。


 

1 代数学的一般性算法

在代数编码理论中,将一个码组表示为一个多项式,码组中各码元当作多项式的系数。例如 1100101 表示为
1·x6+1·x5+0·x4+0·x3+1·x2+0·x+1,即 x6+x5+x2+1。

设编码前的原始信息多项式为P(x),P(x)的最高幂次加1等于k;生成多项式为G(x),G(x)的最高幂次等于r;CRC多项式为R(x);编码后的带CRC的信息多项式为T(x)。

发送方编码方法:将P(x)乘以xr(即对应的二进制码序列左移r位),再除以G(x),所得余式即为R(x)。用公式表示为
T(x)=xrP(x)+R(x)

接收方解码方法:将T(x)除以G(x),如果余数为0,则说明传输中无错误发生,否则说明传输有误。

举例来说,设信息码为1100,生成多项式为1011,即P(x)=x3+x2,G(x)=x3+x+1,计算CRC的过程为

      xrP(x)     x3(x3+x2)     x6+x5                    x
     -------- = ---------- = -------- = (x3+x2+x) + --------
       G(x)       x3+x+1      x3+x+1                 x3+x+1

即 R(x)=x。注意到G(x)最高幂次r=3,得出CRC为010。

如果用竖式除法,计算过程为

               1110
            -------   
      1011 /1100000     (1100左移3位)
            1011
            ----
             1110
             1011
             -----
              1010
              1011
              -----
               0010
               0000
               ----
                010

因此,T(x)=(x6+x5)+(x)=x6+x5+x, 即 1100000+010=1100010

如果传输无误,

       T(x)     x6+x5+x
      ------ = --------- = x3+x2+x,
       G(x)     x3+x+1

无余式。回头看一下上面的竖式除法,如果被除数是1100010,显然在商第三个1时,就能除尽。

上述推算过程,有助于我们理解CRC的概念。但直接编程来实现上面的算法,不仅繁琐,效率也不高。实际上在工程中不会直接这样去计算和验证CRC。

下表中列出了一些见于标准的CRC资料:

 名称 

 生成多项式 

 简记式* 

 应用举例 

 CRC-4 

 x4+x+1 

  

 ITU G.704 

 CRC-12 

 x12+x11+x3+x+1 

  

  

 CRC-16 

 x16+x12+x2+1 

 1005 

 IBM SDLC 

 CRC-ITU** 

 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统一去掉了,如04C11DB7实际上是104C11DB7。
    ** 前称CRC-CCITT。ITU的前身是CCITT。

2 硬件电路的实现方法

多项式除法,可用除法电路来实现。除法电路的主体由一组移位寄存器和模2加法器(异或单元)组成。以CRC-ITU为例,它由16级移位寄存器和3个加法器组成,见下图(编码/解码共用)。编码、解码前将各寄存器初始化为"1",信息位随着时钟移入。当信息位全部输入后,从寄存器组输出CRC结果。

CRC-ITU


3 比特型算法

上面的CRC-ITU除法电路,完全可以用软件来模拟。定义一个寄存器组,初始化为全"1"。依照电路图,每输入一个信息位,相当于一个时钟脉冲到来,从高到低依次移位。移位前信息位与bit0相加产生临时位,其中bit15移入临时位,bit10、bit3还要加上临时位。当全部信息位输入完成后,从寄存器组取出它们的值,这就是CRC码。

typedef unsigned char bit;
typedef unsigned char byte;
typedef unsigned short u16;
    
typedef union {
    u16 val;
    struct {
        u16 bit0 : 1;
        u16 bit1 : 1;
        u16 bit2 : 1;
        u16 bit3 : 1;
        u16 bit4 : 1;
        u16 bit5 : 1;
        u16 bit6 : 1;
        u16 bit7 : 1;
        u16 bit8 : 1;
        u16 bit9 : 1;
        u16 bit10 : 1;
        u16 bit11 : 1;
        u16 bit12 : 1;
        u16 bit13 : 1;
        u16 bit14 : 1;
        u16 bit15 : 1;
    } bits;
} CRCREGS;
    
// 寄存器组
CRCREGS regs;
    
// 初始化CRC寄存器组:移位寄存器置为全"1"
void crcInitRegisters()
{
    regs.val = 0xffff;
}
    
// CRC输入一个bit
void crcInputBit(bit in)
{
    bit a;
    
    a = regs.bits.bit0 ^ in;
    
    regs.bits.bit0 = regs.bits.bit1;
    regs.bits.bit1 = regs.bits.bit2;
    regs.bits.bit2 = regs.bits.bit3;
    regs.bits.bit3 = regs.bits.bit4 ^ a;
    regs.bits.bit4 = regs.bits.bit5;
    regs.bits.bit5 = regs.bits.bit6;
    regs.bits.bit6 = regs.bits.bit7;
    regs.bits.bit7 = regs.bits.bit8;
    regs.bits.bit8 = regs.bits.bit9;
    regs.bits.bit9 = regs.bits.bit10;
    regs.bits.bit10 = regs.bits.bit11 ^ a;
    regs.bits.bit11 = regs.bits.bit12;
    regs.bits.bit12 = regs.bits.bit13;
    regs.bits.bit13 = regs.bits.bit14;
    regs.bits.bit14 = regs.bits.bit15;
    regs.bits.bit15 = a;
}
    
// 输出CRC码(寄存器组的值)
u16 crcGetRegisters()
{
    return regs.val;
}

crcInputBit中一步一步的移位/异或操作,可以进行简化:

void crcInputBit(bit in)
{
    bit a;
    a = regs.bits.bit0 ^ in;
    regs.val >>= 1;
    if(a) regs.val ^= 0x8408;
}

细心的话,可以发现0x8408和0x1021(CRC-ITU的简记式)之间的关系。由于我们是从低到高输出比特流的,将0x1021左右反转就得到0x8408。将生成多项式写成 G(x)=1+x5+x12+x16,是不是更好看一点?

下面是一个典型的PPP帧。最后两个字节称为FCS(Frame Check Sequence),是前面11个字节的CRC。

FF 03 C0 21 04 03 00 07 0D 03 06 D0 3A

我们来计算这个PPP帧的CRC,并验证它。

    byte ppp[13] = {0xFF, 0x03, 0xC0, 0x21, 0x04, 0x03, 0x00, 0x07, 0x0D, 0x03, 0x06, 0x00, 0x00};
    int i,j;
    u16 result;
    
    /////////// 以下计算FCS
    
    // 初始化
    crcInitRegisters();
    
    // 逐位输入,每个字节低位在先,不包括两个FCS字节
    for(i = 0; i < 11; i++)
    {
        for(j = 0; j < 8; j++)
        {
            crcInputBit((ppp[i] >> j) & 1);
        }
    }
    
    // 得到CRC:将寄存器组的值求反
    result = ~crcGetRegisters();
    
    // 填写FCS,先低后高
    ppp[11] = result & 0xff;
    ppp[12] = (result >> 8) & 0xff;
    
    /////////// 以下验证FCS
    
    // 初始化
    crcInitRegisters();
    
    // 逐位输入,每个字节低位在先,包括两个FCS字节
    for(i = 0; i < 13; i++)
    {
        for(j = 0; j < 8; j++)
        {
            crcInputBit((ppp[i] >> j) & 1);
        }
    }
    
    // 得到验证结果
    result = crcGetRegisters();

可以看到,计算出的CRC等于0x3AD0,与原来的FCS相同。验证结果等于0。初始化为全"1",以及将寄存器组的值求反得到CRC,都是CRC-ITU的要求。事实上,不管初始化为全"1"还是全"0",计算CRC取反还是不取反,得到的验证结果都是0。


4 字节型算法

比特型算法逐位进行运算,效率比较低,不适用于高速通信的场合。数字通信系统(各种通信标准)一般是对一帧数据进行CRC校验,而字节是帧的基本单位。最常用的是一种按字节查表的快速算法。该算法基于这样一个事实:计算本字节后的CRC码,等于上一字节余式CRC码的低8位左移8位,加上上一字节CRC右移8位和本字节之和后所求得的CRC码。如果我们把8位二进制序列数的CRC(共256个)全部计算出来,放在一个表里 ,编码时只要从表中查找对应的值进行处理即可。

CRC-ITU的计算算法如下:
a.寄存器组初始化为全"1"(0xFFFF)。
b.寄存器组向右移动一个字节。
c.刚移出的那个字节与数据字节进行异或运算,得出一个指向值表的索引。
d.索引所指的表值与寄存器组做异或运算。
f.数据指针加1,如果数据没有全部处理完,则重复步骤b。
g.寄存器组取反,得到CRC,附加在数据之后。
         
CRC-ITU的验证算法如下:
a.寄存器组初始化为全"1"(0xFFFF)。
b.寄存器组向右移动一个字节。
c.刚移出的那个字节与数据字节进行异或运算,得出一个指向值表的索引。
d.索引所指的表值与寄存器组做异或运算。
e.数据指针加1,如果数据没有全部处理完,则重复步骤b (数据包括CRC的两个字节)。
f.寄存器组的值是否等于“Magic Value”(0xF0B8),若相等则通过,否则失败。

下面是通用的CRC-ITU查找表以及计算和验证CRC的C语言程序:

// CRC-ITU查找表
const u16 crctab16[] = 
{
    0x0000, 0x1189, 0x2312, 0x329b, 0x4624, 0x57ad, 0x6536, 0x74bf,
    0x8c48, 0x9dc1, 0xaf5a, 0xbed3, 0xca6c, 0xdbe5, 0xe97e, 0xf8f7,
    0x1081, 0x0108, 0x3393, 0x221a, 0x56a5, 0x472c, 0x75b7, 0x643e,
    0x9cc9, 0x8d40, 0xbfdb, 0xae52, 0xdaed, 0xcb64, 0xf9ff, 0xe876,
    0x2102, 0x308b, 0x0210, 0x1399, 0x6726, 0x76af, 0x4434, 0x55bd,
    0xad4a, 0xbcc3, 0x8e58, 0x9fd1, 0xeb6e, 0xfae7, 0xc87c, 0xd9f5,
    0x3183, 0x200a, 0x1291, 0x0318, 0x77a7, 0x662e, 0x54b5, 0x453c,
    0xbdcb, 0xac42, 0x9ed9, 0x8f50, 0xfbef, 0xea66, 0xd8fd, 0xc974,
    0x4204, 0x538d, 0x6116, 0x709f, 0x0420, 0x15a9, 0x2732, 0x36bb,
    0xce4c, 0xdfc5, 0xed5e, 0xfcd7, 0x8868, 0x99e1, 0xab7a, 0xbaf3,
    0x5285, 0x430c, 0x7197, 0x601e, 0x14a1, 0x0528, 0x37b3, 0x263a,
    0xdecd, 0xcf44, 0xfddf, 0xec56, 0x98e9, 0x8960, 0xbbfb, 0xaa72,
    0x6306, 0x728f, 0x4014, 0x519d, 0x2522, 0x34ab, 0x0630, 0x17b9,
    0xef4e, 0xfec7, 0xcc5c, 0xddd5, 0xa96a, 0xb8e3, 0x8a78, 0x9bf1,
    0x7387, 0x620e, 0x5095, 0x411c, 0x35a3, 0x242a, 0x16b1, 0x0738,
    0xffcf, 0xee46, 0xdcdd, 0xcd54, 0xb9eb, 0xa862, 0x9af9, 0x8b70,
    0x8408, 0x9581, 0xa71a, 0xb693, 0xc22c, 0xd3a5, 0xe13e, 0xf0b7,
    0x0840, 0x19c9, 0x2b52, 0x3adb, 0x4e64, 0x5fed, 0x6d76, 0x7cff,
    0x9489, 0x8500, 0xb79b, 0xa612, 0xd2ad, 0xc324, 0xf1bf, 0xe036,
    0x18c1, 0x0948, 0x3bd3, 0x2a5a, 0x5ee5, 0x4f6c, 0x7df7, 0x6c7e,
    0xa50a, 0xb483, 0x8618, 0x9791, 0xe32e, 0xf2a7, 0xc03c, 0xd1b5,
    0x2942, 0x38cb, 0x0a50, 0x1bd9, 0x6f66, 0x7eef, 0x4c74, 0x5dfd,
    0xb58b, 0xa402, 0x9699, 0x8710, 0xf3af, 0xe226, 0xd0bd, 0xc134,
    0x39c3, 0x284a, 0x1ad1, 0x0b58, 0x7fe7, 0x6e6e, 0x5cf5, 0x4d7c,
    0xc60c, 0xd785, 0xe51e, 0xf497, 0x8028, 0x91a1, 0xa33a, 0xb2b3,
    0x4a44, 0x5bcd, 0x6956, 0x78df, 0x0c60, 0x1de9, 0x2f72, 0x3efb,
    0xd68d, 0xc704, 0xf59f, 0xe416, 0x90a9, 0x8120, 0xb3bb, 0xa232,
    0x5ac5, 0x4b4c, 0x79d7, 0x685e, 0x1ce1, 0x0d68, 0x3ff3, 0x2e7a,
    0xe70e, 0xf687, 0xc41c, 0xd595, 0xa12a, 0xb0a3, 0x8238, 0x93b1,
    0x6b46, 0x7acf, 0x4854, 0x59dd, 0x2d62, 0x3ceb, 0x0e70, 0x1ff9,
    0xf78f, 0xe606, 0xd49d, 0xc514, 0xb1ab, 0xa022, 0x92b9, 0x8330,
    0x7bc7, 0x6a4e, 0x58d5, 0x495c, 0x3de3, 0x2c6a, 0x1ef1, 0x0f78,
};
    
// 计算给定长度数据的16位CRC。
u16 GetCrc16(const byte* pData, int nLength)
{
    u16 fcs = 0xffff;    // 初始化
    
    while(nLength>0)
    {
        fcs = (fcs >> 8) ^ crctab16[(fcs ^ *pData) & 0xff];
        nLength--;
        pData++;
    }
    
    return ~fcs;    // 取反
}
    
// 检查给定长度数据的16位CRC是否正确。
bool IsCrc16Good(const byte* pData, int nLength)
{
    u16 fcs = 0xffff;    // 初始化
    
    while(nLength>0)
    {
        fcs = (fcs >> 8) ^ crctab16[(fcs ^ *pData) & 0xff];
        nLength--;
        pData++;
    }
    
    return (fcs == 0xf0b8);  // 0xf0b8是CRC-ITU的"Magic Value"
}

使用字节型算法,前面出现的PPP帧FCS计算和验证过程,可用下面的程序片断实现:

    byte ppp[13] = {0xFF, 0x03, 0xC0, 0x21, 0x04, 0x03, 0x00, 0x07, 0x0D, 0x03, 0x06, 0x00, 0x00};
    u16 result;
    
    // 计算CRC
    result = GetCrc16(ppp, 11);
    
    // 填写FCS,先低后高
    ppp[11] = result & 0xff;
    ppp[12] = (result >> 8) & 0xff;
    
    // 验证FCS
    if(IsCrc16Good(ppp, 13))
    {
        ... ...
    }

该例中数据长度为11,说明CRC计算并不要求数据2字节或4字节对齐。

至于查找表的生成算法,以及CRC-32等其它CRC的算法,可参考RFC 1661, RFC 3309等文档。需要注意的是,虽然CRC算法的本质是一样的,但不同的协议、标准所规定的初始化、移位次序、验证方法等可能有所差别。


结语

CRC是现代通信领域的重要技术之一。掌握CRC的算法与实现方法,在通信系统的设计、通信协议的分析以及软件保护等诸多方面,能发挥很大的作用。如在作者曾经设计的一个多串口数据传输系统中,每串口速率为460kbps,不加校验时误码率大于10-6,加上简单的奇偶校验后性能改善不很明显,利用CRC进行检错重传,误码率降低至10-15以下,满足了实际应用的要求。

- 作者: 忧树 2005年11月23日, 星期三 10:14  回复(1) |  引用(0) 加入博采

作为大学教师,我感到羞耻 (转载)
摘要:我怀着一种特殊的心情迎接教师节的到来。      十多年来,我一直没有明白这个节日对我意味着什么。我没有兴奋也没有激动,更多的是迷惘和反感,我迷惘是因为在这样的国度里,教育正在划向一个无底的深渊,教师正在成为一个悲壮的牺牲群体,给这样一个群体享用一个什么节日,还有什么实际意义?我反感是因为每到这个节日,政府和官员就开始了大规模作秀,媒体也开始假惺惺煽情,“蜡烛”、“春蚕”一类的比喻几乎能够让所有的人背过气去!老一套的表演老一套的说辞,让人不知今夕何年。有点变化的是原来的节日发50元钱,现在涨到100元,原来发白糖毛巾之类,现在可能发的是色拉油或者是百货大楼的代金券,恩赐一般!    查看全文

- 作者: 忧树 2005年09月26日, 星期一 17:48  回复(0) |  引用(0) 加入博采

2005高考零分卷
摘要:如果这篇作文得不到高分,的确出人意料,但是想想作文的内容和观点,可能刺疼某些人,所以可能得低分,也就在情理之中 查看全文

- 作者: 忧树 2005年09月24日, 星期六 01:56  回复(0) |  引用(0) 加入博采

小道周杰伦
摘要:“百事风云榜结束的时候,有记者问周杰伦对大陆疯狂的歌迷有什么感受的时候,周杰伦说:"大陆的歌迷素质很低只会跟风好像一个墙头草,他们根本不懂我的音乐。”记者又问那么为什么还在大陆频繁做宣传难道大陆的歌迷对你来说不重要吗?周杰伦说:“我根本不喜欢来大陆,但是公司安排好了我也没有办法。” 查看全文

- 作者: 忧树 2005年09月22日, 星期四 14:09  回复(0) |  引用(0) 加入博采

北大校诗
摘要:我情愿是一阵风 一阵河底的风...... 查看全文

- 作者: 忧树 2005年09月21日, 星期三 20:35  回复(1) |  引用(0) 加入博采

李敖北京大学演讲(完整版)
摘要:李敖:各位终于看到我了,主任,校长,总裁,各位贵宾,各位老师,各位小朋友!来演讲紧张不紧张,紧张,站在大庭广众面前,很多人可以指挥千军万马的军队,可是你让他讲几句话,他就宋了不敢讲话,什么原因,胆小,美国人打赢南北战争的将军歌兰特,指挥千军万马打赢仗,林垦总统请他上台给他勋章,让他几句话,他讲不出口,为什么?怕这玩意,一讲演就紧张。 前天晚上我编了一个故事,北京大学一个女孩子进了一个小房间,突然看到一个男的在一个小房间里嘴巴里面念念有词,来回走动,这个女孩子就问他,你在干吗,他我在背讲演稿,他说你在哪儿讲演,他说我要在北京大学讲演,女孩子说,你紧张吗?他说我不紧张,女孩子说,如果你不紧张为什么你到女厕所来干什么。这个人就是连战。 查看全文

- 作者: 忧树 2005年09月21日, 星期三 19:00  回复(0) |  引用(0) 加入博采

身有恶,爱无罪
摘要:人与人之间真是可笑啊,在金钱几乎主宰着爱情的今天,真情却在阳光照不到的角落里挣扎。 查看全文

- 作者: 忧树 2005年09月18日, 星期日 15:09  回复(1) |  引用(0) 加入博采

中秋思人

山坡羊.思人

疏星寒月,孤灯小楼,凭栏空叹离人愁;

秋风起,人消瘦,长夜无眠意难收。

不求比翼浮云间。生,共朝暮;死,同一窟。

- 作者: 忧树 2005年09月18日, 星期日 01:41  回复(0) |  引用(0) 加入博采

最能让人省钱的钱包

这种时刻,你会忍心把人家分开嘛?呵呵。

说实话,我很喜欢,可惜不知道哪有卖啊。

- 作者: 忧树 2005年09月16日, 星期五 08:23  回复(0) |  引用(0) 加入博采

因为寂寞——《有一点动心》
摘要:西安,我生命中的下一站。当列车骤然停止在站台边的时候,混乱的思绪才被尖啸着拉回现实之中。对未来的无数猜测,对明天的种种疑虑,如挥之不去的幽灵,步步紧逼。或许是因为那内心最深处的悔意在作祟,我竟感觉不到几许面对一个崭新天地应该有的兴奋和激动。没有多久,当一切都慢慢归于平静的时候,我才如梦醒一般恍然发现:我的大学生活早已开始了! 查看全文

- 作者: 忧树 2005年09月14日, 星期三 22:53  回复(0) |  引用(0) 加入博采

《男儿行》完整版
摘要:偶读小说所得,乃网上一流传甚广的词,作者据查为山东大学的仇圣。每每读罢,均感畅快淋漓,荡气回肠。此文所斥指之处虽甚广,然而听者难生反感之意,犹有敬佩情。士子软弱,所见所闻所作所为,无不令人扼腕叹息,却有此文似当头棒喝,醒众生之梦。虽稍有言过,然拳拳之意令人唏嘘不已。 查看全文

- 作者: 忧树 2005年09月13日, 星期二 12:52  回复(4) |  引用(0) 加入博采

今天是教师节

今天是教师节,祝你节日快乐,老婆!

- 作者: 忧树 2005年09月10日, 星期六 20:40  回复(0) |  引用(0) 加入博采