Common way to write generic functions in C is to use macros. Problem with macros is that that they are hard to read, and hard to support. Just take max for example and it gets worse in this terms when complex functions are required. sglib is another example. This code just does not feel C any more.

Approach in generic max without macros is:

//use max with integers
typedef int generic_max_t;

//this inserts static max() 
//that accepts generic_max_t as type
#include "max_generic.h"  

main(){
  int a=1;
  int b=2;
  int m = max(a,b);
  printf("%i",m);
}

and this is implementation of “max_generic.h”

static generic_max_t max(generic_max_t a, generic_max_t b){
  if(a>=b)
    return a;
  else
    return b;
}

Generic functions are implemeted with generic type. In this example generic_max_t was used. Type is first instatiated with typedef int generic_max_t; and after that generic code in injected using #include directive that works like copy-paste. For this to work, all generic functions should be declared as static.

Advantage of such code is that is pure C without funny macros. Code is reusable with different types, but unfortunatly only once in each translation unit.

Here is another example of generic ring buffer with non trivial type to store.

//usage example
#include <stdio.h>


typedef struct {
    double x;
    double y;
    double z;
}point_t;

//we want point_t to be element of our circular buffer
typedef point_t circular_element_t;

//at this point we inject static code that accepts circular_element_t as type
#include "circular.generic.c"  

circular_element_t buffer_space[20];

circular_buffer_t circular_buffer = {
  .buffer = buffer_space,
  .length = sizeof(buffer_space)/sizeof(buffer_space[0]),
};

int main(){
  generic_curcular_buffer_init(&circular_buffer);
  point_t p = {1,2,3};
  generic_curcular_buffer_push(&circular_buffer,&p);
  p.x=p.y=p.z=0;
  generic_curcular_buffer_pop(&circular_buffer,&p);  
  printf("%f %f %f",p.x,p.y,p.z);
  return 0;
}

and implementation

// file: circular.generic.c
// generic ring buffer
//
// extrnaly defined type is 
// circular_element_t;

typedef struct{
  circular_element_t * buffer;
  int length;  
  int push;
  int pop;  
} circular_buffer_t;
static void generic_curcular_buffer_init(circular_buffer_t * cb){
  cb->push = 0;
  cb->pop = 0;
}
//return 0 or sucess or -1 on error
static 
int generic_curcular_buffer_push(circular_buffer_t * cb, 
                                 const circular_element_t* element)
{
  int push = cb->push+1;
  if(push == cb->length) {
    push  = 0;
  }
  if(push == cb->pop){
    return -1; //full
  }
  cb->buffer[cb->push] = *element;
  cb->push = push;
  return 0;
}
//return 0 or sucess or -1 if empty
static
int generic_curcular_buffer_pop(circular_buffer_t * cb, 
                                circular_element_t* element)
{
  if( cb->pop ==  cb->push) {
    return -1; //empty
  }
  int pop = cb->pop+1;
  if(pop == cb->length){
    pop=0;
  }
  *element = cb->buffer[cb->pop];
  cb->pop = pop;
  return 0;
}

Another example whith reuse of generic code, is availabe on github