zoukankan      html  css  js  c++  java
  • 大小端转换定义结构体的技巧

    这篇文章是我在csdn博客发布的,因为csdn支持markdown所以打算转移博客,但是markdown编辑器对linux firefox支持度不是很好,因此放弃csdn博客。暂时先搬到这里,等哪天自己的博客站建好了,好一起搬家。

    =============================================================

           这两年一直在写协议分析和报文填充相关内容。因为PC机是小端(Little Endian),网络序是大端(Big Endian),在写代码的时候必须考虑到大小端转换的问题,否则网卡或者网络设备会解析错误。网上的总结分析甚少,大部分都还处于纠结于大小尾分辨的层次。今天我就来深度分析一下大小尾问题。

            首先,不管是哪一种数据存放方式,对于一个单字节的数据,存储方式都是一样的。比如uint8_t类,char类在大端和小端都可以不经过转换直接读取。

            而对于两字节及以上字节大小的数据,大小端的存储方式就有区别了。大端(网络序)的存储方式与我们的阅读逻辑是相同的。大端的存储方式是高位保存在低地址中,小端(常见的PC机)的存储方式则是高位存高地址。

            举个例子就明白了,比如有个数字0x12345678,存储到内存至少需要4字节,按照大端的存储方式,则是0x12 0x34 0x56 0x78,与我们的阅读逻辑一致;而小端的存储方式则是0x78 0x56 0x34 0x12。记住大端存储方式(网络序)与我们的阅读逻辑相同就可以了。

           那么根据上面的分析,我们就会发现,大小端基于内存的颠倒是以字节为单位的,不是以每一位为单位的,这一点在具体的代码实现中尤其重要。

           假定我们有一个uint16_t类型的数据,在小端机器上面处理(现在的pc机基本都是小端机)需要转换成大端怎么转换呢?前一字节与后一字节位置换。

           假定我们有一个uint32_t类型的数据,在小端机器上面处理(现在的pc机基本都是小端机)需要转换成大端怎么转换呢?前一字节与后一字节置换,中间两个字节也互相置换。

           那么,再进一步,如果有一个结构体,大小32位(4字节),定义如下:

    struct test_u32{

            uint8_t a;

            uint8_t b;

            uint8_t c;

            uint8_t d;

    };

            在大小端转换时,自然是a和d互换,b和c互换。因此,完全可以在小端定义结构体时,就定义成如下结构:

    struct test_u32{

            uint8_t d;

            uint8_t c;

            uint8_t b;

            uint8_t a;

    };

            这样在进行网络通信时就能省去了大小端转换这个步骤,在大小端转换较多的情况下,通过改变结构体定义的方式尽可能省去大小端转换的步骤,对于数据分析和网络传输的速率提升是明显的。

            那么接下来再考虑一下情况,如果上述结构体在大端机定义如下:

    struct test_u32_2{

            uint8_t a;

            uint16_t b;

            uint8_t c;

    };

            如果只是在小端机定义成如下形式,会有什么影响吗?

    struct test_u32_2{

            uint8_t c;

            uint16_t b;

            uint8_t a;

    };

            明显的,a和c由于他们的定义不大于1字节,因此a和c的数据不会有错。而由于b大于1字节了,所以仍然需要进行大小端转换。这里就是一个陷阱。


            另外在实际的网络私有协议定义中,不可能所有的数据都是基于8位(1字节)对齐,有些数据可能只占1位,有些数据可能占35位,遇到这种情况该如何处理呢?

            首先,如果是如下情况,n个变量瓜分一个字节:

    struct test_u8{

            uint8_t a:1;

            uint8_t b:3;

            uint8_t c:4;

    };

            此时变量a占1位,变量b占3位,变量c占4位,他们共享一个字节。一个字节是8位,在内存中的存储方式是“abbbcccc”(每个字符代表该位存储着哪个变量的数据)

            对于这种情况,可以直接在小端定义中进行置换:

    struct test_u8{

            uint8_t  c:4;

            uint8_t b:3;

            uint8_t a:1;

    };

            在小端如此定义,内存中的数据存储方式依然是abbbcccc。

            那么再讨论一个问题,如果在网络序机器中定义如下结构体,小端中也可以如上一例子直接置换吗?

    struct test_u16{

            uint16_t a:3;

            uint16_t b:1;

            uint16_t c:12;

    };

            这个时候我会想当然的认为可以置换成如下形式,但是打印结果告诉我出错了。

    struct test_u16{

            uint16_t c:12;

            uint16_t b:1;

            uint16_t a:3;

    };

            比如我在置换后的结构体中,令a=1,然后按照uint16_t类型输出该结构体数据,按照预想情况应该是0x2000,但是实际打印数据是0x0100。根据打印结果推原因,出错理由如下。

            1.在大端中,数据保存方式应当如下:aaabcccc cccccccc。那么如果a=1,b=0,c=0,数据当是0010000 00000000,也就是0x2000。但是现在存储内容成了00000001 00000000,变成了0x0100,那么此时在内存中,数据保存形式为xxxxxaaa xxxxxxxx (x表示未知是b还是c的存储位),经过测试,如此错误定义结构体,使得本来的数据存储方式变为了ccccbaaa cccccccc 。为什么?这个需要自己画图想一下,注意大小端的转换是以字节为单位转换而不是以位为单位转换的。

    因此需要修改定义为:

    struct test_u16{

            uint8_t c_1:4;

            uint8_t b:1;

            uint8_t a:3;

            uint8_t c_2;

    };

    这时令a=1,b=0,c_1=0,c_2=0,结构体安装uint16_t输出便是0x2000。         结论:在大小端处理时遇到不是整自己的变量,定义结构体以uint8_t为单位定义,对于超过一字节又不足两字节的变量,要拆成两部分处理。

  • 相关阅读:
    160-三个用户同时登录,是怎么实现多线程的?
    159-如何解决缓存穿透?
    158-为什么会引发缓存穿透?
    存储emoji表情,修改字符集为utf8mb4
    java相差小时数
    pom.xml解释
    前端 跨域
    java 获取的是本地的IP地址
    是否超时
    发送验证码
  • 原文地址:https://www.cnblogs.com/tianxizi/p/7908220.html
Copyright © 2011-2022 走看看