目录
[1. 整数在内存中的存储](#1. 整数在内存中的存储)
[2. ⼤⼩端字节序和字节序判断](#2. ⼤⼩端字节序和字节序判断)
[3. 浮点数在内存中的存储](#3. 浮点数在内存中的存储)
1. 整数在内存中的存储
在讲解操作符的时候,我们就讲过了下⾯的内容:
整数的2进制表⽰⽅法有三种,即原码、反码 和补码
有符号的整数,三种表⽰⽅法均有符号位和数值位两部分,符号位都是⽤0表⽰"正" ,⽤1表 ⽰"负",最⾼位的⼀位是被当做符号位,剩余的都是数值位。
正整数的原、反、补码都相同。
负整数的三种表⽰⽅法各不相同
原码:直接将数值按照正负数的形式翻译成⼆进制得到的就是原码。
反码:将原码的符号位不变,其他位依次按位取反就可以得到反码。
补码:反码+1就得到补码。
对于整形来说:数据存放内存中其实存放的是⼆进制的补码。
为什么呢?
- 在计算机系统中,数值⼀律⽤补码来表⽰和存储。
- 原因在于,使⽤补码,可以将符号位和数值域统⼀处理;
- 同时,加法和减法也可以统⼀处理(CPU只有加法器)此外,补码与原码相互转换,其运算过程是 相同的,不需要额外的硬件电路。
2. ⼤⼩端字节序和字节序判断
当我们了解了整数在内存中存储后,我们调试看⼀个细节:
cpp
#include <stdio.h>
int main()
{
int a = 0x11223344;
return 0;
}
调试结果:
调试的时候,我们可以看到在a中的0x11223344, 这个数字是按照字节为单位,倒着存储的。

2.1 什么是⼤⼩端?
其实超过⼀个字节的数据在内存中存储的时候,就有存储顺序的问题,按照不同的存储顺序,我们分 为⼤端字节序存储和⼩端字节序存储,下⾯是具体的概念:
⼤端(存储)模式:(反着来)
是 指数据的低位字节内容保存在内存的⾼地址处,⽽数据的⾼位字节内容,保存在内存的低地址处
⼩端(存储)模式:(正着来)
是 指数据的低位字节内容保存在内存的低地址处,⽽数据的⾼位字节内容,保存在内存的⾼地址处。
上述概念需要记住,⽅便分辨⼤⼩端
2.2 为什么有⼤⼩端?
为什么会有⼤⼩端模式之分呢?
这是因为在计算机系统中,我们是以字节为单位的,每个地址单元都对应着⼀个字节,⼀个字节为8 bit 位,但是在C语⾔中除了8bit的 char 之外,还有16bit的 short 型,32bit的 long 型(要看 具体的编译器),另外,对于位数⼤于8位的处理器,例如16位或者32位的处理器,由于寄存器宽度⼤ 于⼀个字节,那么必然存在着⼀个如何将多个字节安排的问题。因此就导致了⼤端存储模式和⼩端存 储模式。
例如:
⼀个 16bit 的 short 型 x 在内存中的地址为 0x0010,x的值为0x1122(16进制的数) ,那么0x11 为⾼字节,0x22 为低字节。
对于⼤端模式,就将0x11放在低地址中,,即0x0010中,0x22 放在⾼地址中,即 0x0011 中。
⼩端模式,刚好相反。我们常⽤的x86结构是⼩端模式,而KEIL C51则为⼤端模式。很多的ARM,DSP都为⼩端模式。有些ARM处理器还可以由硬件来选择是 ⼤端模式还是⼩端模式。
2.3 练习
2.3.1 练习1
请简述⼤端字节序和⼩端字节序的概念,设计⼀个⼩程序来判断当前机器的字节序。(10分)-百度笔 试题
cpp
int check_sys()
{
int n = 1;
if (*(char*)&n == 1)
{
return 1; //小端
}
else
{
return 0; //大端
}
}
int main()
{
int ret = check_sys();
if (ret == 1)
{
printf("小端\n", ret);
}
else
{
printf("大端\0",ret);
}
return 0;
}
输出结果:
当n==1的时候

解析:
**内存布局与字节序概念:**指多字节数据在内存中的存储顺序。
-
小端(Little Endian):低位字节存储在低地址。
-
大端(Big Endian):高位字节存储在低地址。
以 n = 1 为例(假设 int 为 4 字节):
-
十六进制表示:
0x00000001 -
最低有效字节是
0x01,最高有效字节是0x00。
内存地址从低到高:
-
小端:
01 00 00 00 -
大端:
00 00 00 01
(char*)&n
-
&n:取n的地址,类型是int*。 -
(char*)&n:将int*强制转换为char*。作用:
char*指针每次访问 1 个字节,通过它我们可以读取n的第一个字节(最低地址字节)
*(char*)&n
-
对
(char*)&n解引用,得到n在内存中第一个字节的值。 -
如果系统是小端:
- 第一个字节是
0x01,因此*(char*)&n等于 1。
- 第一个字节是
-
如果系统是大端:
- 第一个字节是
0x00,因此*(char*)&n等于 0。
- 第一个字节是
优化上面的代码
cpp
int check_sys()
{
int n = 1;
return *(char*)&n;
}
int main()
{
int ret = check_sys();
if (ret == 1)
{
printf("小端\n", ret);
}
else
{
printf("大端\0", ret);
}
return 0;
}
输出的结果和上面的是一样的
2.3.2 练习2
注意:char的有无符号取决于编译器,在vs2上,char默认为 signed char
cpp
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 a = -1;
-
在 C 标准中,
char可能等同于signed char或unsigned char,取决于编译器实现(常见的是signed char)。 -
假设当前环境下
char是有符号的,那么a的二进制存储为11111111(8 位有符号数 -1 的补码)。
signed char b = -1;
- 明确声明为有符号字符型,存储的也是
11111111(补码形式)。
unsigned char c = -1;
-
无符号字符型变量,给它赋值
-1时,会发生隐式转换。 -
-1的补码是11111111,当解释为unsigned char时,这个二进制模式表示 255。
printf 中的类型提升
printf("a=%d b=%d c=%d", a, b, c);
-
格式符
%d期望int类型参数。 -
在可变参数函数(如
printf)中,char类型会进行整数提升:
-
a(有符号 char,值 -1)提升为int,保持值-1。 -
b(有符号 char,值 -1)提升为int,保持值-1。 -
c(无符号 char,值 255)提升为int,因为 255 在int范围内,所以结果为255。
2.3.3 练习3
cpp
int main()
{
char a = -128;
printf("%u\n", a);//%u的形式打印,是认为a中存放的是无符号数 ,a是char类型,首先要整型提升
return 0;
}
输出结果:

解析:
char a = -128;
知识点:有符号 char 的范围
-
在大多数系统中,
char默认为signed char,范围是 -128 到 127。 -
-128刚好是有符号 char 的最小值。
知识点:-128 的二进制表示(补码)
-
8 位有符号数的表示:
-128的补码是10000000(二进制)
printf("%u\n", a);
知识点:整数提升
-
在
printf函数调用中,char类型会进行整数提升。 -
由于
a是signed char,提升时会进行符号扩展。
具体过程:
-
a = -128(8位):10000000 -
整数提升为
int(32位):
- 保持数值不变,所以仍然是
-128 - 二进制补码表示:
11111111 11111111 11111111 10000000
知识点:格式不匹配
-
%u期望一个unsigned int类型的参数 -
但我们传递的是提升后的
int类型(值为 -128) -
编译器不会自动转换类型,而是直接按位解释
二进制解释:
-
11111111 11111111 11111111 10000000被当作无符号整数解释 -
计算其无符号值:
- 这是 232−128=4294967168232−128=4294967168
练习3.1
cpp
int main()
{
char a = 128;
printf("%u\n", a);
return 0;
}
输出结果:

解析:
1. char a = 128;
知识点:有符号 char 的溢出
-
在大多数系统中,
char默认为signed char,范围是 -128 到 127。 -
128超出了有符号 char 的最大值 127。
知识点:整数到有符号 char 的转换
-
当赋值超出目标类型范围时,C 标准规定结果是实现定义的。
-
常见的处理方式是截断:取低 8 位,并按有符号解释。
具体过程:
-
128的二进制(32位):00000000 00000000 00000000 10000000 -
取低 8 位:
10000000 -
作为有符号 char 解释:
10000000是 -128 的补码
所以 a 的实际值是 -128。
2. printf("%u\n", a);
知识点:整数提升
-
在
printf函数调用中,char类型会进行整数提升。 -
由于
a是signed char,提升时会进行符号扩展。
具体过程:
-
a = -128(8位):10000000 -
整数提升为
int(32位):-
符号扩展:
11111111 11111111 11111111 10000000 -
数值仍然是
-128
-
知识点:格式不匹配
-
%u期望一个unsigned int类型的参数 -
但我们传递的是提升后的
int类型(值为 -128) -
编译器直接按位解释,不进行数值转换
2.3.4 练习4
cpp
int main()
{
char a[1000];
int i;
for (i = 0; i < 1000; i++)
{
a[i] = -1 - i;
}
printf("%d", strlen(a));
return 0;
}
输出结果:

解析:
关键点:有符号 char 的溢出
知识点:整数到有符号 char 的转换
-
当值超出 -128 到 127 范围时,会发生溢出
-
常见的处理方式是取低 8 位并按有符号解释
i = 128: -129 的转换
-
-129的 32 位补码:11111111 11111111 11111111 01111111 -
取低 8 位:
01111111= 127
数值变化规律:
-
i = 0到127:a[i]=-1到-128(递减) -
i = 128:a[128] = 127 -
i = 129:a[129] = 126 -
i = 130:a[130] = 125 -
...
-
i = 255:a[255] = 0(因为-1 - 255 = -256,低 8 位是 0)
2.3.5 练习5
cpp
unsigned char i = 0; //这个代码的取值范围是0-255
int main()
{
for (i = 1; i <= 255; i++)
{
printf("hello world\n"); //死循环打印
}
return 0;
}
输出结果:
死循环da

解析:
unsigned char i = 0;
-
unsigned char是无符号字符类型 -
取值范围:0 到 255
-
占用 1 字节(8 位)
无符号整数的溢出
-
当
unsigned char达到 255 后再加 1,会发生回绕 -
255 + 1 = 256,但 256 超出范围,取低 8 位:00000000= 0 ,所以i从 255 变成 0
最终进入死循环
练习5.1
cpp
#include<windows.h>
int main()
{
unsigned int i;
for (i = 9; i >= 0; i--)
{
printf("%u\n", i);
Sleep(10); //减缓代码的执行速度 ,需要头文件<windows.h>
}
return 0;
}
输出结果:

解析:
unsigned int i;
-
unsigned int是无符号整型 -
取值范围:0 到 4,294,967,295(假设 32 位系统)
-
永远不能存储负数
无符号整数的下溢
当 i = 0 时执行 i--:
-
0 - 1 = -1 -
但
i是unsigned int,不能存储负数 -
发生下溢 ,结果回绕到最大值:4,294,967,295
最终进入死循环
2.3.6 练习6
cpp
x86坏境,小端字节序
int main()
{
int a[4] = { 1,2,3,4 };
int* ptr1 = (int*)(&a +1);
int* ptr2 = (int*)((int)a + 1);
printf("%x, %x\n", ptr1[-1], *ptr2);
printf("%#x, %#x", ptr1[-1], *ptr2);
return 0;
}
输出结果:
解析:
数组内存布局
-
每个元素的十六进制表示:
-
1=0x00000001 -
2=0x00000002 -
3=0x00000003 -
4=0x00000004
-
小端字节序内存布局(地址从低到高):
cpp
a[0] = 1: 01 00 00 00
a[1] = 2: 02 00 00 00
a[2] = 3: 03 00 00 00
a[3] = 4: 04 00 00 00
int* ptr1 = (int*)(&a +1);
知识点:数组指针运算
-
&a是数组指针 ,类型为int(*)[4] -
&a + 1:跳过整个数组(4 × 4 = 16 字节) -
ptr1指向数组末尾的下一个位置
cpp
a[0] a[1] a[2] a[3] ptr1
↓ ↓ ↓ ↓ ↓
[1] [2] [3] [4] [?]
int* ptr2 = (int*)((int)a + 1);
-
(int)a:将数组首地址转换为整数值 -
(int)a + 1:地址值加 1(不是加 4 字节!) -
(int*):再转换回 int 指针
printf("%x, %x\n", ptr1[-1], *ptr2);
ptr1[-1] 的计算:
-
ptr1指向数组末尾后 -
ptr1[-1]=*(ptr1 - 1)= 指向a[3] -
a[3] = 4=0x00000004 -
输出:
4
*ptr2 的计算:
-
ptr2从a[0]的第二个字节开始读取 4 字节 -
读取的字节序列:
a[0]的后3字节 +a[1]的第1字节 -
小端系统:
00 00 00 02=0x02000000 -
输出:
2000000
%#x 格式说明符的意思
在 printf 中:
-
%x:以十六进制输出,不带前缀 -
%#x:以十六进制输出,带0x前缀
3. 浮点数在内存中的存储
常⻅的浮点数:3.14159、1E10等,浮点数家族包括:float 、 double 、 long double 类型。
浮点数表⽰的范围: float.h 中定义
3.1 练习
cpp
int main()
{
int n = 9;
float* pFloat = (float*)&n;
printf("n的值为:%d\n", n);
printf("*pFloat的值为:%f\n", *pFloat);
*pFloat = 9.0;
printf("n的值为:%d\n", n);
printf("*pFloat的值为:%f\n", *pFloat);
return 0;
}
输出结果:

解析:
printf("*pFloat的值为: %f\n", *pFloat);
- 关键点 :用
%f解释相同的二进制数据
浮点数 IEEE 754 标准:
-
单精度浮点数(32位):1位符号 + 8位指数 + 23位尾数
-
9的整型二进制:00000000 00000000 00000000 00001001
按浮点数解释:
-
符号位:
0(正数) -
指数位:
00000000 -
尾数位:
00000000000000000001001
计算值:
-
指数 = 0 - 127 = -127
-
尾数 = 1.00000000000000000001001(隐含的1)
-
值 = ±1.尾数 × 2^指数 = 1.00000000000000000001001 × 2^(-127)
-
这是一个非常接近于 0 的数
输出 :*pFloat的值为: 0.000000
赋值后的变化
*pFloat = 9.0;
-
将 9.0 的浮点数表示写入同一块内存
-
覆盖了原来整数 9 的二进制表示
9.0 的 IEEE 754 表示:
-
9.0=9=1001(二进制) -
科学计数法:
1.001 × 2^3 -
符号位:
0 -
指数:
3 + 127 = 130=10000010 -
尾数:
00100000000000000000000 -
完整二进制:
0 10000010 00100000000000000000000=0x41100000
第二次输出
printf("n的值为: %d\n", n);
-
用
%d解释现在的内存内容:0x41100000 -
0x41100000=1,091,567,616(十进制)
输出 :n的值为: 1091567616
3.2 浮点数的存储
上⾯的代码中 n 和 * pFloat 在内存中明明是同⼀个数,为什么浮点数和整数的解读结果会差别这么⼤?
要理解这个结果,⼀定要搞懂浮点数在计算机内部的表⽰⽅法
根据国际标准IEEE(电⽓和电⼦⼯程协会)754,任意⼀个⼆进制浮点数V可以表⽰成下⾯的形式:
V = (−1)^S * M * 2^E
- (−1)^S 表⽰符号位,当S=0,V为正数;当S=1,V为负数
- M表⽰有效数字,M是⼤于等于1,⼩于2的
- 2^E表⽰指数位
举例来说:
⼗进制的5.0,写成⼆进制是101.0,相当于 1.01×2^2 。
那么,按照上⾯V的格式,可以得出S=0,M=1.01,E=2
⼗进制的-5.0,写成⼆进制是-101.0,相当于-1.01×2^2.那么,S=1,M=1.01,E=2。
IEEE 754规定:
- 对于32位的浮点数(float),最⾼的1位存储符号位S,接着的8位存储指数E,剩下的23位存储有效数字 M
- 对于64位的浮点数(double),最⾼的1位存储符号位S,接着的11位存储指数E,剩下的52位存储有效 数字M
如图:

3.2.1 浮点数存的过程
IEEE 754对有效数字M和指数E,还有⼀些特别规定。
前⾯说过, 1≤M<2 ,也就是说,M可以写成 1.xxxxxx 的形式,其中 xxxxxx 表⽰⼩数部分。
IEEE 754规定,在计算机内部保存M时,默认这个数的第⼀位总是1,因此可以被舍去,只保存后⾯的 xxxxxx部分。⽐如保存1.01的时候,只保存01,等到读取的时候,再把第⼀位的1加上去。这样做的⽬ 的,是节省1位有效数字。以32位浮点数为例,留给M只有23位,将第⼀位的1舍去以后,等于可以保 存24位有效数字。
⾄于指数E,情况就⽐较复杂
⾸先,E为⼀个⽆符号整数(unsignedint)
这意味着,如果E为8位,它的取值范围为0~255;如果E为11位,它的取值范围为0~2047。但是,我 们知道,科学计数法中的E是可以出现负数的,所以IEEE754规定,存⼊内存时E的真实值必须再加上 ⼀个中间数,对于8位的E,这个中间数是127;对于11位的E,这个中间数是1023。⽐如,2^10的E是 10,所以保存成32位浮点数时,必须保存成10+127=137,即10001001。
3.2.2 浮点数取的过程
指数E从内存中取出还可以再分成三种情况:
E不全为0或不全为1(常规情况)
这时,浮点数就采⽤下⾯的规则表⽰,即指数E的计算值减去127(或1023),得到真实值,再将有效 数字M前加上第⼀位的1。
⽐如:0.5的⼆进制形式为0.1,由于规定正数部分必须为1,即将⼩数点右移1位,则为1.0*2^(-1),其 阶码为-1+127(中间值)=126,表⽰为01111110,⽽尾数1.0去掉整数部分为0,补⻬0到23位 00000000000000000000000,则其⼆进制表⽰形式为:
cpp
1 0 01111110 00000000000000000000000
E全为0
这时,浮点数的指数E等于1-127(或者1-1023)即为真实值,有效数字M不再加上第⼀位的1,⽽是还 原为0.xxxxxx的⼩数。这样做是为了表⽰±0,以及接近于0的很⼩的数字。
cpp
1 0 00000000 00100000000000000000000
E全为1
这时,如果有效数字M全为0,表⽰±⽆穷⼤(正负取决于符号位s);
cpp
1 0 11111111 00010000000000000000000
以上就是我们的全部内容了,谢谢大家!!!
