一.整数在内存里的存储
我们都知道,关于整数的二进制表示方法有三种,原码,反码和补码。而正数的原码,反码,补码都相等。而负数的表示方法各不相同。原码:直接将数值按照正负数的形式翻译成二进制得到的就是原码。反码:除了负数的符号位不变,其他位都取反就行。补码:反码加一就是补码。
对于整型来说,数据存放内存中其实存放的是补码。为什么用补码来存放呢?
原因在于,使用补码可以将符号位和数值域统一处理。同时,加法和减法也可以统一处理(CPU只有加法器),此外补码与原码相互转换,其运算过程是相同的,不需要额外的硬件电路。
二.大小端字节序和字节序判断
1.什么是大小端?
在超过一个字节的数据在内存中存储的时候,就会出现存储顺序的问题。按照顺序,我们就会有大端字节序存储和小端字节序存储。
这里可以看到,我给a的值是0x11223344。但是在内存中这个方向就完全反了过来。变成了44332211。这个就是我们说的小端字节存储。
小端字节序存储:把一个数据的低位字节的内容存储到低地址处,而高字节的的内容存储到高字节处。
大端字节存储:把一个数据的高位字节的内容存储到低地址处,而低字节的的内容存储到高字节处。
大小端存储跟计算机架构有关。我用的是vs2022,用的是小端存储。这就是大小端存储的基本知识。
2.为什么会有大小端?
因为在计算机系统中,我们是以字节为单位的,每个内存单元都对应着一个字节,而一个字节对应八个bit位,但是在c语言中除了八个bit位的char外,还有16位的short,32位的int(具体要看编译器)。另外,对于位数大于8位的处理器,例如16位和32位的处理器,由于寄存器宽度大于一个字节,那么必然存在着一个将多个字节安排的问题。因此有了大小端存储。(x86结果是小端模式,而KEIL C51为大端模式。很多的ARM,DSP都为小端模式。但有些ARM处理器还可以由硬件来选择大端还是小端)
三.一些练习
1.练习1
设计一个代码来判断当前机器的字节序
#include<stdio.h>
int main()
{
int a = 1;
if (*(char*)&a == 1)//注意这里我们是把a的地址给强制转化成char*,解引用的时候其实就访问一个字节
//大端存储就是0,小端存储就是1
printf("小端\n");
else
printf("大端");
return 0;
}
这里我的是小端存储,打印出来的也成功是小端。
2.练习2
不仅是大小端,关于数据的存储也有一些练习,大家来了解一下。
#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;
}
可以思考一下这三个值等于多少。
我们知道关于负数在内存中的存储是补码。注意:单独的一个char 到底是有符号还是无符号是取决于编译器的,这里我用的vs2022是是有符号的。先来看最后打印的是整型。那么这里的a,b,c是要整形提升的。(无符号补0,有符号补1)
#include<stdio.h>
int main()
{
char a = -1;
//1000000000000000000000000000001 -1的原码
//1111111111111111111111111111110 -1的反码
//1111111111111111111111111111111 -1的补码
//因为是char类型,所以咱们就只要11111111
//这里要整形提升,所以是1111111111111111111111111111111,按照步骤,求出原码就是-1
signed char b = -1;
//11111111也是-1
//跟a的步骤一样
unsigned char c = -1;
//11111111也是-1
//这个是无符号整形提升后是00000000000000000000000011111111
//这个就是255
printf("a=%d b=%d c=%d", a, b, c);
// -1 -1 255
return 0;
}
取值范围:无符号的是0~255。有符号的是-128~127。
3.练习3
再来看一个代码
#include <stdio.h>
int main()
{
char a = -128;
printf("%u", a);
return 0;
}
来看解析
#include <stdio.h>
int main()
{
char a = -128;
//原码:100000000000000000000000010000000
//反码:111111111111111111111111101111111
//补码: 111111111111111111111111110000000
//因为是char类型,所以只有10000000-a
printf("%u", a);
//这里是用%u来打印,所以要整型提升
//11111111111111111111111111111110000000
//因为是无符号数,所以就不会存在什么原码反码和补码,直接把这个东西转化成十进制数就是答案
//是一个很大的数字
return 0;
}
输出:4294967168
那我把-128换成128来弄,大家来想一下是什么结果呢?
4.练习4
这个代码就很有意思了。
#include <stdio.h>
int main()
{
char a[1000];
int i = 0;
for (; i < 1000; i++)
{
a[i] = -1 - i;
}
printf("%d", strlen(a));
return 0;
}
来看个图
当我们从0开始一直加,加到127的时候,已经到了最大数的限制。这时再去加一直接就变成了-127,等加到-1,又开始了一次循环。当然这是有符号的char类型。
那么像是这个题我们就好求了。strlen找的是'\0'之前的元素个数。
所以a[i]就是-1 -2 -3 ...-127 -128 127 126...4 3 2 1 0 -1...
看看有几个元素就行了。
5.练习5
前面的太简单了,上点强度。
#include <stdio.h>
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;
}
来看解析
#include <stdio.h>
int main()
{
int a[4] = { 1,2,3,4 };
int* ptr1 = (int*)(&a + 1);
//&a的类型是int(*)[4]是一个数组指针,所以前面有一个强制转换。
//这里有+1跳过的是整个数组,ptr1[-1]的意思就是*(ptr1-1),往后移动一个sizeof(int)的距离,结果就是4.
int* ptr2 = (int*)((int)a + 1);
//这里我直接是把a强制转换成int类型了,现在的a就是个整数,整数加一,那就是加一
//比如像是这里的a在内存中的存储:01 00 00 00 02 00 00 00 03 00 00 00 04 00 00 00
//整数a加一,跳过了一个字节,本来位置在01的a,现在位置在00了
//而现在又强转成了int*类型,一次访问4个字节,所以真正访问的是00 00 00 02
//我用的是小端存储,所以真正打印出来的就是02000000
printf("%x,%x", ptr1[-1], *ptr2);
return 0;
}
最终打印:4,2000000
当然在内存中肯定不止整数的存储,当然还有小数。下篇博客我来介绍一下小数在内存中的存储。感谢大家的观看,如有错误,请大家多多指正。