设为首页 加入收藏

TOP

将代码中的调试信息输出到日志文件中(一)
2023-07-23 13:27:17 】 浏览:97
Tags:文件中

一、将调试信息输出到屏幕中

1.1 一般写法

我们平常在写代码时,肯定会有一些调试信息的输出:

#include <stdio.h>
#include <stdlib.h>

int main()
{
    char szFileName[] = "test.txt";
    FILE *fp = fopen(szFileName, "r");
    if (fp == NULL)
    {
        // 文件打开失败,提示错误并退出
        printf("open file(%s) error.\n", szFileName);
        exit(0);
    }
    else
    {
        // 文件打开成功,进行相应的文件读/写操作
    }

    return 0;
}

假设当前目录下没有 test.txt 文件。当程序执行到第 7 行时,必然返回 NULL,这时候通过第 11 行的调试信息,我们可以帮助我们精确排查到程序退出的原因:原来是文件打开失败了。

image-20230402170006527

那如果当前目录下存在 test.txt 文件,只是不可读呢?

image-20230402175503739

  • 同样输出了 open file(test.txt) error

在这种情况下如何快速定位文件打开失败的原因呢?我们可以考虑使用 errno

1.2 使用 errno

errno 是记录系统的最后一次错误代码。错误代码是一个 int 型的值,在 errno.h 中定义。

#include <errno.h>	// errno 头文件
#include <string.h>	// strerror 头文件

// 文件打开失败,提示错误并退出
printf("open file(%s) error, errno[%d](%s).\n", szFileName, errno, strerror(errno));

修改后再次运行 main.exe:

image-20230402180115008

如果代码中包含很多的调试信息呢?我们并不能一下子知道这条信息到底是在哪里打印出来的,于是,我们又想,能不能把当前调试信息所在的文件名和源码行位置也打印出来呢,这样不就一目了然了吗。基于此,便有了 1.3 的内容。

1.3 编译器内置宏

ANSI C 标准中有几个标准预定义宏:

  • __LINE__:在源代码中插入当前源代码行号
  • __FILE__:在源文件中插入当前源文件名
  • __FUNCTION_:在源文件中插入当前函数名
  • __DATE__:在源文件中插入当前的编译日期
  • __TIME__:在源文件中插入当前编译时间
  • __STDC__:当要求程序严格遵循ANSI C标准时该标识被赋值为 1
  • __cplusplus:当编写C++程序时该标识符被定义

于是我们这么修改输出语句:

// 文件打开失败,提示错误并退出
printf("[%s][%s:%d] open file(%s) error, errno[%d](%s).\n", 
                                                    __FILE__,
                                                    __FUNCTION__,
                                                    __LINE__,
                                                    szFileName, 
                                                    errno, strerror(errno));

image-20230402181519935

  • 从日志信息中,我们可以精确的获取到:main.c 文件中的 main 函数的第 16 行报错了,错误原因是 Permission denied

相比于之前,确实是能帮助我们精准的定位问题,但是,总不能每次都要写这么长的 printf 吧,有没有偷懒的办法呢?

1.4 使用可变宏输出调试信息

1.4.1 可变宏介绍

用可变参数宏(variadic macros)传递可变参数表,你可能很熟悉在函数中使用可变参数表,如:

void printf(const char* format, ...);

在 1999 年版本的 ISO C 标准中,宏可以像函数一样,定义时可以带有可变参数。宏的语法和函数的语法类似,如下所示:

#define DEBUG(...) printf(__VA_ARGS__)

int main()
{
    int x = 10;
    DEBUG("x = %d\n", x); // 等价于 printf("x = %d\n", x);
    
    return 0;
}
  • 缺省号(...)指可变参数
  • __VA_ARGS__宏用来接收不定数量的参数

这类宏在被调用时,它(这里指缺省号...)被表示成零个或多个符号(包括里面的逗号),一直到右括弧结束为止。当被调用时,在宏体( macro body )中,这些符号序列集合将代替里面的 _VA_ARGS_ 标识符。当宏的调用展开时,实际的参数就传递给 printf 了。

相比于 ISO C 标准,GCC 始终支持复杂的宏,它使用一种不同的语法从而可以使你可以给可变参数一个名字,如同其它参数一样。例如下面的例子:

#define DEBUG(format, args...) printf(format, args)

int main()
{
    int x = 10;
    DEBUG("x = %d\n", x); // 等价于 printf("x = %d\n", x);
    
    return 0;
}
  • 这和上面举的「ISO C」定义的宏例子是完全一样的,但是这么写可读性更强并且更容易进行描述

在标准 C 里,你不能省略可变参数,但是你却可以给它传递一个空的参数。例如,下面的宏调用在「ISO C」里是非法的,因为字符串后面没有逗号:

#define DEBUG(...) printf(__VA_ARGS__)

int main()
{
    DEBUG("hello world.\n"); // 非法调用
}

GCC 在这种情况下可以让你完全的忽略可变参数。在上面的例子中,编译是仍然会有问题,因为宏展开后,里面的字符串后面会有个多余的逗号。为了解决这个问题, GCC 使用了一个特殊的##操作。书写格式为:

#define DEBUG(format, args...) printf(format, ##args)
  • 这里,如果可变参数被忽略或为空,##操作将使预处理器去除掉它前面的那个逗号

  • 如果你在宏调用时,确实提供了一些可变参数,该宏定义也会工作正常,它会把这些可变参数放到逗号的后面

1.4.2 使用可变宏输出调试信息

有了 1.4.1 的基础知识,我们可以这么修改代码:

#define DEBUG(format, args...) \
            printf("[%s][%s:%d] "format"\n", \
                        		__FILE__, \
                        		__FUNCTION__, \
                        		__LINE__, \
                        		##args)

// 文件打开失败,提示错误并退出
DEBUG("open file(%s) error, errno[%d](%s).", szFileName, errno, strerror(errno));
  • 通过可变宏,完美解决了调试信息书写过长的问题

书写过长的问题解决后,又来新问题了,如果我想知道某一调试信息是何时被打印的呢?

下面让我们学习一下 Linux 中与时间相关的内容。

二、Linux 中与时间相关的函数

2.1 表示时间的结构体

通过查看头文件「/usr/include/time.h」和「/usr/include/bits/time.h」,我们可以找到下列四种表示「时间」的结构体

首页 上一页 1 2 3 4 5 下一页 尾页 1/5/5
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
上一篇打印圣诞树 下一篇#FREERTOS的和heap_4内存分配算法

最新文章

热门文章

Hot 文章

Python

C 语言

C++基础

大数据基础

linux编程基础

C/C++面试题目