Overview

Summary

The C-Craft Generic Collections Library is a set of generic containers for use in C applications. It uses a rudimentary #define mechanism to simulate templated code, and is able to generate lists, vectors and maps for all basic types as well as user defined custom types. It does not require the use of casting to void * and so is able to preserve type checking.

Features

  • Lists

  • Vectors

  • Maps

  • Stacks

  • Queues

  • Supports primitive and custom types

Here is an example code snippet using a list of doubles.

#include <list-double.h>

double x = 17.256;

MEM_SCOPE mem = sm_create(0);
LIST_DOUBLE l = list_double_create(mem);
list_double_insert(l, x);

/* ...  do some work etc... */

sm_free(mem);

Memory Scopes

The C-Craft collections library uses scoped memory. When a container is created a memory scope must be provided. This will be used by the collection for all internal allocations, and can be used for allocation of container elements. Using this strategy all memory can be freed with a single call when the collection is finished with. For example:

#include <c-craft/mem.h>
#include "stack-person.h"

typedef struct person
{
    char *name;
    char *address;
}
PERSON;

MEM_SCOPE mem = sm_create(0);

/* A stack with 10 initial slots. Slots expand on demand. */
STACK_PERSON stk = stack_person_create(mem, 10);

PERSON *person = sm_alloc(mem, sizeof(person));
person->name = sm_strdup(mem, "Jim Smith");
person->address = sm_strdup(mem, "10 Acacia Avenue");

stack_person_push(stk, person);

person = sm_alloc(mem, sizeof(person));
person->name = "Rose Jones";
person->address = "5 High Street"

stack_person_push(stk, person);

/* ...  do some work etc... */

sm_free(mem); /* Frees persons, names and addresses also */

Note that there is no need to track which elements are allocated, or which are constants. This is a feature of scoped memory.

Generics

This is a C library and does not require a C++ compiler. It is still possible to provide generic code for user defined types, however. Here is an example definition of a vector of unsigned char *:

/* vector-data.h */

#ifndef __VECTOR_DATA__
#define __VECTOR_DATA__

#define TYPE unsigned char *
#define NAME data
#define UNAME DATA

#include <c-craft/vector-decl.h>

#undef TYPE
#undef NAME
#undef UNAME

#endif /* __VECTOR_DATA__ */

and the implementation:

/* vector-data.c */

#include "vector-data.h"

#define TYPE unsigned char *
#define NAME data
#define UNAME DATA

#include <c-craft/vector-impl-c.h>

#undef TYPE
#undef NAME
#undef UNAME

Note that the ‘vector-decl.h’ and ‘vector-impl-c.h’ files are the templated code, part of the C-Craft collections library. These example files, ‘vector-data.h’ and ‘vector-data.c’ would form part of your project in order to instantiate implementations of the templated code, and illustrate how easy it is to establish collections of any type, even custom types.

The #defines are the parameters for the templates and are documented in the reference section of this guide.

To use the above implementation:

#include <c-craft/mem.h>
#include "vector-data.h"

MEM_SCOPE mem = sm_create(0);
VECTOR_DATA vector = vector_data_create(mem, 16);

unsigned char data0[] = { 0x01, 0xD3, 0x0A, 0xFF };
unsigned char data1[] = { 0x02, 0xD4, 0x0A, 0xFF };
unsigned char data2[] = { 0x03, 0xEE, 0xFF };

vector_data_append(vector, data0);
vector_data_append(vector, data1);
vector_data_append(vector, data2);

/* ...  do some work etc... */

sm_free(mem);

Note, to run you must link with the compiled version of ‘vector-data.c’ above.

The C-Craft Collections module provides most of the common types for you ready to use.

Atoms

The standard C library ‘strcmp’ family of functions allow to compare two strings. However, to compare two string requires testing every character at least until a difference is found. This can be avoided by using atoms. Most compilers allocate the same space for two identical strings declared as const char * resulting in the same pointer value, but this cannot be relied on. An atomised string does the same thing and is guaranteed to be always the same pointer for identical strings. Furthermore non-const strings can be atomised.

#include <c-craft/atom.h>

MEM_SCOPE mem = sm_create(0);
ATOM atom = atom_create(mem);

char key[4];
strcpy(key, "Key");

const char *key1 = atom_str(atom, "Key");
const char *key2 = atom_str(atom, key);

/* key1 and key2 are the same pointer value. */
if(key1 == key2)
    printf("Atomised strings!\n");

else if(strcmp(key1, key2) == 0)
    printf("Strings are the same content, but two separate copies.\n");

else
    printf("Strings are not the same content.\n");

Atom strings are unique within the ATOM scope, so the same scope should be used for all atoms you want atomised. This segregation behaviour is optional. Global atom space is also available, simplifying the usage.

#include <c-craft/atom.h>

const char *atom = atom("My atom");

Atoms are useful as keys for maps, since the lookup comparison can be a straightforward equality operation, ‘==’, rather than a ‘strcmp’.