user1610950
user1610950

Reputation: 1917

C header and source files structure

I am writing a math library that I want to later include as a part of my rendering pipeline.

edit I want to stick with C99.

I plan to write 3 versions of the same library, a scalar version that's universal, a neon version for mobile platforms and a SSE implementation for platforms that support that.

I was wondering how could I structure my files to have one interface but depending on compiler flags have different implementation.

For simplicity I'll use basic math functions.

basic_math_scalar

Header basic_math_scalar.h

typedef struct scalar_struct {
    ...
    ...
};
int add(scalar_struct* out, const int* in_a, int* in_b);
int sub(scalar_struct* out, const int* in_a, int* in_b);
int mult(scalar_struct* out, const int* in_a, int* in_b);
int div(scalar_struct* out, const int* in_a, int* in_b);

basic_math_sse

Header basic_math_sse.h

typedef struct sse_struct {
    ...
    ...
};
int add(sse_struct* out, const int* in_a, int* in_b);
int sub(sse_struct* out, const int* in_a, int* in_b);
int mult(sse_struct* out, const int* in_a, int* in_b);
int div(sse_struct* out, const int* in_a, int* in_b);

basic_math_neon

Header basic_math_neon.h

typedef struct neon_struct {
    ...
    ...
};
int add(neon_struct* out, const int* in_a, int* in_b);
int sub(neon_struct* out, const int* in_a, int* in_b);
int mult(neon_struct* out, const int* in_a, int* in_b);
int div(neon_struct* out, const int* in_a, int* in_b);

Each of the above files will have its implementations in a .c file.

Now I will have a class main.c that implements those and doing a switch based on compiler flags.

main.c
#include <stdlib.h>
#include <stdio.h>
#include "basic_math.h"

int main(int argc, char** argv) {

    basic_math_struct* a = create_bms();

    a = add(1, 2);
    printf("value of a: %d\n",bms_value(a)); //should equal 3

    cleanup_bms(a);

    return 0;
}

There's a big piece missing in the above code. I have the actual math functions and then I have the main.c that uses those math functions but there's an intermediate step that I am having trouble designing, basic_math.h

Let's say basic_math.h looks like this.

#ifdef SSE
  #include "basic_math_sse.h"
  typedef sse_struct basic_math_struct;
#elif NEON
   #include "basic_math_neon.h"
   typedef neon_struct basic_math_struct;
#else
   #include "basic_math_scalar.h"
   typedef scalar_struct basic_math_struct;
#endif

basic_math_struct add(basic_math_struct* out, const int* in_a, int* in_b);
basic_math_struct sub(basic_math_struct* out, const int* in_a, int* in_b);
basic_math_struct mult(basic_math_struct* out, const int* in_a, int* in_b);
basic_math_struct div(basic_math_struct* out, const int* in_a, int* in_b);

Basically I want to use the file basic_math.h to hide all the implementation of the underlying functions so that once I include the header file, there will be no need to mess with the underlying files anymore.

Then the scalar, sse, or neon build types can be made at compile time and users of this library never have to worry about adding #ifdefs in their files.

This basic_math.h module will have to be the place where all the #ifdefs are but that shouldn't be the concern if you are using the files in your main.c.

Also if I am overlooking anything I would like the feedback as well.

Upvotes: 0

Views: 565

Answers (1)

Jonathan Leffler
Jonathan Leffler

Reputation: 753615

One obvious problem is that you can only have one function with any given name in a single program in C (in the absence of dynamic loading and access via pointer to function); there is no function overloading. What you're seeking to do would perhaps be easier in C++ where there is function overloading.

The return type of the functions in basic_math.h is unexpected:

basic_math_struct add(basic_math_struct* out, const int* in_a, int* in_b);

In the other headers, the return type of add is int. It isn't clear why the in_a parameter is a const int * and the in_b is a non-constant int *.

Is the calling code ever going to instantiate one of the structures other than via a memory allocation function? That is, could the calling code ever write:

basic_math_struct x;

Or is it constrained to always use pointers:

basic_math_struct *px = bms_create();

more or less as shown in your code? Note that it is more conventional to use bms_ as a prefix than _bms as a suffix, though it is not wholly untenable to use the suffix.

Note that your sample main() includes:

basic_math_struct* a = create_bms();

a = add(1, 2);

This is not a call to any of the add functions in the question. Superficially, you might have intended:

*a = add(a, 1, 2);

This would match the add function in your header, but is extremely dubious code. It would make more sense if the function returned int as in the scalar, sse, and neon code:

if (add(a, 1, 2) != 0)
    …report BMS error…

If the calling code will never allocate structures (it will only ever contain pointers to structures) and if it will never attempt to dereference a pointer to get at an element within the structure (it will only ever use API calls to get at the data), then you can avoid exposing the internals of the type to the calling code altogether, and simply use an opaque type.

Opaque type

basic_math.h

typedef struct basic_math_struct basic_math_struct;

int bms_add(basic_math_struct *out, const int *in_a, int *in_b);
int bms_sub(basic_math_struct *out, const int *in_a, int *in_b);
int bms_mul(basic_math_struct *out, const int *in_a, int *in_b);
int bms_div(basic_math_struct *out, const int *in_a, int *in_b);

basic_math_struct *bms_create(void);

Note that this explicitly does not include any of the specific headers.

The users can write their code accordingly:

basic_math_struct *a = bms_create();

if (bms_add(a, 1, 2) != 0)
    …deal with BMS error…

The implementation code for the three systems will include the basic_math.h header, and then define the struct basic_math_struct appropriately:

#include "basic_math.h"

struct basic_math_struct
{
    ...
    ...
};

and will implement the code accordingly:

int bms_add(basic_math_struct *out, const int *in_a, int *in_b)
{
    …implementation for scalar, or neon, or sse…
}

This is fine and straight-forward as long as no build will ever need to have more than one of the types available in the same run of the program. Then you would have problems, but that isn't yet what you've described.

Look up 'opaque types' on SO with the tag (so enter [c] opaque type in the SO search bar). You should find numerous relevant questions with decent answers.

Upvotes: 2

Related Questions