深入解析小端序:把 4 字节的小端序拼成一个 32 位无符号长整数

深入解析小端序:把 4 字节的小端序拼成一个 32 位无符号长整数

      • 前言
      • 一、什么是大小端序
        • [1. 什么是字节序?](#1. 什么是字节序?)
        • [2. 小端序(Little-Endian)](#2. 小端序(Little-Endian))
        • [3. 大端序(Big-Endian)](#3. 大端序(Big-Endian))
        • [4. 大小端使用场景](#4. 大小端使用场景)
      • 二、小端序拼接代码逐行深度解析
        • [1. buf0 ------ 占据 0~7 位](#1. buf[0] —— 占据 0~7 位)
        • [2. buf1 << 8 ------ 占据 8~15 位](#2. buf[1] << 8 —— 占据 8~15 位)
        • [3. buf2 << 16 ------ 占据 16~23 位](#3. buf[2] << 16 —— 占据 16~23 位)
        • [4. ((unsigned long)buf3) << 24 ------ 占据 24~31 位](#4. ((unsigned long)buf[3]) << 24 —— 占据 24~31 位)
        • [5. 按位或 **|** 合并所有位](#5. 按位或 | 合并所有位)
      • [三、(unsigned long) 强制转换能省略吗?](#三、(unsigned long) 强制转换能省略吗?)
        • [1. 先懂C语言默认整数提升规则](#1. 先懂C语言默认整数提升规则)
        • [2. 省略强转的致命问题(场景复现)](#2. 省略强转的致命问题(场景复现))
        • [3. 加上强转为什么就正常?](#3. 加上强转为什么就正常?)
        • [4. 为什么前面的移位不需要强转?](#4. 为什么前面的移位不需要强转?)
      • 四、总结
      • 五、完整可运行测试代码

作者:高玉涵

时间:2026.8.19 20:48

博客:blog.csdn.net/cg_i

环境:Windows98 SE、VC6.0 SP6

前言

在 C 语言网络编程、文件解析、硬件数据读取的场景中,我们经常会遇到一段经典的小端序4字节转32位整数代码:

c 复制代码
unsigned long res = buf[0] | (buf[1]<<8) | (buf[2]<<16) | ((unsigned long)buf[3]<<24);

同学们也会有下面三问?:

  1. 什么是小端序?为什么硬件/网络数据大多用小端序存储?
  2. 这行代码是如何把 4 个单字节数据拼成一个完整32位整数的?
  3. 最后一个**(unsigned long) 强制转换能不能省略?** 省略会发生什么Bug?

本文从零讲透底层原理,彻底搞定字节序与数据拼接的核心知识点。

一、什么是大小端序

1. 什么是字节序?

计算机中,一个 32 位整数占用 4 个字节 。当多字节数据存入内存、文件、网络缓冲区时,就面临一个问题:究竟是低字节存入低内存地址、高字节存入高内存地址,还是反过来?

这个字节的存储顺序,就是字节序(Endian),分为两种:小端序、大端序。

2. 小端序(Little-Endian)

规则:低字节存低地址,高字节存高地址

举个实例:32 位十六进制数 0x12345678

  • 最高字节(高8位):0x12(权重最大,对应24~31位)
  • 次高字节:0x34(对应16~23位)
  • 次低字节:0x56(对应8~15位)
  • 最低字节(低8位):0x78(权重最小,对应0~7位)

小端序内存/缓冲区中存储顺序为:

复制代码
低地址 buf[0] = 0x78、buf[1] = 0x56、buf[2] = 0x34、高地址 buf[3] = 0x12

这也是为什么我们需要移位拼接:存储顺序和数值真实位序是颠倒的。

3. 大端序(Big-Endian)

规则:高字节存低地址,低字节存高地址

和我们阅读数字的习惯一致,上述 0x12345678 存储顺序为:buf[0]=0x12buf[1]=0x34buf[2]=0x56buf[3]=0x78

4. 大小端使用场景
  • 小端序:x86、x64 电脑、单片机、绝大多数硬件设备、本地文件存储(贴近硬件,运算效率高)
  • 大端序:网络协议、TCP/IP、Java虚拟机(贴近人类阅读习惯,保证跨设备传输统一)

我们本篇的代码,就是专门用于解析硬件/本地存储的小端序 4 字节数据

二、小端序拼接代码逐行深度解析

已知条件:unsigned char buf[4] 存储一组小端序数据,对应真实数值 0x10270000,存储关系:

  • buf[0] = 0x10(低地址、最低字节)
  • buf[1] = 0x27(次低地址、次低字节)
  • buf[2] = 0x00(次高地址)
  • buf[3] = 0x00(高地址、最高字节)

目标:把颠倒存储的4个字节,还原为正确的 32 位整数 0x27100000

核心代码:

c 复制代码
buf[0] | (buf[1]<<8) | (buf[2]<<16) | ((unsigned long)buf[3]<<24);

我们逐段拆解移位逻辑:

1. buf0 ------ 占据 0~7 位

buf0 是最低字节,不需要移位,直接保留在最低8位。

运算结果:0x00000010

2. buf1 << 8 ------ 占据 8~15 位

buf1 = 0x27,向左移动8位,抬升到第二个字节位置。

运算结果:0x00002700

3. buf2 << 16 ------ 占据 16~23 位

buf2 = 0x00,左移16位后仍然为0,不影响数值。

运算结果:0x00000000

4. ((unsigned long)buf3) << 24 ------ 占据 24~31 位

buf3 = 0x00,虽然当前数值为0,但强制转换必须保留,这是代码通用性、安全性的关键。

运算结果:0x00000000

5. 按位或 | 合并所有位

按位或核心规则:二进制有 1 则 1,全 0 才为 0 。 本次拼接的四段数据存在一个关键特点:每一段移位后的数值,1 的二进制位完全不重叠

我们拆解四段移位结果的占用位域:

  • buf[0] 结果 0x00000010:仅占用 0~7位
  • buf[1]<<8 结果 0x00002700:仅占用 8~15位
  • buf[2]<<16buf[3]<<24 结果为0:无任何位占用

四段数据各司其职,占用的二进制区域完全独立、互不冲突。此时按位或运算不再是普通的位运算,而是无损字节拼接:各自的有效位会直接保留、叠加组合,完美拼成完整 32 位数值。

复制代码
  0x00000010
| 0x00002700
| 0x00000000
| 0x00000000
= 0x00002710

最终正确结果:0x00002710,对应十进制 10000

精确验算:0x27 * 256 + 0x10 = 9984 + 16 = 10000,完全符合小端序解析逻辑。

三、(unsigned long) 强制转换能省略吗?

重点说明:当前 buf[3]=0x00,高位为0,不会触发符号扩展,所以对错结果一致。但只要 buf[3] >= 0x80,省略强转会直接数据错乱,因此强转语法必须保留,不能省略

1. 先懂C语言默认整数提升规则

在C语言中:unsigned charcharshort 参与运算时,会自动提升为 int 类型(32位有符号整型)。

也就是说:buf[3] 原本是8位无符号字符,一旦参与移位运算,立刻变成 32 位有符号 int

2. 省略强转的致命问题(场景复现)

buf[3] >= 0x80(最高位为 1,字节数值较大)时,问题爆发。

举个错误案例:真实数据 0xAB563478,小端存储:buf[0]=0x78、buf[1]=0x56、buf[2]=0x34、buf[3]=0xAB

省略强转的错误代码:buf[3] << 24

  1. buf[3] = 0xAB(unsigned char)自动提升为 int:0x000000AB
  2. 执行左移 24 位:0x000000AB << 24 = 0xAB000000
  3. 关键问题 :32 位有符号int的最高位(第31位)是符号位,0xAB000000最高位为 1,代表这是一个负数
  4. 有符号数溢出移位属于C语言未定义行为,编译器会触发符号扩展,高位全部补 1
  5. 最终结果变成:0xFFFFFF78,完全错误!
3. 加上强转为什么就正常?

(unsigned long)buf[3] << 24 的执行顺序:

  1. 先强转:把 8 位的 buf3 提前转为 32 位无符号长整型
  2. 后移位:无符号数移位,没有符号位、没有溢出未定义行为,高位统一补 0

最终得到纯净的 0xAB000000,和其他字节按位或,结果完全正确。

4. 为什么前面的移位不需要强转?
  • buf[1]<<8:最大值0xFF00,仅占用8~15位,碰不到 int 符号位
  • buf[2]<<16:最大值 0xFF0000,仅占用16~23位,也碰不到 int 最高符号位(31位)
  • 只有 <<24 会将数据推到24~31位,触碰符号位,必须强转

四、总结

  1. 小端序核心:低字节存低地址,高字节存高地址,是 x86 硬件、单片机的主流存储方式;
  2. 代码原理:通过移位将颠倒存储的4个字节还原位序,再通过按位或拼接为完整 32 位整数;
  3. 强转不可省略<<24 会触碰有符号 int 符号位,省略强转会触发溢出和符号扩展,导致数据错乱;
  4. 强转顺序不能错:必须先强转数据类型,再移位,不能移位后再强转(Bug已触发,补救无效)。

五、完整可运行测试代码

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

int main(void)
{
    // 小端序数组
    unsigned char buf[4] = {0x10, 0x27, 0x00, 0x00};

    // 正确写法:带强制转换
    unsigned long val_ok = buf[0] | (buf[1]<<8) | (buf[2]<<16) | ((unsigned long)buf[3]<<24);
    // 错误写法:省略强制转换
    unsigned long val_bad = buf[0] | (buf[1]<<8) | (buf[2]<<16) | (buf[3]<<24);

    printf("正确结果  0x%08lX  十进制: %lu\n", val_ok, val_ok);
    printf("省略强转  0x%08lX  十进制: %lu\n", val_bad, val_bad);

    return 0;
}

运行输出

c 复制代码
正确结果  0x00002710  十进制: 10000
省略强转  0x00002710  十进制: 10000
相关推荐
C++ 老炮儿的技术栈5 天前
基于Qt实现轻量化本地音乐播放器
开发语言·c++·qt·c·播放器·音乐
一韦以航.9 天前
C/C++ 基础深挖:strlen 与 sizeof 全方位对比区别
c语言·jvm·c++·算法·面试·c
Ronin-Lotus12 天前
蓝桥杯篇---EEPROM内部存储地址
职场和发展·蓝桥杯·c·eeprom·keil
C++ 老炮儿的技术栈13 天前
海思嵌入式开发笔记|U‑Boot、JFFS2、TSLIB、SQLite 调试常用命令汇总
嵌入式·c·嵌入式linux·tftp·海思·jffs2
gbshha14 天前
C语言的分支与循环结构
c
Chen_LSN19 天前
C语言——深度理解指针(3)
c
向宜xy20 天前
“试试这套 SDD 规范驱动工作流”---我认真研究了“AI乱改代码”的解决方案,然后问了三个问题
c·ai编程
云泽8081 个月前
Visual Studio 2026 新手保姆级教程:环境搭建、界面优化与编译器配置详解
开发语言·c++·c·开发工具·visual studio
众少成多积小致巨1 个月前
C 核心概念、语法
c