整型在内存中的存储与管理

前言:

今天我们讨论的整型涉及到整个整型家族(没有小数点的),不单单是int。

char
unsigned char
signed char
short
unsigned short [int]
signed short [int]
int
unsigned int
signed int
long
unsigned long [int]
signed long [int]

1. 整型在内存中的存储

我们都知道,一个变量的创建要在内存中开辟空间,空间的大小由类型决定。

那么数据在开辟的内存中是如何存储的呢?

比如:

int a = 20;
int b = -10;

a、b 两个变量都被分配了四字节的空间,

那如何存储?

1.1 源码、反码、补码

计算机中的整数有三种表示方法,即源码、反码、补码。

三种表示方法都有符号位数值位两部分(符号位是二进制位的第一位,后面都是数值位)

  • 符号位都是用0表示”正“,用1表示”负“,

  • 而数值位负整数的三种表示方法各不相同

源码

直接将二进制按照正负数的形式翻译成二进制就可以。

反码

将原码的符号位不变,其他位依次按位取反就可以得到了

补码

反码+1就是补码

整数的原、反、补码都相同。

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

为什么选择了这样复杂的方式呢?

  1. 只要遵循这个补码的规则,两个数不管正负,将符号位和数值域统一处理相加,就可得到答案,即cpu只需要有加法器即可。

image-20220806174641360

注意:实际两个char在进行运算的时候,默认会进行整型提升,先转为4字节的int进行计算,结果再截断为1字节存入char变量,以上例子仅展示一个1字节二进制数的补码相加法则。

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

. image-20220805221036502

2. 整型类型间的转换

2.1 整型提升

原则:

  • 如下char和shot家族在进行算数运算时要首先转换为int,再进行算数运算

    char
    unsigned char
    signed char
    short
    unsigned short [int]
    signed short [int]

方法:

  • 整型提升都是在补码的基础上进行
  • 如果符号位为1,前面高位全部补1
  • 如果符号位为0,前面高位全部补0
  • 注意:无符号类型没有符号位,前面统一补0;

2.2 算数转换

在处理不同类型数值之间的运算时,需要先将两个操作数转换为相同的类型才能进行运算。

转换原则:

按照如上的转换级别,向高级别的那个进行对齐转换成相同类型,再进行运算。

例如:intunsigned int进行运算,编译器会将低级别的int转换为unsigned int再进行计算。而这个转换编译器并没有对它在内存中的值做出改变,而是使用了不同的读取方式,编译器将原本的int的符号位也当成数值大小进行读取。

3. 大小端介绍

为了方便理解大小端的概念,我们先谈谈内存窗口和16进制。

3.1 二进制与十六进制的转换

根据前面的案例我们发现,表示一个1字节的数需要8个二进制位,一个int型的4字节数就需要32个二进制位,太过冗长。为了阅读转换方便,如内存窗口,我们通常使用16进制去表示一个数。

转换方式:

四个二进制位可表示的数有 24 = 16 个,即一个16进制位可表示的范围,所以每4个二进制位的长度相当于1个16进制位长度;

一个字节需要8个二进制位,也就是两个16进制位

一个int是4字节,也就是8个二进制位

3.2 内存窗口

3.2.1打开方式

以下是vs编译器的内存窗口打开方式。

  1. 首先进入调试模式(快捷键F10)

  2. 按如下方式打开一个内存窗口

    image-20220806211321218

    • 在顶部地址栏输入变量地址,或直接对变量取地址(&变量名)

    • 一般控制列数为4,一列表示1字节,选择4列,一行就是一个int的大小;

    • 下方三部分,从左至右依次是:地址、对应地址中存的16进制数据、数据作为ASSIC码对应的字符。

    image-20220806211959602

3.2.2理解

上图第一行的数据80是两个16进制数,像这样两个连在一起的16进制数表示内存中的一个字节;

在内存中是以字节为单位区分地址的,每个字节都有一个对应的地址;

这里数据80对应的地址是0x0133FED0(0x前缀表示这是一个16进制数),下一个字节df位置对应的地址就是0x0133FED1,这一行是一个int的大小。

