【C语言】数据在内存中的存储

简介: 前言不同的数据在内存中的存储形式是不同的,而当我们掌握数据在内存中的存储形式之后,会帮助我们更加了解计算机深层工作原理废话不多说,我们接下来直接进入正题

前言

不同的数据在内存中的存储形式是不同的,而当我们掌握数据在内存中的存储形式之后,会帮助我们更加了解计算机深层工作原理

废话不多说,我们接下来直接进入正题



一:数据类型详细介绍

##1.整型家族

char

unsigned char

signed char

int

unsigned int

signed int

short

unsigned short

signed short

long

unsigned long

signed long#

#2.浮点型家族

double

float

##3.构造类型

1.数组类型 int arr[10]={0};

2.结构体类型 struct stu{}

3.枚举类型 enum

4.联合类型 union


##4.指针类型

1.数组指针

2.整型指针

3.字符指针

4.无类型指针

5.单精度浮点型指针


##5.空类型

void*p

这种类型指针可以接收任意的数据类型的地址


二:整型在内存中的存储

2.1原码,反码,补码的介绍

这三种整型的表达形式均有符号位和数值位


原码其实就是把我们所直观看到的数字,用二进制形式表达出来

例如:char型的数字-1表示为10000001

反码其实就是让原码符号位不变,其他数值位按位取反

例如:char型的数字-1表示为11111110

补码其实就是反码+1

例如:char型数字-1表示为11111111

无符号数,有符号数中的正数的原码反码补码相同



2.2整型提升的规则

非常简单,一定要记住啊!!!

有符号数高位补符号位,无符号数高位补0


2.3大小端字节序的介绍


大端字节序存储方式:补码的低位放在内存的高位,高位放在内存的低位

例如:00000000 00000000 00000000 00010100int型的20在表现形式为16进制的大端字节序下存为00 00 00 14

小端字节序存储方式:补码的低位放在内存的低位,高位放在内存的高位

例如:00000000 00000000 00000000 00010100int型的20在表现形式为16进制的大端字节序下存为14 00 00 00

d8c94063dd30459681d7c1b669cb29b8.png


C语言整型提升的规则及样例详解(转载自csdn博主恋浅笑的文章)

C语言的int类型与unsigned int运算的问题。(转载自csdn博主漩涡小林的文章)

2.4还是上练习题吧(介绍的好累😩)


先说一些看似是废话,实际上却是非常重要的话语(不知道这些话很可能产生很多傻逼的疑问,)


1.c语言默认数字是int型的也就是32比特位

2.%d是打印int型的,也就是输出有符号的十进制数字,%u是打印unsigned int,也就是输出无符号的十进制整数

3.整型提升是c程序设计语言中的一项规定,在表达式进行计算时,所有的整型首先要提升为int类型,因为cpu中的整型运算器的操作数的字节长度是int类型的字节长度

OK,有了这些知识的铺垫,我们就可以砍瓜切菜般处理这些智障问题了,哈哈哈哈哈

1.
//输出什么?
#include <stdio.h>
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;
}


bf224e3345d94b83b59aa3daeb731cfb.png



储存: -1的补码为全1,当发生截断后存储在变量abc中的内存形式均为11111111,但打印的是int型的十进制数字,所以要进行整型提升。

ab是有符号数字,高位全补符号位,也就是补1,c是无符号数字高位补0

a:11111111 11111111 11111111 11111111-补码

b:11111111 11111111 11111111 11111111-补码

c: 00000000 00000000 00000000 11111111-原码,反码,补码


读取:ab都是有符号数字,原码,反码,补码都是不相同的所以在我们想要知道他的结果时,要去读取他的原码,但c是无符号数字,所以他的原码,反码,补码相同,直接读取就OK了。

 2.
#include <stdio.h>
int main()
{
    char a = -128;
    printf("%u\n",a);
    return 0;
}

282d8bd16431422c88494121d9005463.png



储存:(注意char默认为signed char)-128的原码是10000000 00000000 00000000 10000000,反码为11111111 11111111 11111111 01111111 ,补码为11111111 11111111 11111111 10000000截断后为10000000。打印的形式是%u,所以要进行整型提升。

怎么进行整型提升呢?当然要先看他是什么类型的啦,既然是有符号数10000000的高位就是符号位,补1后结果为11111111 11111111 11111111 10000000

