C 如何检查我的所有init函数是否都已被调用?

C 如何检查我的所有init函数是否都已被调用?,c,embedded,C,Embedded,我正在编写一个用于嵌入式的大型C程序。该程序中的每个模块都有一个init()函数(类似于构造函数)来设置其静态变量 问题是我必须记住从main()调用所有这些init函数。如果我出于某种原因对它们进行了评论,我还必须记得将它们放回原处 有没有什么聪明的方法可以确保所有这些函数都被调用?在每个init函数中放入一个宏,当您稍后调用check_init()函数时,如果没有调用所有函数,则会向STDOUT发送警告 我可以增加一个计数器,但我必须在某个地方保持正确数量的init函数,这也容易出错 想法

我正在编写一个用于嵌入式的大型C程序。该程序中的每个模块都有一个init()函数(类似于构造函数)来设置其静态变量

问题是我必须记住从
main()
调用所有这些init函数。如果我出于某种原因对它们进行了评论,我还必须记得将它们放回原处

有没有什么聪明的方法可以确保所有这些函数都被调用?在每个init函数中放入一个宏,当您稍后调用
check_init()
函数时,如果没有调用所有函数,则会向STDOUT发送警告

我可以增加一个计数器,但我必须在某个地方保持正确数量的init函数,这也容易出错

想法

以下是我决定的解决方案,该线程中有几个人提供了意见

我的目标是确保所有init函数都被实际调用。我想做什么 这不需要维护多个文件中的模块列表或计数。我不能打电话 正如Nick D所建议的那样自动调用它们,因为它们需要按特定顺序调用

为了实现这一点,每个模块中包含的宏都使用gcc
constructor
属性 将init函数名添加到全局列表中

init函数体中包含的另一个宏更新全局列表以生成 请注意,函数实际上已被调用

最后,在所有初始化完成后,在
main()
中调用check函数

