Ginger is a simple library written to make C programmers' lives easier by augmenting the C standard library with useful utilities that are sorely lacking.

Build Status (Travis CI) Build Status (AppVeyor)

Getting Started

Installation

To build ginger, you will need to have CMake. For most you can use your package manager, e.g. apt-get, pacman or yum on Linux or homebrew, macports or fink on OS X.

Linux, OS X, and Windows (Bash, MinGW and Cygwin)

Once CMake is installed, building, testing and installing the library is a snap

λ cmake . -DCMAKE_BUILD_TYPE=Release -DEXAMPLES=Yes
λ make all tests
λ sudo make install

Windows with MSVC

Building with MSVC is a bit more involved. Open the Visual C++ MSBuild command prompt (should be in your start menu). You can then run cmake build and test from the prompt:

λ cmake -DCMAKE_BUILD_TYPE=Release -G"Visual Studio 14 2015" -DEXAMPLES=Yes
λ msbuild /p:Configuration=Release ALL_BUILD.vcxproj
λ test\Release\ginger_unittest.exe

Installation requires the user to manually copy the headers and libraries to wherever the user would like.

Getting Help

Ginger is developed to help you write better C code faster, but I can’t do it without your feedback. I host the project’s source code and issue tracker on GitHub. Please create an issue if you find a bug, an error in this documentation, or have a feature you’d like to request.

Copyright © 2016-2017 Douglas G. Moore. Free use of this software is granted under the terms of the MIT License.

See the LICENSE file for details.

Dynamics Arrays

Ginger provides a "fat pointer" implementation of dynamic arrays called gvector. This means that ginger’s dynamic arrays look and feel like C-style arrays, but have additional structural informational such as their capacity, length and element size.

Type Definitions

typedef void* gvector;
typedef void const* gvector_const;

Ginger’s vector type is implemented as a fat-pointer. When a gvector is allocated, its capacity, length and element size are stored in a block of memory near the array. A pointer to the first element of the vector can then be used to retrieve the structural information.

The gvector type is a void-pointer to allow for somewhat generic operations. However, this choice makes writing some functions, such as generic printf-type function, infeasible.

#define gvector_cap(v)
#define gvector_len(v)
#define gvector_size(v)

Get the capacity, length or element size of a vector v.

Example:

double *xs = gvector_alloc(5, 2, sizeof(double));
printf("Capacity: %ld\n", gvector_cap(xs));
printf("Length: %ld\n", gvector_len(xs));
printf("Element size: %ld\n", gvector_size(xs));
gvector_free(xs);
#define gvector_isempty(v)

Determine if a vector v is empty. This is equivalent to gvector_len(v) == 0.

Example:

double *xs = gvector_alloc(5, 0, sizeof(double));
printf("Is Empty?: %d", gvector_isempty(xs)); // Is Empty?: 1
gvector_push(xs, 0);
printf("Is Empty?: %d", gvector_isempty(xs)); // Is Empty?: 0
gvector_free(xs);

Memory Management

gvector gvector_alloc(size_t capacity, size_t length, size_t size);

Allocate an uninitialized gvector with a given capacity, length and element size.

Exceptional Cases:

  1. If length > capacity, then the length is overridden by the capacity.

  2. If size == 0, then no memory is allocated and NULL is returned.

  3. If memory allocation fails, then NULL is returned.

Example:

char *xs = gvector_alloc(5, 3, sizeof(char));
assert(xs != NULL);
memset(xs, '\0', gvector_cap(xs) * gvector_size(xs));
assert(gvector_cap(xs) == 5);
assert(gvector_len(xs) == 3);
assert(gvector_size(xs) == sizeof(char));
gvector_free(xs)
void gvector_free(gvector v);

Free any memory associated with the vector v. This function is a noop when v == NULL.

Example:

gvector_free(NULL); // NOOP

int *xs = gvector_alloc(5, 0, sizeof(int));
gvector_free(xs); // succeeds even if xs == NULL
gvector gvector_dup(gvector_const v);

Duplicate a vector v and return the new gvector.

