大端模式、小端模式及其在Qt中的轉換

大端模式和小端模式是計算機中經常涉及到的兩種字節序,也有大端對齊、小端對齊、大尾、小尾等叫法。

一、起源

說起這兩種模式,就不得不提一下大端(Big-endian)和小端(Little-endian)這兩個英文上的起源。

“endian”一詞來源於喬納森·斯威夫特的小說格列佛遊記。Lilliput和Blefuscu這兩個強國在過去的36個月中一直在苦戰。戰爭的原因:大家都知道,喫雞蛋的時候,原始的方法是打破雞蛋較大的一端(Big-End),可以那時的皇帝的祖父由於小時侯喫雞蛋,按這種方法把手指弄破了,因此他的父親,就下令,命令所有的子民喫雞蛋的時候,必須先打破雞蛋較小的一端(Little-End),違令者重罰。然後老百姓對此法令極爲反感,期間發生了多次叛亂,其中一個皇帝因此送命,另一個丟了王位,產生叛亂的原因就是另一個國家Blefuscu的國王大臣煽動起來的,叛亂平息後,就逃到這個帝國避難。據估計,先後幾次有11000餘人情願死也不肯去打破雞蛋較小的端喫雞蛋。這個其實諷刺當時英國和法國之間持續的衝突。(引自http://blog.csdn.net/ce123_zhouwei/article/details/6971544)其中兩種方法喫雞蛋的人分別被稱爲Big-endians和Little-endians。1980年,Danny Cohen在其著名的論文”On Holy Wars and a Plea for Peace”中,爲平息一場關於字節該以什麼樣的順序傳送的爭論,而引用了該詞。

二、存儲模式

接下來就說說爲什麼會有字節序的問題。

計算機在存儲數據的時候,是以字節(byte)爲基本單位來存儲的,因此存儲單字節類型的數據(比如char)不存在字節序的問題。但存儲多字節的數據的時候(比方說4字節的int變量),就涉及到了以一個什麼樣的順序來存儲。下面舉例來說明大端和小端的存儲方式。

定義變量 unsigned long long a=0x1122334455667788 

變量a是一個64位的無符號整數,共需要8個字節來存儲,那麼在兩種模式下是如何存儲的呢?

||--1--||--2--||--3--||--4--||--5--||--6--||--7--||--8--||  地址

||  11 ||  22 ||  33 ||  44 ||  55 ||  66 ||  77 ||  88 ||  大端模式

||  88 ||  77 ||  66 ||  55 ||  44 ||  33 ||  22 ||  11 ||  小端模式

從中很容易可以看出各自的存儲特點。

三、需要注意的幾個問題

1.大端模式和小端模式是以基本類型爲單位的

對於long long a 和 struct{ char a;short b;int c;}二者同樣佔據了8個字節的空間,在存儲上,前者上面已經介紹,後者則是先存儲一個char,空一個字節,然後按照大端/小端模式存儲short,最後按照大端/小端模式存儲int。

2.大端模式與小端模式的實際應用範圍

在我們日常使用的x86架構的計算機中(其他類別的可能會採用大端模式或可配置模式,可以通過查閱資料或者用下文的代碼進行測試),都是使用的小端模式,而網絡字節序是大端模式的。這就使得在網絡通信時進行字節序的轉換變得極爲重要。比方說,通信雙方規定了了通信頭爲一個4字節的魔數(Magic Number),而一方按着大端序的模式發送,一方按着小端序的模式解讀,那麼兩方的通信就會失敗。如果沒有這個魔數,而在內部的數據中出現這樣的問題則會更加的麻煩。

3.文件存儲中的模式

文件的存儲一般都是以字節來進行操作的,因此,在文件中以什麼樣的字節序需要程序的編寫者加以注意。比方說下面的程序:

  1. int a=0x11223344;
  2. FILE *fp;
  3. fp=fopen("test","wb");
  4. fwrite(&a,sizeof(a),1,fp);
  5. fclose(fp);
用十六進制編輯器打開文件之後,我們會發現文件的內容是44332211。原因很簡單,fwrite函數直接把內存中的內容按順序寫入了文件,因此內存中是小端模式存儲的,所以寫入文件也是小端模式。

四、優缺點

大端模式,由於符號位和數值的高位存在地址的低位,會優先被讀到,更容易先確定數據的重要信息。

小端模式,在進行類型轉換的時候不需要調整數據。如int強制轉換到char,計算機不需要做任何調整,直接讀取int的第一個字節即可。

五、大端和小端的檢測

對於大端模式和小端模式的檢測,可以利用上面所說的強制類型轉換。

  1. bool isLittleEndian()
  2. {
  3. short a=0x0061;
  4. if((char)a=='a') return true;
  5. else return false;
  6. }
在查閱資料後,還發現了另外一個方法,利用到了被我遺忘很久的一個數據結構,聯合體union。

  1. bool isLittleEndian()
  2. {
  3. union
  4. {
  5. short a;
  6. char b;
  7. }test;
  8. test.a=0x0061;
  9. if(test.b=='a') return true;
  10. else return false;
  11. }
這個方法利用了聯合體共用內存的特性,因此迴避了強制類型轉換。

六、Qt中大端小端的轉換

Qt中<QtEndian>包含了大端小端轉換的幾個函數

  1. T qFromBigEndian(const uchar * src)
  2. T qFromBigEndian(T src)
  3. T qFromLittleEndian(const uchar * src)
  4. T qFromLittleEndian(T src)
  5. void qToBigEndian(T src, uchar * dest)
  6. T qToBigEndian(T src)
  7. void qToLittleEndian(T src, uchar * dest)
  8. T qToLittleEndian(T src)
下面對這個幾個函數進行簡單的說明。

  1. union{
  2. int a;
  3. char b[4];
  4. }test1,test2;
  5. test1.a=0x61626364;
  6. test2.a=qFromBigEndian(test1.a);
  7. qDebug()<<test1.b[0]<<test1.b[1]<<test1.b[2]<<test1.b[3];
  8. qDebug()<<test2.b[0]<<test2.b[1]<<test2.b[2]<<test2.b[3];
對於qFromBigEndian()函數,它會判斷執行程序的主機的字節序,如果是大端模式的計算機,那麼只是讀取數據,不進行轉換,如果是小端模式的計算機,那麼則進行轉換。

因此我在本機(小端模式)上的的執行結果是:

d c b a

a b c d

可以看出,它將數據進行了轉換。

對於qFromLittleEndian()函數,和前者類似。對於大端模式的計算機進行轉換,對於小端模式的計算機只是讀取數據。



  1. union{
  2. int a;
  3. char b[4];
  4. }test1,test2;
  5. test1.a=0x61626364;
  6. test2.a=qFromLittleEndian(test1.a);
  7. qToBigEndian(test1.a,(uchar*)test2.b);
  8. qDebug()<<test1.b[0]<<test1.b[1]<<test1.b[2]<<test1.b[3];
  9. qDebug()<<test2.b[0]<<test2.b[1]<<test2.b[2]<<test2.b[3];
對於qToBigEndian()函數,也有着上面的規則,對於小端模式的計算機進行轉換,對於大端模式的計算機只進行讀取。

因此,本機(小端模式)的執行結果是:

d c b a

a b c d

對於qToLittleEndian()函數,只對大端模式的計算機進行轉換。


需要注意的是,Qt中的模板T只針對有符號和無符號的整型,對於浮點型(一般也不會用到),需要進行強制類型轉換。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章