繁体   English   中英

如何检查是否已调用所有init函数?

[英]How can I check that all my init functions have been called?

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

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

我有什么聪明才能确保所有这些功能都被调用? 在每个init函数中放置一个宏的行,当你稍后调用check_inited()函数时,如果没有调用所有函数,则向STDOUT发送警告。

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

思考?

以下是我决定使用的解决方案,该线程中有来自几个人的输入

我的目标是确保实际调用所有的init函数。 我想在不维护多个文件的模块列表或计数的情况下执行此操作。 我不能像Nick D建议的那样自动调用它们,因为它们需要按特定顺序调用。

为此,每个模块中包含的宏使用gcc constructor属性将init函数名称添加到全局列表中。

init函数体中包含的另一个宏更新全局列表,以记录该函数实际被调用。

最后,在完成所有的inits之后,在main()调用一个check函数。

笔记:

  1. 我选择将字符串复制到数组中。 这不是绝对必要的,因为传递的函数名称在正常使用中始终是静态字符串。 如果内存很短,你可以只存储一个指向传入的字符串的指针。

  2. 我可重用的实用函数库叫做“nx_lib”。 因此所有'nxl'指定。

  3. 这不是世界上最有效的代码,但它只被称为启动时间,因此对我来说无关紧要。

  4. 需要向每个模块添加两行代码。 如果省略任何一个,检查功能会通知您。

  5. 您可能能够将构造函数设置为静态,这样就无需为其提供在整个项目中唯一的名称。

  6. 这个代码只是经过了轻微的测试而且确实很晚,所以在信任之前请仔细检查。

谢谢至:

pierr向我介绍了constructor属性。

Nick D用于演示##预处理器技巧并给我框架。

tod frye用于基于链接器的聪明方法,可以与许多编译器一起使用。

其他人帮助和分享有用的花絮。

nx_lib_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_func_run_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;
}

solo.c

这是需要初始化的模块的示例

#include "nx_lib_public.h"

NXL_FUNC_RUN_CHECK_ADD(solo_init)
void solo_init(void)
{
  nxl_func_run_check_hit((char *) __func__);

  /* do module initialization here */
}

如果gcc适用于您的项目,则可以使用gcc的扩展名__attribute__((constructor))

#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

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

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

让您的脚本浏览所有.h文件,并创建需要init()编辑的模块名称列表。 然后让脚本检查是否确实在main()中的每个模块上调用了init()。

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

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

我不知道下面看起来有多丑,但无论如何我发布了它:-)

(基本思想是注册函数指针,就像atexit函数那样。
当然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

如果您的单个模块表示“类”实体并具有实例构造函数,则可以使用以下构造:

static inline void init(void) { ... }
static int initialized = 0;
#define INIT if (__predict_false(!initialized)) { init(); initialized = 1; }
struct Foo *
foo_create(void)
{
    INIT;
    ...
}

其中“ __predict_false ”是编译器的分支预测提示。 创建第一个对象时,模块会自动初始化(一次)。

你可以通过链接器部分沿着这些方向做一些事情。 无论何时定义init函数,只需为init函数指针在链接器部分中放置一个指向它的指针。 那么你至少可以找出已经编译了多少个init函数。

如果调用init函数的顺序无关紧要,并且all都具有相同的原型,则可以在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部分只不过是指针。 您可以将其视为模块文件可以添加的可变长度数组。

然后您可以从主要访问节开始和结束地址。 从那里开始

如果init函数都具有相同的protoytpe,你可以迭代这一部分,全部调用它们。

实际上,这是在C中创建自己的静态构造函数系统。

如果你正在做一个大型项目并且你的链接器至少不是这个功能齐全,你可能会遇到问题......

更长的运行时间不是问题

您可以想象为每个模块实现一种“状态机”,其中函数的动作取决于模块所处的状态。该状态可以设置为BEFORE_INIT或INITIALIZED。

例如,假设我们有模块A,其函数为foo和bar。

函数的实际逻辑(即它们实际执行的操作)将被声明为:

void foo_logic();
void bar_logic();

或者无论签名是什么。

然后,模块的实际功能(即,声明为foo()的实际函数)将执行模块状态的运行时检查,并决定该做什么:

void foo() {
       if (module_state == BEFORE_INIT) {
           handle_not_initialized_error();
       }
       foo_logic();
}

对所有功能重复该逻辑。

有几点需要注意:

  1. 这显然会在性能方面产生巨大的惩罚,所以可能不是一个好主意(无论如何我发布,因为你说运行时不是问题)。
  2. 这不是一个真正的状态机,因为只有两个状态使用基本if检查,没有某种智能通用逻辑。
  3. 当你使用单独的线程/任务时,这种“设计模式”很有用,而你调用的函数实际上是使用某种IPC调用的。
  4. 状态机可以很好地用C ++实现,可能值得一读。 可以想象,同样的想法可以在C中用函数指针数组编码,但几乎肯定不值得你花时间。

我可以回答一下我的问题吗?

我的想法是让每个函数将其名称添加到全局函数列表中,例如Nick D的解决方案。

然后我将遍历-gstab生成的符号表,并查找尚未调用的名为init_ *的任何函数。

这是一个嵌入式应用程序,所以我有精灵图像方便闪存。

但是我不喜欢这个想法,因为这意味着我总是要在二进制文件中包含调试信息。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM