C++实现一个16进制打印Hexdump
hexdump
本文内容参考博主双笙子佯谬
我们将会实现一个如下效果的16进制展示器。
❯ ./hexdump -f CMakeCache.txt
00000000 23 20 54 68 69 73 20 69 73 20 74 68 65 20 43 4d |# This is the CM|
00000010 61 6b 65 43 61 63 68 65 20 66 69 6c 65 2e 0a 23 |akeCache file..#|
00000020 20 46 6f 72 20 62 75 69 6c 64 20 69 6e 20 64 69 | For build in di|
00000030 72 65 63 74 6f 72 79 3a 20 2f 68 6f 6d 65 2f 76 |rectory: /home/v|
00000040 69 76 65 6b 2f 43 6f 64 65 73 2f 73 6f 6d 65 5f |ivek/Codes/some_|
00000050 69 6d 70 6c 65 6d 65 6e 74 61 74 69 6f 6e 73 2f |implementations/|
00000060 68 65 78 64 75 6d 70 2f 6f 75 74 2f 62 75 69 6c |hexdump/out/buil|
00000070 64 2f 47 43 43 20 31 35 2e 32 2e 31 20 78 38 36 |d/GCC 15.2.1 x86|
00000080 5f 36 34 2d 70 63 2d 6c 69 6e 75 78 2d 67 6e 75 |_64-pc-linux-gnu|
00000090 0a 23 20 49 74 20 77 61 73 20 67 65 6e 65 72 61 |.# It was genera|
000000a0 74 65 64 20 62 79 20 43 4d 61 6b 65 3a 20 2f 75 |ted by CMake: /u|
000000b0 73 72 2f 62 69 6e 2f 63 6d 61 6b 65 0a 23 20 59 |sr/bin/cmake.# Y|
000000c0 6f 75 20 63 61 6e 20 65 64 69 74 20 74 68 69 73 |ou can edit this|
000000d0 20 66 69 6c 65 20 74 6f 20 63 68 61 6e 67 65 20 | file to change |
000000e0 76 61 6c 75 65 73 20 66 6f 75 6e 64 20 61 6e 64 |values found and|
000000f0 20 75 73 65 64 20 62 79 20 63 6d 61 6b 65 2e 0a | used by cmake..|
00000100 23 20 49 66 20 79 6f 75 20 64 6f 20 6e 6f 74 20 |# If you do not |
00000110 77 61 6e 74 20 74 6f 20 63 68 61 6e 67 65 20 61 |want to change *a|
实现思路
根据上述的形式,不难看出,需要先输出一个地址,指定宽度,然后输出每个字节的16进制数值(一行可以制订不同的数目,比如8/16等),最后打印出每个字节对应的ASCII码(在导入库使用的时候,如果每个元素的不是标准的uint8_t而是uint16_t甚至更大,我们就不打印后面的ASCII码).
template <typename Range>
requires std::ranges::input_range<Range>
inline void hexdump(Range const &s, std::size_t one_line_num = 16) {
using type = std::ranges::range_value_t<Range>;
using UnsignedType = std::make_unsigned_t<type>;
uint32_t addr = 0;
std::vector<char> saved;
for (auto chunk: s | std::views::chunk(one_line_num)) {
std::cout << std::setw(8) << std::setfill('0') << std::hex << addr;
for (auto const &c: chunk) {
std::cout << " " << std::right << std::setw(2 * sizeof(type))
<< std::hex << std::setfill('0')
<< static_cast<unsigned long long>(
static_cast<UnsignedType>(c));
++addr;
saved.push_back(c);
}
if constexpr (sizeof(type) == 1 && std::is_convertible_v<type, char>) {
if (addr % one_line_num != 0) {
for (std::size_t i = 0;
i < (one_line_num - addr % one_line_num) * 3; ++i) {
std::cout << " ";
}
}
std::cout << " |";
for (auto const &c0: saved) {
char c = static_cast<unsigned char>(c0);
if (std::isprint(c)) {
std::cout << c;
} else {
std::cout << ".";
}
}
std::cout << "|";
}
std::cout << "\n";
saved.clear();
}
}
函数前面和约束
template <typename Range>
requires std::ranges::input_range<Range>
inline void hexdump(Range const &s, std::size_t one_line_num = 16)
我们函数接受两个参数,一个是满足ranges输入输出的可遍历的范围,one_line_num参数指定每行的个数。
类型推导
using type = std::ranges::range_value_t<Range>; // 获取元素类型
using UnsignedType = std::make_unsigned_t<type>; // 获取无符号版本
range_value_t:提取范围的元素类型(如char、int等)make_unsigned_t:转为无符号类型(如char→unsigned char)
核心处理逻辑
uint32_t addr = 0;
std::vector<char> saved; // 保存当前行的字符用于ASCII显示
// 将数据按 one_line_num 分块
for (auto chunk: s | std::views::chunk(one_line_num)) {
// 打印地址
std::cout << std::setw(8) << std::setfill('0') << std::hex << addr;
// 处理每个块中的元素
for (auto const &c: chunk) {
// 打印十六进制值
std::cout << " " << std::right << std::setw(2 * sizeof(type))
<< std::hex << std::setfill('0')
<< static_cast<unsigned long long>(
static_cast<UnsignedType>(c));
++addr; // 地址递增
saved.push_back(c); // 保存字符用于ASCII显示
}
setw(8):地址占8位宽度setfill('0'):用0填充hex:十六进制格式setw(2 * sizeof(type)):每个值占的宽度(如 char 占2位,int 占8位)
ASCII显示
if constexpr (sizeof(type) == 1 && std::is_convertible_v<type, char>) {
// 如果元素是1字节且可转换为char,显示ASCII
if (addr % one_line_num != 0) {
// 填充空格对齐
for (std::size_t i = 0;
i < (one_line_num - addr % one_line_num) * 3; ++i) {
std::cout << " ";
}
}
std::cout << " |";
for (auto const &c0: saved) {
char c = static_cast<unsigned char>(c0);
std::cout << (std::isprint(c) ? c : '.'); // 可打印字符显示,否则显示.
}
std::cout << "|";
}
流程图解
数据: "Hello World!"
地址 十六进制值 ASCII
0000: 48 65 6c 6c 6f 20 57 6f 72 6c 64 21 |Hello World!|
↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑
H e l l o W o r l d !
分块过程:
原始数据: [H][e][l][l][o][ ][W][o][r][l][d][!]
chunk1(16) → 全部在一个块中
测试
#include "hexdump.hpp"
#include <cstring>
#include <filesystem>
#include <fstream>
#include <iostream>
#include <iterator>
#include <string>
void print_usage() {
std::cerr << "Options:\n"
<< "-f <file> choose the file path\n"
<< "-x <size> choose the one_line_size\n";
}
template <typename It>
struct Iter {
It b, e;
It begin() {
return b;
}
It end() {
return e;
}
It begin() const {
return b;
}
It end() const {
return e;
}
};
int main(int argc, char **argv) {
std::string context;
std::ifstream file;
std::size_t one_line_size = 16;
for (std::size_t i = 1; i < argc; ++i) {
std::string_view arg = argv[i];
if (arg == "-h") {
print_usage();
std::exit(0);
} else if (arg == "-x") {
if (++i >= argc) {
throw std::runtime_error("-x requires an argument");
}
int size = std::stoi(argv[i]);
if (!size % 8) {
throw std::runtime_error("wrong num of one_line_size");
}
one_line_size = size;
} else if (arg == "-f") {
if (++i >= argc) {
throw std::runtime_error("-f requires an argument");
}
auto path = std::filesystem::path(std::string(argv[i]));
file.open(path);
if (!file.good()) {
std::cerr << std::strerror(errno) << " (" << errno << ") "
<< path << std::endl;
exit(0);
}
} else {
throw std::runtime_error(std::string("Unknown option: ") +
std::string(arg));
}
}
if (!file.is_open()) {
std::istreambuf_iterator<char> begin{std::cin}, end{};
hex::hexdump(Iter{begin, end}, one_line_size);
} else {
std::istreambuf_iterator<char> begin{file}, end{};
hex::hexdump(Iter{begin, end}, one_line_size);
}
}
参数部分
for (std::size_t i = 1; i < argc; ++i) {
std::string_view arg = argv[i];
......
}
这部分是为了能够获取命令行参数读取文件或者指定,每行个数。
运行
if (!file.is_open()) {
std::istreambuf_iterator<char> begin{std::cin}, end{};
hex::hexdump(Iter{begin, end}, one_line_size);
} else {
std::istreambuf_iterator<char> begin{file}, end{};
hex::hexdump(Iter{begin, end}, one_line_size);
}
如果文件未打开,我们就使用文件的输入范围进行hex显示,否则从标准输入读取内容进行hex展示。
这里有一个结构体
template <typename It>
struct Iter {
It b, e;
It begin() {
return b;
}
It end() {
return e;
}
It begin() const {
return b;
}
It end() const {
return e;
}
};
Iter 结构体的核心作用是把两个迭代器包装成一个合法的 range,这样就能用在需要 range 的地方(比如你的 hexdump 函数)。
// 迭代器对本身不是 range
std::istreambuf_iterator<char> begin{file}, end{};
// 不能直接传入 hexdump
hex::hexdump(begin, end); // 错误!hexdump 需要 range,不是两个迭代器
// 用 Iter 包装后就可以了
hex::hexdump(Iter{begin, end}); // Iter 满足 range 概念!
之所以没有直接把文件的内容读取到std::string,一次性处理,是为了流式处理,防止大文件内存不足。
// 一次性读取
std::istringstream ss;
ss << file.rdbuf();
hex::hexdump(file,one_line_num);
// 流式处理
std::istreambuf_iterator<char> begin{file}, end{};
如此,既可以支持文件读取,也可以标准输入读取
❯ ./hexdump -f ../../../CMakeLists.txt
00000000 63 6d 61 6b 65 5f 6d 69 6e 69 6d 75 6d 5f 72 65 |cmake_minimum_re|
00000010 71 75 69 72 65 64 28 56 45 52 53 49 4f 4e 20 33 |quired(VERSION 3|
00000020 2e 31 30 2e 30 29 0a 70 72 6f 6a 65 63 74 28 68 |.10.0).project(h|
00000030 65 78 64 75 6d 70 20 56 45 52 53 49 4f 4e 20 30 |exdump VERSION 0|
00000040 2e 31 2e 30 20 4c 41 4e 47 55 41 47 45 53 20 43 |.1.0 LANGUAGES C|
00000050 20 43 58 58 29 0a 0a 73 65 74 28 43 4d 41 4b 45 | CXX)..set(CMAKE|
00000060 5f 43 58 58 5f 53 54 41 4e 44 41 52 44 20 32 33 |_CXX_STANDARD 23|
00000070 29 0a 0a 61 64 64 5f 65 78 65 63 75 74 61 62 6c |)..add_executabl|
00000080 65 28 68 65 78 64 75 6d 70 20 6d 61 69 6e 2e 63 |e(hexdump main.c|
00000090 70 70 29 0a 0a |pp)..|
❯ ./hexdump
asdasdasdasdasdsa
00000000 61 73 64 61 73 64 61 73 64 61 73 64 61 73 64 73 |asdasdasdasdasds|
00000010 61 0a12349813267948612379461328467134
31 32 33 34 39 38 31 33 32 36 37 39 34 38 |a.12349813267948|
00000020 36 31 32 33 37 39 34 36 31 33 32 38 34 36 37 31 |6123794613284671|
00000030 33 34 0ajsahdfgajhsdasjigdasjid
6a 73 61 68 64 66 67 61 6a 68 73 64 61 |34.jsahdfgajhsda|
00000040 73 6a 69 67 64 61 73 6a 69 64 0a12345678
31 32 33 34 35 |sjigdasjid.12345|
00000050 36 37 38 0a123456789123456
31 32 33 34 35 36 37 38 39 31 32 33 |678.123456789123|
00000060 34 35 36 0a123432546546
31 32 33 34 33 32 35 34 36 35 34 36 |456.123432546546|
00000070 0a2345325235235235
32 33 34 35 33 32 35 32 33 35 32 33 35 32 33 |.234532523523523|
00000080 35 0a

浙公网安备 33010602011771号