C语言之数据类型以及数据存储(包括大小端、截断与整型提升)

一、数据类型

1.数据的类型
在C语言中数据的类型主要可以概括为五大家族:整形家族、浮点数家族、构造类型、指针类型和空类型。

(1.)整型家族
整形家族包括char(字符型)和unsigned char(无符号字符型)、short (短整型)和unsigned short(无符号短整型)、int(整型)和unsigned int(无符号整型)、long(长整型)和unsigned long(无符号长整型)以及long long(更长的整型)和 unsigned long long(无符号更长的整型)。
(2.)浮点数家族
浮点数家族就两种,包括float(单精度浮点数)和double(双精度浮点数)。
(3.)构造类型
构造类型包括数组类型、结构体类型struct、枚举类型enum以及联合类型union。
(4.)指针类型和空类型
指针类型到后面介绍指针的时候在介绍,而空类型void就是无类型,通常用于函数的返回类型、函数参数、指针类型。

2.数据类型的大小
(1.)整型家族和浮点数家族的类型的大小的计算

#include 

int main()
{
    printf("%d\n", sizeof(char));//大小为1个字节。
    printf("%d\n", sizeof(short));//大小为2个字节。
    printf("%d\n", sizeof(int));//大小为4个字节。
    printf("%d\n", sizeof(long));//32位系统下大小为4个字节,而64位系统下大小为8个字节。
    printf("%d\n", sizeof(long long));//大小为8个字节。
    printf("%d\n", sizeof(float));//大小为4个字节。
    printf("%d\n", sizeof(double));//大小为8个字节。
    return 0;
}