Exceptional Cases:

  1. This is a noop if v == NULL.

  2. If memory allocation fails, NULL is returned.

Example:

gvector_dup(NULL); // NOOP

char *xs = gvector_alloc(5, 5, sizeof(char));
assert(xs);
memset(xs, 'a', gvector_cap(xs) * gvector_size(xs));

char *ys = gvector_dup(xs);
assert(ys);
for (size_t i = 0; i < gvector_len(ys); ++i) printf("%c", ys[i]); // aaaaa
gvector_free(ys);
gvector_free(xs);
size_t gvector_copy(gvector dst, gvector_const src);

Copy the contents of one vector src to another dst, and return the number of elements copied.

The number of elements copied will be the smaller of the two lengths, and the size of dst will not change.

Exceptional Cases:

  1. This is a noop if either dst == NULL or src == NULL.

  2. This is a noop if the element sizes of dst and src differ. NULL is returned.

  3. If memory allocation fails, NULL is returned.

Example:

int *xs = NULL, *ys = NULL;

gvector_copy(ys, xs); // NOOP

xs = gvector_alloc(5, 5, sizeof(int));
for (size_t i = 0; i < gvector_len(xs); ++i) xs[i] = i;
gvector_copy(ys, xs); // NOOP since ys == NULL
gvector_copy(xs, ys); // NOOP since ys == NULL

ys = gvector_alloc(3, 3, sizeof(int));
assert(gvector_copy(ys, xs) == 3); // 3 elements are copied from xs to ys
for (size_t i = 0; i < gvector_len(ys); ++i) printf("%d ", ys[i]); // 0 1 2

for (size_t i = 0; i < gvector_len(ys); ++i) ys[i] = -i;
assert(gvector_copy(xs, ys) == 3); // 3 elements are copied from ys to xs
for (size_t i = 0; i < gvector_len(xs); ++i) printf("%d ", xs[i]); // 0 -1 -2 3 4

gvector_free(ys);
gvector_free(xs);
gvector gvector_reserve(gvector v, size_t capacity);

Change the capacity of the vector v, returning a pointer to the newly resized gvector.

Exceptional Cases:

  1. This is a noop if v == NULL.

  2. If the reallocation failed, NULL is returned.

Example:

gvector_reserve(NULL, 0); // NOOP

char *xs = gvectr_alloc(3, 3, sizeof(char));
memset(xs, 'a', 3);

xs = gvector_reserve(xs, 6);
assert(gvector_cap(xs) == 6);
assert(gvector_len(xs) == 3);
assert(strncmp(xs, "aaa", 3) == 0);

xs = gvector_reserve(xs, 0);
assert(gvector_cap(xs) == 0);
assert(gvector_len(xs) == 0);

gvector_free(xs);
gvector gvector_shrink(gvector v);

Reduce the capacity of the vector v to its length, and return the resized gvector.

Exceptional Cases:

  1. If the reallocation failed, NULL is returned.

Examples:

gvector_shrink(NULL); // NOOP

char *xs = gvector_alloc(5, 3, sizeof(char));
memset(xs, 'a', gvector_len(xs) * gvector_size(xs));

xs = gvector_shrink(xs);
assert(gvector_cap(xs) == gvector_len(xs));
for (size_t i = 0; i < gvector_len(xs); ++i) printf("%c", xs[i]); // aaa

gvector_free(xs);

Stack Operations

#define gvector_push(v, x)

Push an element x onto the back of the vector v. The capacity of the vector is doubled if the length exceeds the current capacity.

Exceptional Cases:

  1. If v == NULL, a segmentation fault is raised.

  2. If the resizing fails, then a segmentation fault is raised.

Example:

gvector_push(NULL, 0); // SIGSEGV

int *xs = gvector_alloc(0, 0, sizeof(int))
for (size_t i = 0; i < 5; ++i) gvector_push(xs, i);
assert(gvector_cap(xs) == 8);
assert(gvector_len(xs) == 5);
for(size_t i = 0; i < gvector_len(xs); ++i) printf("%d ", xs[i]); // 0 1 2 3 4
gvector_free(xs);
#define gvector_pop(v)

