整数在内存中的存储
整数的2进制表示方法有三种,原码、反码和补码
原码、反码和补码是用于表示有符号整数的三种方式。
原码:有符号整数的原始表示形式。它的最高位是符号位(0代表正数,1代表负数),其余位表示数值的绝对值。
反码:对于正数,反码就是原码本身;对于负数,反码是对原码除了符号位以外的所有位取反(0变为1,1变为0)。
补码:对于正数,补码就是原码本身;对于负数,补码是对该数的反码加1。
下面我们来举一个例子:
假设我们使用8位表示有符号整数:
对于 +3(原码:00000011),+3的原码、反码和补码都是00000011。
对于 -3,原码为10000011,反码为11111100,补码为11111101(负数的反码由原码的符号位不变,其余位置取反得到,补码为反码加1得到)
反码和补码的存在是为了解决原码的加减法运算问题。使用补码,可以通过简单的加法运算来实现有符号整数的加减法,而不需要单独处理符号位。补码的另一个重要特性是,一个数的补码加上它的补码应该等于零。
在计算机中,通常使用补码来表示和存储有符号整数,因为它可以简化算术运算。
部分类型数据的存储
在内存中,整数的存储通常是以二进制形式表示的。整数占用的存储空间取决于其数据类型的位数。在大多数系统中,整数通常以补码形式存储。
例如,在C语言中,常见的整数类型如下:
char:通常占用1个字节(8位),可以表示-127到127之间的整数(带符号)或0到255之间的整数(无符号)。
short:通常占用2个字节(16位),可以表示-32767到32767之间的整数(带符号)或0到65535之间的整数(无符号)。
int:通常占用4个字节(32位),可以表示-2147483647到2147483647之间的整数(带符号)或0到4294967295之间的整数(无符号)。
long long:通常占用8个字节(64位),可以表示更大范围的整数。
整数在内存中的存储是直接以其二进制表示形式存储的。例如,十进制数19在内存中的存储形式可能是00010011(假设使用8位的存储空间)。整数的存储形式还取决于计算机的字节序,即大端序(高位字节存储在低地址)或小端序(高位字节存储在高地址)。
大小端字节序和字节序判断
我们以一个数据为开始,来观察它在内存中的存储
#include int main() { int a = 0x11223344; return 0; }
我们会发现,在内存中,它是倒着存储的。由此,引出大小端:
在大端字节序中,整数的高位字节存储在内存的低地址处,而低位字节存储在内存的高地址处。换句话说,整数的最高有效位存储在最低的地址,最低有效位存储在最高的地址。这种方式符合我们阅读整数的习惯,也使得多字节整数在内存中的表示更加直观。
而在小端字节序中,整数的低位字节存储在内存的低地址处,高位字节存储在内存的高地址处。整数的最高有效位存储在最高的地址,最低有效位存储在最低的地址。相比大端字节序,小端字节序在内存中的表示可能会更加符合硬件架构的特点,但是在习惯方面可能会有些令人困惑。
上述例子中(0x11223344):其中0x11是最高有效的字节,0x44是最低有效的字节。
当表示为大端字节序时,0x11223344会被存储为:
0x11 0x22 0x33 0x44
而在小端字节序时,0x11223344会被存储为:
0x44 0x33 0x22 0x11
有排序之分,是因为内存的存储以字节为单位,每个字节占八个比特位,而像整形为四个字节,在存储中必然会有排序问题,
那么,如何判断当前编译器环境下的大小端顺序呢?
#include int main() { int n = 1; if (*(char *)&n == 1) { printf("小端\n"); } else { printf("大端\n"); } return 0; }
这段代码的原理是,在内存中使用一个整型变量n,然后通过将n的地址强制转换为指向char类型的指针,接着对这个char类型指针所指向内存的内容进行判断。如果这个地址的第一个字节存储的是1,那么说明这个系统是小端序;如果第一个字节存储的是0,那么说明这个系统是大端序。
有关整形提升与无符号整形的存储等问题
我们以一道题为例,来展开我们的内容:
#include 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占一个字节,而打印的为整数为四个字节,这里就要引入整形提升的知识点
整形提升
整形提升是指将较小的整数类型转换为较大的整数类型的过程,在c语言中,当对较小的整数类型进行算朑运算时,这些值会被自动提升为较大的整数类型再进行运算。这是为了避免精度丢失和提升计算的精度。
举个例子,如果有一个char类型的变量和一个int类型的变量,进行加法运算的时候,char类型的值会被提升为int类型再进行运算。这是因为int类型通常比char类型更大,所以把char类型提升为int类型可以避免精度丢失。
另外,如果有一个int类型的变量和一个unsigned int类型的变量进行运算,int类型的值会被提升为unsigned int类型再进行运算,这是为了避免带符号数和无符号数混合运算时的问题。
那么整形提升的规则是什么呢?
1. 有符号整数提升是按照变量的数据类型的符号位来提升的
2. 无符号整数提升,高位补0
eg:
//负数的整形提升 char c1 = -1; 变量c1的⼆进制位(补码)中只有8个⽐特位: 1111111 因为 char 为有符号的 char 所以整形提升的时候,⾼位补充符号位,即为1 提升之后的结果是: 11111111111111111111111111111111 //正数的整形提升 char c2 = 1; 变量c2的⼆进制位(补码)中只有8个⽐特位: 00000001 因为 char 为有符号的 char 所以整形提升的时候,⾼位补充符号位,即为0 提升之后的结果是: 00000000000000000000000000000001 //⽆符号整形提升,⾼位补0
例题1
所以,回到上面的那道题
1. #include 2. int main() 3. { 4. char a= -1; 5. signed char b=-1; 6. unsigned char c=-1; 7. printf("a=%d,b=%d,c=%d",a,b,c); 8. return 0; 9. }
a的原码如下:
10000001
得到反码补码:
1111110 1111111
打印时,转换为整形,发生整形提升:
111111111111111111111111
得到原码为:
100000000000000000000001
所以a打印值为-1;
同理,b打印值也为-1;
而对于c:
无符号字符的范围是 0 到 255。当你将 -1 赋值给无符号字符时,它会被转换为无符号数,即 255(内存中的表示为 11111111),其转换如下:
-1 是一个整数字面值,它通常由编译器当作 int 类型处理,因此它在内存中的表示(假设 int 是32位)按照补码将是 11111111 11111111 11111111 11111111,它代表十进制中的 -1。
** 当这个 -1 被赋值给一个 unsigned char 变量时,它需要转换成一个无符号的8位值。 unsigned char 类型仅使用值的低8位,进行了截断,所以 -1 的低8位是 11111111。
这8位被直接截断并复制到 unsigned char 类型的变量 c 中。
由于 c 是一个 unsigned char 类型,这8位 11111111 就被解释为无符号整数值,即 255。在无符号数中,11111111 的二进制表示就是十进制中的 255。
所以对c进行整形提升后,用0补位
00000000000000000000000011111111
符号位为0;
则原返补码相同,则打印值为255;
例题2
#include int main() { char a = -128; printf("%u\n",a); return 0; }
这里出现了一个类型不匹配的问题。%u 是用来打印无符号整数的格式说明符,而 a 是有符号的 char 类型。在这种情况下,会发生隐式的整形提升。
整形提升规则表明当 char 类型(假设它是有符号的)参与到表达式中时,它会提升为 int 类型以执行运算。所以,-128 会被提升为 int 类型的 -128。
在32位系统上,-128 的 int 表示为:
11111111 11111111 11111111 10000000
(补码表示)
然而,由于使用了 %u 这个无符号整数的格式说明符,printf 将会把 a 的值当作无符号数来解释和打印。
所以打印结果如下
4294967168
例题3
1. #include 2. int main() 3. { 4. char a = 128; 5. printf("%u\n",a); 6. return 0; 7. }
首先,需要注意的是,char 类型的范围通常是 -128 到 127(假设 char 是有符号的且占用1个字节)。当你尝试将 128 赋值给 char 类型的变量时,会发生溢出。在这种情况下,128 实际上会被解释为有符号 char 的 -128(补码形式是 10000000)
进行整形提升后,-128 会被提升为 int 类型的 -128。在32位系统上,-128 的 int 表示为:
11111111 11111111 11111111 10000000
我们会发现,结果与例题二相同:
4294967168
关于char,unsigned char数据的周期与规律
在C语言中,char 和 unsigned char 类型的数据大小由其位数定义,通常是 8 位或者 1 字节。这意味着这些类型可以有固定数量的可能值,它们的表示范围是有界的,因此当它们的值超出这个范围时会出现周期性的回绕行为——这通常称为溢出。这里是 char 和 unsigned char 溢出的行为规律:
1。char
当我们讨论 char 的周期和规律时,假设 char 类型是有符号的,并且我们使用的机器是 8 位字符的系统。
可能的值范围:-128 到 127。
溢出规律:当 char 增加超过 127 时,它会回绕到 -128,进一步增加则继续从 -127 向上增加;当 char 减少低于 -128 时,它会回绕到 127,进一步减少则继续从 126 向下减少。
2. unsigned char
unsigned char 类型总是无符号的,也通常是 8 位。
可能的值范围:0 到 255。
溢出规律:当 unsigned char 增加超过 255 时,它会回绕到 0,进一步增加则继续从 1 向上增加;当 unsigned char 减少低于 0 时(在C中通过操作导致负数赋值给无符号类型),它会回绕到 255,进一步减少则继续从 254 向下减少。
步骤说明
考虑以下情况,我们对 char 和 unsigned char 类型的变量递增或递减操作:
对 char 类型递增:
初始化 char 变量,例如 char c = 120;。
递增变量 c,一直到它接近边界值 127。
当 c 达到 127 并且再次递增时,它变成 -128(回绕)。
继续递增将会得到 -127,-126,…,直到回到 127 再次开始一个周期。
对 unsigned char 类型递增:
初始化 unsigned char 变量,例如 unsigned char uc = 250;。
递增变量 uc,一直到它接近边界值 255。
当 uc 达到 255 并且再次递增时,它变成 0(回绕)。
继续递增将会得到 1,2,…,直到回到 255 再次开始一个周期。
这种周期性行为是底层数据类型和算术操作直接的结果。这也说明了为什么在实际编程中很重要的一点,那就是确保不会意外地造成数据类型溢出,因为这会导致不可预期的行为。
在此基础上,我们看下面的例题:
例题4
1. #include 2. int main() 3. { 4. char a[1000]; 5. int i; 6. for(i=0; i<1000; i++) 7. { 8. a[i] = -1-i; 9. } 10. printf("%d",strlen(a)); 11. return 0; 12. }
我们了解到,char类型的数据在从-1一直减,知道-128,再减一得到127,继续减,得到0,形成循环
而strlen在遇到’\0’结束,所以上述的结果则为,128+127=255;
例题5
#include unsigned char i = 0; int main() { for(i = 0;i<=255;i++) { printf("hello world\n"); } return 0; }
这道题,首先i为无符号char类型的数据,在i从零加到255后,再加一发生溢出,得到0,所以i恒小于255,程序陷入死循环。
例题6
#include 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; }
我们下面好好分析这个题
&a取出整个数组的地址,加一跳过整个数组。指针由其实位置指向末尾
以16进制的结果打印,假设为小端存储,则上述图形可转化如下:
ptr【-1】;即为*(ptr-1),
此时ptr减一指向04起始位置,解引用,打印的结果即为
4
而对于ptr2
int* ptr2 = (int*)((int)a + 1); 这行代码使 ptr2 指向数组 a[0](即数字 1)的首地址向前移动了 1 个字节。因此,ptr2 实际上指向的是 a[0] 的第二个字节。由于 ptr2 是 int* 类型的指针,当你对其进行解引用操作(*ptr2)时,它将尝试读取四个字节作为一个整数。
所以,*ptr2 解引用后的值应该是 0x02000000
浮点数在内存中的存储
同样以一道例题为开始
#include int main() { int n = 9; float *pFloat = (float *)&n; printf("n的值为:%d\n",n); printf("*pFloat的值为:%f\n",*pFloat); *pFloat = 9.0; printf("num的值为:%d\n",n); printf("*pFloat的值为:%f\n",*pFloat); return 0; }
你认为结果是什么呢?
我们运行结果如下
1. n的值为:9 2. *pFloat的值为:0.000000 3. num的值为:1091567616 4. *pFloat的值为:9.000000
所以为什么第三行第四行的结果可能与预想不同呢?
这正是因为浮点数在内存中存储的特殊性
浮点数在内存中的存储遵循IEEE 754标准,是目前最广泛使用的浮点数表示方法。
任意⼀个⼆进制浮点数V可以表⽰成下⾯的形式:
V=(-1)S×M×2E.
(-1)S表示符号位,当S=0,V为正数;当S=1,V为负数
M 表示有效数字,M是大于等于1,小于2的
2E表示指数位
单精度浮点数(32位):包括1位符号位,8位指数位,和23位尾数位。
双精度浮点数(64位):包括1位符号位,11位指数位,和52位尾数位。
举例来说:
⼗进制的5.0,写成⼆进制是 101.0 ,相当于 1.01×2^2 。
那么可以得出S=0,M=1.01,E=2。
在计算机内部保存M时,默认这个数的第⼀位总是1,因此可以被舍去,只保存后⾯的
xxxxxx部分。⽐如保存1.01的时候,只保存01,等到读取的时候,再把第⼀位的1加上去。
我们只需要存储后面的部分,即 (01)。我们将这个二进制数扩展到23位,剩下的位用0填充。所以尾数位为 (01000000000000000000000)。
符号位首位则为0;
至于E,E为无符号整数,如果E为8位,它的取值范围为0-255;如果E为11位,它的取值范围为0~2047,但是指数位可能出现负数,所以IEEE 754规定,存⼊内存时E的真实值必须再加上
⼀个中间数,对于8位的E,这个中间数是127;对于11位的E,这个中间数是1023。
所以实际存储的指数值为 (2 + 127 = 129)。129的二进制形式为 (10000001)
将这些部分组合起来,得到5.0的IEEE 754单精度浮点数表示为:
0 10000001 01000000000000000000000
浮点数取的过程
当从内存中取出IEEE 754标准浮点数的指数部分时,可以将其分为以下三种情况
E不全为0或不全为1
这意味着这些指数值代表了有效的浮点数。在解析指数时,需要从其值中减去偏移量以得到实际的指数值。
对于32位的单精度浮点数,偏移量是127。如果指数的8位不全是0或1,例如10000001,则实际指数 (E = 129 - 127 = 2)。
对于64位的双精度浮点数,偏移量是1023。如果指数的11位不全是0或1,则类似地减去1023得到实际的指数值。
再将有效数字M前加上第⼀位的1
E全为0
这种情况下,浮点数表示接近于0的非常小的数
这时,浮点数的指数E等于1-127(或者1-1023)即为真实值,有效数字M不再加上第⼀位的1,而是还原为0.xxxxxx的小数
特殊值
如果指数部分全部为1,则表示该数为特殊值。在这种情况下,尾数的不同值表示不同的特殊情况:
无穷大:如果尾数全为0,那么该值表示无穷大。符号位决定了是正无穷还是负无穷。
非数:如果尾数不全为0,那么该值表示非数.
最后,回到开始的那道题
#include int main() { int n = 9; float *pFloat = (float *)&n; printf("n的值为:%d\n",n); printf("*pFloat的值为:%f\n",*pFloat); *pFloat = 9.0; printf("num的值为:%d\n",n); printf("*pFloat的值为:%f\n",*pFloat); return 0; }
9以整型的形式存储在内存中,得到如下⼆进制序列:
0000 0000 0000 0000 0000 0000 0000 1001
将 9 的⼆进制序列按照浮点数的形式拆分,得到第⼀位符号位s=0,后面8位的指数
E=00000000 ,
最后23位的有效数字M=000 0000 0000 0000 0000 1001。
由于指数E全为0,所以符合E为全0的情况。
因此,V是⼀个很小的接近于0的正数,所以用十进制小数表示就是0.000000。
浮点数9.0 等于⼆进制的1001.0,即换算成科学计数法是:1.001×2^3
9.0 = (−1)0×(1.001) × 23 第⼀位的符号位S=0,有效数字M等于001后⾯再加20个0,凑满23位,指数E等于3+127=130,即10000010
0 10000010 001 0000 0000 0000 0000 0000
这个浮点数在被当做整形的时候,得到的值即为1091567616
感谢观看!求点赞和关注!