4

So, I have a huge set of functions to read inputs from some source of the form:

ErrCode_t in_func1(t1_t * const data1);
ErrCode_t in_func2(t2_t * const data2);
...

some trigger functions, telling me if I may call the functions above:

TrCode_t tr_func1();
TrCode_t tr_func2();
...

and corresponding functions to write out the data:

void out_func1(t1_t const * const data1, const uint32_t handled_error);
void out_func2(t2_t const * const data2, const uint32_t handled_error);
...

Also there is a quite complicated algorithm depending on the trigger functions, that decides if I may call the input function, or not. (this is a simplified picture; their more then one trigger function and timers envolved for each I/O).

This algo but basicly says: If the trigger says yes, call the input function with a pointer to a data variable, check the error, do some validation, and then pass the pointer of the updated variable to the output.

 void execute_f1(t1_t * const pData1)
 {
    if(evalTr(tr_func1()))
    {
      const ErrCode_t myErr = in_func1(pData1);
      const uint32_t myOutErr = evalError(myErr);

      out_func1(pData1,myOutErr);
    }
 }

(while evalTr and evalError shall be some evaluation functions that are used correctly)

I would like to encapsulate this algo in a own function,

 void execute_io(???)

to be called with some function pointers to perform this. But I can't think of a pattern that would be conform to the standard, without a huge amount of wrapper functions. I wrap the input-functions and output functions to perfom the correct casts, and to adjust the signatures like:

ErrCode_t my_in_func1(void * const pData1)
{
    t1_t * const data1 = (t1_t*) pData1;
    return in_func1(data1);
}

and output functions alike:

 void my out_func2(void const * const data2, const uint32_t handled_error) {...}

so that I have homogeneous signatures, and that way easy function pointers. But I would really prefer not to wrap all those functions. Do anyone know a pattern that would work "inside" execute_io and surrounding code, so I don't have to wrap all that functions?

Update: And here in a combination as requested from barak manos:

system_interface.h

 ErrCode_t in_func1(t1_t * const data1);
 /* some 500 more of them */

 TrCode_t tr_func1();
 /* some 500 more of them */

 void out_func1(t1_t const * const data1, const uint32_t handled_error);
 /* some 500 more of them */

my_code.c

 static ErrCode_t my_in_func1(void * const data1)
 {
    t1_t * const data1 = (t1_t*) pData1;
    return in_func1(data1);
 }
 /* and some 500 more wrappers */

 static void my_out_func1(void const * const pData1, const uint32_t handled_error)
 {
    t1_t const * const data1 = (t1_t) pData1;
    out_func1(pData1, handled_error);
    return;
 }
 /* and some 500 more wrappers */

typedef ErrCode_t (*inFuncPtr)(void * const);
typedef void (*outFuncPtr)(void const * const, const uint32_t);
typedef TrCode_t (*trFuncPtr)();

execute_io(inFuncPtr inFunc, outFuncPtr outFunc, trFuncPtr trFunc, void * pData)
{
   if(evalTr((*trFunc)()))
   {
      const ErrCode_t myErr = (*inFunc)(pData);
      const uint32_t myOutErr = evalError(myErr);

      (*outFunc)(pData,myOutErr);
   }
   return;
}

void do_all_my_work()
{
   {
     t1_t data1;
     execute_io(&my_in_func1, &my_out_func1, &tr_func1, &data1);
   }
   {
     t2_t data2;
     execute_io(&my_in_func2, &my_out_func2, &tr_func2, &data2);
   }
   /* and some 499 other calls */
}

I want to find another pattern, that does not force me to wrap all that I/O functions. (and no, the above code is surely not an executable example but merely a concept)

Mark A.
  • 579
  • 4
  • 13
  • Can you please just show the complete piece of code that you currently have, and point out what exactly you are not happy with? It would make it a lot easier for us than trying to guess how all these code snippets are combined together into that. – barak manos May 13 '15 at 18:37
  • @barakmanos hope, the update helps. – Mark A. May 13 '15 at 20:00

1 Answers1

0

I wrote something that I believe does what you want. Let me know if I'm missing something. I wrote stubs for the various functions and types that you mentioned just so the code would compile, but it should work no matter the types used or the implementation used in the functions you defined. Basically it works because all pointers are just integers that give the address of a value in memory, and the contents of that memory are unimportant for passing the pointer as long as the pointer points to a location that is sufficiently large to contain the value needed. The only issue that can sometimes throw a wrench into this notion is that some compilers require specific data types to be aligned along specific byte boundaries. To get around this we first notice that these byte boundaries are all powers of 2 and that the alignment will never exceed the size of the largest primitive which for c is currently 64 bits (8 bytes). This is true even for non-primitive types eg. struct/union. Then all we have to do is allocate an additional 8 bytes in memory on the buffer that needs to hold this typed data, and then add a value between 0 and 7 to the pointer to make it divisible by 8 and thus eliminating the alignment problem on all machines that support 64 bit pointers or less.

    /*
 * Test.c
 *
 *  Created on: May 14, 2015
 *      Author: tiger
 */
