C 语言

关注公众号 jb51net

关闭
首页 > 软件编程 > C 语言 > C语言十六进制日志转换

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

作者:小范好好学习

这篇文章主要为大家详细介绍了如何利用C语言编写一个十六进制日志转换工具,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下

当然可以!下面是对您提供的 C 程序 逐行添加详细注释,并附上整体功能说明,帮助你彻底理解每一部分的作用。

整体功能总结

该程序用于 处理带时间戳的十六进制日志文件(如串口/调试日志),自动跳过时间戳部分(如 15:00:30.625 - ),仅提取其后的合法十六进制字节(如 AA 55 36...),并将其转换为 C 语言风格的十六进制格式(如 0xAA 0x55 0x36...),输出到一个 .dat 文本文件中。

带详细注释的完整代码

// 引入标准库:输入输出、内存操作、字符串处理、字符类型判断
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>

// 函数:将单个十六进制字符('0'-'9', 'A'-'F', 'a'-'f')转换为对应的数值(0-15)
// 例如:'A' → 10, 'f' → 15, '3' → 3
// 若输入非法字符(如 'G', 'Z', '@'),返回 -1
int hexCharToValue(char c) {
    if (c >= '0' && c <= '9') return c - '0';          // 数字字符转数值
    if (c >= 'a' && c <= 'f') return c - 'a' + 10;     // 小写 a-f 转 10-15
    if (c >= 'A' && c <= 'F') return c - 'A' + 10;     // 大写 A-F 转 10-15
    return -1;                                         // 非法字符
}

// 主函数
int main() {
    // 定义文件指针,用于读取输入和写入输出
    FILE *inputFile, *outputFile;

    // 存储用户输入的文件名(不含扩展名)
    char inputFileName[256], outputFileName[256];

    // 存储完整的带扩展名的文件路径(预留一点额外空间防溢出)
    char fullInputFileName[260], fullOutputFileName[260];

    // 用于存储从输入文件读取的每一行内容
    char line[4096];  // 支持非常长的日志行

    // 提示用户输入输入文件名(不带 .txt)
    printf("请输入输入文件名(不含.txt): ");
    scanf("%s", inputFileName);  // 注意:不能包含空格

    // 提示用户输入输出文件名(不带 .dat)
    printf("请输入输出文件名(不含.dat): ");
    scanf("%s", outputFileName);

    // 构造完整的输入文件名:拼接 ".txt"
    strcpy(fullInputFileName, inputFileName);        // 先复制基础名
    strcat(fullInputFileName, ".txt");               // 再追加扩展名

    // 构造完整的输出文件名:拼接 ".dat"
    strcpy(fullOutputFileName, outputFileName);
    strcat(fullOutputFileName, ".dat");

    // 以文本模式打开输入文件(只读)
    inputFile = fopen(fullInputFileName, "r");
    if (!inputFile) {  // 如果打开失败(文件不存在/权限问题等)
        perror("无法打开输入文件");  // 打印系统错误信息(如 "No such file")
        return 1;      // 退出程序,返回错误码
    }

    // 以文本模式创建/覆盖输出文件(只写)
    outputFile = fopen(fullOutputFileName, "w");
    if (!outputFile) {
        perror("无法创建输出文件");
        fclose(inputFile);  // 关闭已打开的输入文件,避免资源泄漏
        return 1;
    }

    // 行号计数器(可用于调试或日志)
    int lineNumber = 0;

    // 循环读取输入文件的每一行,直到文件结束
    while (fgets(line, sizeof(line), inputFile)) {
        lineNumber++;

        // 在当前行中查找分隔符 " - "
        // strstr 返回首次出现 " - " 的位置指针,若未找到则返回 NULL
        char *pos = strstr(line, " - ");
        if (!pos) {
            // 如果当前行没有 " - ",说明不是标准日志格式,跳过该行
            continue;
        }

        // 计算十六进制数据开始的位置:跳过 " - "(共3个字符:空格、减号、空格)
        char *hexStart = pos + 3;

        // 使用 strtok 按空白字符(空格、制表符、换行等)分割 hexStart 中的内容
        // 第一次调用传入字符串,后续传入 NULL
        char *token = strtok(hexStart, " \t\r\n");
        int first = 1;  // 标记是否是当前行的第一个有效十六进制值(用于控制空格)

        // 循环处理每一个分割出的 token(即可能的十六进制字节字符串)
        while (token) {
            // 只有当 token 长度恰好为2,且两个字符都是合法十六进制字符时才处理
            if (strlen(token) == 2 && 
                hexCharToValue(token[0]) != -1 && 
                hexCharToValue(token[1]) != -1) {

                // 将两个十六进制字符组合成一个字节:
                // 高4位 = 第一个字符的值 << 4,低4位 = 第二个字符的值
                int byteVal = (hexCharToValue(token[0]) << 4) | hexCharToValue(token[1]);

                // 如果不是第一个值,先输出一个空格(保证格式:0xAA 0x55 ...)
                if (!first) {
                    fprintf(outputFile, " ");
                }

                // 以 "0xXX" 格式写入输出文件(%02X 表示两位大写十六进制,不足补0)
                fprintf(outputFile, "0x%02X", byteVal);

                first = 0;  // 标记已输出至少一个值
            }
            // 继续获取下一个 token(strtok 内部维护状态)
            token = strtok(NULL, " \t\r\n");
        }

        // 如果当前行成功解析出至少一个十六进制值,则在行尾添加换行符
        if (!first) {
            fprintf(outputFile, "\n");
        }
    }

    // 关闭文件,释放资源
    fclose(inputFile);
    fclose(outputFile);

    // 打印成功提示
    printf(" 处理完成!已严格丢弃所有时间戳,仅保留 ' - ' 后的十六进制数据。\n");
    printf("结果保存至: %s\n", fullOutputFileName);

    return 0;  // 正常退出
}

