带中断选项的函数的顺序执行

Sequential Execution of functions with break option

我的操作系统中有一个线程,它以固定间隔调用,然后按顺序执行 10-15 个不同函数的列表。每个函数都有一个 return 参数,表示 0(正常)或非 0(错误)。看起来像这样:

while (1) {
    error &= function_1();
    error &= function_2(some_parameter);
    error &= function_3();
    handleError(error);
}

然而,当其中一个函数 return 出错时,立即处理该错误并且不再执行其他函数(单一错误失败)会更好。

对于两个函数,我可以在每个函数之前做一个 if 条件,但是对于 10-15,这会导致很多不必要的 ifs。

为此,我将使用一个按顺序遍历的函数指针数组:

int (*p_functions[3])() = { function_1, function_2, function_3 }
while (1) {
    for (int i = 0; i < 3, i++) {
        error = p_functions[i];
        if (error) {
            handle_error(error);
            break;
        }
    }
}

我的问题是,正如您在第一个示例中看到的那样,我的 function_2() 有一个参数可能由另一个函数预先生成。所以我无法处理具有不同参数的函数。

还有其他方法可以解决吗?或者可能有一些指针投射技巧?我听说肮脏的铸造是一回事?

给定 return 成功时为 0,错误时为 1 的函数,您可以更改现有代码以使用 ||,它具有短路行为并且更接近您想要的无论如何,而不是 &:

while (1) {
    error = error || function_1();
    error = error || function_2(some_parameter);
    error = error || function_3();
    handleError(error);
}

现在,一旦 error 设置为 1,将不会调用其他函数。

至于处理特定的错误,你可以设置函数的变量 return 值根据哪个函数失败移动一定量,然后检查错误函数中的位图。

uint32_t error_map = 0;
while (1) {
    error_map || (error_map |= (function_1()                << 0));
    error_map || (error_map |= (function_2(some_parameter)  << 1));
    error_map || (error_map |= (function_3()                << 2));
    handleError(error_map);
}

然后在handleError:

if (error_map & (1<<0)) {
    // function 1 error
}
if (error_map & (1<<1)) {
    // function 2 error
}
if (error_map & (1<<2)) {
    // function 3 error
}

如果函数可以 return 任何 非零值出错,您可以在单独的变量中捕获该错误代码:

uint32_t error = 0, error_map = 0;
while (1) {
    error_map||(error_map |= (((error = function_1()) != 0)               << 0));
    error_map||(error_map |= (((error = function_2(some_parameter)) != 0) << 1));
    error_map||(error_map |= (((error = function_3()) != 0)               << 2));
    handleError(error, error_map);
}

上面还有一个宏以使其更具可读性:

#define RUN_ON_NO_ERROR(error, error_map, index, call) \
  ((error_map)||((error_map) |= ((((error) = (call)) != 0) << (index))))

uint32_t error = 0, error_map = 0;
while (1) {
    RUN_ON_NO_ERROR(error, error_map, 0, function_1());
    RUN_ON_NO_ERROR(error, error_map, 1, function_2(some_parameter));
    RUN_ON_NO_ERROR(error, error_map, 2, function_3());
    handleError(error, error_map);
}
#define E(e,x)  e = (e ? e : x)
while (1) {
    error = 0;
    E(error, function_1());
    E(error, function_2(some_parameter));
    E(error, function_3());
    handleError(error);
}

还不错;这是编写 SV 测试套件之类的样式;并保留实际错误值。

我见过的一种方法是使用宏,例如下面的 FUNC_RET_IF_ERR

 1 #include <stdio.h>
 2 #include <stdlib.h>
 3   
 4 #define FUNC_RET_IF_ERR(func, ...) \
 5     do {                           \
 6         if (func(__VA_ARGS__) < 0) \
 7             return EXIT_FAILURE;   \   
 8     } while(0)
 9   
10 void print_integer(char *designator, int value)
11 {
12     printf("%s = %d\n", designator, value);
13 }
14  
15 int func_arg_must_be_positive(int i)
16 {
17     if (i < 0)
18         return -1; 
19  
20     return 0;
21 }
22  
23 int main()
24 {
25     print_integer("line", __LINE__);
26  
27     FUNC_RET_IF_ERR(func_arg_must_be_positive, 1); 
28  
29     print_integer("line", __LINE__);
30  
31     FUNC_RET_IF_ERR(func_arg_must_be_positive, -1);
32  
33     print_integer("line", __LINE__);
34 }

