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