C 语言底层核心:数据在内存中的存储(大小端 + 整数 + 浮点型全解析)

🏠个人主页:黎雁

🎬作者简介:C/C++/JAVA后端开发学习者

❄️个人专栏:C语言数据结构(C语言)EasyX游戏规划

✨ 从来绝巘须孤往,万里同尘即玉京

文章目录

    • [前景回顾:内存操作函数核心速记 📝](#前景回顾:内存操作函数核心速记 📝)
    • [一、整数在内存中的存储:补码为王 🎯](#一、整数在内存中的存储:补码为王 🎯)
    • [二、大小端字节序:多字节数据的存储顺序 🔄](#二、大小端字节序:多字节数据的存储顺序 🔄)
      • [1. 什么是大小端字节序](#1. 什么是大小端字节序)
      • [2. 不同环境的字节序使用](#2. 不同环境的字节序使用)
      • [3. 笔面试真题:设计程序判断机器字节序](#3. 笔面试真题:设计程序判断机器字节序)
      • [4. 经典例题解析:char类型的截断与整型提升](#4. 经典例题解析:char类型的截断与整型提升)
      • [5. 综合例题:指针与内存地址的运算](#5. 综合例题:指针与内存地址的运算)
    • [三、浮点型在内存中的存储:IEEE 754标准 📐](#三、浮点型在内存中的存储:IEEE 754标准 📐)
    • [写在最后 📝](#写在最后 📝)

内存操作函数讲完之后,我们终于要深入C语言的底层核心------数据在内存中的存储规则!这一篇聚焦三大核心知识点:整数的内存存储、大小端字节序的判断,以及浮点型的特殊存储方式,结合大量笔面试真题,帮你彻底搞懂数据存储的底层逻辑!

前景回顾:内存操作函数核心速记 📝

C 语言的内存函数:memcpy/memmove/memset/memcmp 精讲(含模拟实现)

回顾内存操作函数的特性,能帮我们更好理解数据存储的意义:

  1. 内存函数以字节为单位操作内存,不关心数据类型。
  2. 不同数据类型(整数/浮点型)在内存中的存储格式完全不同。
  3. 多字节数据的存储顺序(大小端)会直接影响数据读取结果。

一、整数在内存中的存储:补码为王 🎯

整数在内存中的存储遵循补码规则,这部分内容在《操作符详解》中已经讲解,这里做核心回顾:

  • 原码:直接根据数值正负写出的二进制表示。
  • 反码:正数反码与原码相同;负数反码是原码符号位不变,其余位按位取反。
  • 补码:正数补码与原码相同;负数补码是反码加1。
  • 核心结论 :计算机中整数一律以补码形式存储,原因是补码能把减法运算转化为加法运算,简化硬件设计。

二、大小端字节序:多字节数据的存储顺序 🔄

对于char这种单字节数据,不存在存储顺序问题;但对于int(4字节)、short(2字节)等多字节数据,就会涉及大小端字节序的问题,这是笔面试的高频考点!

1. 什么是大小端字节序

以数据0x11223344(4字节)为例,内存地址有高低之分,数据字节也有高低之分:

  • 数据的高低字节0x11高位字节0x44低位字节
  • 内存的高低地址 :内存地址编号从小到大,依次是低地址→高地址
字节序类型 存储规则 存储示例(低地址→高地址)
小端字节序 低位字节存低地址,高位字节存高地址 44 33 22 11
大端字节序 低位字节存高地址,高位字节存低地址 11 22 33 44

2. 不同环境的字节序使用

  • Intel架构、多数ARM/DSP、VS编译器:小端模式
  • KEIL C51(单片机开发):大端模式
  • 部分ARM处理器:支持硬件切换大小端模式

3. 笔面试真题:设计程序判断机器字节序

这是百度等大厂的经典笔试题,核心思路是利用intchar的类型差异,只读取数据的低地址第一个字节

方案1:直接判断
c 复制代码
#include <stdio.h>
int main()
{
    int a = 1; // 补码:0x00000001
    // 强转为char*,只取低地址第一个字节
    if (*(char*)&a == 1)
        printf("小端\n"); // 小端存储:低地址是01
    else
        printf("大端\n"); // 大端存储:低地址是00
    return 0;
}
方案2:封装成函数(更规范)
c 复制代码
#include <stdio.h>

// 返回1:小端;返回0:大端
int check_sys()
{
    int a = 1;
    return *(char*)&a == 1 ? 1 : 0;
}

int main()
{
    if (check_sys() == 1)
        printf("小端\n");
    else
        printf("大端\n");
    return 0;
}

4. 经典例题解析:char类型的截断与整型提升

char类型占1字节(8位),存储数据时会发生截断 ,读取时会发生整型提升,结合大小端和有无符号属性,会产生很多看似反常识的结果。

例题1:signed/unsigned char的输出差异
c 复制代码
#include <stdio.h>
int main()
{
    char a = -1;
    signed char b = -1;
    unsigned char c = -1;
    printf("a=%d b=%d c=%d\n", a, b, c);
    return 0;
}

分析过程

  1. -1的补码:11111111 11111111 11111111 11111111
  2. 截断(存到char):a=b=c=11111111
  3. 整型提升:
    • a/b(signed char):补符号位→全1,原码是-1
    • c(unsigned char):补0→00000000 00000000 00000000 11111111,值为255
      输出结果a=-1 b=-1 c=255
例题2:unsigned类型的死循环陷阱
c 复制代码
#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
  • i=255时,执行i++会溢出,值变为0
  • 循环条件i<=255恒成立,导致死循环

💡 结论:使用unsigned类型时,要特别注意循环边界条件,避免溢出导致的逻辑错误。

5. 综合例题:指针与内存地址的运算

c 复制代码
#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\n", ptr1[-1], *ptr2);
    return 0;
}

内存分析(小端环境)

数组a的内存布局(低→高):01 00 00 00 02 00 00 00 03 00 00 00 04 00 00 00

  1. &a+1:跳过整个数组,ptr1指向数组末尾后一位;ptr1[-1]等价于*(ptr1-1),指向最后一个元素4,输出4
  2. (int)a+1a是数组首地址,强转为int后+1,指向第一个元素的第二个字节;*ptr2读取4字节为00 00 00 02,输出2000000
    输出结果4 2000000

三、浮点型在内存中的存储:IEEE 754标准 📐

浮点型(float/double)的存储方式和整数完全不同,遵循IEEE 754国际标准,这也是C语言的一个难点。

1. 引子:一个反常识的例子

c 复制代码
#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("n的值为:%d\n", n);
    printf("*pFloat的值为:%f\n", *pFloat);
    return 0;
}

运行输出

复制代码
n的值为:9
*pFloat的值为:0.000000
n的值为:1091567616
*pFloat的值为:9.000000

结论同一个内存数据,按整数解析和按浮点型解析,结果完全不同!

2. IEEE 754标准的核心规则

任意二进制浮点数 V V V都可以表示为:
V = ( − 1 ) S × M × 2 E V = (-1)^S \times M \times 2^E V=(−1)S×M×2E

  • ( − 1 ) S (-1)^S (−1)S:符号位 , S = 0 S=0 S=0表示正数, S = 1 S=1 S=1表示负数
  • M M M:有效数字 ,范围是 1 ≤ M < 2 1 \leq M < 2 1≤M<2
  • 2 E 2^E 2E:指数位,表示数据的量级
示例:浮点数5.5的表示
  • 十进制5.5 → 二进制101.1
  • 科学计数法: 1.011 × 2 2 1.011 \times 2^2 1.011×22
  • 对应参数: S = 0 S=0 S=0, M = 1.011 M=1.011 M=1.011, E = 2 E=2 E=2

3. 浮点型的内存布局

IEEE 754标准规定了floatdouble的内存结构:

类型 总字节数 符号位 S S S 指数位 E E E 尾数位 M M M
float 4(32位) 1位 8位 23位
double 8(64位) 1位 11位 52位
两个关键优化点
  1. 有效数字 M M M的优化

    因为 1 ≤ M < 2 1 \leq M < 2 1≤M<2,所以 M M M的整数部分一定是1,存储时可以省略这个1,只存小数部分。比如 M = 1.011 M=1.011 M=1.011,内存中只存011,读取时再补回1,节省1位空间。

  2. 指数位 E E E的优化
    E E E是无符号整数,但实际可以是负数。存储时 E E E需要加上一个中间值

    • float(8位 E E E):中间值=127
    • double(11位 E E E):中间值=1023
示例:浮点数9.0的存储
  • 十进制9.0 → 二进制1001.0 → 1.001 × 2 3 1.001 \times 2^3 1.001×23
  • S = 0 S=0 S=0, M = 1.001 M=1.001 M=1.001, E = 3 E=3 E=3
  • 存储 E E E: 3 + 127 = 130 3+127=130 3+127=130 → 二进制10000010
  • 存储 M M M:省略1,只存001,后面补0至23位
  • 最终内存结构:0 10000010 00100000000000000000000
  • 转为十进制整数:1091567616(对应引子题的输出结果)

写在最后 📝

数据在内存中的存储是C语言的底层核心知识点,也是笔面试的"拦路虎"。想要吃透这部分内容,关键在于三点:

  1. 整数存储牢记补码规则 ,char类型注意截断与整型提升
  2. 多字节数据要分清大小端字节序,掌握判断方法;
  3. 浮点型存储遵循IEEE 754标准,理解符号位、指数位、尾数位的含义。

这些知识点看似抽象,但结合具体例题分析后,就能发现其中的规律。建议大家把文中的例题都手动敲一遍,观察运行结果,加深对底层逻辑的理解。至此,C语言内存相关的核心内容就全部讲解完毕啦!

相关推荐
feifeigo1232 小时前
MATLAB微光图像增强综合实现
开发语言·计算机视觉·matlab
崇山峻岭之间2 小时前
Matlab学习记录14
开发语言·学习·matlab
lly2024062 小时前
CSS 颜色名详解
开发语言
Biomamba生信基地2 小时前
用R语言画生信基地圣诞树~
开发语言·r语言·单细胞·空间转录组·biomamba生信基地
Nakkhon2 小时前
软件工程实践——个人技术博客
java·开发语言
embrace992 小时前
【数据结构学习】数据结构和算法
c语言·数据结构·c++·学习·算法·链表·哈希算法
智算菩萨2 小时前
【Python进阶】数据结构的精巧与算法的智慧:AI提速的关键
开发语言·人工智能·python
代码or搬砖2 小时前
== 和 equals() 的区别
java·开发语言·jvm
liwulin05062 小时前
【PYTHON】视频转图片
开发语言·python·音视频