最后栏就是80转换位十进制数128作为ASSIC码对应的字符,如果不是字符串,这一栏基本不怎么用。

3.3大小端

在123这个十进制数中,3、2、1分别在个位、十位、百位,这里1所在的位代表的权重最大,3最小,所以我们称左侧的为高权值位;

同样的,一个二进制数、16进制数,都存在高权值位。

我们知道,每个int由4个字节组成,那么这4个字节也存在高权值与地权值。

接下里,我们看一下在VS编译器中一个int对应的四个字节在内存中排布:

image-20220806220613767

image-20220806220801352

可以发现,两处的字节序是相反的,这里11这个字节是高权值位,他被放到了地址最高的位置,

这里就可以引出大小端的概念,

我们规定:

  • 大端(存储)模式,是指数据的低权值字节保存在内存的高地址中,而数据的高权值字节,保存在内存的低地址中;
  • 小端(存储)模式,是指数据的低权值字节保存在内存的低地址中,而数据的高权值字节,保存在内存的高地址中

总结:低权值位于低地址就是小端,位于高地址就是大端

上面的例子中,44这个字节是低权值的字节,它被放在了低地址处,所以VS编译器使用的是小端字节序。

不同操作系统的大小端不尽相同,为了提高程序的可移植性,应尽量避免大小端的影响。

为什么有大端和小端:
为什么会有大小端模式之分呢?这是因为在计算机系统中,我们是以字节为单位的,每个地址单元
都对应着一个字节,一个字节为8 bit。但是在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处理器还可以由硬件来选择是大端模式还是小端模式。

4. 例题讲解

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;
}

解析:

-1

源码:1000 0001

反码:1111 1110

补码:1111 1111

a、b、c都是一字节大小,在内存中的存储结果都是一样的,它们的差别体现在读取时的不同的翻译;

  • charsigned char都是有符号char,没有差别,

    printf()函数用%d整型的形式去打印时,char类型要整型提升为4字节的int:
    符号位为1,高位全部补1:1111 1111 1111 1111 1111 1111 1111 1111,

    printf()再读这个整型数字,先转为它的原码:1000 0000 0000 0000 0000 0000 0000 0001,

    再转为十进制数输出:-1

  • unsigned char是无符号char,在整型提升时,因为没有符号位,高位直接补0:

    0000 0000 0000 0000 0000 0000 1111 1111

    原码与之相同,输出十进制:255

image-20220806224533291

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

解析:

  • char a = -128;

    可以看成是两步:

    1. 在内存中先形成一个-128的整型(补码形式存放)
    2. 截断后放到变量a

image-20220806231117284

  • printf(),格式指定为无符号整型,还需要整型提升:

    char a 符号位为1,高位全部补1

    image-20220806232827558

作为无符号整型输出十进制数:4294967168

image-20220806233143997

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

解析:

与上一题步骤一致:

image-20220806235955771

a 的实际值与上一题完全一致,答案也与上一题一致

image-20220806233143997

4.

#include <stdio.h>

int main()
{
int i = -20;
unsigned int j = 10;
printf(“%d\n”, i + j);
return 0;
}

解析:

image-20220807011447588

还是按照上面的转化成补码的方式,可得出-10;

可能大家会疑惑,这里为什么不考虑intunsigned int相加时的算数转换,

且不考虑根本原因,我们先把源代码直接改为如下这样再推演一遍,

image-20220807012012679

可以发现此时i的内存中的数据与上面一致。

所以足矣证明,发生的intunsigned int的算数转换,与内存中的数据无关,仅仅时读取的过程会发生差异,这里的读取方式依然是%d的整型读取,所以不论怎么理解,答案都是-10;

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

解析:

如下是有符号char和无符号char的补码和十进制的对应表

image-20220807123304906

可以看出:

  • 一个无符号char会从0一直加到255,从255再加1,会变成1 0000 0000,截断后又是0,回到了起点;
  • 一个有符号char会从0加到127,再加1,会突变到-128,然后递增到-1,再加1又回到0。

都形成了循环。

image-20220809115348557

