4

We have a lot of duplicate code that looks like this:

void WriteA(A *to, A *from);
void WriteB(B *to, B *from);
void WriteC(C *to, C *from);

void WriteA(A *to, A *from){
    *to = *from;
}

void WriteB(B *to, B *from){
    *to = *from;
}

void WriteC(C *to, C *from){
    *to = *from;
}

Even though C doesn't have function overloading or templates, I'd like to make this a single generic function. I suppose my only option here, is to use void pointers.

void Write(void *to, void *from);

However, there doesn't seem to be a way to derive the type once inside the function:

void Write(void *to, void *from){
    *to = *from
}

compiler error: illegal type(s): void '=' void


There also doesn't seem to be a way to pass a third parameter which is a typename(again, something I assume only C++ can do with templates).

How would I go about writing this kind of function? I'd like to avoid the pre-processor if possible.


I thought I could maybe do this:

void Write(void *to, void *from){
    memcpy(pTo, pFrom, sizeof(*pTo));
}

but,
compiler error: don't know size of object

Trevor Hickey
  • 36,288
  • 32
  • 162
  • 271
  • 2
    Some library functions use a size_t parameter for these kind of stuff. – Leonardo Herrera May 07 '15 at 14:43
  • 1
    @LeonardoHerrera I should pass a size_t as the third parameter? – Trevor Hickey May 07 '15 at 14:44
  • 2
    @TrevorHickey yes .. but now you've just wrapped `memcpy`. Why not (in your example) simply stick with `*to = *from` in the calling code - then the compiler knows everything it needs to do optimization. – kdopen May 07 '15 at 14:46
  • Use a union that group all your types, anche pass to the function the type you are passing. – LPs May 07 '15 at 14:46
  • @kdopen I'd love to stick with the basic assignment, but since they are void pointers, I get a compiler error saying "illegal type(s): void '=' void". And oh!, the function does other stuff than just memcopy with the types, but I wanted to keep the example simple. – Trevor Hickey May 07 '15 at 14:47
  • Check this post [function overload in C](http://stackoverflow.com/questions/479207/function-overloading-in-c) – yluo_Datalogics May 07 '15 at 14:48
  • They are only void pointers in your `Write` functions. In the calling code they are typed. Otherwise, you really are in trouble because the calling code doesn't know what it's dealing with. – kdopen May 07 '15 at 14:48
  • Do you need to use only C and the C Preprocessor? What if you wrote your own preprocessing application? So that rather than using the C compiler you would instead use your preprocessor which would generate a C source code file and then pass that on to the C compiler? Think in terms of lex and yacc. – Richard Chambers May 07 '15 at 14:53
  • There's a way to do what you want, but it will take time to write a clear answer – kdopen May 07 '15 at 14:55
  • What is the other stuff that the functions do besides an assignment? Do they work with particular members of what I assume are `structs` and the offset of those members vary depending on the `struct` while the member names do not? Could you update your question with a bit more detail as to why a straight assignment is not possible? – Richard Chambers May 07 '15 at 15:02
  • @RichardChambers code generation is a good idea, but I had always hoped that the point of a high level language was to prevent you from having to write a language to generate it(Not that C is very high level). The rest of the code inside the functions doesn't rely on the type, but they occur before and after the assignment , so I wanted to bundle them into a function. Again, the compiler won't let me do assignment because they are void* and not actual types(this could be a limitation of the compiler I'm using though- not sure). – Trevor Hickey May 07 '15 at 15:18
  • Why are you not using C++, if your software has matured to a point where C++ features clearly would enhance maintainability? Is there no C++ toolchain for your environment? – Ben Voigt May 07 '15 at 15:49
  • @BenVoigt VxWorks for embedded real time operating systems. There is C++ support, but it's my employer's decision not to use it at the moment. :( – Trevor Hickey May 07 '15 at 16:31
  • @TrevorHickey: Well, the alternates are C++ or maintaining near-duplicate code, varying only by type (which sounds like it could expand from a macro, but this has its own drawbacks, especially with debugging and single-stepping through). Your responsibility is to let them know the costs of their decision to avoid C++ (additional effort to make changes, plus probably additional testing and debugging iterations when fixes aren't made to all variants at once) and then abide by the decision they make. – Ben Voigt May 07 '15 at 16:35
  • Hopefully you can find out the reason for the decision. If the rationale is that they can hire more and cheaper C programmers than C++ programmers, then they'll be equally unhappy if you find a way to overcome the issue using exotic C features, because you're still taking away their ability to hire cheap C programmers by requiring a level of expertise that understands the feature. Just as an example, I'm pretty sure most C programmers don't understand type-generic macros. – Ben Voigt May 07 '15 at 16:37

2 Answers2

2

Given that you are trying to duplicate what can be achieved in C++, why not duplicate what C++ does and implement inheritance and virtual functions?

This is whiteboard code, neither compiled nor tested, but is very similar to things I've done in the past when implementing OO designs in C. Take it as a pattern you can use and expand.

Sticking to your example code, where you want a generic copy function, first define the abstract base class.

typedef struct base_class
{
    void (*copy) (const void *this, void *to);
}

Now some inherited classes:

typedef struct A
{
    const base_class *base;
    // Other members
} A;

typedef struct B
{
    const base_class *base;
    // Other members
} B;

Create the underlying copy functions, and constructors for the derived classes. The assert tries to provide some type safety

void copy_A(const void *this, void *to)
{
    assert(((const A *)this)->base == ((A *)to)->base)
    memcpy(to, this, sizeof(A));
}

void make_A(A *newA)
{
    const base_class base = {copy_A};
    assert(newA);
    newA->base = &base;
    // other initialization 
}


void copy_B(const void *this, void *to)
{
    assert(((const B *)this)->base == ((B *)to)->base)
    memcpy(to, this, sizeof(B));
}

void make_B(B *newB)
{
    const base_class base = {copy_B};
    assert(newB);
    newB->base = &base;
    // other initialization 
}

Finally, your generic copy method:

void Write (void *to, const void *from)
{
    const base_class *source_vtable = from;
    source_vtable->copy(from, to);
}

This is not typesafe, in that there is no guarantee *from contains a vtable. To make it bullet proof, you really want your base_classto contain an initial tag field which can be checked to ensure there really is a function table there.

The base_class can be expanded to contain multiple common functions.

kdopen
  • 8,032
  • 7
  • 44
  • 52
  • C++ handles this well, but it doesn't use virtual functions to do it. – Ben Voigt May 07 '15 at 15:47
  • The `copy` method is the 'virtual' function in this case, providing polymorphism for the suite of derived classes. And yes, it might be closer to double dispatch than virtual functions, but it's the though that counts ;) – kdopen May 07 '15 at 16:05
  • Your idea indeed closely matches virtual functions, but in C++ you'd not use virtual functions to solve this, you'd use templates. Oh, your implementation is full of dangling pointers and mixes levels of indirection. Will fail horribly at runtime. – Ben Voigt May 07 '15 at 16:40
  • Said it wasn't tested or compiled. Took 10 minutes to write over a cuppa, but it's an outline of an approach. I've done bullet-proof, production strength, implementations that have been shipped in millions of consumer products. Feel free to edit and polish – kdopen May 07 '15 at 16:49
2

One approach would be to use the C Preprocessor if you have a dependency on the struct members.

However based on your comment you really have only a single dependency on the struct type and members which is the assignment which is done in a particular place.

The easiest thing would be to use the suggestion from Leonardo Herrera in the comment above and pass in the struct size and then use memcpy() with the size of the struct argument.

Another possibility is to create two functions, one which does the work before the assignment and a second which does the work after the assignment and then call the first function, do the actual assignment, and then call the second function. For instance:

// define the structs that we are using.
typedef struct {
     int jj;
     int kk;
} s1;

typedef struct {
     int kk;
     int ii;
     int jj;
} s2;

// define the function that does the first part of the work
void PhaseOne (/* arg list */)
{
    // phase one of the functionality
}

// define the function that does the second part of the work
void PhaseTwo (/* arg list */)
{
    // phase two of the functionality
}

myFunctionUser (void)
{
    s1  a1, b1;
    s2  a2, b2;

    // do things with a1 and b1 in prep to call the function
    // do things with a2 and b2 in prep to call the function

    PhaseOne (/* arg list */);
    a1 = b1;
    PhaseTwo (/* arg list */);

    PhaseOne (/* arg list */);
    a2 = b2;
    PhaseTwo (/* arg list */);

    // do more stuff

}

If there is need for state to be communicated between PhaseOne() and PhaseTwo() or if there is some change needed to the variables which are being assigned based on the state from PhaseOne() or PhaseTwo() then you might need to have a struct or other variable type for that.

However another thing would be to write a function that takes a pointer to another function and in the other function do the assignment. This is borrowing the idea of how the C Standard Library uses a comparison function with the qsort() function. And using this approach if there are additional things to do with the structs besides the assignment then the function pointed to would be the place to do it since at that point you can do a cast to the appropriate type and then do whatever.

// define the structs that we are using.
typedef struct {
     int jj;
     int kk;
} s1;

typedef struct {
     int kk;
     int ii;
     int jj;
} s2;

void AssignS1 (void *a, void *b) { *((s1 *)a) = *((s1 *)b); return; }
void AssignS2 (void *a, void *b) { *((s2 *)a) = *((s2 *)b); return; }

void myFunction (void *a, void *b, void (*pFunc)(void *x, void *y))
{
     //   do stuff
     pFunc(a, b);    // do the assignment
     //   do more and more and tons of stuff
}

myFunctionUser (void)
{
    s1  a1, b1;
    s2  a2, b2;

    // do things with a1 and b1 in prep to call the function
    // do things with a2 and b2 in prep to call the function

    myFunction (&a1, &b1, AssignS1);

    myFunction (&a2, &b2, AssignS2);

    // do more stuff

}

Just for grins here is an approach using the C Preprocessor to generate source based on a particular struct type. It is quite ugly and difficult for debugging and requires some degree of discipline on the part of the programmer. This is a small and simple function to illustrate the procedure and I guarantee you that it will not scale well.

// define a macro that will generate various type specific versions of a function.
#define WriteThing(s) void WriteThing##s (s *a, s *b) \
    { *a = *b;  if (a->jj != 4) { a->jj += b->jj + 10;} return; }

// define the function we will use to call the above function based on the type of the arguments
#define WriteThingCall(s,a,b) WriteThing##s (a, b)

// define the structs that we are using.
typedef struct {
    int jj;
    int kk;
} s1;

typedef struct {
    int kk;
    int ii;
    int jj;
} s2;

// generate the functions we are going to need for our different types
WriteThing(s1)

WriteThing(s2)

// example function of how to use this approach.
void func (void)
{
    s1  a1 = {0}, b1 = {0};
    s2  a2 = {0}, b2 = {0};

    WriteThingCall(s1, &a1, &b1);
    WriteThingCall(s2, &a2, &b2);

}
Richard Chambers
  • 16,643
  • 4
  • 81
  • 106