大小端问题

什么是大小端

计算机的最小存储单位为字节(8bit),对于单一的字节(byte),大部分cpu以相同的顺序处理位元(bit),因此单字节的存放方法和传输方式一般相同,但当cpu处理多字节数据时(int),就会有字节的存储顺序问题,一般有两种方式: 大端(big endian) 小端(little endian)

存储方式

以数据 0x01020304 为例,下面是它在两种平台上的存储图示:

  • 大端序 (ppc64)
               地址增长方向
              --------------->
          --------------------------------
             0x01 | 0x02 | 0x03 | 0x04
          --------------------------------
  • 小端序 (x86_64)
                地址增长方向
              --------------->
           --------------------------------
               0x04 | 0x03 | 0x03 | 0x01
           --------------------------------
  • 程序示例
#include <stdio.h>

int main(int argc, char *argv[])
{
	union {
		int i;
		char c[4];
	} u;

	u.i = 0x01020304;

	fprintf(stdout, "%.2x %.2x %.2x %.2x\n",
				u.c[0], u.c[1], u.c[2], u.c[3]);

	return 0;
}
  • x86_64(小端)上面运行:
        04  03  02  01
  • ppc64(大端)上面运行:
        01  02  03  04

网络传输时的问题

网络协议通常规定使用大端方式传输数据,如果在不同的平台间传输数据时不注意大小端,就会产生问题

  • 发送一个char

发送程序:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

int main(int argc, char *argv[])
{
	int sock_fd;
	struct sockaddr_in addr;

	if ((sock_fd = socket(AF_INET, SOCK_DGRAM,
					IPPROTO_UDP)) == -1) {
		perror("socket");
		exit(-1);
	}

	memset(&addr, 0, sizeof(addr));
	addr.sin_family = AF_INET;
	addr.sin_port = htons(8888);
	if (inet_aton(argv[1], &addr.sin_addr) == 0) {
		close(sock_fd);
		fprintf(stderr, "inet_aton: server address error!\n");
		exit(-1);
	}

	char c = 1;
	fprintf(stdout, "send: %d\n", c);
	if (sendto(sock_fd, &c, sizeof(c), 0,
			(const struct sockaddr *)&addr, sizeof(addr)) == -1) {
		close(sock_fd);
		perror("sendto");
		exit(-1);
	}

	return 0;
}

接收程序:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

int main(int argc, char *argv[])
{
	int sock_fd;
	struct sockaddr_in addr;

	if ((sock_fd = socket(AF_INET, SOCK_DGRAM, 0)) == -1) {
		perror("socket");
		exit(-1);
	}

	memset(&addr, 0, sizeof(addr));
	addr.sin_family = AF_INET;
	addr.sin_port = htons(8888);
	addr.sin_addr.s_addr = htonl(INADDR_ANY);

	if (bind(sock_fd, (const struct sockaddr *)&addr,
					sizeof(addr)) == -1) {
		close(sock_fd);
		perror("bind");
		exit(-1);
	}

	char c;
	if (recv(sock_fd, &c, sizeof(c), 0) == -1) {
		close(sock_fd);
		perror("recv");
		exit(-1);
	}
	fprintf(stdout, "recv: %d\n", c);

	return 0;
}

运行结果:

x86_64(小端)上运行发送程序:

       send: 1

ppc64(大端)上运行接收程序:

       recv: 1

可见在发送单字节数据时,是不需要注意大小端问题的

  • 发送一个int

发送程序(将char改为int):

	int c = 1;
	fprintf(stdout, "send: %d\n", c);
	if (sendto(sock_fd, &c, sizeof(c), 0,
			(const struct sockaddr *)&addr, sizeof(addr)) == -1) {
                close(sock_fd);
		perror("sendto");
		exit(-1);
	}

接收程序(将char改为int):

	int c;
	if (recv(sock_fd, &c, sizeof(c), 0) == -1) {
		close(sock_fd);
		perror("recv");
		exit(-1);
	}
	fprintf(stdout, "recv: %d\n", c);

运行结果:

x86_64(小端)上运行发送程序:

       send: 1    

ppc64(大端)上运行接收程序:

      recv: 16777216
  • 正确的发送一个int

发送程序(交换字节序):

	int c = 0x01020304;
	char * p = (char *)&c;
	char buf[4];
	buf[0] = p[3];
	buf[1] = p[2];
	buf[2] = p[1];
	buf[3] = p[0];
	fprintf(stdout, "send: %d\n", c);
	if (sendto(sock_fd, &buf, sizeof(buf), 0,
			(const struct sockaddr *)&addr, sizeof(addr)) == -1) {
		close(sock_fd);
		perror("sendto");
		exit(-1);
	}

运行结果:

x86_64(小端)上运行发送程序:

	send: 16909060

ppc64(大端)上运行接收程序:

      recv: 16909060