网络字节序
为使网络程序具有可移植性,使同样的C代码在大端和小端计算机上编译后都能正常运行,可以调用以下库函数做网络字节序和主机字节序的转换。
#include <arpa/inet.h>
uint32_t htonl(uint32_t hostlong);
uint16_t htons(uint16_t hostshort);
uint32_t ntohl(uint32_t netlong);
uint16_t ntohs(uint16_t netshort);
//h表示host,n表示network,l表示32位长整数,s表示16位短整数。
//如果主机是小端字节序,这些函数将参数做相应的大小端转换然后返回,如果主机是大端字节序,这些函数不做转换,将参数原封不动地返回。
下面是遇到的问题:
使用htonl做一个大端转换的程序,经测试本电脑为小端字节序。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <pthread.h>
#include <arpa/inet.h>
void sys_err(const char *str)
{
perror(str);
exit(1);
}
int main(int argc, char *argv[])
{
//注意:网络里用的都是无符号类型的数据,我们要把他们转化从无符号数
char buf[4] = {192,168,137,1};
unsigned int num = *(unsigned int *)buf;//将buf转为int*,然后对他进行取值,此时取的值就是从buf起始地址+4个字节内的内容
printf("%u\n",num);
unsigned int sum = htonl(num);//将num这个32位数(4个字节)进行大端转换
printf("%u\n",sum);
unsigned char* p = (unsigned char*)∑//再将sum的地址转换位char*(地址操作距离为一个字节),并赋值给p指针
printf("%d %d %d %d\n",*p,*(p+1),*(p+2),*(p+3));
return 0;
}
过程:当{192,168,137,1}被赋到num中后,按理说,num也有一个4字节的地址,从起始地址分别放着192 ,168,137,1,二进制就是:11000000,10101000,10001001,00000001。他们组成整数应该是3232270593。但程序中读取的num值为25798848。
我们把25798840转成二进制看:
00000001 10001001 10101000 11000000
居然是反过来了。为什么呢?
原因在于,当我们创建buf数组,并初始化为{192,168,137,1}时,数组的首地址(低地址)处必须放的是192(11000000) ,最后(高地址)放的是1(00000001),而不是说因为本电脑是小端字节序,就让1放在低地址,192放在高地址。(原因在于,这是数组而不是一个数,在这个数组中,其实192对应的就是低位,1对应的就是高位),所以最终存放顺序也是符合小端存储的。
当将char数组的类型转化为int*并赋值给num后,num中的内存也和数组是一样的如下图。
然后,我们打印num时,printf是按照从左往右,从高到低的顺序,(即低字节的数据放在低位,高字节的数据放在高位)打印数据的,即最终打印的数据为:
00000001 10001001 10101000 11000000
所以,最后printf出来的num的值为25798840。
然后htonl函数发现此电脑是小端字节序,故将num进行大端转换,即将num中的数据转换排序方法。然后放到sum中,即最高地址放的是11000000,最低地址放00000001
如下图sum的内存空间:
然后,我们打印sum时,printf是按照从左往右,从高到低的顺序,即低字节的数据放在低位,高字节的数据放在高位)打印数据的读取数据的,即最终打印的数据为:
11000000,10101000,10001001,00000001
所以,最后printf出来的sum的值为3232270593。
最后,对sum地址转换为char*后,从首地址开始取值,依次为1 ,137,168,192
对printf的一点理解
int64_t a = 1034024421633;
int h = 3;
printf("%u,%d\n", a, h);
printf("%u,%lld\n",a,h);
a的二进制位11110000 11000000 10101000 10001001 00000001
- 首先,printf打印数据时,是根据第一个参数中的%后面的类型来偏移指针的,如果%后面为d或者u则偏移4个字节,h先被压入栈中,a后被压入。
- 所以第一个取的值是a,a的值是看起来虽然是5个字节,但其实他是64位int型在栈中共占据了8个字节,前面还有24个0。而%u只能偏移4个字节,故a打印出来是 11000000 10101000 10001001 00000001即3232270593
- 取第二个值时,指针会从前面偏移的地址开始(由于第二个%后面是d)偏移4个字节,则这4个字节中的内容为00000000 00000000 00000000 11110000。即240
- 最终,到第一个printf结束,其实也没打印到h的值。因为指针还没有偏移到h的栈空间处。
- 而第二个printf中,第二个%后面的内容是lld,表示地址偏移了8个字节,这8个字节的内容为
- 000000000 00000000 00000000 00000011 00000000 00000000 00000000 11110000
- 即:12884902128
- 所以最终程序输出结果为
- 3232270593,240
3232270593,12884902128 - 所以如果要打印出我们原本设的值,那printf的中第一个%后面填llu,第二个%后面填d即可。
printf("%llu,%d\n",a,h);
printf取值会将先取到的值放在整串数值的低位,然后慢慢增加。
综上:对于printf输出变量值时,对于%后面的类型选取也是要非常小心的,因为一不小心可能就不是我们想要的值。