【问题标题】:Sequential Execution of functions with break option具有中断选项的功能的顺序执行
【发布时间】:2021-11-20 11:35:34
【问题描述】:

我的操作系统中有一个线程,它以固定的时间间隔被调用,然后按顺序执行 10-15 个不同函数的列表。每个函数都有一个返回参数,要么是 0(OK)要么不是 0(错误)。看起来像这样:

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

但是,最好是当其中一个函数返回错误时,立即处理该错误并且不再执行其他函数(单个错误失败)。

对于两个函数,我可以在每个函数之前执行 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() 有一个可能由另一个函数预先生成的参数。所以我无法处理具有不同参数的函数。

还有其他方法可以解决这个问题吗?或者也许有一些指针转换的技巧?我听说肮脏的铸造是一回事?

【问题讨论】:

  • 不幸的是,如果你的函数有不同的参数,真的没有很好的方法来解决这个问题。
  • 作为一种可能的解决方法,如果您不想自己编写代码或在需要时更新代码,可以使用其他工具、程序或脚本为您自动生成代码。
  • “会导致很多不必要的如果。”不...这将导致每个函数调用都需要一个必要的 if 语句。没问题。您可以将其隐藏在宏中,但这会更糟
  • 鉴于 0 = OK 且非零返回值表示错误,我假设您打算使用 |= 而不是 &amp;=,对吧?
  • 是的,很抱歉 :)

标签: c pointers function-pointers


【解决方案1】:

给定一个函数,成功返回 0,错误返回 1,您可以将现有代码更改为使用 ||,它具有短路行为并且更接近您想要执行的操作,而不是 &amp;

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

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

就具体错误的处理而言,可以根据哪个函数失败,设置函数返回值偏移一定量的变量,然后检查错误函数中的位图。

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
}

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

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);
}

【讨论】:

  • 谢谢!与仅使用枚举相比,使用错误图有什么好处?
  • 我的函数将返回一个非 0 的值,但该值指示错误类型。或许可以用地图解决这个问题...
  • 也使用了相反的策略,对于在失败时返回 false 的函数:bool ok = true; ok = ok &amp;&amp; function1(); ok = ok &amp;&amp; function2(); ...
  • error_map || error_map |= ((error = function_x()) != 0) &lt;&lt; x 如果感兴趣的话,甚至会保留一个特定的错误代码......
  • error |= function1() 会不会相同甚至更短?
【解决方案2】:
#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 测试套件之类的样式;并保留实际的错误值。

【讨论】:

  • 很酷,谢谢!我喜欢这两种方法并且可能会将它们结合起来,所以我的宏会说:E(e,x) e = e || x
  • 提出“秘密宏语言”通常不是一个好主意,所以我不建议这样做,除非作为维护一些旧代码库的最后手段。
  • 顺便说一句,这个宏偷偷地隐藏了很多无意义的分支。假设第一个函数失败。然后,您必须检查每个以下函数的错误结果,每个此类检查都包括一个毫无意义的分支。因此,这不仅是某种神秘的宏语言,而且还很慢。它本质上是一种编写if-else if 链的晦涩方式。
  • 它几乎没有隐藏任何东西;它只是使意图显而易见,保持代码可读性,并使用一个常见的习惯用法。这个成语在 safety 软件中很流行,因为它最大限度地减少了嵌套,从而减少了理解的认知负担;并将其留给编译器来绘制效率。快速 cc -S 表明现代编译器对这个习语没有任何问题。
  • 我不同意这段代码在安全相关软件中也是正确的方法。见stackoverflow.com/a/42577015/584518。多个返回语句更清晰、更快、更安全。我对 MISRA-C 委员会以及其他人(至少听取了足够多的意见以放松禁止多次返回的规则从必需到咨询)感到愤怒。
【解决方案3】:

您可以使用泛型类型指针void* 作为函数参数,并创建另一个指针数组,其索引与函数指针数组相同。然后,对于对函数的每次调用,您都检索相应的参数引用,如果不需要参数,则只需为空参数使用 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

【讨论】:

  • 为什么不用在主函数的第 3 行指定函数指针数组中的两个 void 指针? int (*p_functions[3])(void param1, void param2) ?
  • 你的意思是函数指针的定义吗?应该是这样的:int (*p_functions[3])(void* param1); 函数模式完全针对用例。由于您正在使用线程实现操作系统,因此使用 void 指针参数实现线程函数模式更加通用和常见。看看FreeRTOS的任务示例。
【解决方案4】:

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

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

但是,处理此类代码的典型模式是使用goto 的级联来清理/错误处理程序:

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;

【讨论】:

  • 这感觉就像是 BASIC 时代的老式风格。还有很多代码重复,这并不理想。
  • @Lundin,请问重复了什么?
  • 我考虑过,但我可以通过只说 if(!error) error = function 来避免在这里使用 goto,那么我根本不必使用 goto。
  • @Julian,不要被“anti-goto”宣传所迷惑,即使是最新的 MISRA C 也允许 forward “goto”跳转作为错误处理的可接受做法
  • 是的。我发现了这个:embeddedgurus.com/barr-code/2018/06/… 有趣的阅读。甚至 linux 内核也使用 goto 错误处理。
【解决方案5】:

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

问题的答案是没有具有不同参数的函数。您必须设计一个适合所有用例的 API。在最简单的形式中,通过让函数采用 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

【讨论】:

  • 参数是动态的,即 func1 可能会为 func2 生成一个参数
  • @Julian 是的,所以只需将指针传递给可以更新的变量。我故意写了void* 而不是const void*
  • @Julian 虽然:考虑通过调用者应用程序中的某个调度程序进行通信是否是这些函数相互通信的正确方式?也许将它们放在同一个翻译单元中并使用static 文件范围变量就足够了? API 更简单、更干净,但不是线程安全的。
  • @Julian 我更新了示例以展示如何在需要时在函数之间传递参数。仍然有些可疑的设计,但如果这两个函数位于代码的完全不相关的部分,它可能会受到激励。
  • 泛型编程的一些例子stackoverflow.com/a/69379395/584518
【解决方案6】:

我见过的一种方法是使用下面的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

【讨论】:

    猜你喜欢
    • 2011-06-17
    • 1970-01-01
    • 2019-04-13
    • 2019-04-19
    • 1970-01-01
    • 1970-01-01
    • 2015-10-31
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多