整型在内存中的存储
整型在二进制中的表示形式有3种:原码、反码、补码。
- 正的整数:原码、反码、补码相同
- 负的整数:原码、反码、补码要进行计算的
整数在内存中存储的是补码的二进制序列。
其中对于有符号整形来说,二进制中最高位是符号位,最高位为“0”表示正数,最高位为“1”表示负数。
那么为什么在内存中存储的是补码呢?
使用补码、可以将符号位和数值域统一处理,同时,加法和减法也可以统一处理。在CPU中只有加法器,补码与原码相互转换,其运算过程是相同的,不需要额外的硬件电路。
我们举个例子:
1-1
因为CPU中是只有加法器的,那么实现减法就是1+(-1)即可。
1的补码:00000000 00000000 00000000 00000001
-1的补码:111111111 11111111 11111111 11111111
1+(-1):00000000 00000000 00000000 00000000
同理在执行乘法与减法中,就是多个数相加,多个数相减。
经过上面的例子,我们确实可以得到,存储的就是补码,但是为什么存储的顺序有些变动呢?
大小端的介绍
我们在创建变量时,操作系统就会给你分配空间,比如你创建了【short/int/double/float】的变量,这些变量的类型都是大于1个字节的,操作系统会根据你这个变量的类型,分配相应的内存空间,在空间分配好之后,在这块内存中进行存储这个变量,反正内存已经分好了,所以在这个内存中如何存储就不关操作系统的事情了。那么这个变量在它内存存储中,是从低地址到高地址呢?还是从高地址到低地址呢?
在一块给定的内存中,其实在内存中存储的时候,其实顺序是可以任意的。只要以你的方式存储进去,关键的是在你需要使用数据的时候,以你的方式原样返回即可。
简而言之,怎么存储其实并不重要,重要的是,你能够存进去并且取出来即可。
如下图,假设int a = 0x11223344,其实下面的这类种存储方式都是可以的,但是因为像类似下面这两个存储方式,在读取的时候,是极其不方便的,所以这之后的C语言发展的过程中,就将类似于下面的存储方式就不再继续使用了,最后就采用了这两种存储方式,一种是正着存储,一种是倒着存储。
这里这就分为了两种存储方式,大端字节序存储和小端字节序存储。
大端字节序存储
大端字节序存储,就是将一个数据的低位字节处的数据存储到内存中高位字节的存储数据,将这个数据的高位字节处的数据存储到内存中低位字节的存储数据。
小端字节序存储
小端字节序存储,就是将一个数据的高位字节处的数据存储到内存中高位字节的存储数据,将这个数据的低位字节处的数据存储到内存中低位字节的存储数据。
而这里注意,在数据类型为char时,就不需要考虑存储顺序的问题,因为char类型的数据只占1个字节。
只有所占的字节数大于1字节的时候,比如short、int、double、float、long long等,我们才考虑存储顺序的问题。
那么为什么会出现大小端字节序的存储问题呢?
因为在计算机系统中,我们是以字节为单位的,每个地址单元都对应着一个字节,一字节为8比特位。但是在C语言中,除了有8比特位的char,还有16比特位的short,32比特位的long(具体需要根据编译器)。另外,对于16位、32位的处理器,由于寄存器是大于一个字节的,那么必然就会存在着如何将一个多字节存储安排的问题,这便导致了会有大小端字节序存储之分。
在学习完了大小端字节序存储之后,这里有一道关于它的笔试题:
百度在2015年系统工程师的笔试题:
请简述大端字节序存储和小端字节序存储的概念,并设计出一个程序来判断当前机器的字节序。
思路:
假如我们存储了int a=0,那么a的16进制就是0x 00 00 00 01。在这个数据中,01方向的是低位数数据,00方向的是高位数数据。那么,在这种情况下存储1,假设是小端字节序存储(高位数数据存储到高地址中),那么就是01存储到了高地址中,那么只要验证出低地址中取出的数等于1的话,那么就可以说明它就是小端字节序存储。如图:
代码:
#include <stdio.h>
int main()
{
int a = 1;
char* p = (char*)&a;//利用char类型1字节,来判断存储在低地址种的一字节的数据是何值
if (*p == 1)
{
printf("小端");
}
else
{
printf("大端");
}
return 0;
}
下面有几个例题,我们来看一下:
整型存储相关例题
- 下面代码的运行结果是什么:
#include <stdio.h>
int main()
{
char a = -1;
signed char b = -1;
unsigned char c = -1;
printf("%d %d %d", a, b, c);
return 0;
}
解析:
- -1的补码:11111111 11111111 11111111 11111111
- 上面的是-1在整型的存储方式,而char只占1个字节,所以char -1在计算机内存中存储的补码就是:11111111
- %d是以十进制的形式打印有符号的整型
- 但是现在是char类型,并不是整型类型,所以整型提升。
- 整型提升的规则:对于有符号的数来说,整形提升补充的是它的符号位;而对于无符号整型来说补充的是0。
- 又因为char类型在大多数的编译器上是有符号的,所以整型提升的是符号位。
- 所以在打印出来的char a=-1的补码就是:11111111 11111111 11111111 11111111
- 所以对应的整型就是-1,所以第一个值打印出来的结果是-1。
运行结果:
-1 -1 255
- 下面代码的运行结果是什么:
#include <stdio.h>
int main()
{
char a = -128;
printf("%u\n", a);
}
解析:
- -128的补码:11111111 11111111 11111111 10000000
- 但是上面的是整型的-128,整型占4个字节,而char类型只占1个字节,所以char -128在计算机内存中只能存储的补码为:10000000
- %u是十进制的形式,打印无符号的整数;
- 而现在这个-128不是整型类型,而是字符类型的,所以就需要整型提升;
- 整型提升的规则:对于有符号的数来说,整形提升补充的是它的符号位;而对于无符号整型来说补充的是0。
- 又因为char类型在大多数的编译器上是有符号的,所以整型提升的是符号位;
- 所以在打印出来的char a=-128的补码就是:11111111 11111111 11111111 10000000
- 而%u打印的是无符号的整数,所以整个二进制中的所有位都是有效位,没有符号位;
- 所以对应的整型就是4,294,967,168,所以第一个值打印出来的结果是4,294,967,168。
- 下面代码的运行结果是什么:
#include <stdio.h>
int main()
{
char a = 128;
printf("%u\n", a);
}
解析:
- 128的补码:00000000 00000000 00000000 10000000
- 但是上面的是整型的128,整型占4个字节,而char类型只占1个字节,所以char128在计算机内存中只能存储的补码为:10000000
- %u是十进制的形式,打印无符号的整数;
- 而现在这个128不是整型类型,而是字符类型的,所以就需要整型提升;
- 整型提升的规则:对于有符号的数来说,整形提升补充的是它的符号位;而对于无符号整型来说补充的是0。
- 又因为char类型在大多数的编译器上是有符号的,所以整型提升的是符号位;
- 所以在打印出来的char a=128的补码就是:11111111 11111111 11111111 10000000
- 而%u打印的是无符号的整数,所以整个二进制中的所有位都是有效位,没有符号位;
- 所以对应的整型就是4,294,967,168,所以第一个值打印出来的结果是4,294,967,168。
- 下面代码的运行结果是什么:
#include <stdio.h>
int main()
{
int i = -20;
unsigned int j = 10;
printf("%d\n", i + j);
}
解析:
- -20的补码:11111111 11111111 11111111 11101100
- 10的补码:00000000 00000000 00000000 00001010
- -20的补码+10的补码的结果补码:11111111 11111111 11111111 11110110
- 原码:10000000 00000000 00000000 00001010
运行结果:-10
- 下面代码的运行结果是什么:
#include <stdio.h>
int main()
{
unsigned int i;
for (i = 9; i >= 0; i--)
{
printf("%u\n", i);
Sleep(1000);
}
return 0;
}
解析:
注意这里是无符号的char类型,任何一个无符号整数都是大于等于零的。无符号的那么它的每一位都是有效数字,在减到0之后,变为:
11111111 11111111 11111111 11111111
11111111 11111111 11111111 11111110
11111111 11111111 11111111 11111101
11111111 11111111 11111111 11111100
......
i这里一直被看做是整数并且i大于等于0,所以就进入了死循环中。
unsigned与signed的轮回:
- 下面代码的运行结果是什么:
#include <stdio.h>
int main()
{
char a[1000];
int i = 0;
for (i = 0; i < 1000; i++)
{
a[i] = -1 - i;
}
printf("%d", strlen(a));
return 0;
}
解析:
- strlen统计的是“\0”之前出现的字符的个数
- “\0”是一种以“\ddd”的形式的字符,它的ASCII码值为0
- 所以此题也就是统计这个数组里面什么时候会遇到0
- char在大多数编译器是有符号的,所以:
- 运行过程为:-1 -2 -3 -4 -5 -6...-128 127 ...6 5 4 3 2 1
- 128+127=255
运行结果:255
- 下面代码的运行结果是什么:
#include <stdio.h>
unsigned char i = 0;
int main()
{
for (i = 0; i <= 255; i++)
{
printf("hello world\n");
}
return 0;
}
解析:
无符号的char的取值范围为0~255
所以i<=255,所以死循环