我正在编写一个用于嵌入式使用的大型C程序。该程序中的每个模块都有一个init()函数(如构造函数)来设置其静态变量。
问题是我必须记得从main()
调用所有这些init函数。如果我出于某种原因对它们进行了评论,我也必须记得把它们放回去。
我有什么聪明才能确保所有这些功能都被调用?在每个init函数中放置一个宏的行,当你稍后调用check_inited()
函数时,如果没有调用所有函数,则向STDOUT发送警告。
我可以增加一个计数器,但我必须在某处保持正确数量的init函数,这也容易出错。
思想?
以下是我决定采用的解决方案,此线程中有来自多个人的意见
我的目标是确保实际调用所有的init函数。我想要做 这没有维护多个文件中的模块列表或计数。我不能打电话 他们自动为尼克D建议,因为他们需要按照一定的顺序进行调用。
为实现此目的,每个模块中包含的宏都使用gcc constructor
属性
将init函数名称添加到全局列表中。
init函数体中包含的另一个宏更新全局列表以生成一个 请注意,该函数实际上已被调用。
最后,在完成所有操作后,在main()
中调用检查函数。
注意:
我选择将字符串复制到数组中。这不是严格必要的,因为 传递的函数名称在正常使用中始终是静态字符串。如果记忆力很短 你可以只存储一个指向传入的字符串的指针。
我的可重用实用程序函数库称为“nx_lib”。因此所有'nxl'指定。
这不是世界上最有效的代码,但它只被称为启动时间 对我来说没关系。
需要向每个模块添加两行代码。如果省略任何一个, 检查功能会让你知道。
您可以将构造函数设置为静态,这样就无需为其提供在整个项目中唯一的名称。
这个代码只是经过了轻微的测试而且已经很晚了所以请在信任之前仔细检查。
谢谢你:
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 */
}
答案 0 :(得分:3)
如果您的项目可以使用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
答案 1 :(得分:1)
为什么不编写后期处理脚本来为您进行检查。然后在构建过程中运行该脚本......或者更好的是,将其作为测试之一。你正在写测试,对吗? :)
例如,如果每个模块都有一个头文件modX.c.如果你的init()函数的签名是“void init()”......
让您的脚本grep通过所有.h文件,并创建需要init()ed的模块名称列表。然后让脚本检查是否确实在main()中的每个模块上调用了init()。
答案 2 :(得分:1)
Splint(可能还有其他Lint变体)可以对已定义但未调用的函数发出警告。
有趣的是,大多数编译器会警告您未使用的变量,但不会使用未使用的函数。
答案 3 :(得分:1)
我不知道以下看起来有多丑,但无论如何我发布了:-)
(基本思想是注册函数指针,就像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
答案 4 :(得分:1)
如果您的单个模块表示“类”实体并且具有实例构造函数,则可以使用以下构造:
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
”是编译器的分支预测提示。创建第一个对象时,模块会自动初始化(一次)。
答案 5 :(得分:1)
您可以使用链接器部分执行这些操作。无论何时定义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中创建自己的静态构造函数系统。
如果你正在做一个大型项目并且你的链接器至少没有这个全功能,你可能会遇到问题......
答案 6 :(得分:1)
更长的运行时间不是问题
您可以想象为每个模块实现一种“状态机”,其中函数的动作取决于模块所处的状态。该状态可以设置为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();
}
对所有功能重复此逻辑。
有几点需要注意:
答案 7 :(得分:0)
我可以回答一下我的问题吗?
我的想法是让每个函数将其名称添加到全局函数列表中,例如Nick D的解决方案。
然后我将遍历-gstab生成的符号表,并查找尚未调用的名为init_ *的任何函数。
这是一款嵌入式应用程序,因此我可以在闪存中使用精灵图像。
但是我不喜欢这个想法,因为这意味着我总是要在二进制文件中包含调试信息。