Home > front end >  Sequential Execution of functions with break option
Sequential Execution of functions with break option

Time:09-29

I have a thread in my operating system that is called in a fixed interval and then executes a list of 10-15 distinct functions sequentially. Each function has a return parameter that either says 0 (OK) or not 0 (error). Looks something like this:

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

However it would be preferred that when one of the functions returns an error, the error is handled immediately and the other functions are not being executed anymore (single error failure).

For two functions I could do an if condition before each function but for 10-15 that would lead to a lot of unnecessary ifs.

For that I would use an array of function pointers that I go through sequentially:

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

My issue here is that as you can see in the first example my function_2() has a parameter that gets maybe generated by another function beforehand. So I can't deal with functions that have different parameters.

Are there any other ways to solve that? Or maybe with some tricks for pointer casting? I heard dirty casting is a thing?

CodePudding user response:

Given a function that returns 0 on success and 1 on error, you can change the existing code to use ||, which has short circuit behavior and more closely matches what you want to do anyway, instead of &:

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

Now, once error is set to 1, no further functions will be called.

As far as handling a specific error, you can set the variable with a function's return value shifted a certain amount based on which function failed, then check the bitmap in the error function.

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

Then in 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
}

If the functions could return any non-zero value on error, you can capture that error code in a separate variable:

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

And the above with a macro to make it more readable:

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

CodePudding user response:

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

Isn't too bad; this is the style things like the SV test suite are written in; and the actual error value is preserved.

CodePudding user response:

You can use generic type pointer void* as function parameters and create another array of pointers that have same indices as array of func pointers. Then for each call to a function you retrieve the corresponding parameter reference, if no parameter required simply a NULL for empty parameters. OR alteratively you can use a task structure which includes a pointer to a function and a generic type parameter. This will be more flexible and let you deal with as many function as you need. Here I modified your code and tested it:

#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])() = { 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);
            }
        }
    }

}

Since the output is large because of the while loop I just share only for one loop. The output is:

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

CodePudding user response:

Why not obfuscate this even more by using short-circuit evaluation from || operator.

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

However, the typical pattern for handling this type of code is by using using a cascade of goto to cleanup/error handlers:

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;

This pattern allows safe release of any resources (i.e. memory, threads, opened files) that were acquired along execution steps. Moreover, it allows to reverse results of some operations if it's possible.

When no resources are taken by the functions then the pattern could squashed to:

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;

CodePudding user response:

The function pointer alternative is how you often implement schedulers, state machines and similar. The problem with function pointers is however that there exists no generic function pointer like void* for object pointers.

The answer to the question is to not have functions with different parameters. You'll have to design an API that fits all use-cases. In the most simple form, that's none by letting the function take a void pointer parameter, which is optional.

Full example:

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

Output:

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

CodePudding user response:

One way I have seen is the use of a macro such as FUNC_RET_IF_ERR below:

 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 }

This will output:

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