初识C语言(数据在内存中的存储)

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

文章目录


前言

这篇博客主要围绕整数、大小端字节序、浮点数在内存中的存储。

一、整数在内存中的存储

1. 二进制表示形式

整数在计算机内存中是以二进制形式存储的,使用补码表示法。补码系统有以下特点:

  • 正数的补码是其二进制原码
  • 负数的补码是其绝对值的二进制取反后加1
  • 最高位为符号位(0表示正数,1表示负数)

例如,8位整数:

  • +5 的补码:00000101
  • -5 的补码:11111011(取反11111010 + 1)

2. 存储方式

整数在内存中的存储遵循以下规则:

  1. 大小端存储

    • 大端模式(Big-endian):高位字节存储在低地址
    • 小端模式(Little-endian):低位字节存储在低地址
    • 例如0x12345678在内存中的存储:
      • 大端:12 34 56 78
      • 小端:78 56 34 12
  2. 对齐方式

    • 现代CPU通常要求数据按照其字长对齐
    • 32位系统通常要求4字节对齐
    • 64位系统通常要求8字节对齐

3. 常见整数类型

不同编程语言中的整数类型及其典型存储大小:

类型 C/C++ Java Python 存储大小
有符号8位 char byte int 1字节
无符号8位 unsigned char - - 1字节
有符号16位 short short int 2字节
无符号16位 unsigned short char - 2字节
有符号32位 int int int 4字节
无符号32位 unsigned int - - 4字节
有符号64位 long long long int 8字节
无符号64位 unsigned long long - - 8字节

注意:Python的int类型会自动扩展以适应大整数,实际存储大小会动态变化。

4. 特殊整数处理

  1. 零的表示

    • 所有位都为0
    • 补码系统中+0和-0的表示相同
  2. 最小负数的表示

    • 对于n位整数,最小负数是-2^(n-1)
    • 例如8位整数的最小值是-128(补码:10000000)
  3. 溢出处理

    • 无符号整数:回绕(0xFFFFFFFF + 1 = 0x00000000)
    • 有符号整数:行为未定义(C/C++)或抛出异常(某些语言)

5. 内存布局示例

32位整数0xAABBCCDD在小端系统中的内存布局:

复制代码
地址   内容
0x1000 DD
0x1001 CC 
0x1002 BB
0x1003 AA

理解整数在内存中的存储方式对于以下场景很重要:

  • 网络协议开发(处理字节序)
  • 二进制文件解析
  • 内存敏感型应用开发
  • 跨平台数据交换

二、⼤⼩端字节序和字节序判断

1. 什么是字节序

字节序(Endianness)指的是多字节数据在计算机内存中的存储顺序。主要有两种类型:

  1. 大端字节序(Big-Endian)

    • 最高有效字节(MSB)存储在最低的内存地址
    • 类似于我们书写数字的顺序(从左到右)
    • 示例:0x12345678 在内存中的存储顺序为 12 34 56 78
    • 采用大端序的架构:PowerPC、SPARC、早期的ARM等
  2. 小端字节序(Little-Endian)

    • 最低有效字节(LSB)存储在最低的内存地址
    • 类似于"倒序"存储
    • 示例:0x12345678 在内存中的存储顺序为 78 56 34 12
    • 采用小端序的架构:x86、x86-64、现代ARM(可配置)

2. 为什么需要关注字节序

字节序问题主要在以下场景中需要注意:

  1. 跨平台数据传输

    • 网络通信(网络协议通常采用大端序)
    • 文件格式交换
    • 不同架构设备间的数据共享
  2. 二进制数据处理

    • 解析网络数据包
    • 读取二进制文件
    • 处理硬件寄存器
  3. 调试和逆向工程

    • 分析内存数据时需要知道字节序
    • 理解寄存器和内存中的值对应关系

3. 判断当前系统的字节序

方法1:使用C程序判断

c 复制代码
#include <stdio.h>

int main() {
    unsigned int num = 0x12345678;
    unsigned char *p = (unsigned char *)&num;
    
    if (*p == 0x12) {
        printf("Big-Endian\n");
    } else if (*p == 0x78) {
        printf("Little-Endian\n");
    } else {
        printf("Unknown Endianness\n");
    }
    
    return 0;
}

方法2:使用Python判断

python 复制代码
import sys

if sys.byteorder == 'little':
    print("Little-Endian")
else:
    print("Big-Endian")

方法3:使用命令行工具(Linux)

bash 复制代码
lscpu | grep "Endian"
# 或
echo -n I | od -to2 | head -n1 | cut -f2 -d" " | cut -c6
# 输出1表示小端,0表示大端

4. 字节序转换函数

C语言中的转换函数

c 复制代码
#include <arpa/inet.h>

uint32_t htonl(uint32_t hostlong);  // 主机到网络(32位)
uint16_t htons(uint16_t hostshort); // 主机到网络(16位)
uint32_t ntohl(uint32_t netlong);   // 网络到主机(32位)
uint16_t ntohs(uint16_t netshort);  // 网络到主机(16位)

