【C语言进阶】深度剖析数据在内存中的存储--上

1. C语言中的数据类型的简单介绍

注:C99标准里面,定义了bool类型变量。这时,只要引入头文件stdbool.h ,就能在C语言里面正常使用bool类型。

【C语言进阶】深度剖析数据在内存中的存储--上_第1张图片
1.1 在C语言中各类型所占内存空间的大小如下

char类型的数据类型大小为1字节即8比特位。

short类型的数据类型大小为2字节即16比特位。

int类型、float类型的数据类型大小为4字节即32比特位。
long类型较为特殊,C语言规定中sizeof(long)>=sizeof(int)
32位平台下long大小为4字节,64位平台下long大小为8字节。
long long类型、double类型的数据类型大小为8字节即64位。

1.2 类型的意义:

  1. 使用这个类型开辟内存空间的大小(大小决定了使用范围)。
  2. 如何看待内存空间的视角
    什么叫做看待内存空间的视角呢?以int类型、float类型为例,虽然大小都为4字节,但是存储的不同的数据类型。

1.3 类型的基本归类:
整型家族:

char类型在内存中以ASCLL码值存储,所以归类于整型家族
注:在C语言标准中,char类型默认为unsigned char 还是signed char是未定义的,取决于编译器,但是大部分编译器char默认为signed char
char

  unsigned char   signed char

short

unsigned short   signed short

int

int unsigned int   signed int

long

unsigned long   signed long

long long

unsigned long long   signed long long

有符号类型的最高位表示符号位,负数最高位是1,正数最高位是0。

浮点数家族:

float
double

构造类型:

数组类型

数组类型是去掉数组名,剩下的部分。
例如int arr1[5]和int arr2[8]两个数组的类型分别是int [5]和int [8].

结构体类型 struct
枚举类型 enum
联合类型 union

指针类型

int pi;
char pc;
float
pf;
void
pv;等等

空类型:

void 表示空类型(无类型) 通常应用于函数的返回类型、函数的参数、指针类型

2. 整形在内存中的存储

我们知道数值有不同的表示方式.
例如十进制的21,用不同进制表示如下:
二进制表示:0b10101 (二进制以0b开头)
八进制表示:025 (八进制以0开头)
十六进制表示:0x15 (十六进制以0x开头)
而在计算机中数据是以2进制存储的。

2.1 原码、反码、补码

计算机中的整数有三种表示方法,即原码、反码和补码。 三种表示方法均有符号位和数值位两部分,符号位都是用0表示“正”,用1表示“负”,
正整数的原码、反码、补码都相同。
负整数的三种表示方法各不相同。
原码 直接将二进制按照正负数的形式翻译成二进制就可以。
反码 将原码的符号位不变,其他位依次按位取反就可以得到了。
补码 反码+1就得到补码。

以正整数20和负整数10为例,原码反码补码如下。
数据在内存中是以二进制存储的,但为了方便程序员观察,在编译器的内存窗口显示的是16进制
【C语言进阶】深度剖析数据在内存中的存储--上_第2张图片

对于整数来说:数据存放内存中其实存放的是补码。

2.2为什么要使用补码存储呢?

举个例子 计算机计算1-1是如何实现的呢,因为(CPU只有加法器)可以将1-1变为1+(-1) 假设都是短整型数据
1的原码是0000000000000001
-1的原码是1000000000000001
相加的结果是1000000000000010 //-2
1+(-1)的结果不应该是0么?这里的结果却是-2.

通过上面的例子我们可以看到,如果采用原码存储,不便于加减运算。无法直接得到计算结果。 而如果使用补码存储 假设都是短整型数据
1的补码是0000000000000001
-1的补码是111111111111111111
相加的结果是0000000000000000 // 0
通过上面的例子我们可以看到,如果采用补码存储,可以可以将符号位和数值域统 一处理,直接得到运算的结果。

补码与原码相互转换,其运算过程 是相同的,不需要额外的硬件电路。
这句话是什么意思呢?

我们知道负整数的补码就是将原码的符号位不变,其他位依次按位取反再加1 那么知道了它的补码如何求源码呢?
将运算过程逆过来,负整数的补码-1就是反码,而反码再符号位不变,其他位按位取反就是原码,这是一种普遍的方法。
但是其实负整数的补码转换为原码也可以采用原码转换为补码时采用的方法 将补码符号位不变,其他位按位取反,再将得到的结果加1就是负整数的原码
例如-10的补码转换为原码:
【C语言进阶】深度剖析数据在内存中的存储--上_第3张图片

总结:在计算机系统中,数值一律用补码来表示和存储。原因在于,使用补码,可以将符号位和数值域统 一处理;
同时,加法和减法也可以统一处理(CPU只有加法器)此外,补码与原码相互转换,其运算过程 是相同的,不需要额外的硬件电路。

2.3 大小端介绍

