7

I am trying to simulate generics in C by having some preprocessor definitions for a matrix type. Here is an excerpt of that:

#define __matrix_struct(TYPE) \
  struct { \
    uint32_t sz; \
    TYPE **ptr; \
  }

#define __matrix_t(TYPE) matrix_ ## TYPE
#define __matrix_ptr_t(TYPE) __matrix_t(TYPE) *

#define __matrix_typedef(TYPE) typedef __matrix_struct(TYPE) __matrix_t(TYPE)

#define __matrix_allocator_name(TYPE) TYPE ## _matrix_alloc
#define __matrix_allocator(TYPE) \
  __matrix_ptr_t(TYPE) __matrix_allocator_name(TYPE) (uint32_t sz) { \
    uint32_t i; \
    __matrix_ptr_t(TYPE) m = (__matrix_ptr_t(TYPE)) malloc(sizeof(__matrix_t(TYPE))); \
    m->ptr = (TYPE **) malloc(sz * sizeof(TYPE *)); \
    for (i = 0; i < sz; ++i) { \
      m->ptr[i] = (TYPE *) calloc(sz, sizeof(TYPE)); \
    } \
    return m; \
  }

#define __matrix_deallocator_name(TYPE) TYPE ## _matrix_free
#define __matrix_deallocator(TYPE) \
  void __matrix_deallocator_name(TYPE) (__matrix_ptr_t(TYPE) m) { \
    uint32_t i; \
    for (i = 0; i < m->sz; i++) { \
      free(m->ptr[i]); \
    } \
    free(m->ptr); \
    free(m); \
  }

#define matrix_alloc_ptr(TYPE, SIZE) __matrix_allocator_name(TYPE) (SIZE)
#define matrix_dealloc_ptr(TYPE, PTR_NAME) __matrix_deallocator_name(TYPE) (PTR_NAME)

In another file, byte_matrix.h, I am trying to define a matrix of uint8_t values, as follows:

#include "matrix.h"

typedef uint8_t byte;

__matrix_typedef(byte);

__matrix_allocator(byte)
__matrix_deallocator(byte)

When I try to compile, I get the following errors:

CMakeFiles/tictac.dir/game/board.c.o: In function `byte_matrix_alloc':
/home/victor/dev/pc/tictac/game/../matrix/byte_matrix.h:13: multiple definition of `byte_matrix_alloc'
CMakeFiles/tictac.dir/main.c.o:/home/victor/dev/pc/tictac/game/../matrix/byte_matrix.h:13: first defined here
CMakeFiles/tictac.dir/game/board.c.o: In function `byte_matrix_free':
/home/victor/dev/pc/tictac/game/../matrix/byte_matrix.h:14: multiple definition of `byte_matrix_free'
CMakeFiles/tictac.dir/main.c.o:/home/victor/dev/pc/tictac/game/../matrix/byte_matrix.h:14: first defined here

I cannot understand why it would point to times to the same line and complain about that definition, since every header I wrote has include guards. Could you please explain this to me? Also if you know of a better approach to my problem, please let me know. Thanks.

Also I need to compile with -std=c99 if that matters in this case.

Amessihel
  • 5,891
  • 3
  • 16
  • 40
Victor
  • 13,914
  • 19
  • 78
  • 147
  • 1
    Did you try to run only the preprocessor? – Amessihel Nov 16 '18 at 10:31
  • Yes, I did run `gcc -E game/byte_matrix.h` and there is only one occurrence of `byte_matrix_alloc` – Victor Nov 16 '18 at 10:32
  • 2
    You are including `byte_matrix.h` in multiple compilation units, which means you get multiple function definitions. You need to make these functions `static`. – vgru Nov 16 '18 at 10:35
  • 1
    You can use two macros -- `__matrix_allocator_declare` for h-files and `__matrix_allocator_define` for one (selected by you) c-file. – ReAl Nov 16 '18 at 10:36
  • @Groo this did it. Could you please elaborate? – Victor Nov 16 '18 at 10:37
  • @ReAl you're right. I have to admit that I thought of that, but I haven't done it because I'm lazy and I didn't think of the implications it would have – Victor Nov 16 '18 at 10:37
  • 2
    If you don't use `static`, functions are by default `extern`. This means that you will have multiple functions named `byte_matrix_alloc` in your program. If you use `static`, then you are saying that this function is private to each compilation unit, i.e. each .c file gets it's own copy of the function. – vgru Nov 16 '18 at 10:38
  • @Groo could you please elaborate an answer on that? I think including ReAl's recommendations would be good too :) – Victor Nov 16 '18 at 10:39
  • 2
    Interesting, C11 (C99 successor) provides [type-generic expressions](http://www.robertgamble.net/2012/01/c11-generic-selections.html). – Amessihel Nov 16 '18 at 10:45
  • @Amessihel if only I could compile with C11 – Victor Nov 16 '18 at 10:47
  • 1
    @Victor: have a look at [klib](https://github.com/attractivechaos/klib), it does a similar thing for many data structures (vector, hashtable, tree). However, it becomes quite easy to get carried away with preprocessor macros. Your code might get seriously convoluted once you go this path, so use it sparingly. – vgru Nov 16 '18 at 10:54

2 Answers2

4

A quick fix would be to add static to your function definitions. This will create a static copy of these functions in each compilation unit which references the header. If you want the functions to be inlined every time, this is the way to go.

An alternative way to do it would be to keep function declarations in a .h file, and actual definitions in a single .c file. This approach will avoid duplication, and the compiler will not inline them (unless your linker supports link time optimization).

The reason is that you are including this header file in multiple compilation units. After the preprocessor does all the textual replacements, you end up with actual separate function definitions inside your .c files. And if you don't specify that you want them to be static, they are by default extern, which means that now the compiler doesn't know how to differentiate them if some other part of the code wants to call them.

This is what you basically do whenever you create a header file: you create a list of declarations which will be included in many compilation units, but there is always a single extern definition in a single .c file.

vgru
  • 49,838
  • 16
  • 120
  • 201
2

Another way (relative to the proposed by Groo) is to create two macros.

  • __matrix_allocator_declare with just prototype of function -- for h-file(s)
  • __matrix_allocator_define with function body -- for one (selected by you) c-file

This way requires to handle two macros and to not forget add function-body macro in some file, but (and it is more important for embedded applications on small microcontrollers) it guarantees that only one function instance will consume memory.

ReAl
  • 1,231
  • 1
  • 8
  • 19