首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >C语言数据在内存中的存储

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

作者头像
君辣堡
发布2025-12-20 09:00:02
发布2025-12-20 09:00:02
210
举报

今天我要讲讲数据在内存中的存储形式,分为三个板块:

1. 整数在内存中的存储

2. ⼤⼩端字节序和字节序判断

3. 浮点数在内存中的存储

1.整数在内存中的存储

整数的二进制的表达方法其实有三种:原码,反码,补码

有符号整数的三种表达方法中均有符号位和数值位,符号位是用0来表示正,1来表示负

最高的一位被当为符号位,其余的都是数值位;

正整数的原码,反码,补码都相同

负整数的原码,反码,补码都各不同

原码:直接将整数按照正负数的形式转换为二进制得到的就是原码;

反码:将原码的符号位不变,数值为都按位取反得到的就是反码;

补码:反码+1,得到的就是补码;

对于整型来说:数据存放在内存中其实存放的是二进制的补码,原因如下:

1.在计算机系统中,数值⼀律⽤补码来表⽰和存储。

2.原因在于,使⽤补码,可以将符号位和数值域统⼀处理;

3.同时,加法和减法也可以统⼀处理(CPU只有加法器)此外,

补码与原码相互转换,其运算过程是相同的,不需要额外的硬件电路。


2.大小端字节序和字节序的判断

了解了以上,我们来看这个代码:

代码语言:javascript
复制
#include <stdio.h>
int main()
{
int a = 0x11223344;
return 0;
}

这串代码调试后,我们会看到:

可以看到,这个数字是按照字节为单位,倒着存储的,这是为什么呢?

这时候我们就需要知道什么是大小端:

其实超过⼀个字节的数据在内存中存储的时候,就有存储顺序的问题,按照不同的存储顺序,我们分为⼤端字节序存储和⼩端字节序存储,下⾯是具体的概念:

⼤端(存储)模式:

是指数据的低位字节内容保存在内存的⾼地址处,⽽数据的⾼位字节内容,保存在内存的低地址处。

⼩端(存储)模式:

是指数据的低位字节内容保存在内存的低地址处,⽽数据的⾼位字节内容,保存在内存的⾼地址处。

原因:

因为存储字节的方式有很多,所以就分为两种方式:大端或小端

相当于统一度量衡;

了解了这些之后,接下来我们做几道练习题吧:

重点练习题:

练习题1:

代码语言:javascript
复制
#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;
}

如图,我们知道signd char的取值范围是-128~127,unsigned char的取值范围是0~255;

题中char a和signed char b其实是等效的,都是有符号型,取值范围-128~127,负一的二进制序列为:  10000000 00000000 00000000 00000001,这是原码

负数的话,补码就是原码取反,+1

取反:11111111 11111111 11111111 11111110

+1  : 11111111 11111111 11111111 11111111;

然后就得到了补码 由于char类型只能放得下一个字节,会发生截断,取的是低位的字节,即:

11111111;

然后,打印是用%d打印,%d是打印10进制的有符号整型,则会发生整型升级填充的是符号位的数字,也就是1:    11111111 11111111 11111111 11111111,这就是升级后的补码;

因为是负数,现在需要把补码转化为原码,转化过程为取反,+1:

取反:10000000 00000000 00000000 00000000(符号位不变)

+1:   10000000 00000000 00000000 00000001  

这就是最终的原码,则输出的结果就是-1

这就是char a用%d输出的值,同理signed char b也是。

接下来看看unsigned char c:

虽然是unsigned ,无符号型,但是呢,我们强行把-1塞进去,这是改变不了的,所以我们照样先分析-1

上面写过,所以我们抄下来,-1的补码是:11111111 11111111 11111111 11111111

同理,会截断,则得到: 11111111;

然后%d打印,同样是整型升级。但是注意!这是无符号整型,默认最高位是0,无负数!所以填充时,全部填0,则得到: 00000000 00000000 00000000 11111111;

显然,这是个正数,则原码即补码,所以直接输出值,答案就是:255;

这就是这道题。

练习题2

代码语言:javascript
复制
#include <stdio.h>
int main()
{
    char a = -128;
    char b =  128;
    printf("%u\n", a);
    printf("%u\n", b);
    return 0;
}

我们看看这题。首先,我们计算一下a:

a的原码:10000000 00000000 00000000 10000000;

取反加1后可得到: 11111111 11111111 11111111 10000000;这就是补码;

同样,发生截断,截取低位字节:10000000;

然后用%u打印,%u是打印十进制的无符号整型.char 中存不下128,强制存放会变成-128,所以符号位是1,用%u打印,整型升级填符号位,所有全部填1:

11111111 11111111 11111111 10000000,即一个非常大的正数:4294967168

,同理,char b是-128,和上面一样:11111111 11111111 11111111 10000000,即4294967168

那,为什么128会变成-128呢?看图:

如图。128就在-128的位置,这是char类型的变化,同理,129就是-127了,255就是-1,256就是0,数字继续增大,就会继续循环。知道这个后,我们来看看下面这练习题3:

代码语言:javascript
复制
int main()
{
char a[1000];
int i;
for(i = 0; i < 1000; i++)
{
a[i] = -1 - i;
}
printf("%d", strlen(a));
return 0;
}

如图,i 会随着for循环一直增大直到999,既然知道了上面的变化规律,那么这题就会变得很简

单,当i增大到254时,即arr[253],也就是第255个元素,得到了-256,-256的规律无非就是反过来

绕圈,那么结果还是0,意思就是在第255个元素时,里面填的是\0,那么后面的元素都不用看了,

