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::formatstd::span 显著提升代码质量和维护性。

七、扩展思考

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

结语

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

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

相关推荐
天月风沙2 小时前
Betaflight飞控、树莓派RP2350B主控编译教程
linux·单片机·嵌入式硬件·mcu·无人机·树莓派
济6172 小时前
FreeRTOS 通信任务设计(3)---基于状态机的串口协议帧解析
stm32·嵌入式·freertos
三道渊2 小时前
嵌入式系统电源设计指南
单片机·嵌入式硬件
水云桐程序员3 小时前
单片机:定时器/PWM 配置 - 呼吸灯效果
单片机·嵌入式硬件·mongodb
WeeJot嵌入式3 小时前
【GPIO】按键控制小灯
单片机·嵌入式硬件·mongodb
水云桐程序员3 小时前
单片机:新建第一个工程,点亮LED
单片机·嵌入式硬件
华芯微特SYNWIT3 小时前
SWM221 Cortex-M0系列MCU环境配置
单片机·嵌入式硬件
普中科技3 小时前
【普中 51-Ai8051 开发攻略】-- 第 12 章 LED 点阵实验-显示字符
单片机·嵌入式硬件·开发板·led点阵屏·普中科技·ai8051u·aicube
【ql君】qlexcel3 小时前
可跑在STM32上的EtherCAT主机协议栈
stm32·soem·ethercat·igh·协议栈