处理器间通过消息(对于C/C++而言就是定义的结构体)进行通信时需要注意字节对齐以及字节序的问题。
1、字节对齐
1.1字节对齐
某些处理器不允许16位和32位的数据在内存中任意排放。例如,Motorola 68000 处理器不允许16位的字存放在奇地址,否则会触发异常。
通常32位的处理器通过总线访问(包括读和写)内存数据。每个总线访问周期可以访问32位内存数据。内存数据是以8位的字节为单位存放的。
假如一个32位的数据没有在4字节整除的内存地址处存放,那么处理器就需要2个总线周期对其进行访问。通过合理的内存对齐可以提高访问效率。
大多数编译器提供内存对其的选项供用户使用。这样用户可以根据处理器的情况选择不同的字节对齐方式。例如C/C++编译器提供的#pragma pack(n) n=1,2,4等,让编译器在生成目标文件时,使内存数据按照指定的方式排布在1,2,4等字节整除的内存地址处。
然而在不同编译平台,或者不同处理器上,字节对齐会造成消息结构长度的变化。现在假设用户定义结构体如下:
struct Message
{
short opcode;
char subfield;
long message_length;
char version;
short destination_processor;
};
由于为了字节对齐而进行的填充是通过编译器进行的编译器可以将上述结构填充成:
struct Message
{
short opcode;
char subfield;
char pad1; // Pad to start the long word at a 4 byte boundary
long message_length;
char version;
char pad2; // Pad to start a short at a 2 byte boundary
short destination_processor;
char pad3[4]; // Pad to align the complete structure to a 16 byte boundary
};
也可能填充成其他形式。如此,不同编译平台,处理器间数据通信的风险就不必说了吧。
1.2处理方法
以下以32位处理器为例,提出一种内存对齐方法。
对于本地使用的数据结构,为提高内存访问效率,采用四字节对齐方式;同时为了减少内存的开销,合理安排结构成员的位置,减少四字节对齐导致的成员之间的空隙,降低内存开销。
对于处理器之间的数据结构,需要保证消息的长度不因为在不同编译平台和不同处理器导致消息结构的长度发生变化,使用一字节对齐方式对消息结构进行紧缩;为保证处理器之间的消息的数据结构的内存访问效率,采用字节填充的方式自己对消息中成员进行四字节对齐。
数据结构的成员位置要兼顾成员之间的关系、数据访问效率和空间利用率。顺序安排的原则是:四字节的放在最前面,两字节的紧接最后一个四字节成员,一字节紧接最后一个两字节成员,填充字节放在最后。举例如下:
typedef struct tag_T_MSG{
long ParaA;
long ParaB;
short ParaC;
char ParaD;
char Pad; /* 填充字节 */
} T_MSG;
2、字节序:
2.1、字节序
小尾(Little Endian)就是低位字节排放在内存的低端,高位字节排放在内存的高端。例如对于一个4字节的整数Byte3 Byte2 Byte1 Byte0将在内存中按照如下顺序排放:
Base Address+0 Byte0
Base Address+1 Byte1
Base Address+2 Byte2
Base Address+3 Byte3
Intel处理器大多数使用小尾(Little Endian)字节序。
大尾(Big Endian)就是高位字节排放在内存的低端,低位字节排放在内存的高端。例如对于一个4字节的整数Byte3 Byte2 Byte1 Byte0将在内存中按照如下顺序排放:
Base Address+0 Byte3
Base Address+1 Byte2
Base Address+2 Byte1
Base Address+3 Byte0
Motorola处理器大多数使用大尾(Big Endian)字节序。
网络字节序:TCP/IP各层协议将字节序定义为大尾,因此TCP/IP协议中使用的字节序通常称之为网络字节序。
2.2处理方法
网络字节序作为一个标准字节序,如果系统并没有提供相关的转换函数,我们可以通过以下4个宏实现本地字节序和网络字节序的相互转换:
htons():将16位无符号整数从本地字节序转换成网络字节序
htonl():将32位无符号整数从本地字节序转换成网络字节序
ntohs():将16位无符号整数从网络字节序转换成本地字节序
ntohl():将32位无符号整数从网络字节序转换成本地字节序
#if defined(BIG_ENDIAN) && !defined(LITTLE_ENDIAN)
#define htons(A) (A)
#define htonl(A) (A)
#define ntohs(A) (A)
#define ntohl(A) (A)
#elif defined(LITTLE_ENDIAN) && !defined(BIG_ENDIAN)
#define htons(A) ((((uint16)(A) & 0xff00) >> 8) | /
(((uint16)(A) & 0x00ff) << 8))
#define htonl(A) ((((uint32)(A) & 0xff000000) >> 24) | /
(((uint32)(A) & 0x00ff0000) >> 8) | /
(((uint32)(A) & 0x0000ff00) << 8) | /
(((uint32)(A) & 0x000000ff) << 24))
#define ntohs htons
#define ntohl htohl
#else
#error "Either BIG_ENDIAN or LITTLE_ENDIAN must be #defined, but not both."
#endif
1、字节对齐
1.1字节对齐
某些处理器不允许16位和32位的数据在内存中任意排放。例如,Motorola 68000 处理器不允许16位的字存放在奇地址,否则会触发异常。
通常32位的处理器通过总线访问(包括读和写)内存数据。每个总线访问周期可以访问32位内存数据。内存数据是以8位的字节为单位存放的。
假如一个32位的数据没有在4字节整除的内存地址处存放,那么处理器就需要2个总线周期对其进行访问。通过合理的内存对齐可以提高访问效率。
大多数编译器提供内存对其的选项供用户使用。这样用户可以根据处理器的情况选择不同的字节对齐方式。例如C/C++编译器提供的#pragma pack(n) n=1,2,4等,让编译器在生成目标文件时,使内存数据按照指定的方式排布在1,2,4等字节整除的内存地址处。
然而在不同编译平台,或者不同处理器上,字节对齐会造成消息结构长度的变化。现在假设用户定义结构体如下:
struct Message
{
short opcode;
char subfield;
long message_length;
char version;
short destination_processor;
};
由于为了字节对齐而进行的填充是通过编译器进行的编译器可以将上述结构填充成:
struct Message
{
short opcode;
char subfield;
char pad1; // Pad to start the long word at a 4 byte boundary
long message_length;
char version;
char pad2; // Pad to start a short at a 2 byte boundary
short destination_processor;
char pad3[4]; // Pad to align the complete structure to a 16 byte boundary
};
也可能填充成其他形式。如此,不同编译平台,处理器间数据通信的风险就不必说了吧。
1.2处理方法
以下以32位处理器为例,提出一种内存对齐方法。
对于本地使用的数据结构,为提高内存访问效率,采用四字节对齐方式;同时为了减少内存的开销,合理安排结构成员的位置,减少四字节对齐导致的成员之间的空隙,降低内存开销。
对于处理器之间的数据结构,需要保证消息的长度不因为在不同编译平台和不同处理器导致消息结构的长度发生变化,使用一字节对齐方式对消息结构进行紧缩;为保证处理器之间的消息的数据结构的内存访问效率,采用字节填充的方式自己对消息中成员进行四字节对齐。
数据结构的成员位置要兼顾成员之间的关系、数据访问效率和空间利用率。顺序安排的原则是:四字节的放在最前面,两字节的紧接最后一个四字节成员,一字节紧接最后一个两字节成员,填充字节放在最后。举例如下:
typedef struct tag_T_MSG{
long ParaA;
long ParaB;
short ParaC;
char ParaD;
char Pad; /* 填充字节 */
} T_MSG;
2、字节序:
2.1、字节序
小尾(Little Endian)就是低位字节排放在内存的低端,高位字节排放在内存的高端。例如对于一个4字节的整数Byte3 Byte2 Byte1 Byte0将在内存中按照如下顺序排放:
Base Address+0 Byte0
Base Address+1 Byte1
Base Address+2 Byte2
Base Address+3 Byte3
Intel处理器大多数使用小尾(Little Endian)字节序。
大尾(Big Endian)就是高位字节排放在内存的低端,低位字节排放在内存的高端。例如对于一个4字节的整数Byte3 Byte2 Byte1 Byte0将在内存中按照如下顺序排放:
Base Address+0 Byte3
Base Address+1 Byte2
Base Address+2 Byte1
Base Address+3 Byte0
Motorola处理器大多数使用大尾(Big Endian)字节序。
网络字节序:TCP/IP各层协议将字节序定义为大尾,因此TCP/IP协议中使用的字节序通常称之为网络字节序。
2.2处理方法
网络字节序作为一个标准字节序,如果系统并没有提供相关的转换函数,我们可以通过以下4个宏实现本地字节序和网络字节序的相互转换:
htons():将16位无符号整数从本地字节序转换成网络字节序
htonl():将32位无符号整数从本地字节序转换成网络字节序
ntohs():将16位无符号整数从网络字节序转换成本地字节序
ntohl():将32位无符号整数从网络字节序转换成本地字节序
#if defined(BIG_ENDIAN) && !defined(LITTLE_ENDIAN)
#define htons(A) (A)
#define htonl(A) (A)
#define ntohs(A) (A)
#define ntohl(A) (A)
#elif defined(LITTLE_ENDIAN) && !defined(BIG_ENDIAN)
#define htons(A) ((((uint16)(A) & 0xff00) >> 8) | /
(((uint16)(A) & 0x00ff) << 8))
#define htonl(A) ((((uint32)(A) & 0xff000000) >> 24) | /
(((uint32)(A) & 0x00ff0000) >> 8) | /
(((uint32)(A) & 0x0000ff00) << 8) | /
(((uint32)(A) & 0x000000ff) << 24))
#define ntohs htons
#define ntohl htohl
#else
#error "Either BIG_ENDIAN or LITTLE_ENDIAN must be #defined, but not both."
#endif