带中断选项的函数的顺序执行
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,这会导致很多不必要的 if
s。
为此,我将使用一个按顺序遍历的函数指针数组:
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
我的操作系统中有一个线程,它以固定间隔调用,然后按顺序执行 10-15 个不同函数的列表。每个函数都有一个 return 参数,表示 0(正常)或非 0(错误)。看起来像这样:
while (1) {
error &= function_1();
error &= function_2(some_parameter);
error &= function_3();
handleError(error);
}
然而,当其中一个函数 return 出错时,立即处理该错误并且不再执行其他函数(单一错误失败)会更好。
对于两个函数,我可以在每个函数之前做一个 if
条件,但是对于 10-15,这会导致很多不必要的 if
s。
为此,我将使用一个按顺序遍历的函数指针数组:
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