【C 语言系统入门教程】第 19 讲:数据在内存中的存储 | 零基础学习笔记

【C 语言系统入门教程】第 19 讲:数据在内存中的存储 | 零基础学习笔记

前言

数据在内存中的存储方式是 C 语言最核心的底层知识之一,也是笔试面试的高频考点

本讲深入讲解整数的原反补码存储、大小端字节序、浮点数的 IEEE 754 标准,通过经典笔试题解析,帮你彻底搞懂数据在内存中的二进制表示,解决 "为什么同样的二进制数据,不同类型解读结果天差地别" 的核心问题。


🎯 本讲学习目标

  1. 掌握整数的原码、反码、补码表示,理解为什么用补码存储。

  2. 理解大小端字节序的概念,能独立编写程序判断机器字节序。

  3. 掌握char/signed char/unsigned char的取值范围与溢出问题。

  4. 理解IEEE 754 浮点数存储标准,掌握 float 和 double 的内存结构。

  5. 能独立解析整数和浮点数存储相关的经典笔试题。

  6. 建立 "数据类型决定内存解读方式" 的核心思维。


📝 核心学习内容

1. 整数在内存中的存储

1.1 原码、反码、补码

有符号整数的二进制表示有三种形式:

  • 原码:符号位 + 数值位,最高位 0 表示正,1 表示负。

  • 反码:原码符号位不变,其余位按位取反。

  • 补码:反码 + 1。

核心规则

  • 正整数:原码 = 反码 = 补码

  • 负整数:原码≠反码≠补码

  • 整数在内存中存储的是补码

1.2 为什么用补码存储?
  1. 统一符号位和数值位:符号位直接参与运算,无需额外硬件。

  2. 统一加法和减法:CPU 只有加法器,减法可以转换为补码加法。

  3. 补码与原码相互转换过程相同:取反 + 1,无需额外电路。

示例:计算 1-1

TypeScript 复制代码
1-1 = 1+(-1)
1的补码:00000000 00000000 00000000 00000001
-1的补码:11111111 11111111 11111111 11111111
相加结果:00000000 00000000 00000000 00000000(正确)

2. 大小端字节序和字节序判断

2.1 什么是大小端?

超过 1 字节的数据在内存中存储时,存在字节顺序问题:

  • 大端模式 :数据的高位字节 存放在内存的低地址,低位字节存放在高地址。

  • 小端模式 :数据的低位字节 存放在内存的低地址,高位字节存放在高地址。

示例int a = 0x11223344

内存地址 大端模式 小端模式
低地址 0x11 0x44
0x22 0x33
0x33 0x22
高地址 0x44 0x11

大小端字节序存储

2.2 为什么有大小端?
  • 硬件设计差异:不同处理器架构采用不同的字节序。

  • 常见架构:X86 是小端模式,ARM、DSP 多为小端,KEIL C51 是大端。

2.3 判断当前机器字节序(百度笔试题)

方法 1:指针法

cpp 复制代码
#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;
}

方法 2:联合体法

cpp 复制代码
int check_sys()
{
    union
    {
        int i;
        char c;
    }un;
    un.i = 1;
    return un.c;
}
2.4 经典整数存储笔试题解析
练习 1:char 类型取值范围
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

解析

  • char默认是signed char,取值范围 - 128~127。

  • -1的补码是11111111unsigned char解读为 255。

练习 2:char 溢出问题
cpp 复制代码
#include <stdio.h>
int main()
{
    char a = -128;
    printf("%u\n", a); // 输出4294967168
    return 0;
}

解析

  • -128的补码是10000000(char 类型)。

  • 提升为 int 时,符号位扩展为11111111 11111111 11111111 10000000

  • %u(无符号)解读,结果为 4294967168。

练习 3:strlen 陷阱
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)); // 输出255
    return 0;
}

解析

  • char取值范围 - 128~127,当i=127时,a[127] = -128

  • i=128时,a[128] = 127,直到i=255时,a[255] = 0

  • strlen遇到'\0'(即 0)停止,所以长度为 255。

练习 4:unsigned char 死循环
cpp 复制代码
#include <stdio.h>
int main()
{
    unsigned char i = 0;
    for(i=0; i<=255; i++)
    {
        printf("hello world\n");
    }
    return 0;
}

解析

  • unsigned char取值范围 0~255,当i=255时,i++变为 0,循环永远成立。

3. 浮点数在内存中的存储

3.1 浮点数存储的疑问
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("n的值为:%d\n", n); // 1091567616
    printf("*pFloat的值为:%f\n", *pFloat); // 9.000000
    return 0;
}

为什么同样的二进制数据,整数和浮点数解读结果完全不同?

3.2 IEEE 754 浮点数标准

任意二进制浮点数 V 可以表示为:

V=(−1)S×M×2E

  • (−1)S:符号位,S=0 表示正数,S=1 表示负数。

  • M:有效数字,1≤M<2。

  • 2E:指数位。

内存结构

类型 符号位 S 指数位 E 有效数字位 M 总位数
float 1 位 8 位 23 位 32 位
double 1 位 11 位 52 位 64 位
3.3 浮点数存储过程
  1. 有效数字 M

    • 标准形式为1.xxxxxx,存储时只保存小数部分xxxxxx,省略整数部分的 1。

    • 这样可以节省 1 位,提高精度。

  2. 指数 E

    • E 是无符号整数,但科学计数法中 E 可以是负数。

    • 存储时需要加上中间值:float 加 127,double 加 1023。

    • 例如:E=3,存储为 3+127=130(二进制 10000010)。

3.4 浮点数读取过程