回归到题目,数组a的每一个元素都是char,存储的元素从前至后依次是-1、-2、-3、……、-128、127、126、……、1、0、-1,循环直至存够1000个数,但是最终输出的是strlen(a),这个函数会一直数到’\0’(’\0’不算入长度)即数值0后结束,从-1到0,共有255个数,所以最后输出255.

177)]

可以看出:

  • 一个无符号char会从0一直加到255,从255再加1,会变成1 0000 0000,截断后又是0,回到了起点;
  • 一个有符号char会从0加到127,再加1,会突变到-128,然后递增到-1,再加1又回到0。

都形成了循环。

[外链图片转存中…(img-3B1ImsFn-1660019240178)]

回归到题目,数组a的每一个元素都是char,存储的元素从前至后依次是-1、-2、-3、……、-128、127、126、……、1、0、-1,循环直至存够1000个数,但是最终输出的是strlen(a),这个函数会一直数到’\0’(’\0’不算入长度)即数值0后结束,从-1到0,共有255个数,所以最后输出255.

  • 8
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
### 回答1: 在整型数组插入数据的方法有很多种。其一种方法是使用数组的插入函数。 例如,在 Python ,可以使用数组的 `insert()` 方法在数组的任意置插入新的数据。该方法接受两个参数:一个是要插入的数据的置的索引,另一个是要插入的数据的值。 下面是一个简单的例子: ``` array = [1, 2, 3, 4, 5] array.insert(3, 6) # 在数组的第 3 个置(即下标为 2)插入 6 print(array) # 输出 [1, 2, 3, 6, 4, 5] ``` 此外,还可以使用切片赋值的方法在数组的任意置插入数据。例如: ``` array = [1, 2, 3, 4, 5] array[2:2] = [6] # 在数组的第 3 个置(即下标为 2)插入 6 print(array) # 输出 [1, 2, 6, 3, 4, 5] ``` 注意:在使用切片赋值插入数据时,必须确保切片的左端点小于等于右端点,否则会出现错误。 另外,还可以使用数组的拼接操作在数组的末尾添加新的数据。例如: ``` array = [1, 2, 3, 4, 5] array += [6] # 在数组的末尾添加 6 print(array) # 输出 [1, 2, 3, 4, 5, 6] ``` 使用这些方法时,需 ### 回答2: 在整形数组插入数据的方法有多种。下面我将介绍两种常用的方法: 方法一:使用循环遍历数组 1. 首先,确定需要插入数据的置。可以根据要求的排序规则来确定插入置,例如插入到有序数组时,可以使用二分查找来确定插入置。 2. 在确定插入置后,需要将插入置之后的元素向后移动一,给插入数据腾出空间。 3. 将需要插入的数据放入插入置即可,插入完成后,数组的长度加一。 方法二:使用ArrayList类 1. 首先,将整形数组转换为ArrayList对象,这样可以方便地对数组进行修改。 2. 使用ArrayList类的insert()方法,在指定置插入需要插入的数据。 3. 插入完成后,可以选择将ArrayList对象转换回整形数组。 需要注意的是,数组本身是固定长度的,如果数组已满,需要提前对数组进行扩容,再进行插入操作。 ### 回答3: 在整形数组插入数据可以通过以下步骤实现: 1. 首先,确定要插入数据的置。可以根据需求选择在数组的开头、间或末尾插入。 2. 然后,判断数组是否已满。如果数组已满,需要扩展数组的容量。 3. 根据插入置,将插入置之后的元素依次向后移动一,为插入数据腾出空间。 4. 将要插入的数据放入插入置。 5. 最后,更新数组的长度。 具体步骤可以用伪代码表示如下: ``` 1. 定义待插入的数据为insertData,插入置为index。 2. 判断数组是否已满,若已满,则扩展数组容量。 3. 将插入置之后的元素依次向后移动一(从最后一个元素开始): for i = 数组长度 - 1 to index do array[i+1] = array[i] end for 4. 将insertData赋值给插入置: array[index] = insertData 5. 更新数组长度: arrayLength = 数组长度 + 1 ``` 这样就可以在整形数组成功插入数据。需要注意的是,在实际应用,还需考虑插入置是否合法、数组扩容的具体方法以及插入数据后的错误处理等情况。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值