深入理解计算机系统-之-数值存储(四)--整数在内存中的存储方式

来源:互联网 发布:javascript 提交表单 编辑:程序博客网 时间:2024/05/20 14:42

前景回顾


前面我们了解到依据CPU的端模式的架构不同,数据的存储的字节序也不同

BE big-endian 大端模式,最直观的字节序 地址低位存储值的高位,地址高位存储值的低位 ,数据填写时,不要考虑对应关系,只需要把内存地址从左到右按照由低到高的顺序写出,把值按照通常的高位到低位的顺序写出,两者对照,一个字节一个字节的填充进去。

LE little-endian 小端模式,则最符合人的思维的字节序,地址低位存储值的低位,地址高位存储值的高位 ,这点比较符合人的思维的字节序,是因为从人的第一观感来说,低位值小,就应该放在内存地址小的地方,也即内存地址低位,反之,高位值就应该放在内存地址大的地方,也即内存地址高位
任何数据在内存中都是以二进制的形式存储的。

具体参照深入理解计算机系统-之-数值存储(一)-CPU大端和小端模式详解

接着我们讨论了如何通过程序打印出变量在内存中的存储形式,具体参照深入理解计算机系统-之-数值存储(二)–C程序打印变量的每一字节或者位

然后我们了解了原码,反码,补码和移码的表示形式,具体参照深入理解计算机系统-之-数值存储(三)– 原码、反码、补码和移码详解

整型数据概述


整型数


C/C++语言中表示整数、字符和布尔值的算术类型合称为整型。

字符类型有两种:char 和 wchar_t。

char 类型保证了有足够的空间,能够存储机器基本字符集中任何字符相应的数值,因此,char 类型通常是单个机器字节byte)。

wchar_t 类型用于扩展字符集,比如汉字,这些字符集中的一些字符不能用单个 char 表示。

short、int 和 long 类型都表示整型值,存储空间的大小不同。一般, short类型为半个机器字长,int 类型为一个机器字长,而long 类型为一个或两个机器字长(在 32 位机器中 int 类型和 long 类型通常字长是相同的)。

bool 类型表示真值 true 和 false。可以将算术类型的任何值赋给 bool对象。0 值算术类型代表 false,任何非 0 的值都代表 true。

带符号和无符号类型


除 bool 类型外,整型可以是带符号的(signed)也可以是无符号的
(unsigned)。顾名思义,带符号类型可以表示正数也可以表示负数(包括 0),
而无符号型只能表示大于或等于 0 的数。

整型 int、short 和 long 都默认为带符号型。要获得无符号型则必须指定该类型为 unsigned,比如 unsigned long。unsigned int 类型可以简写为unsigned,也就是说,unsigned 后不加其他类型说明符意味着是 unsigned int 。
和其他整型不同,char 有三种不同的类型:plain char 、unsigned char 和signed char。虽然 char 有三种不同的类型,但只有两种表示方式。可以使用unsigned char 或 signed char 表示 char 类型。使用哪种 char 表示方式由编译器而定。

整数以补码形式存储


示例程序


那现在我们明细了。我们通过下面的程序来查看,整数在内存中的表现形式。

#include <stdio.h>#include <stdlib.h>int check_end(){    int   i = 0x12345678;    char *c = (char *)&i;     return (*c == 0x12);}int print_bit(void *addr, int size){    unsigned char *ptr = (unsigned char *)addr;    int print_bytes = 0;    if(ptr == NULL)    {        return -1;     }    for(print_bytes = 0;        print_bytes < size;        print_bytes++, ptr++)    {#ifdef DEBUG        printf("byte %d, data = %02x -=>", print_bytes, *ptr); #endif        for(int print_bits = 7;        print_bits >= 0;        print_bits--)        {            printf("%d", ((*ptr >> print_bits) & 1));        }#ifdef DEBUG        printf("\n");#endif    }    printf("\n");     return print_bytes;}int print_byte(void *addr, int size){    unsigned char *paddr = (unsigned char *)addr;    int print_bytes = 0;    if(paddr == NULL)    {        return -1;     }    while(print_bytes < size)    {        printf("%02x", *paddr);         paddr++;         print_bytes++;     }    printf("\n");     return print_bytes; }int main(void){    if(check_end() == 1)    {        printf("大端模式\n");    }    else    {        printf("小端模式\n");    }    int a = 1;    print_bit((void *)&a, sizeof(a));    int b = -1;    print_bit((void *)&b, sizeof(b));/*X   = -00101011 = 0x0000002B [X]原= 10000000 00000000 00000000 00101011[X]反= 11111111 11111111 11111111 11010100[X]补= 11111111 11111111 11111111 11010101[X]移=01010101*/    int x = 0x2B;    print_bit((void *)&x, sizeof(x));    int y = -0x2B;    print_bit((void *)&y, sizeof(y));    return EXIT_SUCCESS;}