srelen读取到\0就停止,那么输出的结果就是255,这就是这道题。

接下来,看这道最难的练习题

代码语言:javascript
复制
#include <stdio.h>
//X86环境 ⼩端字节序
int main()
{
int a[4] = { 1, 2, 3, 4 };
int *ptr1 = (int *)(&a + 1);
int *ptr2 = (int *)((int)a + 1);
printf("%x, %x", ptr1[-1], *ptr2);
return 0;
}

如图,32位环境下,小端字节序:

我们直接看这句:int *ptr1 = (int *)(&a + 1);       &a代表取了整个数组的地址,那么&a+1就是代表跳过了整个数组,指向了最后一个元素的下一个元素:

如图所示,指向4后面。因为是数组的地址,是int  (*)[4]类型的,所以要强制类型转换为int*,然后让指针ptr1指向这个地址。

那么打印时用%x,即以十六进制形式打印无符号整数,打印的是ptr1[-1],我们知道,这个的意思是

*(prt1-1),那么就是指针向前走一位置,指向4的开头,然后解引用指针,指针向后访问,得到4;4用%x打印就是0x4(或者0x 00 00 00 04),最后输出结果省略0x,那么结果就是4.

再看:int *ptr2 = (int *)((int)a + 1);       老样子,先从内层看起,a是数组名,将a强制类型转换为int类型,然后+1,意思就是:首元素地址(假设为0x11223344),转化为int类型,那么+1就是直接在地址的基础上+1,那么就得到了0x11223345,意思是指向了首元素的第2个字节,而非第2个元素,如图:

(int)a+1指向了首元素的第二个字节,因为是小端字节序存储,所以00 00 00 01会反过来排列:01 00 00 00 ,那么这时候再*ptr2,就会在这个基础上向后访问四个字节,即:00 00 00 02

那么最后输出的结果就是:00000002

这就是所有的练习题,接下来我们来讲浮点数:


3. 浮点数在内存中的存储

根据国际标准IEEE(电⽓和电⼦⼯程协会)754,任意⼀个⼆进制浮点数V可以表⽰成下⾯的形式:

V = (−1) S M ∗ 2 E(其中,S,E都是指数(1<=M<2)

• (−1) S 表⽰符号位,当S=0,V为正数;当S=1,V为负数

M 表⽰有效数字,M是⼤于等于1,⼩于2的

2 E 表⽰指数位

举例子:5.0的二进制表示就是101.0,,相当于1.01* 2 2,可以得出S=0,M=1.01,E=2

反过来:⼗进制的-5.0,写成⼆进制是 -101.0 ,相当于-1.01* 2 2 ;那么,S=1,M=1.01,E=2

注意:

对于32位的浮点数(float),最⾼的1位存储符号位S,接着的8位存储指数E,剩下的23位存储有效数字M

对于64位的浮点数(double),最⾼的1位存储符号位S,接着的11位存储指数E,剩下的52位存储有效数字M

M:

由于m大于等于1小于2,所以存储浮点数时,默认这个数字第一位总是1,因此可以被舍去,只保留小数位数,等到计算完所有后再加上那个1。这样做的目的是:节省1位有效数字;

以32位浮点数为例,留给M只有23位,将第⼀位的1舍去以后,等于可以保存24位有效数字,精度更高了

E:

⾸先,E为⼀个⽆符号整数.他的代表指数位。如果E为8,则取值范围就是0-255,E为11,就是

0-2047。因为科学计数法,指数位是可以为负的,但是负数不好以二进制形式存入内存,于IEEE 754规定,存⼊内存时E的真实值必须再加上⼀个中间数,对于8位的E,这个中间数是127;对于11位的E,这个中间数是1023

⽐如, 2 10 的E是10,所以保存成32位浮点数(8位放E)时,必须保存成10+127=137,即10001001。

这样的浮点数存储⽅式很巧妙,但是我们也要注意到有的浮点数是⽆法精确保存的。⽐如:1.2,我们可以在VS上调试看⼀下,我们发现会有些许误差:

这是为什么呢?因为1的二进制序列就是1,1.5就是 1.1,   1.25的二进制就是1.01   那1.225就是 1.001,我们发现,无论后面接多少个1,这个二进制序列永远表达出1,2那么到最后就会出现一个非常接近1.2的数字,这就是误差的来源。


4.浮点数取的过程

E全为0:

这时,浮点数的指数E等于1-127(或者1-1023)即为真实值,有效数字M不再加上第⼀位的1,⽽是还原为0.xxxxxx的⼩数。这样做是为了表⽰±0,以及接近于0的很⼩的数字

图中有解释。

E全为1:

这时,如果有效数字M全为0,表⽰±⽆穷⼤(正负取决于符号位s

解释如图

E不全为0或1:

这是常规情况

这时,浮点数就采⽤下⾯的规则表⽰,即指数E的计算值减去127(或1023),得到真实值,再将

有效数字M前加上第⼀位的1。⽐如:0.5 的⼆进制形式为0.1,由于规定正数部分必须为1,即将⼩

数点右移1位,则为1.0* ,其 阶码为-1+127(中间值) = 126,表⽰为01111110,⽽尾数1.0去掉整数

部分为0,补⻬0到23位 0000000000000000000000,则其二进制码为:

1

0 10000010 00100000000000000000000 

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2025-12-09,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1.整数在内存中的存储
  • 2.大小端字节序和字节序的判断
    • 重点练习题:
  • 3. 浮点数在内存中的存储
  • 4.浮点数取的过程:
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档