(2.)构造类型的大小
①结构体的大小的计算
计算结构的大小涉及内存对齐的知识。
首先内存对齐存在的原因有两个。第一个原因就是平台原因(移植原因):不是所有的硬件平台都能访问任意地址上的任意数据的,某些硬件平台只能在某些地址处取某些特定类型的数据,否则会抛出硬件异常。第二个原因就是性能原因:数据结构(特别是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要访问两次,而对齐的内存访问仅需访问一次。
实际上,结构体的内存对齐是拿空间换时间的做法。
结构体的内存对齐的规则:
a.第一个成员在与结构体变量偏移量为0的地址处。
b.其他成员变量要对齐到对齐数的整数倍的地方。
对齐数=编译器默认的对齐数与该成员大小的较小值。VS中默认的对齐数是8,Linux中默认的对齐数是4。
c.结构体的总大小为最大对齐数(每个成员变量都有的一个对齐数)的整数倍。
d.若是结构体嵌套的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小为所有最大对齐数(含嵌套结构体的对齐数)的整数倍。

//例题1
struct sheena1
{
    char c1;
    char c2;
    int i;
};
printf("%d\n", sizeof(struct sheena1));//结果为8    

struct sheena2
{
    int i;
    char c1;
    char c2;
};
printf("%d\n", sizeof(struct sheena2)); //结果为8


struct sheena3
{
    char c1;
    int i;
    char c2;
};
printf("%d\n", sizeof(struct sheena3));//结果为12                

C语言之数据类型以及数据存储(包括大小端、截断与整型提升)_第1张图片

//例题2
struct sheena5
{
    double d;
    char c;
    struct sheena2 s;
};
printf("%d\n", struct sheena5);

C语言之数据类型以及数据存储(包括大小端、截断与整型提升)_第2张图片
当然我们可以通过#pragma这个预处理指令来修改默认的对齐数。

#pragma pack(8)//这个指令就是将默认对齐数更改为8个字节。
#pragma pack()//这个指令就是取消更改默认对齐数,还原为默认的对齐数。

那在设计结构体的时候我们可以通过尽可能让占用空间小的成员集中在一起,来满足对齐和节省空间两方面。

②枚举类型的大小
C语言中,对于枚举类型,其大小是由编译器根据定义值的大小来选择合适的整数类型的,故枚举类型的大小不是固定的。

③联合类型(共用体)的大小

  • 联合的大小至少是最大成员的大小
  • 当最大成员大小不是最大对齐数的整数倍时,就要对齐到最大对齐数的整数倍。
//例题
union sheena1
{
    int i;//大小为4
    char c[6];//大小为6*1=6
};
/*最大成员大小为6,
最大对齐数为4,
6不是4的整数倍。
故其对齐之后大小为8
*/
printf("%d\n", sizeof(union sheena1));//8

union sheena2
{
   short s[6];//大小为6*2=12
   char c;//大小为1
};
/*最大成员大小为12,
最大对齐数为4,
12是4的整数倍。
故其对齐之后大小为12
*/
printf("%d\n", sizeof(union sheena2));//12


union sheena3
{
   char c[3];//大小为3
   char c;//大小为1
};
/*最大成员大小为3
最大对齐数为4,
最大成员大小小于最大对齐数时,其大小为最大成员大小。
故其对齐之后大小为12
*/
printf("%d\n", sizeof(union sheena3));//3

(3.)指针类型、空类型的大小

  • 指针类型的大小是固定的,在32位机器下位4个字节,在64位机器下位8个字节。
  • void是无类型,只有强转为其它类型后才有大小。

二、数据存储

1.整型在内存中的存储
要知道整型在内存中是如何存储的,首先我们就要了解原码、反码和补码。

  • 原码:一个数的二进制形式。如int a = 6;其原码为00000000 00000000 00000000 00000110,其中最高位是符号位,负数符号位是1,正数是0。若int a = -6;其原码为10000000 00000000 00000000 00000110.
  • 反码:一个数的原码符号位不变,其余位按位取反。正数的反码就等于正数的原码。-6的反码就是11111111 11111111 11111111 11111001。
  • 补码:一个数的反码加一。其中正数的补码就等于正数的原码。故-6的补码为11111111 11111111 11111111 11111010。
  • 故正数的原码、反码、补码都是一样的。

为了使计算机对数据处理方便(如利于减法运算),故数值在计算机中都是按照补码形式存储的。

而数值在内存中的存储形式如下所示:
int a = 6; 06 00 00 00(以补码形式存储,以十六进制形式表示,但其顺序并不是从左向右存储的,而是从右向左存储的。)
存储顺序就是由大端机或小端机造成的了。

2.大小端

(1.)大小端的含义

  • 大端存储模式:是指数据的低位保存在内存的高地址中,而数据的高位保存在低地址中;
  • 小端存储模式:是指数据的低位保存在内存的低地址中,而数据的高位保存在高地址中。

(2.)大端和小端模式存在的理由
因为在计算机系统中,我们是以字节为单位的。每个地址单元都对应着一个字节,一个字节是八位。但C语言中不仅有一个字节的char类型,还有两个字节的short类型和四个字节的int类型等,另外,对于位数大于8位的处理机,例如16位或者32位的处理机,由于寄存器宽度大于一个字节,那么必然存在着一个如何将多个字节安排的问题。因此就导致的大端存储模式和小端存储模式的产生。例如一个16位的short型x,在内存中的地址位0x0010,x的值位0x1122,那么0x11为高字节,而0x22为低字节。对于大端存储模式,就将0x11放在
低地址0x0010中,而0x22放在高地址0x0011中。小端存储模式,就是将就将0x11放在高地址0x0011中,而0x22放在高=低地址0x0010中。我们常用的X86结构为小端存储模式。

(3.)用代码判断当前为小端模式还是大端模式

//联合的特性就是:所有的成员共用一个空间,故又称共用体。
#include 
#include 

union
{
    char c;
    int i;
}un;
/*
联合体中的变量i和ch共用一个空间,它们都是从低地址开始存放的。
变量i的值为1即0x00 00 00 01,
如果是小端模式则01存放在低地址上,ch的值就是1
若ch的值不是1则是大端模式。
*/
int check_sys1()
{
    un.i = 1;
    //因为成员共用一个地址空间,un.i=1相当于低位的数值是1
    if(un.c == 1)
    {
         return 1;
    }
    else
    {  
         return 0;
    }
}


/*
变量i的值为1即0x00 00 00 01,
强转成一个字节char型(一个字节为低地址)时,若为1,
则说明低字节01存放在低地址中,为小端机
若不等于1则为大端机。
*/
int check_sys2() 
{  
    int i = 1;    
    return (*(char *)&i);
}

int main()
{
    int ret1 = 0;
    int ret2 = 0;
    ret1 = check_sys1();
    ret2 = check_sys2();
    if(ret1 == 1)
    { 
        printf("当前为小端模式!\n");
    }
    else
    {
        printf("当前为大端模式!\n");
    }
    if(ret2 == 1)
    { 
        printf("当前为小端模式!\n");
    }
    else
    {
        printf("当前为大端模式!\n");
    }
    system("pause");
    return 0;
}

3.整型提升以及截断

//例题
#include 
#include 

int main()
{
    char a = -1;
    unsigned char b = -1;
    printf("a=%d,b=%d\n",a,b);//a=-1,b=255
    system("pause");
    return 0;
}

分析:
首先整型提升的规则是:
a.若是有符号的数,则前面补符号位。
b.若是无符号数,则前面补0。

①对a来进行分析
因为-1为int类型,大小为4个字节,且为负数,故其在内存中是按照补码形式存储的,其原码为10000000 00000000 00000000 00000001,其反码为11111111 11111111 11111111 11111110,其补码为11111111 11111111 11111111 11111111,但是a是char类型的,大小为1个字节,故这里会发生截断,将4个字节截断成为1个字节,其截断后结果为 11111111。但是最后要求以%d的形式输出,所以此处要进行整型的提升,将截断后的1个字节提升为4个字节的整型。a为有符号类型的数,整型提升就是在截断后的结果前面补3个字节长度的符号位(即1)。整型提升后的结果为11111111 11111111 11111111 11111111。根据最高位(即符号位)是1可得出该结果为负数,则此为补码形式需要转换成原码为10000000 00000000 00000000 00000001。转为十进制为-1。所以最终以%d形式输出的a为-1。
②对b来进行分析
因为-1为int类型,大小为4个字节,且为负数,故其在内存中是按照补码形式存储的。由对a的分析中可知,其补码为11111111 11111111 11111111 11111111,但是a是unsigned char类型的,大小为1个字节,故这里会发生截断,将4个字节截断成为1个字节,其截断后结果为 11111111。但是最后要求以%d的形式输出,所以此处要进行整型的提升,将截断后的1个字节提升为4个字节的整型。a为无符号类型的数,整型提升就是在截断后的结果前面补3个字节长度的0。整型提升后的结果为00000000 00000000 00000000 11111111。转成十进制的结果为255。

4.浮点型在内存中的存储
在C语言中,浮点型的变量在内存中的存储是遵循IEEE标准。浮点型数据包括float、double、long double类型。
IEEE 754规定

  • 任意一个二进制浮点数可表示为 V = (-1)^S M 2^E,其中 (-1) ^S表示符号位,当S=0时,V为正数;当S=1时,V为负数。M表示有效数字,大于等于1且小于2。2 ^E表示指数位。
  • 对于32位的浮点数(如float型),最高的一位是符号位S,接着的8位是指数E,剩下的23位是有效数字M。
  • 对于64位的浮点数(如double型),最高的一位是符号位S,接着的11位是指数E,剩下的52位是有效数字M。
  • E是一个无符号整数,若E为8位,其取值范围为0~255;若E为11位,其取值范围为0 ~2047。对于8位的E,保存成浮点数时应该保存成E+127的二进制形式。

例如:
-8.25转换为二进制浮点数表示:
①其二进制的形式为00001000.01(只保留了最后一个字节,符号位是1)。
②因为M需要是大于等于1且小于2的值,故M = 1.00001。其位数为为00001,因为尾数为第一个数都是1,所以1就省略了。
③要得到有效的M故,E = 3,
其指数位为3+127=130=10000010
④因为符号位是1,为负数,故S = 1
⑤最终-8.25的二进制浮点数表示为(-1)^1 * 1.00001* 2^3
则在内存中表示为
1 10000010 00000000000000000000000
最高的1位是符号位,中间的8位是指数位,最后的23位是尾数位。

你可能感兴趣的:(C语言)