当前位置:C++技术网 > 精选软件 > 如何使用固定字节长度的数据类型

如何使用固定字节长度的数据类型

更新时间:2016-08-22 14:23:14浏览次数:1+次

    在定义协议的结构体的时候,会遇到一个问题,那就是,用什么样的数据类型来表示一个固定长度的字段?
    假如协议的一个字段是2字节,一个4字节,一个是8字节,像这样的字节长度,到底用什么类型合适呢?相信很多没有经验的人都会充满疑虑。我们都知道,C/C++语言中的整型类型定义,字节长度char<=short<=int<=long<=long long,但是没有一个固定的字节长度。在语法上是这样的,那么我们到底如何用我们的整型来表示2字节、4字节和8字节呢?
    当然,如果只是表示这么个字节长度,你完全可以用char数组来定义,因为char是固定的一个字节。多少个字节长度也就用多少个元素的char数组就好了。然而,本来是数字的,结果都用char数组表示的,那就是这样的:
struct protocol
{
    char a;
    char[2] len;
    char[4] value;
    char[8] bigvalue;
};
    当然,这样的定义没有错。然而本来一个数值的成员,可以直接进行数学运算的,都char数组表示,那使用起来还不累死。另外说明一点,如果要确切的定义每一个成员的长度,不仅要让每一成员的字节长度固定,而且内存对齐也要做好。如果字段的长度有奇数有偶数之类的,要让内存按照1字节对齐,这样你定义的时候就随便定义。如果你的结构成员都是2的整数倍个字节的话,可以按照2字节来对齐。内存对齐产生的问题就是,如果结构成员字节数不是整倍数,会对齐到整倍数,这样就让成员的字节数增加了,和协议上定义的准确的字节数不一样的,也就产生错误。常见的就是错位,严重的就完全错乱。在通信的时候,严格按照协议上的字节数来传输,接收到的数据存入不正确的结构体中,数据就乱了。
    按照一字节对齐的指令如下:
#pragma  pack (push,1)//按1字节对齐结构体
struct protocol
{
    char a;
    char[2] len;
    char[4] value;
    char[8] bigvalue;
};
#pragma pack(pop)
    这样,结构体成员定义多少字节就是多少字节,不会再编译器对齐产生字节变多的情况了。结构体内存对齐更多参考《关于结构体内存分配对齐深入理解》、《结构体对齐问题分析》、《关于成员对齐方式》。
    回归我们继续讨论的,以整数类型来定义固定的成员的字节长度。使用了char数组后,让操作变得困难,所以不可取。然而如果用short、int、long、long long呢,我们又不敢确定长度一定是多少。
    实际上,这是我们存在了误解。虽说语言的语法规定short、int、long、long long不是固定长度的,然而在一个具体的平台实现中,如32位Windows操作系统的VS编译器,总是固定的长度。所以我们通常认为的,int类型占4个字节,是在32位的系统中的。如果在64位系统中,那就不一定了。
    你不要理解为int类型固定长度的,但是在固定的平台和固定的编译器下,所有的类型都有固定的长度的。这一点可能并没有引起很多人的注意,以至于在考虑整型的长度的时候,又感觉是固定长度,又感觉没有底气。所以才遇到了本文所说的整型类型定义固定长度结构体成员问题。
    在win32环境下,你可以把int长度看作是固定的4个字节。至于其他几个长度,你不用记忆,需要用的时候,可以用sizeof测试下。然后就知道有多长了。在一个具体的平台写代码,都可以用sizeof来测定类型的长度,然后基于这个长度写代码。这样的话,你的代码,移植性很差。因为你将类型的长度固定下了。代码要是移植到其他平台,那这都需要改。如果不用移植,就不用理会了。如果你的代码一直工作在win32下,你考虑移植干嘛,考虑跨平台干嘛呢?没事找事吗?
    那么我们在具体平台下,我们可以写一个测试代码来测试各个数据类型的字节长度,代码如下:
#include <stdio.h>
int main()
{
    int len_short = sizeof(short);
    int len_int = sizeof(int);
    int len_long = sizeof(long);
    int len_longlong = sizeof(long long);
    printf("short=%d\nint=%d,\nlong=%d,\nlong long=%d\n",len_short,len_int,len_long,len_longlong);
}
   运行截图如下:

    我们看到,int和long都是4个字节。short是2字节,而long long是8个字节。既然如此,那么我们就可以用这些长度的类型来重新定义结构体,代码如下:
struct protocol
{
    char a;
    short len;
    int value;
    long long bigvalue;
};
    这样定义后,我们可以直接取到结构体中的成员,然后进行数值运算,比char数组方便多了。不过,我们这个代码如果放在64位环境中,则会出问题,因为64位系统的这几个整型类型的字节长度和32位系统的不一样。如果不考虑64位系统,是没有问题。
    另外一个问题,如果协议中定义大量的字段,然后你看到代码到处都是int\short\long long的,你还得将这些类型对应到字节数,来检测结构体是否定义正确,在调试的时候也不能直观看出每一个成员的字节数。所以这个也是挺不方便的。所以,我们可以请typedef来帮忙。代码如下:
typedef char byte_1;
typedef short byte_2;
typedef int byte_4;
typedef long long byte_8;
    然后结构体重新定义一下:
struct protocol
{
    byte_1 a;
    byte_2 len;
    byte_4 value;
    byte_8 bigvalue;
};
    这样定义之后,你可以一目了然,从类型就可以看出每一个字段的字节数。这样在检查和调试的时候非常方便了。这个做法是非常普遍的。如果你连这个做法都不理解,只能说明你经验不足。有很多人会抱怨,为什么老是要定义一些新类型名字,原因就是这了。
    如果不考虑代码移植性,这样就很好了。假如你记错了,将8字节记忆成了long,而你直接用long在代码中定义的类型,那么你就要到处去找,然后再改过来。这个过程绝对是痛心疾首的,非常的麻烦,很费劲。如果你在整个项目做完后,发现这个错误,你还想活吗?哈哈哈。所以,typedef这个做法,不仅可以让你可以迅速知道字段的字节长度,而且发现错误后,可以改一下typedef就可以搞定了。
    实际上,编译器一般都准备好了一个头文件,一般名称为stdint.h。所以,我们不需要自己去定义一遍了。如果你要考虑跨平台,你只需要在这个typedef中下手就行了。代码中都使用定义后的类型名来写,这样在移植的时候,只需要改一下typedef即可。QT中的stdint.h已经考虑了跨平台,你可以直接将QT的这个头文件弄过来,这样就可以自动识别系统和定义对应的数据类型别名了。
    所以,现在来看,我们不仅可以用指定的类型来定义固定长度的结构体成员,还可以考虑代码移植性,还可以实现代码的跨平台特性。这种工作模式是非常值得推荐的,所以才做此总结。