HexDump 实现

三种 HexDump 实现对比:从 C 经典到现代 C++

在系统编程和底层调试中,以十六进制形式查看文件内容(Hex Dump)是一项基础而实用的需求。本文将展示三种不同风格的实现:Windows API 版 C 、标准 C 库版 C 以及 现代 C++ 版。通过对比,你不仅能学会写一个 Hex Dump 工具,还能体会到 C/C++ 语言数十年的演进。

一、什么是 Hex Dump?

Hex Dump 将文件的每个字节以十六进制形式输出,同时显示对应的可打印字符(不可打印字符用 . 代替)。典型的输出格式如下:

复制代码
00000000 | 89 50 4E 47 0D 0A 1A 0A 00 00 00 0D 49 48 44 52 | .PNG........IHDR |

每行 16 个字节,依次显示偏移地址、十六进制数据、ASCII 表示。

二、版本一:标准 C 库(fopen / fread)

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

typedef unsigned char BYTE;

void HexAscii(const BYTE* data, size_t offset, size_t length) {
    char ascii[17] = { 0 };
    printf("%08X | ", (unsigned int)offset);
    for (size_t i = 0; i < 16; i++) {
        if (i < length) {
            printf("%02X ", data[i]);
            ascii[i] = isprint(data[i]) ? data[i] : '.';
        } else {
            printf("   ");
            ascii[i] = ' ';
        }
    }
    printf(" |%s|\n", ascii);
}

void HexDump(const char* fileName) {
    FILE* pFile;
    fopen_s(&pFile, fileName, "rb");
    if (!pFile) return;

    BYTE buffer[16] = { 0 };
    size_t byteRead = 0, offset = 0;

    while ((byteRead = fread(buffer, 1, sizeof(buffer), pFile)) > 0) {
        HexAscii(buffer, offset, byteRead);
        offset += byteRead;
    }
    fclose(pFile);
}

特点分析

  • 可移植性:使用标准 C 库,可在任何支持 C 的平台上编译。
  • 简单直观 :fopen/fread 是经典的文件读取方式。
  • 不足 :fopen_s 是 C11 安全函数,Windows 上需要;Linux 可使用 fopen。另外 printf 类型不匹配时容易产生警告。

三、版本二:Windows API(CreateFile / ReadFile)

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

