0% found this document useful (0 votes)
7 views13 pages

Generic Data Structures in C - Andreinc

This document is a tutorial on implementing generic data structures in C, focusing on the use of macros and void pointers. It provides examples of creating a generic stack data structure using macros to define push and pop functions for different data types. The tutorial also discusses the limitations and advantages of using the C preprocessor for generic programming.

Uploaded by

Bobby Weche
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
7 views13 pages

Generic Data Structures in C - Andreinc

This document is a tutorial on implementing generic data structures in C, focusing on the use of macros and void pointers. It provides examples of creating a generic stack data structure using macros to define push and pop functions for different data types. The tutorial also discusses the limitations and advantages of using the C preprocessor for generic programming.

Uploaded by

Bobby Weche
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd

Generic data structures in C | andreinc https://www.andreinc.net/2010/09/30/generic-data-stru...

Generic data structures in C


 September 30, 2010 •  8 minute read

This tutorial assumes the reader is familiar with C macros, C pointers, and basic data-
structures.

Let’s face it, The C Language is not friendly towards generic programming, but we can use a
few tricks:

• Hacking with the #preprocessor

• Using the flexibility of the void pointer ( void* )

You can always try both of the approaches and see which one is more suitable for your particular
case .

Also note that there are already generic C libraries available (see GLib (http://library.gnome.org/devel/
glib/)).

An existing github project (https://github.com/nomemory/blog-generic-data-structures-in-c) contains all the


code from this article, to clone it:

gh repo clone nomemory/blog-generic-data-structures-in-c

Hacking with the #preprocessor


To understand the magic (not really) behind this approach you will need to be familiar with C
macros and the associated concepts: function-like macros, macro arguments, stringification and
concatenation. You can find a very nice tutorial on macros here (http://gcc.gnu.org/onlinedocs/cpp/
Macros.html). If you already know this, you can skip the following paragraphs (or you can read
them to refresh your memory / find errors and correct me).

1 of 13 7/3/24, 12:08
Generic data structures in C | andreinc https://www.andreinc.net/2010/09/30/generic-data-stru...

What is a macro ?

A macro is a piece of code that was labeled with a name. Whenever the preprocessor
encounters it, the label is replaced by the associated code. Basically there are two kinds
of macros : object-like macros (resemble data objects when used) and function-like
macros (resemble function calls).

Example for object-like macros:

#include <stdio.h>

// Macro bellow !
#define HELLO "Hello World Macro!"

int main(){
printf(HELLO);
return 0;
}

In the above example the label is HELLO , and the associated data is "Hello World Macro!" .

Before the compile-time the preprocessor will replace the label with the associated code. If we
compile and run the above code the output will be:

Hello World Macro!

Example for function-like macros:

#include <stdio.h>

#define MAX(a, b) ((a) > (b)) ? (a) : (b)

int main(){
printf("%dn", MAX(1,3));
return 0;
}

In the case above MAX works exactly as a function that receives two arguments a , b and
returns the maximum of the two .

2 of 13 7/3/24, 12:08
Generic data structures in C | andreinc https://www.andreinc.net/2010/09/30/generic-data-stru...

Note that the arguments are generic and appear to be typeless, the preprocessor is not
performing any validation on types (that’s the compiler job) - but bear in mind this an advantage
is also a disadvantage.

If we want to expand the above macro (seeing with the eyes of the compiler) you can use the -E
switch with gcc (http://gcc.gnu.org/).

After the macro is expanded, the sample code above becomes:

int main(){
printf("%dn", ((1) > (3)) ? (1) : (3));
return 0;
}

Note that the notion of macro will be unknown for the compiler, as the code has been already
replaced: ((1) > (3)) ? (1) : (3)

Now let’s focus on a more important aspect: macro concatenation.

How do we proceed when we want to merge two tokens into one while expanding macros ? The
## preprocessing operator performs token pasting .

Example for macro-concatenation:

3 of 13 7/3/24, 12:08
Generic data structures in C | andreinc https://www.andreinc.net/2010/09/30/generic-data-stru...

#include <stdio.h>

#define SHOW(type, msg) show_##type(msg)

void show_error(char *);


void show_info(char *);

void show_error(char *message) {


fprintf(stderr, "ERROR: %s", message);
}

void show_info(char *message){


fprintf(stdout, "INFO: %s", message);
}

int main(){
SHOW(error, "An errorn");
SHOW(info, "Some messagen");
return (0);
}

Output:

ERROR: An error
INFO: Some message

As you can see we supplied the token arguments (not strings!) error / info to the SHOW
macro.

The tokens were concatenated with show_? , and the resulting two tokens were actually two real
functions: show_error and show_info .

Now lets jump into writing our generic<> Stack data structure:

Declaring the (Stack) data structure, and the


associated functions (pop() and push()):

4 of 13 7/3/24, 12:08
Generic data structures in C | andreinc https://www.andreinc.net/2010/09/30/generic-data-stru...

#define STACK_DECLARE(type) \
typedef struct stack_##type##_s { \
type data; \
struct stack_##type##_s *next; \
} stack_##type; \
void stack_##type##_push(stack_##type **stack, type data); \
type stack_##type##_pop(stack_##type **stack); \

Depending on the type supplied as the argument, different code will be generated. A macro can
be associated with blocks of code, we just need to use \ to signal a multi-line macro.

#define STACK_DEFINE(type) \
void stack_##type##_push(stack_##type **stack, type data) { \
stack_##type *new_node = malloc(sizeof(*new_node)); \
if (NULL == new_node) { \
fputs("Couldn't allocate memoryn", stderr); \
abort(); \
} \
new_node->data = data; \
new_node->next = *stack; \
*stack = new_node; \
} \
type stack_##type##_pop(stack_##type **stack) { \
if (NULL == stack || NULL == *stack){ \
fputs("Stack underflow", stderr); \
abort(); \
} \
stack_##type *top = *stack; \
type value = top->data; \
*stack = top->next; \
free(top); \
return value; \
} \

