CRC原理及實現
CRC(Cyclic Redundancy Check)中文名是迴圈冗餘校驗,由於計算簡單等,被廣泛用於資料校驗,具有很強的檢錯能力。最常見的有網路傳輸中資訊的校驗,但同樣的道理,我們也可以將其應用到軟體中,如:winrar對檔案的校驗。另外,我們還可以將它應用到軟體上面來保護軟體被惡意修改,還可以用在記憶體動態校驗上面。用途多多,變通方式也不一,但基本原理都是這個。
CRC演算法的是以GF(2)(2元素伽羅瓦域)多項式算術為數學基礎的,嗯,聽著是有點恐怖,但別被嚇著了,其實說白了也很簡單。雖然我們大可跳過數學部分用計算機的術語來描述,但我依舊不想跳過這部分,以防後來者忽略數學在計算機中的重要性,能看到數學在計算機的應用也能鼓舞起他們學習數學的信心。無可置疑,很多演算法都源於數學,都以數學為支撐,數學功底在一定程度上決定你能走到的高度。
首先,我們來介紹一下2元素伽羅瓦域及其基本運算。
GF(2)多項式中只有一個變數x,其係數也只有0和1,如:
1*x^6 + 1*x^5 + 0*x^4 + 0*x^3 + 1*x^2 +1*x^1 + 1*x^0
即:
x^6 + x^5 + x^2 + x + 1
(x^n表示x的n次冪)
GF(2)的多項式加減運算其實都一樣:模2運算,運算時不考慮借位和進位。咋一聽好像很高階似的,其實說白了就是異或運算。
0 + 0 = 0 0 - 0 = 0
0 + 1 = 1 0 - 1 = 1
1 + 0 = 1 1 - 0 = 1
1 + 1 = 0 1 - 1 = 0
比如:P1 = x^6 +x^5 + x^2 + x + 1,P2= x^3+ x^2 + 1。P1 + P2 = x^6+ x^5 + x^3+ x。即對應項想加減。
GF(2)的多項式乘除運算也與一般的多項式大體一樣,只是加減時進行的是模2運算。不再詳述,待會看下去就行。
CRC的數學基礎就說到這裡吧。再說下去只會讓大家都懵了,我們還是換一些直白點的語言來描述下CRC原理。
在小學我們就知道,乘法可以通過一直加某個數來得到結果,同樣除法也可以通過一直減某個數來得到結果。看下下面兩個式子:
9/3=3 (餘數是0),即9 – 3 – 3 –3 = 0
9/6 = 1 (餘數是3),即9 – 6 = 3
用二進位制來表示就是:
1001 -->9
0011 - -->3
------------------------------------------------
0110 -->6
0011 - -->3
------------------------------------------------
0011 -->3
0011 - -->3
------------------------------------------------
0000 -->餘數
減了三次,顯然商是3,而最後剛好減完,餘數為0。
再來看下另外一個:
1001 -->9
0110 - -->6
------------------------------------------------
0011 -->3 , 餘數
這次商是1,餘數是3。
很容易吧。但上面的是有借位的,並不是我們之前說的2元素伽羅瓦域的減法。我們再來看看2元素伽羅瓦域的除法,也即CRC的運算。
如果忽略借位,那8/3 =?……?
1000 -->8
11 - -->3
------------------------------------------------
100 -->4
11 - -->3
------------------------------------------------
10 -->2
11 - -->3
------------------------------------------------
1 -->1 , 餘數 ……The CRC
好奇怪,是吧,為什麼從高位開始減呢?我們還原成除法就不奇怪了。
111 -->商
----------
11)1000
11
-------------------
100
11
--------------------
10
11
--------------------
1 -->餘數,The CRC
普通的除法可以還原為熟悉的減法,但2元素伽羅瓦域的除法記得注意點了。從高位開始減。另外,餘數得比除數的字長少,以二進位制來準。不懂?11的字長是2,1的字長是1,這樣懂了吧。而餘數就是我們所關心的CRC。
進行一個CRC運算我們需要選擇一個除數,這個除數我們叫它為“poly”(生成項),寬度W就是最高位的位置,所以我剛才舉的例子中的除數3,這個poly 11的W是1,而不是2。注意最高位總是1,就像普通的數字小數點前面的0可以忽略一樣。這裡還要注意寬度與字長的區別,字長是從1開始計算的,而寬度是從0開始計算的。
現在我們再來看看改進後的CRC除法。
Poly = 1001,寬度W = 3
位串Bitstring = 11110
Bitstring + W zeroes = 11110 + 000 = 11110000
11110000
1001|||| -
-------------
1100|||
1001||| -
------------
1010||
1001|| -
-----------
0110|
0000| -
----------
1100
1001 -
---------
101 --> 5,餘數 --> The CRC!
最後,還有兩點要注意一下。咋這麼多注意啊,別急,我們慢慢來。
1、只有當Bitstring的最高位為1,我們才將它與Poly進行XOR運算,否則我們只是將Bitstring左移一位。其實這很好理解,想下除法運算就明白了,這裡只是用簡單的減法來代替而已。
2、XOR運算的結果就是被操作位串Bitstring與Poly的低W位進行XOR運算,因為最高位總為0。
好了,以上就是CRC的原理了,暈了?那就再看一遍吧。其實也很好理解,也就一個XOR亦或運算。
明白了原理,我們還得講它轉化為程式的思想啊,我們用計算機的思維再來理下頭緒看看。
我們假設待測資料是1101 0110 11,生成項Poly是10011,假設有一個4 bits的暫存器,通過反覆的移位和進行CRC的除法,最終該暫存器中的值就是我們所要求的餘數。
3 2 1 0 Bits
+---+---+---+---+
Pop <-- | | | | | <----- Augmented message(已加0擴張的原始資料)
+---+---+---+---+
1 0 0 1 1 = The Poly 生成項
依據這個模型,我們得到了一個最簡單的演算法:
1、把register中的值置0.
2、把原始的資料後新增w個0.
While (還有剩餘沒有處理的資料)
Begin
把register中的值左移一位,讀入一個新的資料並置於register最低位的位置。
If (如果上一步的左移操作中的移出的一位是1)
register = register XOR Poly.
End
這裡的Poly是10011,字長是5,位寬w是4,則生成最後的CRC的位寬也是4,所以,我們用一個4位的暫存器就可以了。另外,目標位串最高位如果是0的話,則是簡單的移出去。真正參與運算的是1,而1 xor 1 = 0。所以我們可以將Poly最前面的1忽略,以及將Register移出的最高位忽略,因為這裡的運算總是0。
唔~~,好像都解釋一遍了吧,自己在腦海裡面模擬下這個過程。還不懂的就回頭再仔細看看,一些暫存器之類術語不懂的,我也不想解釋了,百度一下吧。
下面看下完整的程式:
#include "StdAfx.h"
#include "stdio.h"
int main()
{
char POLY=0x13; //生成項Poly,0x13=10011b,這樣CRC是4bit
unsigned short data = 0x035B; //待測資料是0x35B = 1101011011b,12bit,注意,資料不是16bit
unsigned short regi = 0x0000; //用0來填充暫存器的值,防止原先的垃圾值影響結果
//按CRC計算的定義,待測資料後加入4位 0,以容納4bit的CRC;
//這樣共有16bit待測資料,從第5bit開始做除法,就要做16-5+1=12次XOR
data <<= 4;
// 按二進位制一位一位地開始計算
for ( int cur_bit = 15; cur_bit >= 0; -- cur_bit ) //處理16次,前4次實際上只是載入資料
{
//判斷最高位是否為1
if ( ( ( regi >> 4 ) & 0x0001 ) == 0x1 ) regi = regi ^ POLY;
regi <<= 1; // 暫存器左移一位
// 讀取目標位串下一位
unsigned short tmp = ( data >> cur_bit ) & 0x0001; //載入待測資料1bit到tmp中,tmp只有1bit
regi |= tmp; //這1bit載入到暫存器中
}
if ( ( ( regi >> 4 ) & 0x0001 ) == 0x1 ) regi = regi ^ POLY; //做最後一次XOR
//這時, regi中的值就是CRC
printf("0x%08X\n",regi);
return 0;
}
執行後列印出結果0x0000000E,也就是二進位制的1110b,結果正確。
唔~~,原理說了,演算法描述了,程式也給出來了。好像都完成了。其實不然,上面這個演算法效率比較低,每次只能處理1bit,這速度得計算到什麼時候啊,雖然計算機速度快,但也經不起耗啊,浪費資源,可恥。
那如何改進呢?如果我們能一次性運算一位元組是不是就會快點了呢?顯然的,但1 xor 1 =0,這很顯然的,一位元組怎麼xor呢,一位元組進行異或可以有256種結果,該怎麼做好呢?我們可以先建立一張表,把這些結果都計算出來,然後放到這張表裡面,為了方便索引,我們用暫存器移出的那一位元組位串來進行索引。聰明的你會說,不對啊,結果還跟Poly生成項有關啊,畢竟xor是個二元運算。呵呵,Poly是一定的,我們只要根據我們程式使用的Poly來生成一個表就行了,或者事先在程式儲存下來。在速度改進的同時,空間佔用會大一點,不過這點空間相對來說還是可以忽略的。
顯然,不同的生成項檢錯能力也不同,找到一個好的生成項需要具備比較好的數學基礎。但不用擔心,很多先驅已經為我們做好這件事。
以下是一些標準的CRC演算法的生成多項式:
標準 |
生成多項式 |
16進製表示 |
CRC12 |
x^12 + x^11 + x^3 + x^2 + x + 1 |
80F |
CRC16 |
x^16 + x^15 + x^2 + 1 |
8005 |
CRC16-CCITT |
x^16 + x^12 + x^5 + 1 |
1021 |
CRC32 |
x^32 + x^26 + x^23 + x^22 + x^16 + x^12 + x^11+ x^10 + x^8 + x^7 + x^5 + x^4 + x^2 + x + 1 |
04C11DB7 |
其實,這個索引表,網上很多的地方都不一樣,很雜亂,很多人都是根據自己的理解來生成一個CRC表,這樣也可以滿足基本的校驗需要。其實這個表有好幾種,這裡只介紹一種,正規查詢表,也就是被正式引用的一種。Winrar對生成的壓縮包裡面的每個檔案都有一個CRC32校驗值,那就是用正規查詢表來生成的。但還不止這樣,還有各種顛倒(reflect)啊之類的,其實正規查詢表本身就是經過反射(reflect)的,有興趣的再下載附件裡面的資料來看看。這裡只給出實現。
先看看CRC32的正規查詢表吧
unsigned LONG CRC32Table[256] = {
0x00000000,0x77073096,0xEE0E612C,0x990951BA,0x076DC419,0x706AF48F,0xE963A535,0x9E6495A3,
0x0EDB8832,0x79DCB8A4,0xE0D5E91E,0x97D2D988,0x09B64C2B,0x7EB17CBD,0xE7B82D07,0x90BF1D91,
0x1DB71064,0x6AB020F2,0xF3B97148,0x84BE41DE,0x1ADAD47D,0x6DDDE4EB,0xF4D4B551,0x83D385C7,
0x136C9856,0x646BA8C0,0xFD62F97A,0x8A65C9EC,0x14015C4F,0x63066CD9,0xFA0F3D63,0x8D080DF5,
0x3B6E20C8,0x4C69105E,0xD56041E4,0xA2677172,0x3C03E4D1,0x4B04D447,0xD20D85FD,0xA50AB56B,
0x35B5A8FA,0x42B2986C,0xDBBBC9D6,0xACBCF940,0x32D86CE3,0x45DF5C75,0xDCD60DCF,0xABD13D59,
0x26D930AC,0x51DE003A,0xC8D75180,0xBFD06116,0x21B4F4B5,0x56B3C423,0xCFBA9599,0xB8BDA50F,
0x2802B89E,0x5F058808,0xC60CD9B2,0xB10BE924,0x2F6F7C87,0x58684C11,0xC1611DAB,0xB6662D3D,
0x76DC4190,0x01DB7106,0x98D220BC,0xEFD5102A,0x71B18589,0x06B6B51F,0x9FBFE4A5,0xE8B8D433,
0x7807C9A2,0x0F00F934,0x9609A88E,0xE10E9818,0x7F6A0DBB,0x086D3D2D,0x91646C97,0xE6635C01,
0x6B6B51F4,0x1C6C6162,0x856530D8,0xF262004E,0x6C0695ED,0x1B01A57B,0x8208F4C1,0xF50FC457,
0x65B0D9C6,0x12B7E950,0x8BBEB8EA,0xFCB9887C,0x62DD1DDF,0x15DA2D49,0x8CD37CF3,0xFBD44C65,
0x4DB26158,0x3AB551CE,0xA3BC0074,0xD4BB30E2,0x4ADFA541,0x3DD895D7,0xA4D1C46D,0xD3D6F4FB,
0x4369E96A,0x346ED9FC,0xAD678846,0xDA60B8D0,0x44042D73,0x33031DE5,0xAA0A4C5F,0xDD0D7CC9,
0x5005713C,0x270241AA,0xBE0B1010,0xC90C2086,0x5768B525,0x206F85B3,0xB966D409,0xCE61E49F,
0x5EDEF90E,0x29D9C998,0xB0D09822,0xC7D7A8B4,0x59B33D17,0x2EB40D81,0xB7BD5C3B,0xC0BA6CAD,
0xEDB88320,0x9ABFB3B6,0x03B6E20C,0x74B1D29A,0xEAD54739,0x9DD277AF,0x04DB2615,0x73DC1683,
0xE3630B12,0x94643B84,0x0D6D6A3E,0x7A6A5AA8,0xE40ECF0B,0x9309FF9D,0x0A00AE27,0x7D079EB1,
0xF00F9344,0x8708A3D2,0x1E01F268,0x6906C2FE,0xF762575D,0x806567CB,0x196C3671,0x6E6B06E7,
0xFED41B76,0x89D32BE0,0x10DA7A5A,0x67DD4ACC,0xF9B9DF6F,0x8EBEEFF9,0x17B7BE43,0x60B08ED5,
0xD6D6A3E8,0xA1D1937E,0x38D8C2C4,0x4FDFF252,0xD1BB67F1,0xA6BC5767,0x3FB506DD,0x48B2364B,
0xD80D2BDA,0xAF0A1B4C,0x36034AF6,0x41047A60,0xDF60EFC3,0xA867DF55,0x316E8EEF,0x4669BE79,
0xCB61B38C,0xBC66831A,0x256FD2A0,0x5268E236,0xCC0C7795,0xBB0B4703,0x220216B9,0x5505262F,
0xC5BA3BBE,0xB2BD0B28,0x2BB45A92,0x5CB36A04,0xC2D7FFA7,0xB5D0CF31,0x2CD99E8B,0x5BDEAE1D,
0x9B64C2B0,0xEC63F226,0x756AA39C,0x026D930A,0x9C0906A9,0xEB0E363F,0x72076785,0x05005713,
0x95BF4A82,0xE2B87A14,0x7BB12BAE,0x0CB61B38,0x92D28E9B,0xE5D5BE0D,0x7CDCEFB7,0x0BDBDF21,
0x86D3D2D4,0xF1D4E242,0x68DDB3F8,0x1FDA836E,0x81BE16CD,0xF6B9265B,0x6FB077E1,0x18B74777,
0x88085AE6,0xFF0F6A70,0x66063BCA,0x11010B5C,0x8F659EFF,0xF862AE69,0x616BFFD3,0x166CCF45,
0xA00AE278,0xD70DD2EE,0x4E048354,0x3903B3C2,0xA7672661,0xD06016F7,0x4969474D,0x3E6E77DB,
0xAED16A4A,0xD9D65ADC,0x40DF0B66,0x37D83BF0,0xA9BCAE53,0xDEBB9EC5,0x47B2CF7F,0x30B5FFE9,
0xBDBDF21C,0xCABAC28A,0x53B39330,0x24B4A3A6,0xBAD03605,0xCDD70693,0x54DE5729,0x23D967BF,
0xB3667A2E,0xC4614AB8,0x5D681B02,0x2A6F2B94,0xB40BBE37,0xC30C8EA1,0x5A05DF1B,0x2D02EF8D
};
這個表其實可以在程式裡生成,下面給出的完整程式碼就是在程式裡面生成的。
#include "StdAfx.h"
#include "stdio.h"
//顛倒字串的順序
unsigned long int Reflect(unsigned long int ulReflect, int nBitCount)
{
unsigned long int value=0;
// 交換bit0和bit7,bit1和bit6,類推
for(int i = 1; i < (nBitCount + 1); i++)
{
if(ulReflect & 1)
value |= 1 << (nBitCount - i);
ulReflect >>= 1;
}
return value;
}
unsigned long CRC32(void * DataBuf,unsigned long ulLen)
{
unsigned long int crc,temp;
unsigned long int ulPolynomial = 0x04c11db7; //標準的CRC32生成項
static unsigned long int crc32_table[256] = {0};
static int IsDoTable = 0;
//檢查是否已生成正規查詢表
if (0 == IsDoTable)
{
for(int i = 0; i <= 0xFF; i++) // 生成CRC32“正規查詢表”
{
temp=Reflect(i, 8);
crc32_table[i]= temp<< 24;
for (int j = 0; j < 8; j++)
{
unsigned long int t1,t2;
unsigned long int flag=crc32_table[i]&0x80000000;
t1=(crc32_table[i] << 1);
if(flag==0)
t2=0;
else
t2=ulPolynomial;
crc32_table[i] =t1^t2 ;
}
crc=crc32_table[i];
crc32_table[i] = Reflect(crc32_table[i], 32);
}
IsDoTable = 1;
}
//計算CRC32值
unsigned long lRes;
unsigned long ii;
unsigned long m_CRC = 0xFFFFFFFF; //暫存器中預置初始值
char *p = (char *)DataBuf;
for(ii=0; ii <ulLen; ii++)
{
m_CRC = crc32_table[( m_CRC^(*(p+ii)) ) & 0xff] ^ (m_CRC >> 8); //計算
}
lRes= ~m_CRC; //取反
return lRes;
}
int main()
{
char DataBuf[512]={0x31,0x32,0x33,0x34}; //待測資料,為Ascii字串"1234"
printf("0x%08X\n",CRC32(DataBuf,4));
return 0;
}
下面是結果:
與WINRAR的完全一樣。
學了兩天CRC了,這只是一些總結。包括基本的原理及最終優化實現,但更深一層的優化演算法沒有細說,如果有讀者想要繼續瞭解更深一層的優化,可以下載附件來看看,在此再次感謝附件的作者。
忘了CSDN的附件是單獨管理的,這裡給出個資源連結吧:http://download.csdn.net/detail/epluguo/5980501
關於CRC的更多資料,而已參閱wiki百科:http://zh.wikipedia.org/zh-cn/%E5%BE%AA%E7%8E%AF%E5%86%97%E4%BD%99%E6%A0%A1%E9%AA%8C
相關文章
- CRC校驗查表法原理及實現(CRC-16)
- CRC演算法原理、推導及實現演算法
- CRC校驗原理簡介及C程式碼實現說明C程式
- CRC校驗原理
- AOP如何實現及實現原理
- 【CRC筆記】CRC-16 MAXIM-DOW C語言實現筆記C語言
- JS實現 CRC加密 ModbusCRC16JS加密
- SpringMVC實現原理及解析SpringMVC
- KVO使用及實現原理
- Promise原理探究及實現Promise
- NNLM原理及Pytorch實現PyTorch
- vue 實現原理及簡單示例實現Vue
- TreeMap原理實現及常用方法
- DES原理及程式碼實現
- LRU cache原理及go實現Go
- JSONP 跨域原理及實現JSON跨域
- MapReduce原理及簡單實現
- 【CRC校驗方法】+【FPGA實現(傳送端)】FPGA
- 求助:EXCEL,VB,實現 CRC16 校驗Excel
- CGLib動態代理原理及實現CGLib
- consistent hash 原理,優化及實現優化
- Svm演算法原理及實現演算法
- RPC基本原理及實現RPC
- HashMap實現原理及原始碼分析HashMap原始碼
- ARouter原理剖析及手動實現
- synchronized實現原理及鎖優化synchronized優化
- simple-mybatis的原理及實現MyBatis
- ES6 Promise 及實現原理Promise
- 單頁面路由原理及實現路由
- 分散式鎖的實現及原理分散式
- async/await 原理及簡單實現AI
- java中的鎖及實現原理Java
- 常見排序原理及 python 實現排序Python
- synchronized實現原理及ReentrantLock原始碼synchronizedReentrantLock原始碼
- USB中TOKEN的CRC5與CRC16校驗(神奇的工具生成Verilog實現)
- C++ 多型的實現及原理C++多型
- 【資料結構】ArrayList原理及實現資料結構
- 淺談Generator和Promise原理及實現Promise
- Vue 3 響應式原理及實現Vue