#include <stdio.h>

typedef int ErrCode_t;
typedef int TrCode_t;
typedef unsigned int uint32_t;
typedef unsigned char uint8_t;
typedef unsigned long long int uint64_t;

typedef int t1_t;
typedef short t2_t;
typedef float t3_t;
typedef double t4_t;
typedef long long int t5_t;

#define MAX_DATA_TYPE_SIZE ((uint32_t)64) //or whatever the size is of your largest data type

uint32_t evalTr(TrCode_t code);
uint32_t evalError(ErrCode_t code);

ErrCode_t in_func1(t1_t* const data1);
ErrCode_t in_func2(t2_t* const data2);
ErrCode_t in_func3(t3_t* const data3);
ErrCode_t in_func4(t4_t* const data4);
ErrCode_t in_func5(t5_t* const data5);

TrCode_t tr_func1();
TrCode_t tr_func2();
TrCode_t tr_func3();
TrCode_t tr_func4();
TrCode_t tr_func5();

void out_func1(t1_t const* const data1, const uint32_t handled_error);
void out_func2(t2_t const* const data2, const uint32_t handled_error);
void out_func3(t3_t const* const data3, const uint32_t handled_error);
void out_func4(t4_t const* const data4, const uint32_t handled_error);
void out_func5(t5_t const* const data5, const uint32_t handled_error);

typedef struct
{
    TrCode_t (*tr_func)(void);
    ErrCode_t (*in_func)(void* const data);
    void (*out_func)(const void* const data, const uint32_t handled_error);
}IOSet;

#define FUNCTION_COUNT ((uint32_t)5)
IOSet ioMap[FUNCTION_COUNT] =
{
    {.tr_func = (void*)&tr_func1, .in_func = (void*)&in_func1, .out_func = (void*)&out_func1},
    {.tr_func = (void*)&tr_func2, .in_func = (void*)&in_func2, .out_func = (void*)&out_func2},
    {.tr_func = (void*)&tr_func3, .in_func = (void*)&in_func3, .out_func = (void*)&out_func3},
    {.tr_func = (void*)&tr_func4, .in_func = (void*)&in_func4, .out_func = (void*)&out_func4},
    {.tr_func = (void*)&tr_func5, .in_func = (void*)&in_func5, .out_func = (void*)&out_func5}
};

void execute_io(IOSet io, void * const pData)
{
    if(evalTr(io.tr_func()))
    {
        const ErrCode_t myErr = io.in_func(pData);
        const uint32_t myOutErr = evalError(myErr);

        io.out_func(pData,myOutErr);
    }
}

void do_all_my_work()
{
    uint32_t i;
    //allocate a buffer sufficiently large to hold any of the data types on the stack
    // + 8 to allow correcting pointer for any alignment issues
    uint8_t dataBuffer[MAX_DATA_TYPE_SIZE + 8];
    uint64_t dataHandle = (uint64_t)&dataBuffer;
    //ensure the dataHandle is divisible by 8 to satisfy all possible alignments
    //all 8 byte alignments are also valid 4 byte alignments
    //all 4 byte alignments are also valid 2 byte alignments
    //all 2 byte alignments are also valid 1 byte alignments

    //you can use a smaller type than uint64_t for this computation if your pointers are smaller,
    //but this should work for pointers of all sizes up to 64 bits
    if((dataHandle % 8) > 0)
    {
        dataHandle += 8ULL-(dataHandle % 8);
    }

    for(i = 0; i < FUNCTION_COUNT; i++)
    {
        execute_io(ioMap[i], (void*)dataHandle);
    }
}

uint32_t evalTr(TrCode_t code)
{
    static uint32_t result = 0;
    result ^= 1;
    return result;
}

uint32_t evalError(ErrCode_t code)
{
    return 0;
}

ErrCode_t in_func1(t1_t* const data1)
{
    *data1 = 1;
    return 0;
}

ErrCode_t in_func2(t2_t* const data2)
{
    *data2 = 2;
    return 0;
}

ErrCode_t in_func3(t3_t* const data3)
{
    *data3 = 3;
    return 0;
}

ErrCode_t in_func4(t4_t* const data4)
{
    *data4 = 4;
    return 0;
}

ErrCode_t in_func5(t5_t* const data5)
{
    *data5 = 5;
    return 0;
}