注:

  • 我选择将字符串复制到数组中。这并不是绝对必要的,因为 在正常使用中,传递的函数名始终是静态字符串。如果记忆不足 您可以只存储一个指向传入字符串的指针

  • 我的实用程序函数的可重用库称为“nx_lib”。因此,所有的“nxl”名称

  • 这不是世界上最有效的代码,但它只被称为引导时间,因此 对我来说没关系

  • 有两行代码需要添加到每个模块中。如果省略其中一个, 检查功能会让您知道

  • 您可能能够使构造函数成为静态的,这将避免在整个项目中为其指定唯一的名称

  • 这段代码只是经过了轻微的测试,而且已经很晚了,所以请在信任它之前仔细检查

  • 多谢:

    pierr谁向我介绍了
    constructor
    属性

    Nick D演示了##预处理器技巧并给出了框架

    tod frye提供了一种基于链接器的聪明方法,可用于许多编译器

    其他所有人帮助他人并分享有用的花絮

    nx\u lib\u public.h

    这是我的库头文件的相关片段

    #define NX_FUNC_RUN_CHECK_NAME_SIZE 20
    
    typedef struct _nxl_function_element{
      char func[NX_FUNC_RUN_CHECK_NAME_SIZE];
      BOOL called;
    } nxl_function_element;
    
    void nxl_func_run_check_add(char *func_name);
    BOOL nxl_func_run_check(void);
    void nxl_func_run_check_hit(char *func_name);
    
    #define NXL_FUNC_RUN_CHECK_ADD(function_name) \
      void cons_ ## function_name() __attribute__((constructor)); \
      void cons_ ## function_name() { nxl_func_run_check_add(#function_name); }
    
    nxl\u func\u run\u check.c

    这是为添加函数名并在以后检查它们而调用的库代码

    #define MAX_CHECKED_FUNCTIONS 100
    
    static nxl_function_element  m_functions[MAX_CHECKED_FUNCTIONS]; 
    static int                   m_func_cnt = 0; 
    
    
    // call automatically before main runs to register a function name.
    void nxl_func_run_check_add(char *func_name)
    {
      // fail and complain if no more room.
      if (m_func_cnt >= MAX_CHECKED_FUNCTIONS) {
        print ("nxl_func_run_check_add failed, out of space\r\n");
        return; 
      }
    
      strncpy (m_functions[m_func_cnt].func, func_name, 
               NX_FUNC_RUN_CHECK_NAME_SIZE);
    
      m_functions[m_func_cnt].func[NX_FUNC_RUN_CHECK_NAME_SIZE-1] = 0;
    
      m_functions[m_func_cnt++].called = FALSE;
    }
    
    // call from inside the init function
    void nxl_func_run_check_hit(char *func_name)
    {
      int i;
    
      for (i=0; i< m_func_cnt; i++) {
        if (! strncmp(m_functions[i].func, func_name, 
                      NX_FUNC_RUN_CHECK_NAME_SIZE)) {
          m_functions[i].called = TRUE;   
          return;
        }
      }
    
      print("nxl_func_run_check_hit(): error, unregistered function was hit\r\n");
    }
    
    // checks that all registered functions were called
    BOOL nxl_func_run_check(void) {
      int i;
      BOOL success=TRUE;
    
      for (i=0; i< m_func_cnt; i++) {
        if (m_functions[i].called == FALSE) {
          success = FALSE;
          xil_printf("nxl_func_run_check error: %s() not called\r\n", 
                     m_functions[i].func);
         } 
      }
      return success;
    }
    

    为什么不编写一个后处理脚本来为您进行检查呢。然后将该脚本作为构建过程的一部分运行。。。或者更好的是,让它成为你的测试之一。您正在编写测试,对吗?:)

    例如,如果每个模块都有一个头文件modX.c。如果init()函数的签名是“void init()”

    让脚本grep遍历所有的.h文件,并创建需要初始化()的模块名称列表。然后让脚本检查main()中的每个模块是否确实调用了init()。

    Splint(可能还有其他Lint变体)可以对已定义但未调用的函数发出警告


    有趣的是,大多数编译器都会警告您未使用的变量,而不是未使用的函数。

    我不知道下面的内容有多难看,但我还是发布了:-)

    (基本思想是注册函数指针,就像函数所做的一样。
    当然,
    atexit
    实现是不同的)

    在主模块中,我们可以有如下内容:

    typedef int (*function_t)(void);
    
    static function_t  vfunctions[100]; // we can store max 100 function pointers
    static int         vcnt = 0; // count the registered function pointers
    
    int add2init(function_t f)
    {
      // todo: error checks
      vfunctions[vcnt++] = f;
      return 0;
    }
    ...
    
    int main(void) {
     ...
     // iterate vfunctions[] and call the functions
     ...
    }
    
    。。。在其他模块中:

    typedef int (*function_t)(void);
    extern int add2init(function_t f);
    #define M_add2init(function_name)  static int int_ ## function_name = add2init(function_name)
    
    int foo(void)
    {
       printf("foo\n");
       return 0;
    }
    M_add2init(foo); // <--- register foo function
    
    typedef int(*函数(无效);
    外部int add2init(函数f);
    #定义M_add2init(函数名称)静态int_35;#函数名称=add2init(函数名称)
    int foo(无效)
    {
    printf(“foo\n”);
    返回0;
    }
    
    M_add2init(foo);// 如果单个模块表示“类”实体并具有实例构造函数,则可以使用以下构造:

    static inline void init(void) { ... }
    static int initialized = 0;
    #define INIT if (__predict_false(!initialized)) { init(); initialized = 1; }
    struct Foo *
    foo_create(void)
    {
        INIT;
        ...
    }
    
    其中“
    \uuuupredict\ufalse
    ”是编译器的分支预测提示。创建第一个对象时,模块将自动初始化(一次)。

    如果gcc适合您的项目,您可以使用的扩展名
    \uuuuuuuu属性((构造函数))

    #include <stdio.h>
    void func1() __attribute__((constructor));
    void func2() __attribute__((constructor));
    
    void func1()
    {
        printf("%s\n",__func__);
    }
    
    void func2()
    {
        printf("%s\n",__func__);
    }
    
    int main()
    {
        printf("main\n");
        return 0;
    }
    
    //the output
    func2
    func1
    main
    
    #包括
    void func1()_属性__((构造函数));
    void func2()_属性__((构造函数));
    void func1()
    {
    printf(“%s\n”,函数);
    }
    void func2()
    {
    printf(“%s\n”,函数);
    }
    int main()
    {
    printf(“主\n”);
    返回0;
    }
    //输出
    功能2
    职能1
    主要的
    
    您可以使用链接器部分按照这些思路进行操作。无论何时定义init函数,都要在链接器部分中放置一个指向它的指针,仅用于init函数指针。然后,您至少可以知道编译了多少init函数

    如果调用init函数的顺序不重要,并且所有函数都具有相同的原型,那么您可以从main在一个循环中调用它们

    确切的细节我记不清了,但它是这样工作的: 在模块文件中

    //this is the syntax in GCC..(or would be if the underscores came through in this text editor)
    initFuncPtr thisInit __attribute((section(.myinits)))__= &moduleInit;
    
    void moduleInit(void)
    {
    // so init here
    }
    
    这将在.myinits节中放置一个指向模块init函数的指针,但将代码保留在.code节中。因此.myinits部分只是指针。您可以将其视为模块文件可以添加到的可变长度数组

    那你就可以进入门派了
    //this is the syntax in GCC..(or would be if the underscores came through in this text editor)
    initFuncPtr thisInit __attribute((section(.myinits)))__= &moduleInit;
    
    void moduleInit(void)
    {
    // so init here
    }
    
    void foo_logic();
    void bar_logic();
    
    void foo() {
           if (module_state == BEFORE_INIT) {
               handle_not_initialized_error();
           }
           foo_logic();
    }