程序分析


这里写图片描述
不管是原码,反码还是,补码,正数的表示方法是一致的。
但是负数来说,是有区别的。
X = -0X2B = -00101011 = 0x0000002B
[X]原 = 10000000 00000000 00000000 00101011 = 0x8000002B
[X]反 = 11111111 11111111 11111111 11010100 = 0xFFFFFFD4
[X]补 = 11111111 11111111 11111111 11010101 = 0xFFFFFFD5
[X]移 =01010101

由于我们的intel的CPU架构采用小端模式,那么用补码存储的时候,0xFFFFFFD5用小端模式存储下来,低字节到高字节的数据排列下来依次是0x D5 FF FF FF,即11010101 11111111 11111111 11111111
可见,整数是以补码的形式在内存中存储的

溢出


整型值的表示


无符号型中,所有的位都表示数值。如果在某种机器中,定义一种类型使用8 位表示,那么这种类型的 unsigned 型可以取值 0 到 255。

C++ 标准并未定义 signed 类型如何用位来表示,而是由每个编译器自由决定如何表示 signed 类型。这些表示方式会影响signed 类型的取值范围。

8 位signed 类型的取值肯定至少是从 -127 到 127,但也有许多实现允许取值从-128 到 127。

表示 signed 整型类型最常见的策略是用其中一个位作为符号位。符号位为1,值就为负数;符号位为 0,值就为 0 或正数。一个 signed 整型取值是从 -128到 127。

对于 unsigned 类型来说,编译器必须调整越界值使其满足要求。编译器会将该值对 unsigned 类型的可能取值数目求模,然后取所得值。比如 8 位的unsigned char,其取值范围从 0 到 255(包括 255)。如果赋给超出这个范围的值,那么编译器将会取该值对 256 求模后的值(其本质就是多出的位被截断,舍弃掉)。

例如,如果试图将 336 存储到 8 位的unsigned char 中,则实际赋值为 80,因为 80 是 336 对 256 求模后的值。
高字节的进位1被截断后舍弃掉,这样就剩下80=336-256

对于 unsigned 类型来说,负数总是超出其取值范围。unsigned 类型的对象可能永远不会保存负数。有些语言中将负数赋给 unsigned 类型是非法的,但在 C/C++ 中这是合法的。

C/C++ 中,把负值赋给 unsigned 对象是完全合法的,其结果是
该负数对该类型的取值个数求模后的值。

所以,如果把 -1 赋给8 位的 unsigned char,那么结果是 255,因为 255 是 -1 对256 求模后的值。
同样是截断,-1用补码存储,高位溢出位被截断,剩下的就是255

当将超过取值范围的值赋给 signed 类型时,由编译器决定实际赋的值。在实际操作中,很多的编译器处理 signed 类型的方式和 unsigned 类型类似。也就是说,赋值时是取该值对该类型取值数目求模后的值。然而我们不能保证编译器都会这样处理 signed 类型。

#include <stdio.h>#include <stdlib.h>int check_end(){    int   i = 0x12345678;    char *c = (char *)&i;    return (*c == 0x12);}int print_bit(void *addr, int size){    unsigned char *ptr = (unsigned char *)addr;    int print_bytes = 0;    if(ptr == NULL)    {        return -1;    }    for(print_bytes = 0;        print_bytes < size;        print_bytes++, ptr++)    {#ifdef DEBUG        printf("byte %d, data = %02x -=>", print_bytes, *ptr);#endif        for(int print_bits = 7;        print_bits >= 0;        print_bits--)        {            printf("%d", ((*ptr >> print_bits) & 1));        }#ifdef DEBUG        printf("\n");#endif    }    printf("\n");    return print_bytes;}int print_byte(void *addr, int size){    unsigned char *paddr = (unsigned char *)addr;    int print_bytes = 0;    if(paddr == NULL)    {        return -1;    }    while(print_bytes < size)    {        printf("%02x", *paddr);        paddr++;        print_bytes++;    }    printf("\n");    return print_bytes;}int main(void){    printf("%d\n", sizeof(long));    unsigned char a = 336;    print_bit((void *)&a, sizeof(char));    print_bit((void *)&a, sizeof(short));    printf("%d\n", a);    int *p = &a;    print_bit((void *)p, sizeof(int));    printf("%d\n", *p);    unsigned char b = -1;    print_bit((void *)&b, sizeof(b));    printf("%d\n", b);    union A    {        short ia;        unsigned char ca[2];    }aa;    aa.ia = 336;    printf("%d %d\n", aa.ca[0], aa.ca[1]);    aa.ca[0] = 336;    printf("%d %d\n", aa.ca[0], aa.ca[1]);    aa.ca[1] = 336;    printf("%d %d\n", aa.ca[0], aa.ca[1]);    return EXIT_SUCCESS;}
0 0
原创粉丝点击