TrCode_t tr_func1()
{
    return 0;
}

TrCode_t tr_func2()
{
    return 0;
}

TrCode_t tr_func3()
{
    return 0;
}

TrCode_t tr_func4()
{
    return 0;
}

TrCode_t tr_func5()
{
    return 0;
}


void out_func1(t1_t const* const data1, const uint32_t handled_error)
{
    printf("%d\n", *data1);
    return;
}

void out_func2(t2_t const* const data2, const uint32_t handled_error)
{
    printf("%d\n", *data2);
    return;
}

void out_func3(t3_t const* const data3, const uint32_t handled_error)
{
    printf("%f\n", *data3);
    return;
}

void out_func4(t4_t const* const data4, const uint32_t handled_error)
{
    printf("%f\n", *data4);
    return;
}

void out_func5(t5_t const* const data5, const uint32_t handled_error)
{
    printf("%llu\n", *data5);
    return;
}

int main()
{
    for(;;)
    {
        do_all_my_work();
    }
    return 0;
}
  • I have to walk true your solution for a little to understand your solution. But my first question would be: Is it safe to cast a function pointer to void * as you did in the IOMap table? (are function pointer guaranteed fit in a void *. I thought this guarantee only holds for object pointer) – Mark A. May 14 '15 at 09:12
  • all pointers in a system are the same size, the only size that changes is the size of the value that they point to. Now the alignment of void* is only guarenteed to be the same as char*, however, i convert the function pointer to a void* and then assign that void* to a preallocated function pointer so the pointer that i invoke as a function is guarenteed to have the correct alignment and value as a function pointer –  May 14 '15 at 09:15
  • And second question: is it save to dereference a function pointer declared to have different signature, than the function pointed to? That happens when you use oi.in_func and oi.out_func as the data pointer of the function pointed to is not of void * but of tx_t. – Mark A. May 14 '15 at 09:20
  • And regarding the pointer size of function and object pointer are the same size (and compatible with void * casts): Is this "just" reality, or also the case according to the C standards? Besides that, the solution really looks good. – Mark A. May 14 '15 at 09:22
  • again c doesn't actually care about the type of your variables, only the size and alignment of variables. so as long as the arguments i pass have the same size and alignments as the original function it doesnt matter if the superficial type is different –  May 14 '15 at 09:23
  • as far as pointer sizes go, i don't know if its part of the c standard or not, but computers have a bus for addressing memory, and that bus is a fixed size, that is what determines the address size and thus the pointer size on a system so it couldnt really be any bigger or smaller. if it were bigger, you would not be able to read the value at that location of the ram chip and if smaller it would point to more than one value in ram and no longer be unique –  May 14 '15 at 09:24
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/77849/discussion-between-s-e-and-mark-a). –  May 14 '15 at 21:36
  • Hm. Now at work I checked my C90 standard (of course this might have changed): casting function pointers to void * seems to as undefined behavior by obmission: ANSI 9899-1990, 6.3.4 (with 6.3.16.1) only talks object pointers that can be cast to void * and function pointers to be cast to other function pointers. Anyway this could be fixed by using casts to the functions directly (or some big enough integer and back)... but: (see next comment)... – Mark A. May 15 '15 at 10:22
  • but: The functions and the pointers to that functions are of incompatible type (see ANSI 9899-1990, 6.3.4, with 6.1.2.6 and 6.5.4.3), as one has a parameter of type void* and the other of some specific type T1_t. This is explicit undefined behavior, and though I believe you that there will be no practical impact on any real life C compiler or newer iterations of the language C, i will not be able to use your solution, as I am bound to C90 and strictly forbidden to use code, that uses undefined behavior, if if proven to have no impact atm. – Mark A. May 15 '15 at 10:36
  • im not sure the function is an incompatible type though as you can see from this segment of the ansi c specification: A `TypeIs_VoidPointer may be converted to or from a pointer to any incomplete or object type. In this specification, the conversion to a TypeIs_VoidPointer may be either implicit or explicit; the conversion from TypeIs_VoidPointer must be explicit.` –  May 15 '15 at 12:22
  • Hm. Don't have a newer spec but C90 ( ANSI/ISO 8999-1990) says at 6.5.4.1: "[...]. For 2 pointer types to be compatible, [...] both shall be pointers to compatible types". If I have not missed some exception, I assume, that though conversion is allowed, between T1_t *, and void *, they are not compatible, as void is not compatible to T1_t. Woud be interessing to get a clarification here. – Mark A. May 15 '15 at 13:27
  • And here: http://stackoverflow.com/questions/27767392/c-function-pointer-type-compatibility I have an answer that fits to my suspect. – Mark A. May 16 '15 at 06:04