資料在記憶體中儲存的方式:大端模式與小端模式

weixin_34321977發表於2016-06-13

以下內容大多數來自百度百科,很容易理解的.

什麼是大端模式,什麼是小端模式?

所謂的大端模式(Big-endian),是指資料的高位元組,儲存在記憶體的低地址中,而資料的低位元組,儲存在記憶體的高地址中,這樣的儲存模式有點兒類似於把資料當作字串順序處理:地址由小向大增加,而資料從高位往低位放;

所謂小端模式(Little-endian), 是指資料的高位元組儲存在記憶體的高地址中,而資料的低位元組儲存在內在的低地址中,這種儲存模式將地址的高低和資料位 權有效結合起來,高地址部分權值高,低地址部分權值低,和我們的邏輯方法一致;

為什麼有大小端之分:

因為在計算機系統中,我們是以位元組為單位的,每個地址單元都對應著一個位元組,一個位元組為 8bit。但是在C語言中除了8bit的char之外,還有16bit的short型,32bit的long型(要看具體的編譯器),另外,對於位數大於 8位的處理器,例如16位或者32位的處理器,由於暫存器寬度大於一個位元組,那麼必然存在著一個如何將多個位元組安排的問題。因此就導致了大端儲存模式和小端儲存模式。我們常用的X86結構是小端模式,而KEIL C51則為大端模式。很多的ARM,DSP都為小端模式。有些ARM處理器還可以由硬體來選擇是大端模式還是小端模式。

用圖來形象地說明一下:

QQ截圖20160613120912QQ截圖20160613120931

如何檢測自己電腦是大端還是小端模式:

輸入以下程式,即可以檢測:

#include<stdio.h>
int main()
{
short int x;
char x0, x1;
x = 0x1122;
x0 = *((char *)&x);      //把x的低位地址的值賦給x0;
x1 = *((char *)&x + 1);      //把x的高位地址的值賦給x1;
if( x0 == 0x11 && x1 == 0x22)
printf(" This is big-endian \n");
else if( x0 == 0x22 && x1 == 0x11)
printf("This is little-endian \n");
else
printf("呵呵,你這個方法有誤啊\n");
return 0;
}

如何把資料轉換呢??

具體要看資料是如何儲存的啦,以我遇到的一個問題為例,在人工手寫體的資料庫中,60000張訓練圖片的檔案為:train-labels-idex1-ubyte.首先說明的是它的儲存格式為大端模式,而我的計算機為小端模式,那我怎麼辦??

我們首先要做的就是知道它內部是如何存放資料的,即多少個字字為一個資料單位.我現在有在matlab讀取檔案的原始碼,如下:

function images = loadMNISTImages(filename)
%loadMNISTImages returns a 28x28x[number of MNIST images] matrix containing
%the raw MNIST images

fp = fopen(filename, 'rb');
assert(fp ~= -1, ['Could not open ', filename, ''])

magic = fread(fp, 1, 'int32', 0, 'ieee-be')
assert(magic == 2051, ['Bad magic number in ', filename, ''])

numImages = fread(fp, 1, 'int32', 0, 'ieee-be');
numRows = fread(fp, 1, 'int32', 0, 'ieee-be');
numCols = fread(fp, 1, 'int32', 0, 'ieee-be');

images = fread(fp, inf, 'unsigned char');
images = reshape(images, numCols, numRows, numImages);
images = permute(images,[2 1 3]);

fclose(fp);

% Reshape to #pixels x #examples
images = reshape(images, size(images, 1) * size(images, 2), size(images, 3));
% Convert to double and rescale to [0,1]
images = double(images) / 255;

end

從上面我們可以看出檔案的開頭為4個 int32 型別的數,後面就是 unsigned char型別的數. 所以得出:int32 佔4個位元組即32個bit, 檔案的前 4 * 4 個位元組需要 由大端模式轉為小端模式,而後面的unsigned char 型別資料本身佔8 個bit, 不需要轉換.以下是如何讀取檔案的原始碼:

#include<stdio.h>
#include<stdlib.h>
int main()
{
    int temp, i, nClose;
    int num1[4];               //用於存放前四個int32的數;
    unsigned char num2[1000];    //用於存放讀出的1000個unsigned char型別的數;
    FILE *fp;
    fp = fopen("train-images-idx3-ubyte","rb");
    if ( NULL == fp )
    {
        printf("Open file error");
        exit(-1);
    }

    fread(num1, 4, 4, fp);        //讀取前4個int32型別的資料;
    for(i=0; i<4; i++)          
    {                             // 由大端模式轉換為小端模式,其實對於佔4個位元組的資料來說,由小端轉大端,也是一樣的程式碼;
        temp = (num1[i]>>24 & 0x000000FF) | (num1[i] >> 8 & 0x0000FF00) | (num1[i] << 8 & 0x00FF0000 ) | (num1[i] << 24 & 0xFF000000); 
        num1[i] = temp;
    }

    fread(num2, 1, 1000, fp);          //讀取1000個char型別的資料;
    
    for(i=0; i<4; i++)              //輸出4個數;
        printf(" %d\n", num1[i]);
    for(i=0; i<1000; i++)           // 輸出1000個數;
        printf("%d    ", num2[i]);

    nClose = fclose(fp);         // 關閉檔案;
    if(EOF == nClose)
    {
        printf("Close file Error\n");
        exit(-1);
    }
    return 0;
}

 

 

最再補充一個轉換32位的更精簡的方法,直接上程式碼(2016年11.28補充),可以由大端轉為小端,也可以由小端轉為大端:

uint32_t swap_endian(uint32_t val) {
      val = ((val << 8) & 0xFF00FF00) | ((val >> 8) & 0xFF00FF);
      return (val << 16) | (val >> 16);
 }

 

 

 

 

 

Reference:

 

  

 

相關文章