Python中的转换

python 复制代码
import socket

value = 0x12345678
network_order = socket.htonl(value)  # 主机序转网络序
host_order = socket.ntohl(network_order)  # 网络序转主机序

手动实现字节序转换

32位整数的转换示例:

c 复制代码
uint32_t swap_endian(uint32_t val) {
    return ((val << 24) & 0xff000000) |
           ((val << 8)  & 0x00ff0000) |
           ((val >> 8)  & 0x0000ff00) |
           ((val >> 24) & 0x000000ff);
}

5. 实际应用示例

网络协议处理

处理TCP/IP首部时需要注意:

  • IP首部中的所有字段都是大端序
  • TCP首部中的所有字段都是大端序
  • 接收数据后需要使用ntohs/ntohl转换

文件格式处理

常见二进制文件的字节序:

  • PNG:大端序
  • GIF:小端序
  • JPEG:大端序
  • ELF:与目标平台相关

嵌入式开发

访问硬件寄存器时需要特别注意:

  • 读取传感器数据
  • 配置设备寄存器
  • 与FPGA通信

6. 常见问题与解决方案

  1. 数据错位问题

    • 现象:接收到的数据与预期不符
    • 解决方案:检查发送方和接收方的字节序是否一致
  2. 跨平台兼容性

    • 使用标准网络字节序(大端)进行数据交换
    • 在文件头中加入字节序标识
  3. 性能考虑

    • 在已知字节序的系统中避免不必要的转换
    • 批量转换优于逐个转换
  4. 调试技巧

    • 使用十六进制查看器检查原始数据
    • 打印内存内容对比预期值

三、浮点数在内存中的存储

1. IEEE 754标准

浮点数在内存中的存储遵循IEEE 754标准,该标准定义了浮点数的二进制表示格式。主要有两种格式:

  • 单精度浮点数(32位):

    • 1位符号位
    • 8位指数位
    • 23位尾数位
  • 双精度浮点数(64位):

    • 1位符号位
    • 11位指数位
    • 52位尾数位

2. 存储结构详解

符号位(Sign)

  • 位于最高位
  • 0表示正数,1表示负数
  • 示例:-3.14的符号位为1

指数部分(Exponent)

  • 采用偏移表示法(Bias)
    • 单精度:偏移量为127
    • 双精度:偏移量为1023
  • 实际指数 = 存储值 - 偏移量
  • 示例:单精度浮点数中存储的指数值为130,实际指数为130-127=3

尾数部分(Mantissa/Significand)

  • 采用隐含最高位1的表示方法(规范化数)
  • 只存储小数部分
  • 示例:1.1011只需存储1011

3. 特殊值表示

IEEE 754标准还定义了特殊值的表示:

  • 零值:全0表示
  • 无穷大:指数全1,尾数全0
    • +∞:符号位0
    • -∞:符号位1
  • NaN(非数):指数全1,尾数非0
    • 用于表示无效运算结果(如0/0)

4. 舍入规则

IEEE 754定义了四种舍入模式:

  1. 向最近值舍入(默认)
  2. 向零舍入
  3. 向正无穷舍入
  4. 向负无穷舍入

5. 精度问题

由于浮点数的二进制表示特性,会导致一些十进制小数无法精确表示,如:

  • 0.1在二进制中是无限循环小数
  • 这解释了为什么0.1 + 0.2 != 0.3

6. 实际应用注意事项

在编程中需要注意:

  1. 避免直接比较浮点数相等

    • 应该比较两者差值是否小于某个极小值(如1e-6)
  2. 注意累积误差

    • 多次运算可能导致误差累积
  3. 选择合适的浮点类型

    • 根据精度需求选择单精度或双精度
  4. 注意特殊值的处理

    • 检查NaN和无穷大的情况

总结

以上就是本文总结的内容,关于数据在内存中的存储。

相关推荐
by__csdn1 小时前
javascript 性能优化实战:异步和延迟加载
开发语言·前端·javascript·vue.js·性能优化·typescript·ecmascript
青铜弟弟1 小时前
R语言与python升级包的问题
开发语言·python·r语言
R-G-B1 小时前
BM53 缺失的第一个正整数,哈希表,原地哈希(扩展思路)
算法·哈希算法·哈希表·原地哈希
CC.GG1 小时前
【C++】STL容器----map和set的使用
开发语言·c++
Luna-player1 小时前
Spring整合MyBatis-Pluss 部分课堂学习笔记
java·开发语言·tomcat
AI科技星1 小时前
观察者与宇宙:描述如何创造物理实在
数据结构·人工智能·算法·机器学习·重构
八个程序员1 小时前
汉字古诗生成c++
开发语言·c++
zore_c1 小时前
【C语言】数据结构——顺序表超详解!!!(包含顺序表的实现)
c语言·开发语言·数据结构·c++·经验分享·笔记·线性回归
发疯幼稚鬼1 小时前
简单介绍二项队列及其实现
c语言·数据结构·算法