目录
目前已经学了的数据类型:
- char——1
- short——2
- int——4(在16位电脑是2个字节,32位电脑是4个字节)
- long(sizeof(long)≥sizeof(int))
- long long——8
- float——4
- double——8
其中还有布尔类型:(但是布尔类型在c99中才引入)
#include <stdio.h>
int main()
{
_Bool flag=true;
if(flag)
{
printf("hehe\n");
}
return 0;
}
要进行内存存储的学习,我们要先把学过的类型做以下分类讨论:
整型家族
整型家族中的每个类型又分为有无符号类型,而以下的类型都默认为:
- int===>signed int
- long===>signed long
- short===>signed short
但char≠signed char,要取决于编译器,常见的编译器下都是char===》signed char
我们都知道无符号数不能存储有符号的数字,但请看下面的代码:
#include <stdio.h>
int main()
{
unsigned int num=10;
printf("%d\n",num);
num=-10;
printf("%d\n",num);
}
此时输出的依然是10 -10,为什么呢?
是因为我们用有符号数的方式打印出来了无符号数,且把有符号数字赋给了无符号数,此时只能打印出有符号了。
unsigned int也是四个字节存储空间,于是使用%d的时候就可以用有符号数的形式输出了。
应该改为:
#include <stdio.h>
int main()
{
unsigned int num=10;
printf("%u\n",num);
num=-10;
printf("%u\n",num);
}
此时的输出结果才正确:
为什么会有这么大的差异?我们这里先保有一些疑惑。
浮点型家族:
- float——4
- double——8
构造类型(自定义类型):
- 数组类型
- 结构体类型 struct
- 枚举类型 enum
- 联合类型 union
指针类型:
- int*
- char*
- float*
- void*
空类型:
- void
整型的内存存储
输入下列代码并按下f10运行后:
#include <stdio.h>
int main()
{
int a=10;
int b=-10;
return 0;
}
在调试器的内存窗口中我们可以看到如下:
我们都知道a是用四个字节空间存储的,那么a是以怎样的方式进行存储的呢?
原码 反码 补码
整数在计算机中都是以二进制形式的原码,反码,补码的方式表示的。
三种表示方法都有符号位和数值位的概念,最高位上若为0,则表示为正数,若最高位上为1,则为负数。
内存中是以二进制的补码来存储的。
原码:二进制原本的样子
反码:将符号位保留,数值位上的其他位数全部取反
补码:在反码基础上将最低位+1,便得到补码。
(Tip:在这里要注意反码与学习的“~”按位取反操作符不同,按位取反是将二进制(包括符号位)上的所以位置都取反,而反码是符号位不变,其他为按位取反。)
而正数与负数的原、反、补码并不相同:
Q:整数为什么在内存中以补码的形式存在?
计算器中只有加法器。
计算器中是无法计算1-1的,而只能用1+(-1)的方式来计算。
下面让我们来验证此时整型在运算中是以原、反、补码哪种方式进行运算的。
若为原码:
若为补码:
通过验证,我们可以知道运用补码运算才是正确的结果,而原、补码的转换也相对容易:原码→补码(取反、+1) 补码→原码(也是取反、+1)
所以,计算机中整型以补码形式存在并进行运算。
创造一个变量int a=0x11223344,运行后打开编译器的内存窗口,我们会发现内存窗口中并不是以11223344的顺序存储的,这是为什么呢?这就相关大小端字节序的问题了。
#include <stdio.h>
int main()
{
int a=0x11223344;
return 0;
}
大小端字节序
在我们创造一个变量int a=0x11223344时,打开我们的内存窗口,会发现并不是以11223344的顺序存储的,这是为什么呢?这就相关大小端字节序的问题了。
大端字节序存储:低字节的数据存放在高地址处,高字节的数据存放在低地址处。
小端字节序存储:高字节的数据存放在高地址处,低字节的数据存放在低地址处。
Q:为什么会有大小端字节序之分?(以下来源于百度出的内容)
作者:starmier
来源:简书
这是因为在计算机系统中,我们是以字节为单位的,每个地址单元都对应着一个字节,一个字节为8bit。但是在C语言中除了8bit的char之外,还有16bit的short型,32bit的long型(要看具体的编译器),另外,对于位数大于8位的处理器,例如16位或者32位的处理器,由于寄存器宽度大于一个字节,那么必然存在着一个如何将多个字节安排
的问题。因此就导致了大端存储模式
和小端存储模式
。 例如一个16bit的short型x,在内存中的地址为0x0010,x的值为0x1122,那么0x11为高字节,0x22为低字节。对于大端模式,就将0x11放在低地址中,即0x0010中,0x22放在高地址中,即0x0011中。小端模式,刚好相反。我们常用的X86结构是小端模式,而KEIL C51则为大端模式。很多的ARM,DSP都为小端模式。有些ARM处理器还可以由硬件来选择是大端模式还是小端模式。
在搜索大小端字节序相关知识的时候,偶然发现了一道十分有趣的题目,想在这里记录下:
百度神奇题目:简述大小端字节序的概念,并写一个小程序来判断大小字节序。
方法一:
#include <stdio.h>
int main()
{
int a = 1;
char* p = (char*)&a;
if (1 == *p)
{
printf("小端");
}
else
{
printf("大端");
}
return 0;
}
方法二(方法一改良):
#include <stdio.h>
int main()
{
int a = 1;
if (*(char*)&a)
{
printf("小端");
}
else
{
printf("大端");
}
return 0;
}
Q:char类型有字节序么?
A:无,因为char只有一个字节大小,而字节序表示的是字节的排列顺序,所以char类型没有字节序。
有关字节存储的相关练习:
but 在做题前先认识两个东西:整型提升与整型截断。
整型截断
比如说char占1个字节,int 占4个字节,当强制将int 赋给char时就会截短,只将4字节的最后的一个字节给char
举例的话,譬如:
int a = 305419896;
char b = a;
a转换成2进制是:0001 0010 0011 0100 0101 0110 0111 1000 他的16进制是12 34 56 78,
那么b就是a的最后一个byte,即b的二进制表示是0111 1000,16进制是78,十进制是120.
就是这么样的一个转换和截断。
整型提升
若原本整型家族的类型中的一个变量所占的字节比int少,那么在以int形式使用时要发生整型提升。
整型提升的规则:
- 若为无符号数,则在最高位上补齐0
- 若为有符号数,则在最高位上用符号位上的数补齐
看以下例题:
请计算输出结果:
#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;
}
//输出结果是什么?
#include <stdio.h>
int main()
{
char a=-128;
printf("%u\n",a);
return 0;
}
#include <stdio.h>
int main()*
*{
char a=128;
printf("%u\n",a);
return 0;*
*}
#include <stdio.h>
int main()
{
int i=-20;
unsigned int j=10;
printf("%d\n",i+j);
return 0;
}
#include <stdio.h>
int main()
{
unsigned int i;
for(i=9;i>=0;i--)
{
printf("%u\n",i);
}
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;
}
最终输出结果:
为什么这里会最终输出255呢?这里便涉及了char类型变量能存储的数值范围。
探讨:一个char类型的变量到底能存放什么数值?
unsigned char:从0到255后再回到0
signed char:从0到128到-1到-127再回到0
**Tip:**当我们遇到10000000时直接解析为128,因为就算我们将原码转为补码后也依然会是10000000
在这里推荐一个头文件:<limits.h>
头文件:#include <limits.h>点开里面可以查各种类型的取值范围
#include <stdio.h>
unsigned char i=0;
int main()
{
for(i=0;i<=255;i++)
{
printf("hello world\n");
}
return 0;
}
浮点类型在内存中的存储
常见浮点类型:
3.14159 1E10(代表1×10的-10次方)
浮点数表示范围可以用<float.h>打开来看
#incldue <stdio.h>
int main()
{
int n=9;
float* pFloat =(float*)&n;
printf("n的值为:%f\n",*pFloat);
printf("*pFloat的值为:%f\n",*pFloat);
*pFloat=9.0;
printf("num的值为:%d\n",n);
printf(""*pFloat的值为:%f\n",*pFloat);
return 0;
}
(无论是%f还是%lf默认都是由小数点后6个0,编译器决定的)
小数的二进制表示方式:IEEE 754
根据国际标准IEEE(电气电子工程师学会)754,任意一个二进制浮点数可以表示成下面的形式:
- (-1)^SM2^E
- (-1)^s表示符号位,当s=0,V为正数;当s=1,V为负数
- M表示为有效数字,大于等于1,小于2
- 2^E表示指数位
现在拿5.5来举例:
将5.5转换为二进制后表示为101.1→1.0112^2→(-1)^01.011*2^2
此时S=0,M=1.011,E=2。计算机只需要把这三个值给存储起来便可以存储浮点数了。
那么关于S、M、E具体是如何存储的呢?以下是S、M、E的存储细则:
依照规定,E是无符号整型,而在存-1时在32位上是126,在64位上存的是1023,但是当E为负数时,又该如何表示?
例如0.5,转换为二进制后变为1.0*2^-1
为了阻止这种情况出现,IEEE754规定E的真实值必须加上一个中间数,对于8位的E,这个中间数就是127;对于11位的E来说,中间数为1023
所以1.0*2^-1中的E应该由-1→126,作为126存储在内存空间中
指数E取出的三种情况:
E不全为1或不全为0
这时浮点数就采用将E减去127或1023后得到真实值,再将M的前面加上第一位1。
E为全0
由于E-127后得到的真实值是-127,则指数为-127,此时此时有效数字M不再加上第一位的1,而是还原为0.xxxxx的小数。这样做是为了表示+-0,以及接近于0的很小的数字
E全为1
则真实值非常大,此时表示为+-∞
学了上述浮点数存储知识后,自己来试一下对上述程序输出结果预测是否正确吧~