Defining the STACK functions (push() and


pop()) declared in the previous step:

5 of 13 7/3/24, 12:08
Generic data structures in C | andreinc https://www.andreinc.net/2010/09/30/generic-data-stru...

#define STACK_DEFINE(type) \
void stack_##type##_push(stack_##type **stack, type data) { \
stack_##type *new_node = malloc(sizeof(*new_node)); \
if (NULL == new_node) { \
fputs("Couldn't allocate memoryn", stderr); \
abort(); \
} \
new_node->data = data; \
new_node->next = *stack; \
*stack = new_node; \
} \
type stack_##type##_pop(stack_##type **stack) { \
if (NULL == stack || NULL == *stack){ \
fputs("Stack underflow", stderr); \
abort(); \
} \
stack_##type *top = *stack; \
type value = top->data; \
*stack = top->next; \
free(top); \
return value; \
} \

Expanding the macro:

6 of 13 7/3/24, 12:08
Generic data structures in C | andreinc https://www.andreinc.net/2010/09/30/generic-data-stru...

/* Expansion if int is supplied as the macro argument */


void stack_int_push(stack_int **stack, int data) {
stack_int *new_node = malloc(sizeof(*new_node));
if (NULL == new_node) {
fputs("Couldn't allocate memoryn", stderr);
abort();
}
new_node->data = data;
new_node->next = *stack;
*stack = new_node;
}
int stack_int_pop(stack_int **stack) {
if (NULL == stack || NULL == *stack) {
fputs("Stack underflow.n", stderr);
abort();
}
stack_int *top = *stack;
int value = top->data;
*stack = top->next;
free(top);
return value;
}

Wrapping the generic functions into macros

#define STACK_TYPE(type) \
stack_##type \

#define STACK_DATA(stack) \
(stack)->data \

#define STACK_PUSH(type, stack, data) \


stack_##type##_push(stack, data) \

#define STACK_POP(type, stack) \


stack_##type##_pop(stack) \

Putting all together

7 of 13 7/3/24, 12:08
Generic data structures in C | andreinc https://www.andreinc.net/2010/09/30/generic-data-stru...

#include <stdio.h>
#include <stdlib.h>

#define STACK_DECLARE(type) \
typedef struct stack_##type##_s { \
type data; \
struct stack_##type##_s *next; \
} stack_##type; \
void stack_##type##_push(stack_##type **stack, type data); \
type stack_##type##_pop(stack_##type **stack); \

#define STACK_DEFINE(type) \
void stack_##type##_push(stack_##type **stack, type data) { \
stack_##type *new_node = malloc(sizeof(*new_node)); \
if (NULL == new_node) { \
fputs("Couldn't allocate memoryn", stderr); \
abort(); \
} \
new_node->data = data; \
new_node->next = *stack; \
*stack = new_node; \
} \
type stack_##type##_pop(stack_##type **stack) { \
if (NULL == stack || NULL == *stack){ \
fputs("Stack underflow", stderr); \
abort(); \
} \
stack_##type *top = *stack; \
type value = top->data; \
*stack = top->next; \
free(top); \
return value; \
} \

#define STACK_TYPE(type) \
stack_##type \

#define STACK_DATA(stack) \
(stack)->data \

#define STACK_PUSH(type, stack, data) \


stack_##type##_push(stack, data) \

8 of 13 7/3/24, 12:08
Generic data structures in C | andreinc https://www.andreinc.net/2010/09/30/generic-data-stru...

#define STACK_POP(type, stack) \


stack_##type##_pop(stack) \

