天天看點

c語言中的位元組序和位元組對齊,位元組序與位元組對齊

一.網絡位元組序與主機位元組序

1.大端和小端存儲

大端(Big Endian):高位存低位址。符合人類的正常思維。網絡位元組序采用大端(網絡傳輸的是位元組流)。

小端(Littile Endian):低位存低位址。

如果将一個32位的整數0x12345678存放到一個整型變量(int)中,這個整型變量采用大端或者小端模式在記憶體中的存儲由下表所示。

---------------------------

位址偏移 大端模式 小端模式

0x00      12     78

0x01      34     56

0x02      56     34

0x03      78     12

---------------------------

如果将一個16位的整數0x1234存放到一個短整型變量(short)中。這個短整型變量在記憶體中的存儲在大小端模式由下表所示。

---------------------------

位址偏移 大端模式 小端模式

0x00      12     34

0x01      34     12

---------------------------

例:

int main(int argc,char** argv)

{

int num = 0x12345678;

unsigned char* pc = (unsigned char*)(&num);

printf("local order:\n");

printf("[0]: 0x%X addr:%u\n", pc[0], &pc[0]);

printf("[1]: 0x%X addr:%u\n", pc[1], &pc[1]);

printf("[2]: 0x%X addr:%u\n", pc[2], &pc[2]);

printf("[3]: 0x%X addr:%u\n", pc[3], &pc[3]);

num = htonl(num);

printf("htonl order:\n");

printf("[0]: 0x%X addr:%u\n", pc[0], &pc[0]);

printf("[1]: 0x%X addr:%u\n", pc[1], &pc[1]);

printf("[2]: 0x%X addr:%u\n", pc[2], &pc[2]);

printf("[3]: 0x%X addr:%u\n", pc[3], &pc[3]);

return 0;

}

SPARC平台上的輸出:

local order:

[0]: 0x12 addr:4290770212 //高位位元組存放在低位址處,則是大端法;

[1]: 0x34 addr:4290770213

[2]: 0x56 addr:4290770214

[3]: 0x78 addr:4290770215 //低位位元組存放在高位址處;

htonl order:

[0]: 0x12 addr:4290770212 //由此看出,主機位元組序與網絡位元組一樣;

[1]: 0x34 addr:4290770213

[2]: 0x56 addr:4290770214

[3]: 0x78 addr:4290770215

X86平台上的輸出:

local order:

[0]: 0x78 addr:4289157020 //低位位元組存放在低位址處,則是小端法;

[1]: 0x56 addr:4289157021

[2]: 0x34 addr:4289157022

[3]: 0x12 addr:4289157023 //高位位元組存放在高位址處;

htonl order:

[0]: 0x12 addr:4289157020 //由此看出,主機位元組序與網絡位元組不一樣;

[1]: 0x34 addr:4289157021

[2]: 0x56 addr:4289157022

[3]: 0x78 addr:4289157023

2.位元組序轉化使用htons()還是使用htonl()?還是兩者都不行?

首先這兩個函數不是随便使用的,單位元組資料無需也不能轉化,2位元組資料隻能使用htons()轉換,4位元組資料隻能使用htonl()

原因:例子:對于2個short資料0x1234和0x5678,存儲如下

---------------------------

位址偏移 大端模式

0x00      12

0x01      34

0x02      56

0x03      78

如果使用long 轉化函數。發送端假如為大端,則0x1234 0x5678使用htonl()轉化成網絡位元組序後,任然為0x1234 0x5678;

接收端如果為小端,使用ntohl()轉化後變為0x5678,0x1234這兩個資料交換了位置。

---------------------------

位址偏移   小端模式

0x00         78

0x01         56    小端0x5678

0x02         34

0x03         12    小端0x1234

---------------------------

是以2位元組和4位元組的轉換不能混用。那麼問題來了,8位元組的double,怎麼轉換?

解答:如果目前主機為小端,接收時先使用ntohl()轉換前4位元組,再使用ntohl()轉換後4位元組,然後交換前後4位元組。

在使用socket傳輸類于T_WfsTpMsg結構體的資料時,msg部分由于轉換時不确定資料類型,給轉換帶來了難度,是以一定要定義好msg的資料類型,友善轉換。msg最好能是字元。

轉化錯了會存在倒序的問題。

3.位元組對齊對位元組序的影響

如結構體

#pragma pack(4)

typedef struct {

char a;

int  b;

}AM;

void main(void)

{

AM a={1,2};

unsigned char *p=(unsigned char *)(&a);

int i=0;

while(i<8)

{

printf("%4X",(INT)p[i]);

cout<

i++;

}

}

列印該結構體資料儲存為:(大端)

1

CC

CC

CC

2

htonl(*((int *)(&a)))==30198988==0x01CCCCCC !=1

是以如果使用&AM指針,像msg一樣用htonl()來轉化,那麼肯定錯了。msg申明了#pragma pack(1),這樣使得資料的存儲是緊湊的,雖然可以規避該問題,但那樣不分資料類型直接全部使用htonl()轉換,任然存在一些問題。

