资讯详情

利用C语言编写一个十六进制日志转换工具

📅 2026/10/22 23:05:26 | 华诺云谱 👁 阅读
利用C语言编写一个十六进制日志转换工具
前言二进制日志网络包、传感器采样、游戏存档、崩溃转储直接用文本编辑器打开往往是一片乱码。十六进制转储hex dump工具把每个字节显示成两位十六进制并在右侧附上可打印字符既能看结构又能定位偏移。xxd、hexdump、od都能做这件事但自己用 C 写一个是练习文件 I/O、缓冲区处理和边界判断的好题目。需要先说明这个工具是C 语言程序不是 C。它只用到 C 标准库编译时应使用 C 编译器如gcc -stdc17代码里也没有std::、类、模板这些东西。用 C 编译器按 C 模式编译通常也能通过但语义上应按 C 来理解。另一个常见误解是「读文件就该用while (!feof(fp))」。feof只有在一次读取已经失败之后才返回真用它当循环条件会多处理一次旧数据。正确做法是以fread的返回值为准循环结束后再用feof/ferror区分「正常结束」和「读取出错」。本文实现一个按 16 字节一行输出的十六进制查看器支持命令行指定文件、处理最后一行不足 16 字节的情况、区分 EOF 与错误并讨论大文件偏移和可扩展方向。一、输出格式设计目标格式如下每行 16 字节00000000 48 65 6c 6c 6f 2c 20 57 6f 72 6c 64 21 0a 00 00 |Hello, World!...|00000010 ...设计要点偏移列用十六进制至少 8 位文件超过 4 GiB 后自然扩展。用uint64_t保存偏移配合inttypes.h的PRIx64打印避免平台间long宽度差异Windows 的long是 32 位。每行 16 字节第 8 字节后加一个额外空格便于肉眼分组。不足一行时补空格保证右侧 ASCII 列对齐补位不能用00否则会和真实的零字节混淆。ASCII 列只显示可打印字符其余包括\0、换行、制表符统一显示为.。isprint的参数必须是unsigned char提升后的值或EOF传负数会有 UB。字节顺序hex dump 按文件中的原始顺序显示不做大小端解释。大小端是「多字节值如何解释」的问题不是转储工具该做的事。这些规则和xxd的默认格式接近但不必逐字符模仿关键是偏移、字节、ASCII 三列对齐且无歧义。二、按块读取与 EOF 判断核心循环只有几行但边界条件不少// 适用于 C17unsigned char buf[16];size_t n;while ((n fread(buf, 1, sizeof buf, fp)) 0) {/* 处理 n 个字节 */}if (ferror(fp)) {/* 读取出错 */}为什么这样写是对的fread返回本次实际读到的元素个数。这里元素大小是 1 字节所以返回值就是字节数。返回 0 有两种可能读到文件末尾或者发生错误。循环退出后再用ferror判断feof只用于确认「是正常 EOF」。短读返回值小于 16 但大于 0是合法的可能发生在管道、终端或文件末尾循环会继续下一次读到 0 才结束。最后一行不足 16 字节时只处理n个字节其余位置打印空格。不要写// 适用于 C17反面示例不要这样写while (!feof(fp)) {n fread(buf, 1, sizeof buf, fp);/* 如果上次已到 EOF这里 n 为 0buf 里是旧数据 */}feof在「读操作尝试越过末尾」之后才置位所以这种写法会多循环一次把上一次的缓冲区内容重复输出。这是 C 文件 I/O 最经典的错误之一。三、完整实现// 适用于 C17#include ctype.h#include inttypes.h#include stdint.h#include stdio.h#include stdlib.h#define BYTES_PER_LINE 16static void dump_stream(FILE *fp) {unsigned char buf[BYTES_PER_LINE];uint64_t offset 0;size_t n;size_t i;while ((n fread(buf, 1, sizeof buf, fp)) 0) {/* 偏移列 */printf(%08 PRIx64 , offset);/* 十六进制列每 8 字节多一个空格不足补位 */for (i 0; i BYTES_PER_LINE; i) {if (i n) {printf(%02x , (unsigned)buf[i]);} else {printf( );}if (i 7) {putchar( );}}/* ASCII 列不足 16 字节时补空格以保持右侧竖线对齐 */putchar(|);for (i 0; i BYTES_PER_LINE; i) {if (i n) {unsigned char c buf[i];putchar(isprint(c) ? (int)c : .);} else {putchar( );}}putchar(|);putchar(\n);offset (uint64_t)n;}if (ferror(fp)) {perror(read);}}int main(int argc, char **argv) {FILE *fp;int rc EXIT_SUCCESS;if (argc ! 2) {fprintf(stderr, usage: %s FILE\n, argv[0]);return EXIT_FAILURE;}fp fopen(argv[1], rb);if (fp NULL) {perror(argv[1]);return EXIT_FAILURE;}dump_stream(fp);if (fclose(fp) ! 0) {perror(fclose);rc EXIT_FAILURE;}return rc;}逐点说明fopen用rb。在 Windows 上文本模式会把\r\n转换成\n破坏二进制内容b必须显式加。fread(buf, 1, sizeof buf, fp)中元素大小固定为 1避免「元素个数」和「字节数」混淆。printf(%02x , (unsigned)buf[i])%x要求unsigned intunsigned char会提升为int显式转unsigned更清楚。printf(%08 PRIx64 , offset)PRIx64展开为对应uint64_t的格式串宽度 8 是最小宽度超过 8 位会自然变长不会截断。最后一个不足 16 字节的块十六进制列和 ASCII 列都补空格右侧的|仍然对齐。读取错误时perror(read)打印系统错误信息但函数仍返回main的退出码没有反映读取失败。实际工具应让dump_stream返回状态下面讨论。fclose失败也要报告对只读文件来说少见但写工具时应养成检查习惯。四、错误处理与可扩展方向上面的实现适合教学和临时排障工程化还需要补几块返回值传播。让dump_stream返回int0 成功、非 0 失败main据此设置退出码。脚本调用时才能判断是否成功。偏移与长度限制。支持--offset和--length时用fseek定位再按剩余长度循环读取。注意fseek的偏移类型是long大文件要用平台提供的 64 位定位接口如 POSIX 的fseeko标准 C 没有统一的 64 位版本。从标准输入读取。argc 1或参数为-时用stdin此时不能fseek只能顺序读。多文件。循环处理argv[1..argc-1]每个文件输出一段标题。输出到文件。把printf换成fprintf(out, ...)并检查写入错误。避免system()拼接命令。如果工具要调用外部命令不要把文件名直接拼进 shell 字符串否则文件名里的分号、反引号会造成命令注入。能用标准库完成就不要调用外部命令。扩展关键函数注意点指定偏移fseeklong宽度平台相关大文件需 64 位接口限制长度计数器 fread不要读超过剩余长度标准输入stdin不可 seek只能顺序读多文件argv循环每个文件单独打开/关闭输出到文件fopen(..., wb)检查fwrite/fclose返回值常见坑点❌while (!feof(fp)) { fread(...); ... }。✅ 以fread返回值控制循环结束后用ferror区分错误。❌ 用r打开二进制日志Windows 上\r\n被改写。✅ 用rb跨平台都显式指定二进制模式。❌ 最后一行不足 16 字节时把缓冲区剩余旧数据也打印出来。✅ 只处理fread返回的n个字节其余补空格。❌isprint(buf[i])直接传char遇到负值非 ASCII 字节是 UB。✅ 先存为unsigned char再传给isprint。❌ 用int保存文件偏移超过 2 GiB 后溢出。✅ 用uint64_tPRIx64定位大文件用平台的 64 位接口。❌ 用%x打印uint64_t在部分平台上截断或行为未定义。✅ 用PRIx64宏格式串与类型严格匹配。❌ 只检查fopen不检查fread的错误状态和fclose的返回值。✅ 读取后用ferror关闭后检查fclose并把失败反映到退出码。❌ 把文件名拼进system(xxd ...)文件名含空格或分号时出错甚至被注入。✅ 用标准库自己读文件必须调用外部程序时使用参数数组形式的接口并校验输入。总结环节正确做法打开fopen(path, rb)失败用perror读取fread(buf, 1, sizeof buf, fp)按返回值循环结束判断ferror判错feof仅作确认输出偏移 十六进制 补位 ASCII 列偏移类型uint64_tPRIx64关闭检查fclose返回值一个十六进制日志工具的核心就是「按块读、按块打、正确处理最后一块」。把feof的误用、二进制模式、无符号字符和 64 位偏移这四个点处理对代码就具备了实用价值再补上参数解析和错误码就可以作为日常排障工具使用。编译时用 C 编译器并开启警告如-Wall -Wextra能提前发现格式串与类型不匹配的问题。
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