//
// If you want to work with a stack that holds integers you should
// use those macros. They will expand and the associated functions will be
// generated .
//

STACK_DECLARE(int)
STACK_DEFINE(int)
STACK_DECLARE(double)
STACK_DEFINE(double)

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


int i;

//New stack . Alaways assign NULL


STACK_TYPE(int) *st = NULL;
STACK_TYPE(double) *st2 = NULL;

for (i = 0; i < 100; ++i) {


printf("PUSH: %d\n", i);
STACK_PUSH(int, &st, i);
STACK_PUSH(double, &st2, i);
}

while (i--> 0) {
printf("POP: %d %2.2f\n", STACK_POP(int, &st), STACK_POP(double, &st2));
}
return (0);
}

Note: The type argument cannot contain * or spaces. For example, STACK_DECLARE(char*)
won’t work, a typedef should be used instead.

Using the void pointer (void*)


Typecasting is one of the powerful features of C, and it represents the ability to convert between
different type variables.

9 of 13 7/3/24, 12:08
Generic data structures in C | andreinc https://www.andreinc.net/2010/09/30/generic-data-stru...

Not let’s focus on pointers: there are pointers of type int* , char* , or float* , however there’s
a certain pointer that does not have a type known as the void pointer. Any type of pointer can
be cast to a void pointer, and conversely, any void pointer can be cast to a pointer of a type .

Generic data, can be referenced using *void .

typedef struct stack_s {


void *data; // Can reference "anything"
struct stack_s *next; // The stack is built as a linked list
} stack;

The next step would be now to declare & define the functions involved in the stack manipulation:
push() and pop() . Their signatures could look like this:

void stack_push(stack **head, void *data);


void *stack_pop(stack **head);

void stack_push(stack **head, void *data) {


stack *new_node = malloc(sizeof(*new_node));
if (NULL == new_node) {
fputs("Couldn't allocate memory\n", stderr);
abort();
}
new_node->data = data;
new_node->next = *head;
*head = new_node;
}

void *stack_pop(stack **head) {


stack *top;
void *value;
if (NULL == head || NULL == *head) {
fputs("Stack underflow\n", stderr);
abort();
}
top = *head;
value = top->data;
*head = top->next;
free(top);
return value;
}

10 of 13 7/3/24, 12:08
Generic data structures in C | andreinc https://www.andreinc.net/2010/09/30/generic-data-stru...

Main method:

int main() {
stack *s = NULL;
int i, *tmp;

/* Add values from [1..100] into the stack */


printf("Pushing: \n");

for (i = 0; i < 100; ++i) {


tmp = malloc(sizeof (*tmp));
if (NULL == tmp) {
fputs("Couldn't allocate memory \n", stderr);
abort();
}
*tmp = i;
printf("%d ", *tmp);
stack_push(&s, tmp);
}

// Remove all elements of the stack

printf("\nPopping: \n");
while(i-->0){
tmp = stack_pop(&s);
printf("%d \n", *tmp);
free(tmp);
}

return (0);
}

Putting all together:

11 of 13 7/3/24, 12:08
Generic data structures in C | andreinc https://www.andreinc.net/2010/09/30/generic-data-stru...

#include <stdio.h>
#include <stdlib.h>

typedef struct stack_s {


void *data; // Can reference "anything"
struct stack_s *next; // The stack is built as a linked list
} stack;

void stack_push(stack **head, void *data);


void *stack_pop(stack **head);

void stack_push(stack **head, void *data) {


stack *new_node = malloc(sizeof(*new_node));
if (NULL == new_node) {
fputs("Couldn't allocate memoryn", stderr);
abort();
}
new_node->data = data;
new_node->next = *head;
*head = new_node;
}

void *stack_pop(stack **head) {


stack *top;
void *value;
if (NULL == head || NULL == *head) {
fputs("Stack underflow\n", stderr);
abort();
}
top = *head;
value = top->data;
*head = top->next;
free(top);
return value;
}

int main() {
stack *s = NULL;
int i, *tmp;

/* Add values from [1..100] into the stack */


printf("Pushing: \n");

12 of 13 7/3/24, 12:08
Generic data structures in C | andreinc https://www.andreinc.net/2010/09/30/generic-data-stru...

for (i = 0; i < 100; ++i) {


tmp = malloc(sizeof (*tmp));
if (NULL == tmp) {
fputs("Couldn't allocate memory\n", stderr);
abort();
}
*tmp = i;
printf("%d ", *tmp);
stack_push(&s, tmp);
}

// Remove all elements of the stack

printf("\nPopping: n");
while(i-->0){
tmp = stack_pop(&s);
printf("%d ", *tmp);
free(tmp);
}

return (0);
}

 Updated: September 30, 2010

COMMENTS

13 of 13 7/3/24, 12:08

You might also like