指数 E 分三种情况:

  1. E 不全为 0 且不全为 1(常规情况)

    • 真实指数 = 存储的 E - 127(或 1023)。

    • 有效数字 M = 1 + 存储的小数部分。

  2. E 全为 0

    • 真实指数 = 1 - 127(或 1023)。

    • 有效数字 M = 0 + 存储的小数部分(不再加 1)。

    • 用于表示 ±0 和接近 0 的极小值。

  3. E 全为 1

    • 如果 M 全为 0,表示 ± 无穷大。

    • 如果 M 不全为 0,表示 NaN(非数字)。

3.5 例题解析

问题 1:为什么 int 9 按 float 解读是 0.000000?

  • 9 的二进制补码:00000000 00000000 00000000 00001001

  • 按 float 解读:

    • S=0,E=00000000,M=00000000000000000001001

    • E 全为 0,真实指数 = 1-127=-126

    • M=0.00000000000000000001001

    • V = (−1)0×0.00000000000000000001001×2−126≈1.001×2−146

    • 这是一个极小的数,用 % f 打印显示为 0.000000。

问题 2:为什么 float 9.0 按 int 解读是 1091567616?

  • 9.0 的二进制:1001.0 = 1.001×23

  • S=0,M=00100000000000000000000(省略 1),E=3+127=130(10000010)

  • 二进制表示:0 10000010 00100000000000000000000

  • 按 int 解读:这个二进制数的十进制值就是 1091567616。

float类型浮点数内存分配 double类型浮点数内存分配


📝 课后习题

一、选择题

  1. 整数在内存中存储的是()

    A. 原码

    B. 反码

    C. 补码

    D. ASCII 码

  2. 小端模式下,int a = 0x12345678,第一个字节的值是()

    A. 0x12

    B. 0x34

    C. 0x56

    D. 0x78

  3. signed char的取值范围是()

    A. 0~255

    B. -127~127

    C. -128~127

    D. -256~255

  4. float 类型中,指数位占多少位()

    A. 1 位

    B. 8 位

    C. 23 位

    D. 32 位

  5. 浮点数 9.0 的二进制科学计数法表示是()

    A. 1.001×23

    B. 10.01×22

    C. 0.1001×24

    D. 1.001×22

二、判断题

  1. 正整数的原码、反码、补码都相同。()

  2. 大端模式是低位字节存放在低地址。()

  3. unsigned char i=255; i++; 后 i 的值是 0。()

  4. 浮点数可以精确表示所有小数。()

  5. float 类型的有效数字精度比 double 高。()

三、编程题

  1. 编写程序,判断当前机器是大端还是小端模式。

  2. 分析以下代码的输出结果,并解释原因。

cpp 复制代码
#include <stdio.h>
int main()
{
    unsigned int a = 10;
    int b = -20;
    if(a + b > 0)
        printf("a+b>0\n");
    else
        printf("a+b<=0\n");
    return 0;
}
  1. 解释为什么0.1 + 0.2 != 0.3

📝 参考答案

一、选择题

  1. C

  2. D

  3. C

  4. B

  5. A

二、判断题

  1. ×(小端模式)

  2. ×(部分小数无法精确表示)

  3. ×(double 精度更高)

三、编程题

1. 判断大小端(见正文)
2. 代码输出
cpp 复制代码
a+b>0

解析

  • a是 unsigned int,b是 int,运算时b会被转换为 unsigned int。

  • -20的补码作为 unsigned int 解读是一个很大的正数,10 + 4294967276 = 4294967286 > 0

3. 0.1+0.2 问题
  • 0.1 和 0.2 的二进制表示都是无限循环小数,无法精确存储。

  • 浮点数存储时会截断,导致精度损失,所以相加结果不等于 0.3。


📝 本讲总结

  1. 整数存储:以补码形式存储,统一符号位和加减法运算。

  2. 大小端字节序:小端低位存低地址,大端高位存低地址,可通过指针或联合体判断。

  3. char 类型:注意有符号和无符号的取值范围,溢出会导致循环。

  4. 浮点数存储:遵循 IEEE 754 标准,分为符号位、指数位、有效数字位。

  5. 核心思想内存中存储的都是二进制数据,数据类型决定了如何解读这些二进制

  6. 本讲是 C 语言底层核心,彻底掌握才能写出健壮、高效的代码,应对各类笔试面试。


📌 版权说明

本文为 C 语言系统学习原创笔记,基于标准课件体系整理,纯干货零基础友好,未经允许禁止转载,如有错误欢迎评论区指正~

相关推荐
斯维赤2 小时前
Python学习超简单第八弹:网络编程
网络·python·学习
handler0111 小时前
从源码到二进制:深度拆解 Linux 下 C 程序的编译与链接全流程
linux·c语言·开发语言·c++·笔记·学习
电子云与长程纠缠11 小时前
UE5 两种方式解决Decal Actor贴花拉伸问题
学习·ue5·游戏引擎
red_redemption11 小时前
自由学习记录(172)
学习·cache line 64b·重用距离
阿荻在肝了12 小时前
Agent学习六:LangGraph学习-持久化与记忆一
python·学习·agent
Aurorar0rua12 小时前
CS50 x 2024 Notes C - 05
java·c语言·数据结构
棋子入局13 小时前
C语言制作消消乐游戏(2)
c语言·开发语言·游戏
良木生香13 小时前
【C++初阶】:STL——String从入门到应用完全指南(1)
c语言·开发语言·数据结构·c++·算法
寒秋花开曾相惜14 小时前
(学习笔记)4.1 Y86-64指令集体系结构(4.1.4 Y86-64异常&4.1.5 Y86-64程序)
开发语言·jvm·数据结构·笔记·学习