读取:打印时,因为打印的是无符号数,那么高位就会被我们的电脑认为是数值位,因为不方便读出他的数字,所以我们可以借助程序员专属计算器,算一下它的结果就是4294967168

085d0c0736b148e29424267818004f48.png



3.
#include <stdio.h>
int main()
{
    char a = 128;
    printf("%u\n",a);
    return 0;
}

b91ed31a507d4036be71802c2eb2b224.png

我们在看到char和unsigned char时一点要敏感他们的范围

1.char -128到127

2.unsigned char 0到255

存储:128在有符号数中其实就是127+1,也就是-128那么他的结果就是4294967168

 4.
int i= -20;
unsigned  int  j = 10;
printf("%d\n", i+j); 
//按照补码的形式进行运算,最后格式化成为有符号整数

5aa2862924dd4cfd93c0bb2cdd59495f.png


存储:i为有符号整型,存储的是补码,-20的原码为10000000 00000000 00000000 00010100,反码为11111111 11111111 11111111 11101011,补码为11111111 11111111 11111111 11101100,j为无符号整数补码即为原码,10的原码为00000000 00000000 00000000 00001010,i+j结果为11111111 11111111 11111111 11110110

读取:将i+j的结果强制输出为有符号数,则我们想要知道结果,就得先把它转换为原码,原码就是我们能够看到的输出结果,现在我们进行原码的读取(写的好累啊),i+j的反码为10000000 00000000 00000000 00001001,反码+1结果为10000000 00000000 00000000 00001010,那么输出结果就是-10

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


c694e382553d40c697b4591d55a61e3c.png



答案陷入了死循环

**存储:**首先i是个无符号整型0-255,当i=-1时,由于是无符号数,则原码,反码补码都相同,所以全1(补码)会被当做原码处理,并且打印出来,下一次i又变为-2,则原码,反码,补码依旧还是相同的,那其实就是全1减1(补码)的形式,被继续当作原码处理并输出,那么程序会一直走下去

32个比特位,从4294967295开始走,输出一次-1一下,把程序都干死了走的

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


e490fdf84b764baaaf68d5b5be1cea24.png


知识准备:1.strlen是求字符串长度的库函数,计算\0之前的字符串个数

2.char能表示的范围是-128到127,所以创建的char型的大小为1000的数组a只能存放-128到127的数字

3.char型数字表示的范围,我们可以把它想像成一个钟表,从右向左超过127时,回到-128的位置(我们的知识储备OK了,可以消灭这道题了)

储存:a[i]从-1开始被赋值,会被先赋值到-128,然后再到127,逐次-1,最后被赋值成0,那么被赋值为0的那个数组元素在数组的哪个位置呢?经过加法运算,我们知道下标为255的数组元素被赋值成0,那个数字在我们看来其实就是第256个数字

输出结果:我们知道第256个数组元素被赋值成为了0,那么strlen只能计算到255个元素,所以我们的结果为255

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


9a1b6ae9a74947b2975bc6c42c99ea55.png



知识准备:我们定义了一个无符号char型变量i,我们知道无符号整型能表示的范围为0到255

程序的解读:我们在上面的题目中可以知道,char型表示数字范围可以想象为一个钟表,那么unsigned char当然也可以这样表示啦!

输出结果:我们其实可以知道,无论i的数值为多少,它永远都在0到255这个循环里面,那么for循环的判断条件就永远成立,所以这个循环就会一直进行下去,自然程序输出的结果为死循环


三:浮点型在内存中的存储

3.1浮点型在内存中的存储规则(概念来了😂):

3.1.1直接上一波硬性规定的概念(哈哈哈🤣):


根据国际标准协会IEEE(电气和电子工程协会)754规定: 任意一个二进制浮点数可以表示成下面这种形式

1.(-1)^S * M * 2 ^ E

2.S代表符号位,当S等于0,二进制浮点数为正数,当S等于1,二进制浮点数为负数

3.M表示为有效数字,大于等于1小于2

4.E表示指数位:为什么他是2的E次方呢?(当然是因为他是二进制啦,你十进制用的不是10的E次方?哼😝)


3.1.2举例解释上面的概念:

举例来说:


十进制的浮点数9.0,转换为二进制为1001.0,那么他又可以写成1.0012^3,那么他的符号位是0,有效位是1.001,指数位是3
十进制的浮点数-9.0,转换为二进制为-1001.0,那么它又可以写成-1.001
2^3,那么他的符号位是1,有效位是1.001,指数位是3


