赞
踩
哈喽,各位小伙伴大家好!我们都知道计算机的数据都是存储在内存中的。那它是如何存储,以什么形式存储,存储方法又是什么呢?今天小编就带着大家一起去学习数据在内存中的存储。向着大厂冲锋!
整数的2进制表示方法有三种,即原码、反码和补码。
三种表示方法均有符号位和数值位两部分,
符号位都是用0表示“正”,用1表示“负”,
而数值位最高位的⼀位是被当做符号位,剩余的都是数值位。
正数:
正整数的原、反、补码都相同。
负数:
负整数的三种表示方法各不相同。
原码:直接将数值按照正负数的形式翻译成⼆进制得到的就是原码。
反码:将原码的符号位不变,其他位依次按位取反就可以得到反码。
补码:反码+1就得到补码。
注意,补码转原码有两种方式。
一:先-1后取反。
二:先取反后+1。
对于整形来说:数据存放内存中其实存放的是补码。
在计算机系统中,数值⼀律⽤补码来表示和存储。
为什么呢?
当我们了解了整数在内存中存储后,我们调试看⼀个细节:
#include <stdio.h>
int main()
{
int a = 0x11223344;
return 0;
}
调试的时候,我们可以看到在a中的 0x11223344 这个数字是按照字节为单位,倒着存储的。
这是为什么呢?
这就涉及到大小段字节序的问题了。
其实超过⼀个字节的数据在内存中存储的时候,就有存储顺序的问题。
我们来想一个问题
#include <stdio.h>
int main()
{
int a = 0x11223344;
return 0;
}
如果我们要把a存在内存里我们该怎么存。
比如这四种存放顺序。
其实只要保证存放时的数据和我们拿出来的数据是一样的,
任何顺序都可以。
但是为了为了方便理解,我们就选用第一和第二种存放方式。
这两种就会大端字节序存放和小端字节序存放。、
大端字节序存储
将一个数据低位字节的内容存放到高地址,把高位字节的内容存放到低地址处。
小端字节序存储
将一个数据高位字节的内容存放到高地址,把低位字节的内容存放到低地址处。
注意无论是大端还是小段存储,我存放的是什么,从内存拿出来时就是什么。不会因为倒着存放,拿出来的数据就是倒着的。
这是因为在计算机系统中,我们是以字节为单位的,每个地址单元都对应着⼀个字节,⼀个字节为8bit位,
但是在C语言中除了8bit的 char 之外,还有16bit的 short 型,32bit的 long 型(要看具体的编译器),另外,对于位数大于8位的处理器,例如16位或者32位的处理器,
由于寄存器宽度大于⼀个字节,那么必然存在着⼀个如何将多个字节安排的问题。因此就导致了大端存储模式和小端存储模式。
C语言中整型算术运算总是至少以默认整型类型的精度来进行的。
为了获得这个精度,表达式中的字符和短整型操作数在使用之前被转换为普通整型,这种转换称为整型提升。
//实例1
char a,b,c;
...
a = b + c;
b和c的值被提升为普通整型,然后再执行加法运算。
加法运算完成之后,结果将被截断,然后再存储于a中。
那如何整型提升呢?
//负数的整形提升 char c1 = -1; 变量c1的⼆进制位(补码)中只有8个⽐特位: 1111111 因为 char 为有符号的 char 所以整形提升的时候,⾼位补充符号位,即为1 提升之后的结果是: 11111111111111111111111111111111 //正数的整形提升 char c2 = 1; 变量c2的⼆进制位(补码)中只有8个⽐特位: 00000001 因为 char 为有符号的 char 所以整形提升的时候,⾼位补充符号位,即为0 提升之后的结果是: 00000000000000000000000000000001 //⽆符号整形提升,⾼位补0
如果某个操作符的各个操作数属于不同的类型,那么除非其中⼀个操作数的转换为另⼀个操作数的类型,否则操作就无法进行。下⾯的层次体系称为寻常算术转换。
long double
double
float
unsigned long int
long int
unsigned int
int
如果某个操作数的类型在上面这个列表中排名靠后,那么首先要转换为另外⼀个操作数的类型后执行运算。
请简述大端字节序和小端字节序的概念,设计⼀个小程序来判断当前机器的字节序。(10分)-百度笔试题
以1为例。
我们只需要拿到低地址处的字节内容即可。
那怎样才能拿到呢?
#include <stdio.h> int check_sys() { int i = 1; return (*(char*)&i);//拿到低地址处的字节内容 } int main() { int ret = check_sys(); if (ret == 1) { printf("⼩端\n"); } else { printf("⼤端\n"); } return 0; }
我们只需要取出变量i的地址,因为&取出的总是低地址的那个字节地址。
但是因为是int类型,而我们只需要一个字节,所以我们强制类型转化为char*。
再解引用即可访问低地址的那个字节,再判断是1还是0即可。
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;
}
上面的代码会输出啥?
首先我们需要知道signed char和unsigned char的区别。
signed char
unsigned char
我们现在再回到题目。
补码存储
因为内存中存储的是二进制的补码,所以我们先把-1的补码写出来。
截断
因为-1是整数,放在char类型变量需要发生截断,只保留后八位比特位。
整型提升
注意char是有符号的char还是无符号的char是取决于编译器的!
在vs中char等价于signed char。
char a = -1;
signed char b = -1;
unsigned char c = -1;
printf("a=%d,b=%d,c=%d", a, b, c);
因为是%d打印,%d是打印有符号整数。所以这里需要发生整型提升。
整型提升有符号数按照符号位填充,无符号数用0填充。
所以结果就是-1 -1 255。
#include <stdio.h>
int main()
{
char a = -128;
printf("%u\n", a);
return 0;
}
补码存储
这里我们写出-128的补码。
截断
整型提升
%u认为内存存放的是无符号数。
a是char类型,需要发生整形提升。
验证:
#include <stdio.h>
int main()
{
char a = 128;
printf("%u\n",a);
return 0;
}
所以这两个代码的输出一样。
#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;
}
我们再来看这道题。
首先我们需要知道strlen求得是字符串的长度,
统计\0之前字符的个数,\0的ASCLL码值是0。
所以这道题的意思就是统计在0之前的字符数。
那我们怎么找呢?
我们前面说过signed char的取值范围是-128到217。
那如果超出范围会怎样呢?
大家来看图解。
如果超出范围内存中存的数据就会继续绕圈循环。
顺时针看是+1的循环,逆时针看是-1的循环。
for(i=0; i<1000; i++)
{
a[i] = -1-i;
}
题目的for循环是-1,那我们就逆时针看。
数字变化过程就应该是这样的:
所以答案应该是255。
#include <stdio.h>
unsigned char i = 0;
int main()
{
for(i = 0;i<=255;i++)
{
printf("hello world\n");
}
return 0;
}
我们来看这个代码。
这是无符号的char的循环图。
所以unsigned char的取值范围为0到255。
所以i<=255的条件恒成立,代码死循环。
#include <stdio.h>
int main()
{
unsigned int i;
for(i = 9; i >= 0; i--)
{
printf("%u\n",i);
}
return 0;
}
同理unsigned int存在内存的数据永远>=0.
所以i >= 0判断条件恒成立。代码也是死循环。
#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;
}
小端字节序,X86环境下代码输出的结果是啥?
常见的浮点数:3.14159、1E10等,浮点数家族包括: float、double、long double 类型。
浮点数表示的范围: float.h 中定义
#include <stdio.h>
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 9.0 9 9.0 。
但其实不是。
通过观察我们可以发现,只有当我们以整型存储并且以整形读取输出时,或者浮点数存储并且以浮点数读取输出时结果才和存储的数据一样。
这就说明整型和都浮点型的存储和读取方式不一样。
那浮点型的存储和读取方式是怎么样的呢?
我们先来思考一个问题: 浮点数如何用二进制表示?
上面的代码中, num 和 *pFloat 在内存中明明是同⼀个数,为什么浮点数和整数的解读结果会差别这么大?
要理解这个结果,⼀定要搞懂浮点数在计算机内部的表示方法。
根据国际标准IEEE(电气和电子工程协会)754,任意⼀个二进制浮点数V可以表示成下面的形式:
举例来说:
十进制的5.0,写成二进制是 101.0 ,相当于 1.01×2^2 。
那么,按照上面的格式,可以得出S=0,M=1.01,E=2。
十进制的-5.0,写成⼆进制是 -101.0 ,相当于 -1.01×2^2 。那么,S=1,M=1.01,E=2。
IEEE754规定:
对于32位的浮点数(float),最高的1位存储符号位S,接着的8位存储指数E,剩下的23位存储有效数字M
对于64位的浮点数(double),最高的1位存储符号位S,接着的11位存储指数E,剩下的52位存储有效数字M。
所以浮点数的存储过程其实就是存储S E M三个数据的过程。
EEE754对有效数字M和指数E,还有⼀些特别规定。
至于指数E,情况就比较复杂
首先,E为⼀个无符号整数(unsigned int)。
这里我们来验证一下吧。
int main()
{
float f = 5.5f;
return 0;
}
因为是小端存放,所以内存是倒着存放。
特殊浮点数无法精确保存
大家注意,并不是所有浮点数都能精确表示的。
可能会出现这种情况:
验证:
指数E从内存中取出还可以再分成三种情况:
1 0 01111110 00000000000000000000000
1 0 00000000 00100000000000000000000
1 0 11111111 00010000000000000000000
这是我们再来看回开始的代码
所以代码输出就是:
这就是数据再内存中的存储啦!这些知识看似不起眼,其实都是在修炼我们编程学习的内功。大家下去一定要认真学习。感谢大家的垂阅。今天就分享到这里,咱们下期见!拜拜~
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。