Pop an element off of the back of the vector v. The capacity is left unchanged, and the length is decremented. The value of element is discarded.

Example:

gvector_pop(NULL); // NOOP

int *xs = gvector_alloc(3, 0, sizeof(int));
gvector_pop(NULL); // NOOP
gvector_free(xs);

xs = gvector_alloc(5, 5, sizeof(int));
for (size_t i = 0; i < gvector_len(xs); ++i) xs[i] = i;
for (size_t i = 0; i < gvector_len(xs); ++i) printf("%d ", xs[i]); // 0 1 2 3 4

gvector_pop(xs);
gvector_pop(xs);
assert(gvector_cap(xs) == 5);
assert(gvector_len(xs) == 3);
for (size_t i = 0; i < gvector_len(xs); ++i) printf("%d ", xs[i]); // 0 1 2

gvector_free(xs);

Unit Testing Framework

Ginger provides a simple unit testing framework, called unit. Unit lets the user create unit tests, test suites, and simplifies creating a single unit testing executable. In this section we’ll walk through the recommended file structure for using unit. Ginger uses unit for its own unit testing, so a working example can be found in test.

Examples

A Single Test Suite

We start by creating and registering a canary test suite, and setting up a basic entry point for our test executable.

main.c
#include <ginger/unit.h>

UNIT(CanaryTest) {
    ASSERT_EQUAL(5, 1 + 2); // Should fail
}

BEGIN_SUITE(CanarySuite)
    ADD_UNIT(CanaryTest)
END_SUITE

BEGIN_REGISTRATION
    REGISTER(CanarySuite)
END_REGISTRATION

UNIT_MAIN()

We can then compile and run our unit tests

λ gcc -std=c11 -o unittest main.c
λ ./unittest
[SUITE] CanarySuite
  [TEST] CanaryTest main.c:4 - expected 5, got 3 [FAIL]
RESULTS: 1 tests (0 ok, 1 failed)

Since unit is a header-only utility, there is no need to link against the ginger library. However, you will need to make sure the ginger/unit.h header is in your include path.

Additional Test Suites

Now, let’s add another suite, Vector, to test ginger’s dynamic array type gvector.

vector.c
#include <ginger/vector.h>
#include <ginger/unit.h>

UNIT(VectorAlloc) {
    int *xs = gvector_alloc(5, 3, sizeof(int));
    ASSERT_NOT_NULL(xs);
    ASSERT_EQUAL_U(5, gvector_cap(xs));
    ASSERT_EQUAL_U(3, gvector_len(xs));
    ASSERT_EQUAL_U(sizeof(int), gvector_size(xs));
    gvector_free(xs);
}

BEGIN_SUITE(Vector)
    ADD_UNIT(VectorAlloc)
END_SUITE

Once we’ve created the new suite, we need to register it. We use the IMPORT_SUITE macro to import the suite, and modify the registration section of main.c.

IMPORT_SUITE(Vector);

BEGIN_REGISTRATION
    REGISTER(CanarySuite)
    REGISTER(Vector)
END_REGISTRATION

We can now compile and run the unit tests. Note that we are now linking against libginger because we’re using ginger’s vector functionality.

λ gcc -std=c11 -o unittest main.c vector.c -lginger
λ ./unittest
[SUITE] CanarySuite
  [TEST] CanaryTest main.c:4 - expected 5, got 3 [FAIL]
[SUITE] Vector
  [TEST] VectorAlloc  [OK]
RESULTS: 2 tests (1 ok, 1 failed)

Test Suite

#define UNIT(TEST_NAME)
#define BEGIN_SUITE(SUITE_NAME)
#define ADD_UNIT(TEST_NAME)
#define END_SUITE

These macros are used to create unit tests (UNIT), test suites (BEGIN_SUITE, END_SUITE), add add those tests to the suite (ADD_UNIT).

Example:

#include <ginger/unit.h>

UNIT(CanaryTest) {
    ASSERT_EQUAL(3, 1 + 2);
}