二、位元組對齊

如何修改編譯器的預設對齊值?

1.在VC IDE中,可以這樣修改:[Project]|[Settings],c/c++頁籤Category的Code Generation選項的Struct Member Alignment中修改,預設是8位元組。

2.在編碼時,可以這樣動态修改:#pragma pack(2) .注意:是pragma而不是progma.

位元組對齊可能帶來的隐患:代碼中關于對齊的隐患,很多是隐式的。比如在強制類型轉換的時候。例如:

unsigned int i = 0x12345678;

unsigned char *p=NULL;

unsigned short *p1=NULL;

p=&i;

*p=0x00;

p1=(unsigned short *)(p+1); //在有些CUP的體系結構,不支援位元組非對齊通路。

*p1=0x0000;

最後兩句代碼,從奇數邊界去通路unsignedshort型變量,顯然不符合對齊的規定。

在x86上,類似的操作隻會影響效率,但是在MIPS或者sparc上,可能就是一個error,因為它們要求必須位元組對齊.

如何查找與位元組對齊方面的問題:

如果出現對齊或者指派問題首先檢視

1. 編譯器的big little端設定

2. 看這種體系本身是否支援非對齊通路

3. 如果支援,看設定了對齊與否,如果沒有則看通路時需要加某些特殊的修飾來标志其特殊通路操作。

結構體所占用的存儲單元數的計算(即使用sizeof所計算出來的大小)-

位元組對齊:即各個資料類型的位址從哪裡開始,以及占多少個存儲單元。

2個定理:(解決所有問題)

同步定理:結構體自身和有效對齊與最大成員同步

整數倍定理:結構體或普通資料類型的起始記憶體位址為有效對齊位元組的整數倍,并且結構體或普通資料類型所占位元組數一定是其有效對齊位元組的整數倍

1.首先結構體中的3個對齊概念:

資料類型自身對齊:32位系統中char 1,short 2,int 4,long 4,float 4,double 8

資料類型指定對齊:使用#pragma pack(x) 指定的系統使用x位元組對齊方式。

資料類型有效對齊:資料類型自身對齊與指定對齊中較小者作為資料類型的有效對齊位元組數。

同步定理:結構體自身和有效對齊與最大成員同步

2.各個資料類型所占記憶體的位元組數計算

#pragma pack(x)

char:起始位址(有效對齊)為1和x中較小者,占1個位元組

short: 起始位址(有效對齊)為2和x中較小者,占2個位元組

int: 起始位址(有效對齊)為4和x中較小者,(如果x=2,則位址位于2的整數倍位置),占4個位元組

double: 起始位址(有效對齊)為8和x中較小者,(如果x=4,則位址位于4的整數倍位置),占8個位元組

整數倍定理:結構體或普通資料類型的起始記憶體位址為有效對齊位元組的整數倍,并且結構體或普通資料類型所占位元組數一定是其有效對齊位元組的整數倍

3.結構體所占位元組數計算

分3步計算:

(1)分别計算各個成員所占的記憶體空間(這個對于簡單資料類型int/char等是固定的,但對于結構體需要計算得出)。

(2)以初始位址0為坐标,按有效對齊(起始位址),依次放置各個成員;計算第一個成員到最後一個成員的記憶體長度。

(3)根據定理(結構體所占位元組數一定是其有效對齊位元組的整數倍),補齊記憶體長度,即為最後結構體所占的記憶體長度。

以struct A{int a;char b;short c;char d;};結構體為例#pragma pack(4)

1.各成員所在記憶體位址及所占位元組(該計算與結構體無關):int 0:4;char 4,1;short 6:2,char 8:1   一共占了9個位址空間

2.結構體有效對齊與成員中自身對齊最大的成員的有效對齊相同為:成員中最大的為4,指定對齊為4,故有效對齊為4(兩者中較小者),是以最後結構體所占的記憶體空間一定是4的整數倍

3.是以結構體所占的位元組數為 (9/4+1)*4=12   ,上述結構體與struct A{int a;char b;short c;char d;char e;char f;};所占的空間是一樣的。

計算結構體大小時回答3個問題:

1.各成員占幾個位元組

2.各成員起始位址從哪裡開始(由有效對齊值決定)

3.結構體所占空間是幾的整數倍

struct A{  //#pragma pack(4)

int a; //0:4

char b;//4:1

short c;//6:2

char d;//8:1

int  e;//12:4

char f://16:1  最後占了17個位元組,結構體所占空間是4的整數倍,故該結構體占20個位元組

};

struct A{  //#pragma pack(2)

int a; //0:4

char b;//4:1

short c;//6:2

char d;//8:1

int  e;//10:4

char f://14:1  最後占了15個位元組,結構體所占空間是2的整數倍,故該結構體占16個位元組

};

struct A{  //#pragma pack(1)

int a; //0:4

char b;//4:1

short c;//5:2

char d;//7:1

int  e;//8:4

char f://12:1  最後占了13個位元組,結構體所占空間是1的整數倍,故該結構體占13個位元組

};

時間: 08-12