什么大端小端: 大端(存储)模式,是指数据的低位保存在内存的高地址中,而数据的高位,保存在内存的低地址 中;
小端(存储)模式,是指数据的低位保存在内存的低地址中,而数据的高位,,保存在内存的高地 址中。 为什么有大端和小端:
为什么会有大小端模式之分呢?这是因为在计算机系统中,我们是以字节为单位的,每个地址单元 都对应着一个字节,一个字节为8bit。但是在C语言中除了8 bit的char之外,还有16 bit的short型,32 bit的long型(要看具体的编
译器),另外,对于位数大于8位的处理器,例如16位或者32位的处理器,由于寄存器宽度大于一个字节,那么必然存在着一个如何将多个字节安排的问题。因此就导致了大端存储模式和小端存储模式。
例如:一个 16bit 的 short 型 x ,在内存中的地址为 0x0010 , x 的值为 0x1122 ,那么 0x11 为
高字节, 0x22 为低字节。对于大端
模式,就将 0x11 放在低地址中,即 0x0010 中, 0x22 放在高地址中,即 0x0011 中。小端模式,
刚好相反。我们常用的 X86 结构是
小端模式,而 KEIL C51 则为大端模式。很多的ARM,DSP都为小端模式。有些ARM处理器还可以由硬件来选择是大端模式还是小端模式

【C语言进阶】深度剖析数据在内存中的存储--上_第4张图片

百度2015年系统工程师笔试题:
请简述大端字节序和小端字节序的概念,设计一个小程序来判断当前机器的字节序。
#include 
int check_sys()
{
 int i = 1;
 return (*(char *)&i);
}
int main()
{
 int ret = check_sys();
 if(ret == 1)
 {
 printf("小端\n");
 }
 else
 {
 printf("大端\n");
 }
 return 0;
}

3.练习巩固

学习了上面诸多知识后,让我们来实践一下吧。

1.打印a,b,c的结果分别是什么?
注:signed char 取值范围-128~127 unsigned char取值范围0-255

#include 
int main()
{
    char a= -1;
    signed char b=-1;
    unsigned char c=-1;
    printf("a=%d,b=%d,c=%d",a,b,c);
    return 0;
}

解析:

  char类型在vs编译器下默认是 signed char,将 -1存到signed char中会发生截断,-1默认是一个32位的整数。存到signed char中会截断低8位存储,所以a和b里存放的是11111111,而%d是打印有符号的整型数值,所以打印时会发生整型提升,提升时看a,b的类型,ab都是有符号类型,所以我们看它的最高位(符号位)来进行提升,这里的最高位是1,所以提升时在高位补24个1变为11111111111111111111111111111111,这时我们再将补码转换为原码1000000000000000000000000001得到的就是最终打印出来的结果的2进制序列,转换为10进制就是 -1,所以打印a,b的结果是 -1 .
  而c里存放的也是8个1,但是整型提升时因为是无符号类型,所以高位直接补24个0,凑齐32比特位。得到的补码是00000000000000000000000011111111,再将补码转换为原码依旧是00000000000000000000000011111111转换为10进制就是255,所以打印c的结果是 255。

2. 打印a的结果是什么?

#include 
int main()
{
    signed char a = -128;
    printf("%u\n",a);//%u打印无符号整数
    return 0;
    }

解析:
  -128的补码是11111111111111111111111110000000,截断低8位10000000,以%u形式打印发生整型提升,因为signed
char为有符号类型所以高位补符号位变为11111111111111111111111110000000,因为是以%u形式打印,所以会认为这串补码是一串非常大的正数的二进制序列,因为是正数所以补码原码相同,转换为10进制就是4294967168,所以a打印出来就是4294967168。

3.打印a的结果分别是是什么?

#include 
int main()
{
    signed char a = 128;
    printf("%u\n",a);  //4294967168
    printf("%d\n",a); //-128
    return 0;
}

答案: printf(“%u\n”,a); //4294967168
printf(“%d\n”,a); //-128

4…打印i的结果是什么?

unsigned int i;
for(i = 9; i >= 0; i--)
{
    printf("%u\n",i);
}

答案:9 8 7 6 5 4 3 2 1 0 4294967295 4294967294 4294967293 …死循环下去
为什么结果会是这样呢? 解析: 因为i是一个无符号整数,所以9~0都是正常循环打印的,但是i–到了-1的时候,因为unsigned int
i是无符号整数,所以不会将-1的补码11111111111111111111111111111111看作是一个负数,而是会看作一个很大的正数,所以依旧满足循环条件。

【C语言进阶】深度剖析数据在内存中的存储--上_第5张图片
5.字符串a的长度是多少?

int main()
{
    signed char a[1000];
    int i;
    for(i=0; i<1000; i++)
   {
        a[i] = -1-i;
   }
    printf("%d",strlen(a));
    return 0;
}

解析:a[i]里存放的值依次是-1,-2,-3 …到-128为一个轮回,然后变为127,126
…2,1,0,然后再依次是-1,-2,-3
…一直循环到数组存满为止。本题求的是字符串长度,strlen在字符数组里找到‘\0’为止,而‘\0’的ascll码值为0,所以字符串a的长度是255。
【C语言进阶】深度剖析数据在内存中的存储--上_第6张图片

6.下面代码的运行结果是什么?

#include 
unsigned char i = 0;
int main()
{
    for(i = 0;i<=255;i++)
   {
        printf("hello world\n");
   }
    return 0;
    }

答案:无限打印hello world,死循环下去。
解析:无符号数到255后+1又会变为0,无限循环下去。

你可能感兴趣的:(C语言进阶,c语言,开发语言,visualstudio)