目录
[1. 整数在内存中的存储](#1. 整数在内存中的存储)
[1.1 原码、反码、补码](#1.1 原码、反码、补码)
[2. 大小端字节序和字节序判断](#2. 大小端字节序和字节序判断)
[2.1 什么是大小端?](#2.1 什么是大小端?)
[2.2 为什么有大小端?](#2.2 为什么有大小端?)
[2.3 判断当前机器字节序的方法](#2.3 判断当前机器字节序的方法)
[3. 浮点数在内存中的存储](#3. 浮点数在内存中的存储)
[3.2 示例分析](#3.2 示例分析)
[4. 练习巩固与解析](#4. 练习巩固与解析)
引言
在C语言编程中,理解数据在内存中的存储方式至关重要。它不仅关系到程序的正确性,还涉及性能优化、跨平台兼容性等高级话题。本文将深入探讨整数、浮点数在内存中的存储方式,以及大小端字节序的概念和判断方法,并通过多个练习巩固理解。
1. 整数在内存中的存储
整数在计算机内存中是以补码形式存储的。为什么使用补码?原因如下:
-
统一符号位和数值位的处理
-
加法和减法可以统一用加法器实现
-
补码与原码转换过程相同,无需额外硬件电路
1.1 原码、反码、补码
-
原码:直接将数值转换为二进制,最高位为符号位(0正1负)
-
反码:正数反码与原码相同;负数反码为原码符号位不变,其余位取反
-
补码:正数补码与原码相同;负数补码为反码加1
示例:
cpp
int a = -10;
原码:10000000 00000000 00000000 00001010
反码:11111111 11111111 11111111 11110101
补码:11111111 11111111 11111111 11110110
实际内存中存储的是补码。
2. 大小端字节序和字节序判断
2.1 什么是大小端?
对于多字节数据(如int、short),在内存中的存储顺序有两种:
-
大端模式:数据的高位字节存放在低地址处,低位字节存放在高地址处
-
小端模式:数据的低位字节存放在低地址处,高位字节存放在高地址处
示例:
cpp
int a = 0x11223344;

我们在VS上调试发现,这里满足数据的低位字节存放在低地址处,高位字节存放在高地址处,也就是小端排序。
2.2 为什么有大小端?
这是因为在计算机系统中,我们是以字节为单位的,每个地址单元都对应着⼀个字节,⼀个字节为8 bit 位,但是在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处理器还可以由硬件来选择是⼤端模式还是小端模式。
2.3 判断当前机器字节序的方法
设计⼀个小程序来判断当前机器的字节序。
这里我们采用指针的方法进行操作,将整型的1(0000001)的地址进行强制类型转换为(char*)并进行解引用,如果得到的是1,那么就是小端字节序,是0则是大端字节序。
cpp
#include<stdio.h>
int is_sys(int num)
{
int* p = #
return *(char*)p;
}
int main()
{
int ret=is_sys(1);
if (ret)
printf("小端排序\n");
else printf("大端排序\n");
return 0;
}
3. 浮点数在内存中的存储
常见的浮点数:3.14159、1E10等,浮点数家族包括: float 、 double 、 long double 类型。
浮点数存储遵循IEEE 754标准,将浮点数表示为:


举例来说:
十进制的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。
IEEE 754规定:
对于32位的浮点数,最⾼的1位存储符号位S,接着的8位存储指数E,剩下的23位存储有效数字M
对于64位的浮点数,最⾼的1位存储符号位S,接着的11位存储指数E,剩下的52位存储有效数字M

存储特点:
-
M存储时省略首位的1(因为总是1),只存储小数部分
-
E存储时为实际值加偏移量(8位E加127,11位E加1023)
3.2 示例分析
cpp
#include <stdio.h>
int main() {
int n = 9;
float *pFloat = (float*)&n;
printf("n的值为:%d\n", n); // 输出9
printf("*pFloat的值为:%f\n", *pFloat); // 输出0.000000
*pFloat = 9.0;
printf("num的值为:%d\n", n); // 输出1091567616
printf("*pFloat的值为:%f\n", *pFloat); // 输出9.000000
return 0;
}
解释:
-
整数9的补码:00000000 00000000 00000000 00001001按浮点数解释:S=0, E=00000000, M=000...01001由于E全0,结果为接近0的小数,输出0.000000
-
浮点数9.0二进制:1001.0 = 1.001×2³,存储时:S=0, E=3+127=130 (10000010), M=001...
组合后的二进制被当作整数解释,即为1091567616
4. 练习巩固与解析
练习一:
cpp
#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;
}
// 输出:a=-1,b=-1,c=255
解析:a和b为有符号char,-1的补码为11111111,按%d输出时整型提升为-1。c为无符号,整型提升后为255。
练习二:
cpp
#include <stdio.h>
int main() {
char a = -128;
printf("%u\n", a);
return 0;
}
解析:-128的补码为10000000,按%u输出时整型提升为无符号整数,即4294967168。
练习三:
cpp
#include <stdio.h>
#include <string.h>
int main() {
char a[1000];
int i;
for(i = 0; i < 1000; i++) {
a[i] = -1 - i;
}
printf("%d", strlen(a));
return 0;
}
解析:char的取值范围为-128~127。当i从0到999,a[i]的值从-1递减,经过-128后变为127,再到0。strlen遇到'\0'(即0)停止,因此统计从-1到-128(128个值)和127到1(127个值),共255个字符。
练习四:
cpp
#include <stdio.h>
unsigned char i = 0;
int main() {
for(i = 0; i <= 255; i++) {
printf("hello world\n");
}
return 0;
}
解析:unsigned char 的范围是0~255,不会跳出循环,所以会死循环地打印hello world。
练习五:
cpp
//小端环境下
#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;
}
解析:
-
&a + 1跳过了整个数组,ptr1[-1]即*(ptr1-1)为a[3]=4 -
(int)a + 1将地址按字节偏移1,在小端模式下,a[0]的存储为01 00 00 00,偏移1字节后指向00,接着读取4字节得到00 00 00 02,即0x2000000
总结
-
整数存储采用补码,统一了加减法运算
-
大小端字节序影响多字节数据的存储顺序,可通过简单程序判断
-
浮点数存储遵循IEEE 754标准,通过S、M、E三部分表示,存储和读取有特殊规则
理解数据在内存中的存储方式,有助于编写更健壮、高效的C语言程序,尤其是在涉及底层操作、跨平台开发和数据序列化时。
欢迎在评论区交流讨论,如果觉得有帮助,请点赞收藏支持!
更多C语言技术文章,请访问我的博客主页:https://blog.csdn.net/2402_87657156