http://blog.csdn.net/kukumouse/article/details/2270356
(1)對於位域結構中的變量來說,其長度不能跨越字節,也就是說不能超過8位。當然如果設置空白位(無名變量,僅僅用作占位的)是沒有這個限制的。如果一個字節剩下的位長度不夠一個位域,那么從下個字節開始,也可有意置某個位域從下個字節開始。例如:
struct bits4_5
{ //一個從到小的存放順序結構體
unsigned : 10; //從位15向下跳到位5
unsigned bit5:1;
unsigned bit4:1;
};
例二:
struct xx
{
unsigned a:4;
unsigned :2;
unsigned b:4; // 從第二個字節開始
};
(2)不同的編譯器結構存儲的方式不一樣相同,這個同不同的機器內存中存放變量的字節序不同一個道理。我們知道,關於字節序有大端和小端之分。一般來說,Intel處理器使用的是小端(little endian),Moto處理器使用的是大端(記憶中好像就Intel的用小端,其它的基本上都是大端)。小端和大端的區別在於,內存對於變量的存放規則不同。對於小端來說:低位字節存放在內存中相對低地址;高位字節存放在內存中相對高地址。示例如下:一個WORD32內容的變量,占4個字節:Byte3 Byte2 Byte1 Byte0,在內容中存放:
Base Address +0 Byte0
Base Address +1 Byte1
Base Address +2 Byte2
Base Address +3 Byte3
類推,大端存放恰恰相反。這個主要在網絡處理中使用的比較多。因為客戶端因為受到不同的處理器限制,因為要實現不同機器間數據的傳遞正確性,要同一規則。我們在網絡中使用的是網絡字節序,也就是大端,所以網絡上發送字節序要注意,特別是建立socket時目的方的IP地址和Port的表示要進行處理才行。可能有人會問及這里為什么名說到字節序卻沒有提及傳輸的數據。因為我們大部分都是使用字節流byte stream的TCP方式,所以不存在字節序問題。
上面說的是字節序。除此之外,不同的編譯器存儲結構(類)的方式也是不一樣的,有的是采用“從小到大的字節存儲順序”,有的是采用“從大到小的字節存儲順序”。例如下面的結構定義:
struct bits4_5 // 按照從小到大字節存儲順序
{
unsigned :4; // 跳過位0—位3
unsigned bit4:1;
unsigned bit5:1;
};
struct bits4_5 // 按照從大到小字節存儲順序
{
unsigned:10; // 從位15跳過10位至為6
unsigned bit5:1;
unsigned bit4:1;
}
下面看一個簡單的例子,主要是調試前后台程序的時候發現的問題。當時的環境為:前台是跑在單板上的程序,其中單板上運行的是VxWorks實時操作系統;后台的程序是運行在PC機器上,前台單板通過網口和后台連在一個Hub上實現互通。
在后台有結構如下(為了不引起麻煩,修改了其中的變量名稱)typedef struct NetAddr
{
DWORD a:2;
DWORD b:2;
DWORD c:3;
DWORD d:1;
DWORD e:4;
DWORD f:4;
DWORD g:4;
DWORD h:4;
DWORD i:8;
}T_NetAddr;
typedef union VNetAddr
{ // 針對不同的平台使用的地址不同
T_NetAddr tNetAddr;
DWORD dwAddr;
}T_VNetAddr;
在后台中傳遞給前台,由於字節序不一樣,要進行轉換,這里轉換就要求按照字節進行調整了,這個這里就不說了,這里我們只是看看結構體中位域的存放規則。這里我們的PC機器是按照小端進行存放的。
在main里面我們如下調用:
T_VNetAddr net;
net.tNetAddr.a=1;
net.tNetAddr.b=0;
net.tNetAddr.c=0;
net.tNetAddr.d=0;
net.tNetAddr.e=2;
net.tNetAddr.f=9;
net.tNetAddr.g=0;
net.tNetAddr.h=0;
net.tNetAddr.i=128;
我們可以觀察內存中的net變量的值,各個位域的值如上面的賦值所示,存放的時候按照結構體中定義的順序一個挨着一個順序存放(按照從小到大的地址存放順序,最前面定義的位域存放在低地址,后面定義的位域存放在高地址端):
b7 b6 b5 b4 b3 b2 b1 b0
第一個字節: 0 0 0 0 0 0 0 1 b1b0=01 對應於位域a
第二個字節 1 0 0 1 0 0 1 0 b7b6b5b4=9 對應於位域f b3b2b1b0=2對應e
第三個字節 0 0 0 0 0 0 0 0
第四個字節 1 0 0 0 0 0 0 0 b7b6b5b4b3b2b1b0=128對應於i=128
這樣看就比較清晰了,如果我們查看一下net.dwAddr就會發現其值為0x80009201,為什么吶?
結構體存放內部位域的順序是按照小端,前面定義的位域存放在低地址,這樣依次從前到后存放的地址是由低向高的存放。讀取DWORD類型的變量的時候,按照小端,地位字節存放在低地址,高位字節存放在高地址的規則,DWORD類型的dwAddr的字節排列如下:
第四個字節 第三個字節 第二個字節 第一個字節
80 00 92 01
沒有問題!!!
注意:千萬不要誤以為是:01920080(從前往后讀)。
-------------------------------------------------------
最近看到網上的一篇文章,寫的是關於大端小端互相轉換的問題。其中定義了這樣一個宏
#define sw16(x) \
((short)( \
(((short)(x) & (short)0x00ffU) << 8 ) | \
(((short)(x) & (short)0xff00U) >> 8 ) ))
這里實現的是一個交換兩個字節順序。
不知哪位能解釋一下這個,小弟實在是看不懂。先謝謝了!
--------
假設x=0xaabb
(short)(x) & (short)0x00ffU) 與將16位數高8位置0 成了0x00bb 然后<<8 向左移8位后 低8位變成了高8位 低8位補0 結果為 0xbb00
(((short)(x) & (short)0xff00U) >> 8 ) )) 恰好相反 得到的結果為 0x00aa
兩個結果再 或一下 0xbb00 | 0x00aa 就成了 0xbbaa
------------------------------------------------------------
在網絡傳輸時,將long類型先轉化為byte數組,步驟如下:
long l;
byte[] b;
b[0]=(byte)(l>>>24);
b[1]]=(byte)(l>>>16);
b[2]]=(byte)(l>>>8);
b[3]]=(byte)(l);
此時,b[]中就按照網絡字節順序(大端法,即l的高位數據存放在byte[]的低位地址,因為地址是
從低向高發展的)存放着4個bytes的數據
使用OutputStream的public void write(byte[] b,int off,int len)方法來向Socket寫字節流
,寫byte[0]至byte[3]的字節。
java.io
Class OutputStream
write
public abstract void write(int b) throws IOException
-
Writes the specified byte to this output stream. The general contract for
write
is that one byte is written to the output stream. The byte to be written is the eight low-order bits of the argumentb
. The 24 high-order bits ofb
are ignored.Subclasses of
OutputStream
must provide an implementation for this method. -
- Parameters:
-
b
- thebyte
. - Throws:
-
IOException
- if an I/O error occurs. In particular, anIOException
may be thrown if the output stream has been closed.
write
public void write(byte[] b, int off, int len) throws IOException
-
Writes
len
bytes from the specified byte array starting at offsetoff
to this output stream. The general contract forwrite(b, off, len)
is that some of the bytes in the arrayb
are written to the output stream in order; elementb[off]
is the first byte written andb[off+len-1]
is the last byte written by this operation.The
write
method ofOutputStream
calls the write method of one argument on each of the bytes to be written out. Subclasses are encouraged to override this method and provide a more efficient implementation.If
b
isnull
, aNullPointerException
is thrown.If
off
is negative, orlen
is negative, oroff+len
is greater than the length of the arrayb
, then an IndexOutOfBoundsException is thrown. -
- Parameters:
-
b
- the data. -
off
- the start offset in the data. -
len
- the number of bytes to write. - Throws:
-
IOException
- if an I/O error occurs. In particular, anIOException
is thrown if the output stream is closed.
------關於網絡、主機字節順序的文章
http://www-128.ibm.com/developerworks/cn/java/l-datanet/index.html
主機和網絡字節序的轉換
最近使用C#進行網絡開發,需要處理ISO8583報文,由於其中有些域是數值型的,於是在傳輸的時候涉及到了字節序的轉換。字節順序是指占內存多於一個字節類型的數據在內存中的存放順序,通常有兩種字節順序,根據他們所處的位置我們分別稱為主機節序和網絡字節序。
通常我們認為網絡字節序為標准順序,封包的時候,將主機字節序轉換為網絡字節序,拆包的時候要將網絡字節序轉換為主機字節序。原以為還要自己寫函數,其實網絡庫已經提供了。
主機到網絡:short/int/long IPAddress.HostToNetworkOrder(short/int/long)
網絡到主機:short/int/long IPAddress.NetworkToHostOrder(short/int/long)
主機字節序指低字節數據存放在內存低地址處,高字節數據存放在內存高地址處,如:
int x=1; //此時x為主機字節序:[1][0][0][0] 低位到高位
int y=65536 //此時y為主機字節序:[0][0][1][0] 低位到高位
我們通過主機到網絡字節序的轉換函數分別對x和y進行轉換得到他們對應的網絡字節序值,網絡節序是高字節數據存放在低地址處,低字節數據存放在高地址處,如:
int m=IPAddress.HostToNetworkOrder(x);
//此時m為主機字節序:[0][0][0][1] 高位到低位
int n=IPAddress.HostToNetworkOrder(y);
//此時n為主機字節序:[0][1][0][0] 高位到低位
經過轉換以后,我們就可以通過
byte[]btValue=BitConverter.GetBytes(m);
得到一個長度為4的byte數組,然后將這個數組設置到報文的相應位置發送出去即可。
同樣,收到報文后,可以將報文按域拆分,得到btValue,使用
int m=BitConverter.ToInt32(btValue,0);//從btValue的第0位開始轉換
得到該域的值,此時還不能直接使用,應該再用網絡到主機字節序的轉換函數進行轉換:
int x=IPAddress.NetworkToHostOrder(m);
這時得到的x才是報文中的實際值。
---------------------------------------------------
也談字節序問題
http://bigwhite.blogbus.com/logs/2005/09/
一次Sun SPARC到Intel X86的平台移植讓我們的程序遭遇了“字節序問題”,既然遇到了也就不妨深入的學習一下。
一、字節序定義
字節序,顧名思義字節的順序,再多說兩句就是大於一個字節類型的數據在內存中的存放順序(一個字節的數據當然就無需談順序的問題了)。
其實大部分人在實際的開發中都很少會直接和字節序打交道。唯有在跨平台以及網絡程序中字節序才是一個應該被考慮的問題。
在所有的介紹字節序的文章中都會提到字節序分為兩類:Big-Endian和Little-Endian。引用標准的Big-Endian和Little-Endian的定義如下:
a) Little-Endian就是低位字節排放在內存的低地址端,高位字節排放在內存的高地址端。
b) Big-Endian就是高位字節排放在內存的低地址端,低位字節排放在內存的高地址端。
c) 網絡字節序:TCP/IP各層協議將字節序定義為Big-Endian,因此TCP/IP協議中使用的字節序通常稱之為網絡字節序。
其實我在第一次看到這個定義時就很糊塗,看了幾個例子后也很是朦朧。什么高/低地址端?又什么高低位?翻閱了一些資料后略有心得。
二、高/低地址與高低字節
首先我們要知道我們C程序映像中內存的空間布局情況:在《C專家編程》中或者《Unix環境高級編程》中有關於內存空間布局情況的說明,大致如下圖:
----------------------- 最高內存地址 0xffffffff
| 棧底
.
. 棧
.
棧頂
-----------------------
|
|
/|/
NULL (空洞)
/|/
|
|
-----------------------
堆
-----------------------
未初始化的數據
----------------(統稱數據段)
初始化的數據
-----------------------
正文段(代碼段)
----------------------- 最低內存地址 0x00000000
以上圖為例如果我們在棧上分配一個unsigned char buf[4],那么這個數組變量在棧上是如何布局的呢[注1]?看下圖:
棧底 (高地址)
----------
buf[3]
buf[2]
buf[1]
buf[0]
----------
棧頂 (低地址)
現在我們弄清了高低地址,接着我來弄清高/低字節,如果我們有一個32位無符號整型0x12345678(呵呵,恰好是把上面的那4個字節buf看成一個整型),那么高位是什么,低位又是什么呢?其實很簡單。在十進制中我們都說靠左邊的是高位,靠右邊的是低位,在其他進制也是如此。就拿 0x12345678來說,從高位到低位的字節依次是0x12、0x34、0x56和0x78。
高低地址和高低字節都弄清了。我們再來回顧一下Big-Endian和Little-Endian的定義,並用圖示說明兩種字節序:
以unsigned int value = 0x12345678為例,分別看看在兩種字節序下其存儲情況,我們可以用unsigned char buf[4]來表示value:
Big-Endian: 低地址存放高位,如下圖:
棧底 (高地址)
---------------
buf[3] (0x78) -- 低位
buf[2] (0x56)
buf[1] (0x34)
buf[0] (0x12) -- 高位
---------------
棧頂 (低地址)
Little-Endian: 低地址存放低位,如下圖:
棧底 (高地址)
---------------
buf[3] (0x12) -- 高位
buf[2] (0x34)
buf[1] (0x56)
buf[0] (0x78) -- 低位
---------------
棧頂 (低地址)
在現有的平台上Intel的X86采用的是Little-Endian,而像Sun的SPARC采用的就是Big-Endian。
三、例子
測試平台: Sun SPARC Solaris 9和Intel X86 Solaris 9
我們的例子是這樣的:在使用不同字節序的平台上使用相同的程序讀取同一個二進制文件的內容。
生成二進制文件的程序如下:
/* gen_binary.c */
int main() {
FILE *fp = NULL;
int value = 0x12345678;
int rv = 0;
fp = fopen("temp.dat", "wb");
if (fp == NULL) {
printf("fopen error/n");
return -1;
}
rv = fwrite(&value, sizeof(value), 1, fp);
if (rv != 1) {
printf("fwrite error/n");
return -1;
}
fclose(fp);
return 0;
}
讀取二進制文件的程序如下:
int main() {
int value = 0;
FILE *fp = NULL;
int rv = 0;
unsigned char buf[4];
fp = fopen("temp.dat", "rb");
if (fp == NULL) {
printf("fopen error/n");
return -1;
}
rv = fread(buf, sizeof(unsigned char), 4, fp);
if (rv != 4) {
printf("fread error/n");
return -1;
}
memcpy(&value, buf, 4); // or value = *((int*)buf);
printf("the value is %x/n", value);
fclose(fp);
return 0;
}
測試過程:
(1) 在SPARC平台下生成temp.dat文件
在SPARC平台下讀取temp.dat文件的結果:
the value is 12345678
在X86平台下讀取temp.dat文件的結果:
the value is 78563412
(1) 在X86平台下生成temp.dat文件
在SPARC平台下讀取temp.dat文件的結果:
the value is 78563412
在X86平台下讀取temp.dat文件的結果:
the value is 12345678
[注1]
buf[4]在棧的布局我也是通過例子程序得到的:
int main() {
unsigned char buf[4];
printf("the buf[0] addr is %x/n", buf);
printf("the buf[1] addr is %x/n", &buf[1]);
return 0;
}
output:
SPARC平台:
the buf[0] addr is ffbff788
the buf[1] addr is ffbff789
X86平台:
the buf[0] addr is 8047ae4
the buf[1] addr is 8047ae5
兩個平台都是buf[x]所在地址高於buf[y] (x > y)。
----------------------------------------------------
一、大端模式&小端模式
所謂的“大端模式”,是指數據的低位(就是權值較小的后面那幾位)保存在內存的高地址中,而數據的高位,保存在內存的低地址中,這樣的存儲模式有點兒類似於把數據當作字符串順序處理:地址由小向大增加,而數據從高位往低位放;
所謂的“小端模式”,是指數據的低位保存在內存的低地址中,而數據的高位保存在內存的高地址中,這種存儲模式將地址的高低和數據位權有效地結合起來,高地址部分權值高,低地址部分權值低,和我們的邏輯方法一致。
如果將一個32位的整數0x12345678 存放到一個整型變量(int)中,這個整型變量采用大端或者小端模式在內存中的存儲由下表所示。為簡單起見,本文使用OP0表示一個32位數據的最高字節MSB(Most Significant Byte),使用OP3表示一個32位數據最低字節LSB(Least Significant Byte)。
地址偏移 大端模式 小端模式
0x00 12(OP0) 78(OP3)
0x01 34(OP1) 56(OP2)
0x02 56(OP2) 34(OP1)
0x03 78(OP3) 12(OP0)
小端:較高的有效字節存放在較高的存儲器地址,較低的有效字節存放在較低的存儲器地址。
大端:較高的有效字節存放在較低的存儲器地址,較低的有效字節存放在較高的存儲器地址。
采用大小模式對數據進行存放的主要區別在於在存放的字節順序,大端方式將高位存放在低地址,小端方式將高位存放在高地址。采用大端方式進行數據存放符合人類的正常思維,而采用小端方式進行數據存放利於計算機處理。到目前為止,采用大端或者小端進行數據存放,其孰優孰劣也沒有定論。
下面這段代碼可以用來測試一下你的編譯器是大端模式還是小端模式:
short int x;
char x0,x1;
x=0x1122;
x0=((char*)&x)[0]; //低地址單元
x1=((char*)&x)[1]; //高地址單元
若x0=0x11,則是大端; 若x0=0x22,則是小端......
上面的程序還可以看出,數據尋址時,用的是低位字節的地址
二、主機序&網絡序
不同的 CPU 有不同的字節序類型這些字節序是指整數在內存中保存的順序這個叫做主機序,最常見的有兩種:
1、Little endian :將低序字節存儲在起始地址
2、Big endian :將高序字節存儲在起始地址
網絡字節順序是TCP/IP中規定好的一種數據表示格式,它與具體的CPU類型、操作系統等無關,從而可以保證數據在不同主機之間傳輸時能夠被正確解釋。網絡字節順序采用big endian排序方式。
為了進行轉換 bsd socket提供了轉換的函數 有下面四個:
htons 把unsigned short類型從主機序轉換到網絡序
htonl 把unsigned long類型從主機序轉換到網絡序
ntohs 把unsigned short類型從網絡序轉換到主機序
ntohl 把unsigned long類型從網絡序轉換到主機序
在使用little endian的系統中,這些函數會把字節序進行轉換
在使用big endian類型的系統中,這些函數會定義成空宏
同樣,在網絡程序開發時,或是跨平台開發時,也應該注意保證只用一種字節序,不然兩方的解釋不一樣就會產生BUG。
注:
1、網絡與主機字節轉換函數:htons ntohs htonl ntohl (s 就是short l是long h是host n是network)
2、不同的CPU上運行不同的操作系統,字節序也是不同的,參見下表:
處理器 操作系統 字節排序
Alpha 全部 Little endian
HP-PA NT Little endian
HP-PA UNIX Big endian
Intelx86 全部 Little endian <-----x86系統是小端字節序系統
Motorola680x() 全部 Big endian
MIPS NT Little endian
MIPS UNIX Big endian
PowerPC NT Little endian
PowerPC 非NT Big endian <-----PPC系統是大端字節序系統
RS/6000 UNIX Big endian
SPARC UNIX Big endian
IXP1200 ARM核心 全部 Little endian
下面是一個檢驗本機字節序的簡便方法:
//判斷本機的字節序
//返回true表為小段序。返回false表示為大段序
bool am_little_endian ()
{
unsigned short i=1;
return (int)*((char *)(&i)) ? true : false;
}
int main()
{
if(am_little_endian())
{
printf("本機字節序為小段序!\n");
}
else
{
printf("本機字節序為大段序!\n");
}
return 0;
}
三、入棧地址高低問題
堆棧是在內存中指定的一段特殊存儲區,存起始單元的地址叫棧底,當前存儲單元地址叫棧頂,堆棧存儲區一旦指定,棧底就固定不變了,而棧頂是隨入棧、出棧操作呈動態。而不同機型的堆棧設計,有兩種情況:一是每入棧一個數,棧頂地址加1,每出棧一個數,棧頂地址減1,即堆棧區是由內存的低地址向高地址。另一種是每入棧一個數,棧頂地址減1,每出棧一個數,棧頂地址加1,即堆棧區是由內存的高地址向低地址。高地址、低地址是相對而言,即相對地址編碼的大小而言。
===============================================================
參考地址:
http://blog.csdn.net/fatshaw/article/details/5690073
http://hi.baidu.com/%C2%ED%D0%C2%CC%CE/blog/item/b3d8267b89d8cae62f73b35d.html