BEGIN_SUITE(CanarySuite)
    ADD_UNIT(CanaryTest)
END_SUITE

Entry Point

#define IMPORT_SUITE(SUITE_NAME)
#define BEGIN_REGISTRATION
#define REGISTER(SUITE_NAME)
#define END_REGISTRATION
#define UNIT_MAIN()

These macros are used to import and register test suites with the main entry point. The UNIT_MAIN creates a standard main function for the executable.

Example:

#include <ginger/unit.h>

IMPORT_SUITE(CanaryTest);

BEGIN_REGISTRATION
    REGISTER(CanaryTest)
END_REGISTRATION

UNIT_MAIN()

Assertions

#define ASSERT_TRUE(real)
#define ASSERT_FALSE(real)

Assert that an expression evaluates to true or false.

Example:

ASSERT_TRUE(true);
ASSERT_FALSE(false);

ASSERT_TRUE(0); // FAIL
ASSERT_FALSE(1); //FAIL
#define ASSERT_EQUAL(exp, real)
#define ASSERT_NOT_EQUAL(nexp, real)
#define ASSERT_EQUAL_U(exp, real)

Assert that two integral values are (not) equal. ASSERT_EQUAL_U performs unsigned comparisons.

Example:

ASSERT_EQUAL(3, 1 + 2);
ASSERT_NOT_EQUAL(5, 3);
#define ASSERT_EQUAL_P(exp, real)
#define ASSERT_NULL(real)
#define ASSERT_NOT_NULL(real)

Compare pointers for equality or ensure that a pointer is (not) NULL.

Example:

int x = 5;
int *a = &x, *b = &x, *c = NULL;

ASSERT_EQUAL_P(a, b);
ASSERT_NULL(c);
ASSERT_NOT_NULL(a);
#define ASSERT_NAN(real)
#define ASSERT_NOT_NAN(real)
#define ASSERT_INF(real)
#define ASSERT_NOT_INF(real)

#define ASSERT_DBL_NEAR(exp, real)
#define ASSERT_DBL_NEAR_TOL(exp, real, tol)

Test floating-point values. Floating-point comparisions are tricky. The best we can do is compare for equality up to some tolerance. If the user would like to specify that tolerance, they should use ASSERT_DBL_NEAR_TOL. ASSERT_DBL_NEAR uses a tolerance equal to the machine epsilon DBL_EPSILON.

ASSERT_DBL_NEAR and ASSERT_DBL_NEAR_TOL fail if real is either NAN or INF.

Example:

ASSERT_NAN(0.0/0.0);
ASSERT_INF(5.0/0.0);

ASSERT_DBL_NEAR(0.5, 1.0 / 2.0);
ASSERT_DBL_NEAR_TOL(0.5, 0.5001, 1e-3);

ASSERT_DBL_NEAR(0.5, NAN); // FAILS
ASSERT_DBL_NEAR(0.5, INF); // FAILS
#define ASSERT_DBL_ARRAY_NEAR(exp, real, n)
#define ASSERT_DBL_ARRAY_NEAR_TOL(exp, real, n, tol)

These macros compare arrays of floating-point values, and essentially amount to iterated applications of the ASSERT_DBL_NEAR or ASSERT_DBL_NEAR_TOL.

Example:

#include <math.h>

double expect[6] = {0.0, 1.0, 1.414214, 1.732051, 2.0, 2.236068};
double got[6] = {sqrt(0.), sqrt(1.), sqrt(2.), sqrt(3.), sqrt(4.), sqrt(5.)};

ASSERT_DBL_ARRAY_NEAR(expect, got, 6); // FAILS DUE TO PRECISION
ASSERT_DBL_ARRAY_NEAR_TOL(expect, got, 6, 1e-6); // SUCCEEDS
#define ASSERT_SIGNAL(sig, code)

Assert that a signal is raised a code snippet.

Example:

#include <ginger/vector.h>

int *xs = NULL;
ASSERT_SIGNAL(SIGSEGV, gvector_push(xs, 5));