工具准备清单

最后鼓励

你现在的状态非常好——不是从抽象语法开始学,而是从一个真实问题出发。这种“做中学”的方式效率最高!

记住:每一个资深程序员,都曾卡在 fopen 返回 NULL 的问题上。

如果你愿意,我可以:

这是一份为你量身定制的 「以十六进制日志解析器」为项目驱动的C语言学习路线图。整个路线紧密围绕你的代码展开,逐层深入,每个阶段都有明确的实践目标。

阶段一:夯实C语言基础(1-2周)

章节1.1:开发环境搭建与调试入门

目标:能编译运行你的代码,并理解每一步执行过程。

知识点:GCC编译命令、调试器GDB基础、断点、单步执行、观察变量

实践任务

关键技能:学会用调试器验证你的假设,这是最高效的学习方式

章节1.2:C程序结构解剖 —— 你的代码是第一课

目标:彻底理解代码骨架,能复述每行作用。

知识点#include预处理器、main函数签名、返回值意义、注释规范

实践任务

关联代码:你代码中的#include、函数定义、返回值

章节1.3:数据类型与变量 —— 内存的抽象

目标:理解你代码中每个变量的内存布局和大小。

知识点intchar、数组的本质、sizeof运算符

实践任务

关联你的代码char line[4096]int byteValFILE *

章节1.4:运算符与表达式 —— 位运算专项

目标:亲手重写并理解十六进制转换的核心逻辑。

知识点:位移<<、按位或|、算术转换、运算符优先级

实践任务

拆解表达式:int byteVal = (hexCharToValue(token[0]) << 4) | hexCharToValue(token[1]);

token[0] = 'A', token[1] = 'F'时,在纸上画出每一步的位变化

重写hexCharToValue函数,用switch-case代替if链(理解不同实现)

手动实现一个decCharToValue函数,转换十进制字符'0'-'9'

核心洞察:位运算是C高效处理底层数据的精髓

章节1.5:控制流 —— 代码的决策逻辑

目标:画出你代码的执行流程图。

知识点if-else链、while循环、continuebreak

实践任务

关联代码if (!inputFile)while (token)if (strlen(token)==2)

阶段二:字符串与内存操作精讲(2-3周)

章节2.1:字符操作深度实践

目标:重写并优化hexCharToValue函数。

知识点:ASCII码表、ctype.h库(isxdigittolower)、字符与数字关系

实践任务

关键理解:字符在C中就是小整数,'A''a'小32

章节2.2:字符串基础与string.h全家桶

目标:从代码出发,掌握所有用到的字符串函数。

知识点strcpystrcatstrstrstrlenstrtok的行为与陷阱

实践任务

必读手册man strtok时特别注意"WARNING"部分

