数据在内存中的存储
1.整数在内存中的存储
- 整数的2进制表⽰⽅法有三种,即 原码、反码和补码
有符号的整数,三种表⽰⽅法均有符号位和数值位两部分,符号位都是⽤0表示“正”,⽤1表示“负”,最⾼位的⼀位是被当做符号位,剩余的都是数值位。 正整数的原、反、补码都相同 负整数的三种表⽰⽅法各不相同
原码:直接将数值按照正负数的形式翻译成⼆进制得到的就是原码 反码:将原码的符号位不变,其他位依次按位取反就可以得到反码 补码:反码+1就得到补码 对于整形来说:数据存放内存中其实存放的是补码
在计算机系统中,数值⼀律⽤补码来表⽰和存储。 原因在于,使⽤补码,可以将符号位和数值域统⼀处理; 同时,加法和减法也可以统⼀处理(CPU只有加法器)此外,补码与原码相互转换,其运算过程是相同的,不需要额外的硬件电路;
2.大小端字节序和字节序判断
当我们了解了整数在内存中存储后,我们调试看⼀个细节:#include <stdio.h> int main() { int a = 0x11223344; return 0; }
调试的时候,我们可以看到在a中的 0x11223344 这个数字是按照字节为单位,倒着存储的。这是为什么呢?
- 什么是⼤⼩端?
其实超过⼀个字节的数据在内存中存储的时候,就有存储顺序的问题,按照不同的存储顺序,我们分为⼤端字节序存储和⼩端字节序存储,下⾯是具体的概念: 为什么有⼤⼩端?
这是因为在计算机系统中,我们是以字节为单位的,每个地址单元都对应着⼀个字节,⼀个字节为8bit 位,但是在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处理器还可以由硬件来选择是⼤端模式还是⼩端模式。 练习
- 请简述⼤端字节序和⼩端字节序的概念,设计⼀个⼩程序来判断当前机器的字节序。(10分)-百度笔试题
//代码1 #include <stdio.h> int check_sys() { //1的源码:00000000 00000000 00000000 00000001 //正数的原反补相同 int i = 1; //int---4个字节大小 //由于i是整形数据,首先取出i的地址,强制类型转换为char *访问一个字节的数据 //返回0就证明是计算机是大端存储,返回1则相反 return (*(char*)&i); } int main() { int ret = check_sys(); if (ret == 1) { printf("小端\n"); } else { printf("大端\n"); } return 0; }
//代码2 #include <stdio.h> int check_sys() { union { int i; char c; }un; un.i = 1; return un.c; } int main() { int ret = check_sys(); if (ret == 1) { printf("小端\n"); } else { printf("大端\n"); } return 0; }
通过联合体访问同一块空间,因为i和c占用同一块空间,得出大小端存储
练习1
#include <stdio.h> int main() { //-1的源码:10000000 00000000 00000000 00000001 //-1的反码:11111111 11111111 11111111 11111110 //-1的补码:11111111 11111111 11111111 11111111 //由于a是char类型,数据截断---11111111 //打印a使用的是%d类型--整形类型--会将数据补齐--补符号位--11111111 11111111 11111111 11111111 //将a的补码转换为源码进行打印--10000000 00000000 00000000 00000001--打印-1 char a = -1; //和上面char类型一样的原理--打印-1 signed char b = -1; //-1的源码:10000000 00000000 00000000 00000001 //-1的反码:11111111 11111111 11111111 11111110 //-1的补码:11111111 11111111 11111111 11111111 //由于c是unsigned char类型,数据截断---11111111 //打印的时候会把c看作无符号字符类型8位数据 //数据补齐--高位补0--00000000 00000000 00000000 11111111 //正数的原反补相同:所以会打印00000000 00000000 00000000 11111111--255 unsigned char c = -1; printf("a=%d,b=%d,c=%d", a, b, c); return 0; }
练习2
#include <stdio.h> int main() { //-128的源码:10000000 00000000 00000000 10000000 //-128的反码:11111111 11111111 11111111 01111111 //-128的补码:11111111 11111111 11111111 10000000 //a是char型数据--存储时发生数据截断---10000000 //将10000000高位使用1补齐(由于char是有符号浮点型)---11111111 11111111 11111111 10000000 //使用%u进行打印数据时,打印的是无符号整型--相当于把上面一行数据当成整形进行打印 //正数原反补相同,直接打印11111111 11111111 11111111 10000000 char a = -128; printf("%u\n", a); return 0; }
练习3
#include <stdio.h> int main() { //128的源反补码:00000000 00000000 00000000 10000000 //a是char型数据--存储时发生数据截断---10000000 //将10000000高位使用1补齐(由于char是有符号浮点型)---11111111 11111111 11111111 10000000 //使用%u进行打印数据时,打印的是无符号整型--相当于把上面一行数据当成整形进行打印 //正数原反补相同,直接打印11111111 11111111 11111111 10000000 char a = 128; printf("%u\n", a); return 0; }
练习4
#include <stdio.h> int main() { char a[1000]; int i; for (i = 0; i < 1000; i++) { a[i] = -1 - i; } printf("%d", strlen(a)); return 0; }
不知道大家能不能想明白这道题是怎么回事,也希望大家可以在评论区进行讨论,我能做的就是提供上面一张数据图了。大家最好每次循环都把原始数据二进制表示来,一步一步就会发现和图上运行是一样的,当循环进行255次就会被赋值一个0,strlen函数也就会停止计算长度,所以打印出来的是255