近日在工作中遇到一个奇怪问题,A程序发送一个结构给B程序,在B程序接收后,B程序按照A程序的结构进行解析,但始终不对。看过结构定义后,虽然知道有可能会出现字节对齐问题,一直怀疑是他们消息传输构造不对。经过GDB确认A程序发出的结构是正确的,在B程序接收处也同样没问题。当调试到转化处时才发现确实是字节对齐导致的。下面举例说明(32bit Linux Gcc)。
server.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 #include <stdio.h> #include <string.h> #include <sys/socket.h> #include <arpa/inet.h> #pragma pack(1) typedef struct { unsigned int a; short int b; long long int c; unsigned int d; } type1; #pragma pack() #define PORT 8888 int main (int argc, char *argv[]) { int fd = 0 ; int len = 0 ; int err = 0 ; char *buff = NULL ; char recvbuf[1024 ]={0 }; struct sockaddr_in server_addr ; struct sockaddr_in client_addr ; type1 *C = NULL ; fd = socket(AF_INET, SOCK_DGRAM, 0 ); if (fd < 0 ) { printf ("Socket create failure" ); return -1 ; } bzero(&server_addr, sizeof (server_addr)); server_addr.sin_family = AF_INET; server_addr.sin_addr.s_addr = htonl(INADDR_ANY); server_addr.sin_port = htons(PORT); len = sizeof (struct sockaddr_in); err = bind(fd, (struct sockaddr *)&server_addr, sizeof (server_addr)); if (-1 == err) { printf ("bind error\n" ); return -1 ; } while (1 ) { recvfrom(fd, recvbuf, sizeof (recvbuf), 0 , (struct sockaddr *)&client_addr, &len); C = (type1 *)recvbuf; printf ("C={0x%x,0x%x,0x%llx,0x%x}\n" , C->a, C->b, C->c, C->d); } return 0 ; } [root@smart Desktop]# ./server C={0x11223344 ,0x5566 ,0x5678901234560019 ,0x43211234 }
client.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 #include <stdio.h> #include <string.h> #include <sys/socket.h> #include <arpa/inet.h> #pragma pack(4) typedef struct { unsigned int a; short int b; long long int c; unsigned int d; } type1; #pragma pack() #define PORT 8888 int main (int argc, char *argv[]) { int fd = 0 ; int len = 0 ; int err = 0 ; char *buff = NULL ; char recvbuf[1024 ]={0 }; struct sockaddr_in server_addr ; struct sockaddr_in client_addr ; type1 A = {0x11223344 , 0x5566 , 0x1234567890123456L L, 0x87654321 }; printf ("sizeof(A)=%d,A={0x%x,0x%x,0x%llx,0x%x}\n" , sizeof (A), A.a, A.b, A.c, A.d); fd = socket(AF_INET, SOCK_DGRAM, 0 ); if (fd < 0 ) { printf ("Socket create failure" ); return -1 ; } buff = (char *)&A; bzero(&server_addr, sizeof (server_addr)); server_addr.sin_family = AF_INET; server_addr.sin_addr.s_addr = htonl(INADDR_ANY); server_addr.sin_port = htons(PORT); len = sizeof (struct sockaddr_in); sendto(fd, buff, sizeof (type1), 0 , (struct sockaddr *)&server_addr, len); return 0 ; } [root@smart Desktop]# ./client sizeof (A) =20 ,A={0x11223344 ,0x5566 ,0x1234567890123456 ,0x87654321 }[root@smart Desktop]#
从上面这个示例可以看出,输出的结果与我们预期的不一样。这是因为在32位系统下,数据存储将按结构体成员的最大字节数对齐,并且最大字节数为4。 上面的用例采用了手工设置对齐方式来测试该问题。从上述的运行结果看,我们需要在两个程序间保持一致,最好手动设置一样的字节对齐方式。
pragma pack(1)表示按一字节对齐,#pragma pack()表示取消前面的字节对齐,按默认方式对齐。
另外在编码时我们就应该考虑结构设计,尽量避免会引入需要考虑字节对齐的问题,如将short int 改为int,就无需考虑该问题。
–质量高的代码会将问题消灭在编码阶段,修复bug将会让你效率更低。