章节2.3:字符串高级操作与内存安全

目标:让你的代码健壮到能处理恶意输入。

知识点:缓冲区溢出、strncpy/snprintfstrnlen、防御性编程

实践任务

核心价值:C程序员的第一职责是保护内存

章节2.4:十六进制与字节流专项

目标:扩展代码,支持更多格式。

知识点:十六进制字符串、字节序、二进制表示

实践任务

关联代码:你的核心转换逻辑是项目基石

章节2.5:缓冲区管理艺术

目标:彻底理解fgets(line, sizeof(line), inputFile)的边界行为。

知识点fgetsgets的区别、缓冲区大小计算、行截断处理

实践任务

关键理解:C中没有"字符串类型",只有字符数组和指针约定

阶段三:文件与I/O系统实战(1-2周)

章节3.1:标准I/O流深度解析

目标:理解printfscanffprintf的底层。

知识点stdin/stdout/stderr、缓冲区机制、格式字符串

实践任务

关联代码:所有I/O函数都是FILE*操作的特例

章节3.2:文件操作API全掌握

目标:用多种方式重写文件读写逻辑。

知识点fopen模式字符串、fclose必要性、perrorstrerror

实践任务

关键习惯每个系统调用后检查返回值

章节3.3:文本处理管线模式

目标:模仿Unix工具链,让程序更通用。

知识点:管道、过滤器模式、行处理架构

实践任务

设计哲学:一个好程序应该能嵌入更大的流程

章节3.4:错误处理与健壮性工程

目标:让你的程序在极端环境下也不崩溃。

知识点:错误码设计、资源清理(RAII模式)、断言assert

实践任务

最佳实践:错误处理代码应占30%以上

阶段四:代码重构与进阶技术(2-3周)

章节4.1:指针进阶 —— 从数组到指针的跃迁

目标:将代码中所有数组操作改为指针操作。

知识点:数组名退化、ptr++、指针算术、const指针

实践任务

核心理解:指针是C的灵魂,也是最大的风险源

章节4.2:动态内存管理

目标:摆脱固定大小缓冲区限制。

知识点malloc/free/reallocsizeof陷阱、内存泄漏

实践任务

关键原则:谁malloc,谁free;配对出现

章节4.3:模块化与接口设计

目标:将代码拆分为可复用的模块。

知识点:头文件.h、实现文件.c、API设计、封装

实践任务

工程意义:这是从"脚本"到"软件"的转变

章节4.4:性能优化实战

目标:处理GB级日志文件速度提升10倍。

知识点:缓冲区大小调优、mmap内存映射、减少系统调用

实践任务

优化信条:先测量,再优化;算法 > 实现 > 编译器

阶段五:项目扩展与工程化(持续进行)

章节5.1:命令行工具化

目标:支持./hexparser -i input.log -o output.dat --format=binary

知识点argc/argvgetopt_long、配置文件

扩展项目

章节5.2:多格式支持

目标:成为通用十六进制转换工具。

扩展项目

章节5.3:跨平台与国际化

目标:在Windows/Linux/macOS无差别运行。

知识点:条件编译_WIN32、Unicode路径、编码转换

实践

章节5.4:测试与持续集成

目标:写出一个"自信"的程序。

知识点:单元测试框架(Check/CUnit)、自动化测试

实践项目

学习进度建议与检查点

周数攻克章节产出物自我检测
11.1-1.5能调试代码,画出流程图不看代码能复述hexCharToValue逻辑
2-32.1-2.5代码无缓冲区溢出隐患valgrind检测零错误
43.1-3.4支持管道操作,错误处理完善输入不存在的文件,输出清晰错误
5-64.1-4.4模块化代码 + Makefilemake test通过所有用例
7+5.x支持命令行参数,处理GB文件处理1GB文件内存稳定 < 10MB

终极挑战项目

用你积累的所有知识,重写本项目并满足

当你完成时,你已经是合格的C语言系统程序员了!

记住,C语言学习的唯一捷径就是亲手敲每一行代码,用调试器看每一个变量的变化。你的这段代码是绝佳的起点,因为它涵盖了C语言90%的核心知识点。坚持按路线图实践,3个月后你会感谢现在的自己!

到此这篇关于利用C语言编写一个十六进制日志转换工具的文章就介绍到这了,更多相关C语言十六进制日志转换内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文