3.1.3描述浮点数在内存中的存储形式:

单精度浮点数: 对于32比特位的单精度浮点数,最高位是符号位S,接着的8比特位是指数位,剩下的23比特位是有效位

faa9ad1a174c4129b89a09f61df171fa.png

双精度浮点数: 对于64比特位的双精度浮点数,最高位是符号位S,接着的11比特位是指数位,剩下的52比特位是有效位


e6d1b02b87b547aea1d1d82fa0224de5.png

科学研究表明人类的大脑更容易记住图像哦😎

记住表示形式是SME,储存时却是按照SEM的顺序



3.1.4IEEE的一些特殊规定(针对于指数位和有效数字位):


1.有效数字位: 有效数字M可以表示成1.xxxxxx,其中xxxxxx是小数部分,所以IEEE规定,当储存有效数字位时,我们只储存小数部分,等到读取时,我们再把开头的1补上去,这样做可以帮助机器多储存一个有效数字位

2.指数位(指数是非常复杂的):

(1.指数的存储):首先E为一个无符号整数(unsigned int),如果是单精度浮点数,E的取值范围是0到255,如果是双精度浮点数,E的取值范围是0到2047.但是科学计数法中的E是可以出现负数的,所以IEEE规定,当我们在内存中存储整数E时,E的真实值要加上一个中间数,对于不同的精度浮点数,这个中间数分别是127和1023

(2.指数从内存中的取出):当我们存储的知识点介绍完之后,读取指数的方式又分为3种

当E不全为0或不全为1时: 这时浮点数采取下面的规则表示,真实有效数字=内存存储+1,真实指数数字=内存数字-127或1023

当E全为0时: 读取有效数字时,不再加上1,直接写成0.XXXXXX,读取指数数字时,指数都被相关组织硬性规定为1-127和1-1023,其实就是-126和-1022

当E为全1时: 这时如果有效数字M全为0,表示±无穷大

由于上面两种的极端特殊情况计算机也无法进行相应的计算来体现,所以不是很重要的,我们就介绍到这里,大家把第一种经常常见的读取方式就完全OK了嘻嘻😁

————————————————


3.2 上一个例题(要不然不会运用知识内容😋):

int main()
{
 int n = 9;
 float *pFloat = (float *)&n;
 printf("n的值为:%d\n",n);
 printf("*pFloat的值为:%f\n",*pFloat);
 *pFloat = 9.0;
 printf("num的值为:%d\n",n);
 printf("*pFloat的值为:%f\n",*pFloat);
 return 0;
}


372d609105eb48a1bcf1274b550e439e.png

知识准备: int型的数字9在内存中的存储形式为00000000 00000000 00000000 00001001,转换为float型后被分为0 00000000 00000000000000000001001,分别对应的是符号位,指数位,有效数字位。

第一部分的结果剖析: 所以我们打印整数n时,结果肯定是9,因为他的存储方式和读取方式我们都很熟悉,存储按二进制,读取也是按二进制。

但当我们打印浮点数时,我们在存储和读取是于int型是不同的,符号位是0,指数位也是0,因为指数位是全0,那么属于读取的第二种情形,将浮点数表示为0.92^-126,这个数字表示非常小的数字,机器就会输出0

第二部分的结果剖析: 十进制的浮点数9.0表示为二进制为1001.0,浮点表示为1.0012^3,存储到内存里为

0 10000010 00100000000000000000000按照整数形式输出我们可以看到结果就是1091567616

最后那个9.000000肯定不用我说了吧,你按浮点数存储,又按浮点数的形式进行打印,结果当然不变啦!!!😍😎😋


bf21d359825548f3abfc073c788c2cc9.png


3.3总结整数和浮点数在存储时的一些区别



在小数部分的存储,我们直接将浮点数二进制表示形式下的小数部分存到23比特位的部分即可(或者52比特位),在读取时我们也直接将其整体拿出来加上个1就是二进制形式下的浮点数表达了


为什么要特殊说一句这个呢?因为我们已经习惯读取整数的方法了,我怕你们在读出内存中的小数部分时,忍不住按照二进制翻译为十进制的形式把它作为小数写到浮点数二进制的表示形式当中