void HexDump(const char* fileName) {
    HANDLE hFile = CreateFileA(fileName, GENERIC_READ, FILE_SHARE_READ,
                               NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
    if (hFile == INVALID_HANDLE_VALUE) return;

    BYTE buffer[16];
    DWORD bytesRead = 0, offset = 0;

    while (ReadFile(hFile, buffer, sizeof(buffer), &bytesRead, NULL) && bytesRead > 0) {
        HexAscii(buffer, offset, bytesRead);
        offset += bytesRead;
    }
    CloseHandle(hFile);
}

特点分析

  • Windows 原生:直接调用操作系统 API,理论上可获得更好的底层控制(如异步 I/O、文件映射等)。
  • 类型安全 :使用 DWORD 等明确长度的类型,避免可移植性问题(但仅限于 Windows)。
  • 不足 :不可移植,代码中混入 windows.h 会增加编译依赖。

四、版本三:现代 C++(std::span / std::format)

cpp 复制代码
#include <iostream>
#include <iomanip>
#include <fstream>
#include <format>
#include <array>
#include <span>
#include <cctype>

void HexAscii(std::span<const std::byte> data, std::uint32_t offset) {
    std::array<char, 17> ascii{};
    std::cout << std::format("{:08X} | ", offset);

    for (std::size_t i = 0; i < 16; ++i) {
        if (i < data.size()) {
            unsigned char c = static_cast<unsigned char>(data[i]);
            std::cout << std::format("{:02X} ", c);
            ascii[i] = std::isprint(c) ? static_cast<char>(c) : '.';
        } else {
            std::cout << "   ";
            ascii[i] = ' ';
        }
    }
    std::cout << std::format(" |{}|\n", ascii.data());
}

void HexDump(std::string_view filename) {
    std::ifstream file(filename.data(), std::ios::binary);
    if (!file) return;

    std::array<std::byte, 16> buffer{};
    std::uint32_t offset = 0;

    while (file.read(reinterpret_cast<char*>(buffer.data()), buffer.size())) {
        HexAscii(std::span<const std::byte>(buffer), offset);
        offset += static_cast<std::uint32_t>(buffer.size());
    }

    if (file.gcount() > 0) {
        auto lastSpan = std::span<const std::byte>(buffer.data(), file.gcount());
        HexAscii(lastSpan, offset);
    }
}

现代特性解析

  1. std::span:作为非拥有视图传递连续数据,避免了指针+长度的分离,类型安全且语义清晰。
  2. std::byte :C++17 引入的字节类型,明确表达"原始内存",避免 char 的歧义。
  3. std::format :C++20 的格式化库,类型安全、性能接近 printf,不再需要繁琐的 iomanip。
  4. std::array :替代 C 风格数组,提供 STL 接口(如 .data()、.size())。
  5. std::ifstream :RAII 管理文件句柄,无需手动 close。
  6. std::string_view:高效传递文件名,避免拷贝。

五、三种方案对比

特性 标准 C 库 Windows API 现代 C++
跨平台 ✅ 优秀 ❌ 仅 Windows ✅ 优秀
类型安全 ⚠️ 一般 ⚠️ 一般 ✅ 优秀
错误处理 需手动检查 需手动检查 RAII + 异常
代码可读性 ⭐⭐⭐ ⭐⭐ ⭐⭐⭐⭐⭐
格式化灵活性 printf printf std::format
依赖 C++ 标准 C89 - C++20
二进制数据表示 unsigned char* BYTE std::span<std::byte>

六、如何选择?

  • 维护老项目:标准 C 库版最稳妥,无需升级工具链。
  • Windows 专用工具:可用 API 版,但现代 C++ 在 Windows 上同样高效且更安全。
  • 新项目(C++17/20 可用) :毫不犹豫选择现代 C++ 版。std::format 和 std::span 显著提升代码质量和维护性。

七、扩展思考

  1. 性能优化 :现代 C++ 版本可以轻松改为一次读取更大块(如 4KB),然后循环调用 HexAscii 处理每 16 字节,减少系统调用次数。
  2. 大文件支持 :当前偏移量使用 uint32_t,最大 4GB。实际生产代码应使用 uint64_t 或 std::streamsize。
  3. 彩色输出:结合 ANSI 转义序列,可以让 ASCII 区域不同类别字符显示不同颜色。
  4. 命令行参数 :现代 C++ 版可轻松扩展 main(int argc, char** argv) 接受用户输入的文件名。

结语

从 C 风格的指针运算到现代 C++ 的类型安全视图,Hex Dump 这个小工具折射出语言进化的光辉。std::span 和 std::format 让我们写出的代码既接近底层,又不失高级语言的表达力。希望本文能帮助你理解三种实现的设计思想,并在实际项目中做出合适的选择。

(完整代码已附于文中,复制即可编译运行。现代 C++ 版需要支持 C++20 的编译器,如 GCC 11+ / Clang 14+ / MSVC 2022。)

相关推荐
HQB202611 小时前
EG1198|屹晶微电子 120V 内置 MOS 异步降压 DC‑DC(高性价比)
嵌入式硬件·硬件工程
果壳science13 小时前
张祥前实验如何获取正电荷
笔记·科技·嵌入式硬件·硬件工程·制造
南山电子nscn13 小时前
中科芯CETC汽车电池管理系统BMS进口芯片国产型号替代指南
单片机·汽车·bms
沉淀的.晴天13 小时前
can总线
stm32
桀人14 小时前
认识AT24C02
stm32·单片机·嵌入式硬件
国科安芯15 小时前
星间激光通信终端CANFD控制网络的时延确定性与辐射可靠性
网络·嵌入式硬件·信息与通信·canfd·低轨卫星·商业航天·抗辐射
一直C15 小时前
嵌入式 Linux 驱动开发学习笔记:内核定时器、platform 总线与 I²C 驱动
linux·驱动开发·嵌入式硬件·学习·imx6ull
传感器与混合集成电路15 小时前
150MHz主频、8路DAC与6路PWM:JLH221360伺服控制SIP解析
嵌入式硬件
comyccccc15 小时前
STM32PID控制板设计全解析学习笔记
单片机·嵌入式硬件
碧海银沙音频科技研究院16 小时前
实现炬芯ATS308X的usb cdc 串口IAP 固件升级功能实现
人工智能·嵌入式硬件·深度学习·算法