数据的储存(C语言解释)
一、整形在内存中的存储
1.原码、反码、补码的引入
1.1概念:
①原码: 直接将二进制按照正负的形式翻译成二进制即可。 ②反码: 将原码的符号位不变,其他为此按位取反即可。 ③补码: 反码+1即是补码。
注:正数原、反、补相同,而负数以补码形式存储在内存当中,所有数据在运算都用其补码进行加减,而在打印出来的时候,打印的是其原码而非补码。 |
计算器中只有加法运算,因此1-1的运算如下: 若负数也以原码形式存储,那么1+(-1)的二进制运算形式如下: 0000 0000 0000 0000 0000 0000 0000 0001 + 1000 0000 0000 0000 0000 0000 0000 0001 = 1000 0000 0000 0000 0000 0000 0000 0001 可以看到其结果并不等于0。 而引入补码的概念后: 0000 0000 0000 0000 0000 0000 0000 0001 + 1111 1111 1111 1111 1111 1111 1111 1111 = 1 0000 0000 0000 0000 0000 0000 0000 000 溢出的1被截断,即结果为0。
2.整形的存储
2.1大小端
2.1.1 大小端概念:
①大端模式: 指数据的低位保存在内存的高地址中,而数据的高位,保存在内存的低地址中,即a中的数据从下图的4开始存,顺序为4–>3–>2–>1。 ②小端模式: 数据的低位保存在内存的低地址中,而数据的高位,保存在内存的高地址中,即a中的数据从1开始存,顺序为1–>2–>3–>4。
指针p维护开辟的四个字节,而开辟的四个字节也有着高低的顺序,因为在计算机系统中,我们是以字节为单位的,每个地址单元都对应着一个字节。我们按照上图将其标上序号,即 1,2,3,4。那么a中的数据先存入哪个字节便成了大小端的问题。
2.1.2 例题:
(1)请设计一个小程序来判断当前机器的字节序(小端还是大端?) 代码实现:
#include<stdio.h>
int check()
{
int a = 1;
return *((char*)(&a));
}
int main()
{
if (1 == check())
{
printf("小端存储模式\n");
}
else if (0 == check())
{
printf("大端存储模式\n");
}
}
原理: 通过强制类型转换,判断int中四个字节的低地址字节所储存的数值,若为1,说明原本数据的低位存储在低地址中,属于小端存储,反之为大端存储。 |
2.2 char类型存储原理
我们知道, signed char类型的取值范围是-128~127,unsigned char的取值范围是0~255,背后的原理让我们一起来探究探究! |
2.2.1 整形提升
定义:C语言的整型算数运算总是至少以缺省整形类型的精度来进行的。 为获得这个精度,表达式中的字符和短整型操作数在使用之前被转换为普通整形,这种转换称为整形提升****(即小于int型的整形在运算时一般都会发生整形提升,产生的结果再发生截断)
整形提升的意义: 表达式的整型运算要在CPU的相应运算器件内执行,CPU内整型运算器(ALU)的操作数的字节长度 一般就是int的字节长度,同时也是CPU的通用寄存器的长度。 因此,即使两个char类型的相加,在CPU执行时实际上也要先转换为CPU内整型操作数的标准长 度。 通用CPU(general-purpose CPU)是难以直接实现两个8比特字节直接相加运算(虽然机器指令 中可能有这种字节相加指令)。所以,表达式中各种长度可能小于int长度的整型值,都必须先转 换为int或unsigned int,然后才能送入CPU去执行运算。
整形提升的过程:
①负数的整形提升: 例如char c1=-1; 要整形提升为四个字节,最高位补充符号位: 1111 1111 1111 1111 1111 1111 1111 1111 ②正数的整形提升: char c2=1; 整形提升时,最高位补充符号位,即为0: 0000 0000 0000 0000 0000 0000 0000 0001 ③unsigned类型的整形提升: 直接补充0
我们用例子来理解:
实例1
#include<stdio.h>
int main()
{
char a = 127;
char b = 1;
char c = a + b;
printf("%d\n", c);
printf("%d\n", a + b);
printf("%d\n", sizeof(c));
printf("%d\n", sizeof(a+b));
}
运行结果:
这里其实就是发生了整形提升,我们知道char类型的范围是-128~127,而当127+1时,其结果却为-128。 整形提升的过程我们用下图来解释:
a+b存入c时又发生了截断,所以在用%d打印c的时候又要进行一次整形提升: 因此,最终打印出c的结果为-128。 而将a+b的结果存储入c中直接打印,编译器自动将其转换为四个字节,这个由运行结果我们可以看出,他们的字节数一个是1,一个是4。
实例
#include<stdio.h>
int main()
{
char a = 0xb6;
short b = 0xb600;
int c = 0xb6000000;
if (a == 0xb6)
printf("a");
if (b == 0xb600)
printf("b");
if (c == 0xb6000000)
printf("c");
}
因为a,b都要发生整形提升,由于补位符号位为1,全变成了负数,只有c未发生整形提升。
2.2.2 char类型的存储
根据上面的整形提升,其实我们就可以知道char他存储整数的过程了,下面我们还是通过一个个实例来理解它的过程:
实例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);
}
运行结果: 可以看到,输出结果为a=-1,b=-1,c=255。
原理: 其中-1都以补码形式1111 1111存储入a,b,c中,但在用%d打印的时候发生的整形提升不一样,a和b为一类,c为一类。 (1)a和b(其中vs编译器下char默认为signed char): a和b的打印过程为 ①整形提升过程: 1111 1111->1111 1111 1111 1111 1111 1111 1111 1111 ②反码转原码过程(由于最高符号位为1,需转换为原码): 1111 1111 1111 1111 1111 1111 1111 1111–> 1000 0000 0000 0000 0000 0000 0000 0001 即为-1 (2)c的打印过程: ①整形提升: 1111 1111-> 0000 0000 0000 0000 0000 0000 1111 1111 (unsigned补0) ②由于最高位符号位为0,原码=补码 0000 0000 0000 0000 0000 0000 1111 1111=255。
实例2 下面代码输出结果为?
#include<stdio.h>
int main()
{
char a = -128;
printf("%u", a);
}
结果:
char 类型的范围不是-128~127吗?为什么呢可以打印出这么大的数呢? 其实就是前面说的,在打印整数的时候要进行整形提升。 存储入a中为-128的补码形式:1000 0000 ①整形提升-> 1111 1111 1111 1111 1111 1111 1000 0000 ②打印-> %u打印为无符号类型,即原码=补码 1111 1111 1111 1111 1111 1111 1000 0000=4294967168
实例3
#include<stdio.h>
int main()
{
char a = 128;
printf("%u", a);
}
结果:
a中存储截断后的128补码形式-> 1000 0000 %u打印直接打印其整形提升后的补码-> 1111 1111 1111 1111 1111 1111 1000 0000=4294967168
实例4
#include<stdio.h>
int main()
{
int i = -20;
unsigned int j = 10;
printf("%d\n", i + j);
}
结果:
还是一样,i+j按照补码的形式进行运算,最后格式化用%d打印的时候又因其最高符号位为1而打印其原码形式。
实例5
#include<stdio.h>
#include<windows.h>
int main()
{
unsigned int i;
for (i = 9; i >= 0; i--)
{
printf("%u\n", i);
Sleep(100);
}
}
结果:
当i<0时,由于i为unsigned类型,在for里面判断结果始终是正数,用%u打印的结果和上面例子一样,就不叙述了。
实例6
#include<stdio.h>
int main()
{
char a[1000];
int i;
for (i = 0; i < 1000; i++)
{
a[i] = -1 - i;
}
printf("%d", strlen(a));
}
结果: 原理如下图:
分为unsigned和signed两类: ①unsigned类型: 因为无符号位的存在,255+1—>0 ②signed类型: 有符号位,因此以符号位为分界线,当0111 1111加上1时,由127----->-128;
总结:共同的分界线都是当加到1111 1111时,由于发生截断,再加1时就变成了0。而signed还有一个分界线就是符号位的存在。 |
而实例6就是找到数组里面的第一个0,按照上述原理,第255个为0.
实例7
#include<stdio.h>
unsigned char i = 0;
int main()
{
for (i = 0; i <= 255; i++)
{
printf("hello world\n");
}
return 0;
}
弄清楚实例6,那么实例7的结果可想而知,是死循环打印“hello world” 结果:
|