这将输出:

$ gcc main.c && ./a.out
line = 25
line = 29

为什么不使用 || 运算符的短路评估来进一步混淆它。

error = function_1() ||
        function_2(some_parameter) ||
        function_3();

但是,处理此类代码的典型模式是使用 goto 到 cleanup/error 处理程序的级联:

error = function_1();
if (error) goto handle_error;

error = function_2(some_parameter)
if (error) goto cleanup_funtion_1;

error = function_3();
if (error) goto cleanup_function_2;

// ... enjoy SUCCESS !
return;

cleanup_function_2:
  // release resource from function_2
cleanup_function_1:
  // release resource from function_1
handle_error:
  handleError(error);
return;

此模式允许安全释放在执行步骤中获取的任何资源(即内存、线程、打开的文件)。 此外,如果可能的话,它允许反转某些操作的结果。

当函数没有占用资源时,模式可以压缩为:

error = function_1();
if (error) goto handle_error;
error = function_2(some_parameter)
if (error) goto handle_error;
error = function_3();
if (error) goto handle_error;
// success
return;

handle_error:
  handleError(error);
  return;

函数指针替代方案是您经常实现调度程序、状态机等的方式。然而,函数指针的问题在于,对象指针不存在像 void* 这样的通用函数指针。

问题的答案是没有具有不同参数的函数。您必须设计适合所有用例的 API。在最简单的形式中,就是 none 让函数接受一个可选的 void 指针参数。

完整示例:

#include <stdio.h>

typedef enum
{
  ERR_NONE,
  ERR_THIS,
  ERR_THAT,
} err_t;

typedef err_t func_t (void* opt);

static err_t function1 (void* opt);
static err_t function2 (void* opt);
static err_t function3 (void* opt);

static void handle_error(err_t err);

int main (void)
{
  func_t* const functions[] = 
  {
    function1,
    function2,
    function3,
  };
  
  int something_for_function_3;
  void* params[] = 
  {
    &something_for_function_3,
    "hello",
    &something_for_function_3,
  };

  for(size_t i=0; i< sizeof functions/sizeof *functions; i++)
  {
    err_t err = functions[i] (params[i]);
    if(err != ERR_NONE)
    {
      handle_error(err);
      // break; // stop here if necessary
    }
  }
}

static err_t function1 (void* opt)
{
  *(int*)opt = 123;
  puts(__func__);
  return ERR_NONE;
}

static err_t function2 (void* opt)
{
  printf("%s: %s\n", __func__, (const char*)opt);
  return ERR_THIS;
}

static err_t function3 (void* opt)
{
  printf("%s: %d\n", __func__, *(int*)opt);
  return ERR_THAT;
}

static void handle_error(err_t err)
{
  printf("Bad things happened, code: %d\n", err);
}

输出:

function1
function2: hello
Bad things happened, code: 1
function3: 123
Bad things happened, code: 2

您可以使用通用类型指针 void* 作为函数参数,并创建另一个指针数组,该指针数组具有与 func 指针数组相同的索引。然后,对于每次调用函数,您都会检索相应的参数引用,如果不需要参数,则只需为空参数设置 NULL。或者,您可以使用包含指向函数的指针和泛型类型参数的任务结构。这将更加灵活,让您可以根据需要处理尽可能多的功能。在这里我修改了你的代码并测试了它:

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

int function_1(void* param){
    if(param == NULL) printf("function_1 parameter is NULL\n");
    return 1;
}

int function_2(void* param){
    if(param == NULL) printf("function_2 parameter is NULL\n");
    printf("function_2 parameter value is: %d\n", *((int*)param));
    return 0;
}

int function_3(void* param){
    if(param == NULL) printf("function_3 parameter is NULL\n");
    return 1;
}

int main()
{
    int p_f2 = 100;
    void* param_f2 = &p_f2;
    int (*p_functions[3])(void*) = { function_1, function_2, function_3 };
    void* params[] = { NULL, param_f2, NULL };

    while (1) {
        for (int i = 0; i < 3; i++) {
            int error = p_functions[i](params[i]);
            if (error == 1) {
                printf("function %d returned with error\n", i+1);
            }
        }
    }

}

由于 while 循环导致输出很大,我只分享一个循环。输出为:

function_1 parameter is NULL
function 1 returned with error
function_2 parameter value is: 100
function_3 parameter is NULL
function 3 returned with error