轉載於:https://blog.csdn.net/usownh/article/details/42614185
大端模式和小端模式是計算機中經常涉及到的兩種字節序,也有大端對齊、小端對齊、大尾、小尾等叫法。
一、起源
說起這兩種模式,就不得不提一下大端(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.文件存儲中的模式
文件的存儲一般都是以字節來進行操作的,因此,在文件中以什么樣的字節序需要程序的編寫者加以注意。比方說下面的程序:
int a=0x11223344; FILE *fp; fp=fopen("test","wb"); fwrite(&a,sizeof(a),1,fp); fclose(fp);
用十六進制編輯器打開文件之后,我們會發現文件的內容是44332211。原因很簡單,fwrite函數直接把內存中的內容按順序寫入了文件,因此內存中是小端模式存儲的,所以寫入文件也是小端模式。
四、優缺點
大端模式,由於符號位和數值的高位存在地址的低位,會優先被讀到,更容易先確定數據的重要信息。
小端模式,在進行類型轉換的時候不需要調整數據。如int強制轉換到char,計算機不需要做任何調整,直接讀取int的第一個字節即可。
五、大端和小端的檢測
對於大端模式和小端模式的檢測,可以利用上面所說的強制類型轉換。
bool isLittleEndian() { short a=0x0061; if((char)a=='a') return true; else return false; }
在查閱資料后,還發現了另外一個方法,利用到了被我遺忘很久的一個數據結構,聯合體union。
bool isLittleEndian() { union { short a; char b; }test; test.a=0x0061; if(test.b=='a') return true; else return false; }
這個方法利用了聯合體共用內存的特性,因此回避了強制類型轉換。
六、Qt中大端小端的轉換
Qt中<QtEndian>包含了大端小端轉換的幾個函數
T qFromBigEndian(const uchar * src) T qFromBigEndian(T src) T qFromLittleEndian(const uchar * src) T qFromLittleEndian(T src) void qToBigEndian(T src, uchar * dest) T qToBigEndian(T src) void qToLittleEndian(T src, uchar * dest) T qToLittleEndian(T src)
下面對這個幾個函數進行簡單的說明。
union{ int a; char b[4]; }test1,test2; test1.a=0x61626364; test2.a=qFromBigEndian(test1.a); qDebug()<<test1.b[0]<<test1.b[1]<<test1.b[2]<<test1.b[3]; qDebug()<<test2.b[0]<<test2.b[1]<<test2.b[2]<<test2.b[3];
對於qFromBigEndian()函數,它會判斷執行程序的主機的字節序,如果是大端模式的計算機,那么只是讀取數據,不進行轉換,如果是小端模式的計算機,那么則進行轉換。
因此我在本機(小端模式)上的的執行結果是:
d c b a
a b c d
可以看出,它將數據進行了轉換。
對於qFromLittleEndian()函數,和前者類似。對於大端模式的計算機進行轉換,對於小端模式的計算機只是讀取數據。
union{ int a; char b[4]; }test1,test2; test1.a=0x61626364; test2.a=qFromLittleEndian(test1.a); qToBigEndian(test1.a,(uchar*)test2.b); qDebug()<<test1.b[0]<<test1.b[1]<<test1.b[2]<<test1.b[3]; 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只針對有符號和無符號的整型,對於浮點型(一般也不會用到),需要進行強制類型轉換。