【问题标题】:Tracing of function calls in C跟踪 C 中的函数调用
【发布时间】:2015-03-12 00:40:27
【问题描述】:

我正在为用 C 语言编写的自动化系统开发一些模块,我需要使用硬件执行大量工作。而且我认为没有简单的方法(如传统方法)来调试事物而不是跟踪日志。所以我正在寻找一种记录函数调用的好习惯。至少是调用顺序和返回值。

它在应用程序中的执行方式非常简单,实际上用不相关的结构污染了代码,例如

int function (int param){
   if(trace_level & LOG_FCALLS){
      writelog("Entering function()");
   }

   /* something useful */

   if(trace_level & LOG_FCALLS){
      writelog("Exit from function()=%d", ret);
   }
}

我决定使用一个宏来完成所有的脏活。现在看起来像这样

#define LOG_E(fn) const char *__fname=fn;  printf("LOG: Entry to %s\n",__fname)
#define return(ret)     printf("LOG: Exit from %s()=%d\n",__fname,ret)

int testFunc(){
   LOG_E("testFunc");

   /*do useful things */

   return(ret);
}

我发现这段代码有问题

  1. 我正在重写 return 语句,它需要一直写 return(ret) 而不是 return ret。这个问题很容易忘记。

  2. 我正在宏中定义字符串变量。我知道 C99 中存在 __func__ 宏,但不幸的是,我的编译器不支持此宏或任何其他相关宏。

  3. 如何记录函数参数的值?

我很确定这不是一个新问题,我也不是第一个遇到它的人。我也知道 AOP 的事情,但是代码检测对于我的系统来说不是可接受的解决方案,而且我还没有发现用我的编译器来做这件事的任何可能性。

所以我正在寻找如何以最优雅的方式实现跟踪的好主意。

我的环境: 旧代码、C、Watcom 10.x、实时操作系统

【问题讨论】:

  • 你实际上根本没有回来的事实呢?即您的 return (ret) 被宏吃掉并变成了 printf 然后该函数没有要执行的返回语句。你没有在宏中返回。
  • Watcom C 支持__FUNCTION__ 宏。
  • 以防万一您通过#ifdef 测试了__func__ 的存在:它不是一个宏,而是一个静态数组(就像它在一个开头用static cosnt char __func__[] = "..."; 声明一样函数)。
  • @ArhujShankar 是的,你是对的。应该有return语句
  • @user4419802 是的,至少谷歌是这么说的。但是在版本 11 中,我有 10.6

标签: c debugging design-patterns trace


【解决方案1】:

执行此操作的超级认真、专业的方法是制作一个单独的调试/测试项目,该项目与生产代码完全分开。它是这样的:

  • 确保对生产代码进行备份/提交。
  • 在硬盘上制作生产代码的硬拷贝。这将成为您的测试项目。
  • 创建一个 .txt 日志文件,在其中写入要记录的每个函数的完整签名,例如:

    int function (int param)
    float function2 (void)
    ...
    
  • 创建一个小的 PC 程序/脚本,将上述 .txt 文件作为输入,然后在源代码中搜索匹配的函数定义行。然后,PC 程序将基于原始代码生成一个新的 .c 文件,其中它在 { 之后和 } 之前将调试日志代码插入到所需函数中。制作这样一个程序需要花费您几个小时的时间。
  • 使用脚本创建的修改后的源代码链接您的测试项目。

上述方法是我自己在任务关键型软件上执行的方法,其中您有安全标准(MISRA、代码覆盖率等)的要求,即不允许未在最终产品中执行的代码。

此方法可确保生产代码的完整性,并保证测试/调试代码不会将意外错误添加到程序中。它还将编译开关等的混乱排除在生产代码之外。并且您的项目中不会保留任何您忘记删除的旧调试代码(否则我总是会忘记我程序中某处的一些调试代码的 sn-p)。

【讨论】:

  • > 生产代码的硬拷贝<. svn scm>
  • @myaut 这就是我说“确保备份/commit”的原因。提交是硬拷贝。
  • 这是自动化的一个很好的例子,我会记下。当我们需要来自生产系统的跟踪日志时,这也是一种情况。例如,对于可以在真实环境中重现的错误。
  • 这就是我用非常有限的调试器为我的硬件项目所做的。我用 C# 编写了我的“调试代码插入器”程序。它不到 100 行代码。
  • @Hooch 确实,编写这样的程序对程序员来说应该不是什么挑战,你可以用任何你想要的语言来编写。程序员有时会忘记他们能够通过编程来解决他们的编程问题:)
【解决方案2】:
#if defined(DEBUG_BUILD)
#  define START_FUNCTION if(trace_level & LOG_FCALLS){writelog("+++ %s()", __func__)
   }
#  define END_FUNCTION if(trace_level & LOG_FCALLS){writelog("--- %s()", __func__)
#elif defined (TIMING_BUILD)
#  define START_FUNCTION  WRITE_TIMED_LOG("+++")
#  define END_FUNCTION WRITE_TIMED_LOG("---")
#else
#  define START_FUNCTION
#  define END_FUNCTION
#endif
int function (int param){
   START_FUNCTION;
   ...
   if(error_occurred) {
     END_FUNCTION;
     return errror_code;
   }
   ...
   END_FUNCTION;
   return 42;
}

【讨论】:

  • 在我看来是一个选择。因为我不想破坏 return 关键字。但令人不安的是,我们需要在每次返回之前写END_FUNCTION
【解决方案3】:

您可以自定义编译器来处理它。如果您使用GCC 进行编译,则可以使用MELT(自定义您的gcc 编译器)。

也许您可以自定义 openwatcom(或支付一些 OpenWatcom 专家来做这件事)...

【讨论】:

    【解决方案4】:

    这适用于 MS Visual C。对于不同的数据类型(或没有),您将需要不同版本的 return 宏。

    #include <stdio.h>
    
    #define TRACING
    
    #ifdef TRACING
    #define LOG_E printf("Func: %s\n", __FUNCTION__);
    #define LOG_R printf("Exit: %s\n", __FUNCTION__);
    #define LOG_I(ival) printf("Exit: %s %d\n", __FUNCTION__, ival);
    
    #else
    #define LOG_E
    #define LOG_R
    #define LOG_I(ival)
    #endif
    
    int main(void){
        int retval = 0;
        LOG_E
        printf("Hello world!\n");
        LOG_I(retval)
        return retval;
    
    }
    

    输出:

    Func: main
    Hello world!
    Exit: main 0
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-02-29
      • 1970-01-01
      • 1970-01-01
      • 2018-10-26
      • 1970-01-01
      • 2014-05-23
      • 1970-01-01
      • 2018-07-13
      相关资源
      最近更新 更多