而且我们存储小数部分时,必须要将二进制的小数部分从左向右排放到相应的比特位当中,(后面的空余比特位直接补0即可),千万不要按照高位低位的形式存储小数位,这样存储直接就反过来了,存到后面去了 举例:比如例题中的001,你若存错的话,就变为00000000000000000000001,直接这题就被你给做报废了,大兄弟!!!😱😱😱



四:小编对你说的话


小编写这篇稿子真的是用心了,如果你觉得小编的文章对你真心有帮助的话,请不要吝啬您的点赞和关注,随着我的实力的成长,将来小编也会继续发出更多优秀的文章,帮助大家进步的,我们一起卷死别人吧!!!😎😎😎哈哈哈


































相关文章
|
17天前
|
监控 算法 应用服务中间件
“四两拨千斤” —— 1.2MB 数据如何吃掉 10GB 内存
一个特殊请求引发服务器内存用量暴涨进而导致进程 OOM 的惨案。
|
15天前
|
C语言
【c语言】动态内存管理
本文介绍了C语言中的动态内存管理,包括其必要性及相关的四个函数:`malloc`、``calloc``、`realloc`和`free`。`malloc`用于申请内存,`calloc`申请并初始化内存,`realloc`调整内存大小,`free`释放内存。文章还列举了常见的动态内存管理错误,如空指针解引用、越界访问、错误释放等,并提供了示例代码帮助理解。
28 3
|
16天前
|
存储 C语言
数据在内存中的存储方式
本文介绍了计算机中整数和浮点数的存储方式,包括整数的原码、反码、补码,以及浮点数的IEEE754标准存储格式。同时,探讨了大小端字节序的概念及其判断方法,通过实例代码展示了这些概念的实际应用。
31 1
|
20天前
|
存储
共用体在内存中如何存储数据
共用体(Union)在内存中为所有成员分配同一段内存空间,大小等于最大成员所需的空间。这意味着所有成员共享同一块内存,但同一时间只能存储其中一个成员的数据,无法同时保存多个成员的值。
|
22天前
|
监控 Java easyexcel
面试官:POI大量数据读取内存溢出?如何解决?
【10月更文挑战第14天】 在处理大量数据时,使用Apache POI库读取Excel文件可能会导致内存溢出的问题。这是因为POI在读取Excel文件时,会将整个文档加载到内存中,如果文件过大,就会消耗大量内存。以下是一些解决这一问题的策略:
54 1
|
17天前
|
存储 C语言
【c语言】字符串函数和内存函数
本文介绍了C语言中常用的字符串函数和内存函数,包括`strlen`、`strcpy`、`strcat`、`strcmp`、`strstr`、`strncpy`、`strncat`、`strncmp`、`strtok`、`memcpy`、`memmove`和`memset`等函数的使用方法及模拟实现。文章详细讲解了每个函数的功能、参数、返回值,并提供了具体的代码示例,帮助读者更好地理解和掌握这些函数的应用。
16 0
|
存储 程序员 C语言
程序员之路:C语言中存储类别
程序员之路:C语言中存储类别
133 0
|
30天前
|
C语言 C++
C语言 之 内存函数
C语言 之 内存函数
32 3
|
23小时前
|
C语言
c语言调用的函数的声明
被调用的函数的声明: 一个函数调用另一个函数需具备的条件: 首先被调用的函数必须是已经存在的函数,即头文件中存在或已经定义过; 如果使用库函数,一般应该在本文件开头用#include命令将调用有关库函数时在所需要用到的信息“包含”到本文件中。.h文件是头文件所用的后缀。 如果使用用户自己定义的函数,而且该函数与使用它的函数在同一个文件中,一般还应该在主调函数中对被调用的函数做声明。 如果被调用的函数定义出现在主调函数之前可以不必声明。 如果已在所有函数定义之前,在函数的外部已做了函数声明,则在各个主调函数中不必多所调用的函数在做声明
16 6
|
20天前
|
存储 缓存 C语言
【c语言】简单的算术操作符、输入输出函数
本文介绍了C语言中的算术操作符、赋值操作符、单目操作符以及输入输出函数 `printf` 和 `scanf` 的基本用法。算术操作符包括加、减、乘、除和求余,其中除法和求余运算有特殊规则。赋值操作符用于给变量赋值,并支持复合赋值。单目操作符包括自增自减、正负号和强制类型转换。输入输出函数 `printf` 和 `scanf` 用于格式化输入和输出,支持多种占位符和格式控制。通过示例代码详细解释了这些操作符和函数的使用方法。
33 10