I decided to learn generic programming in C (and take a break from templates in C++) by implementing my first simple data structure in C: the singly linked list.
What I'm looking to gain out of reviews is:
- Clarity/quality
- Algorithmic correctness
- Code style for C
- Structure and design
- Any bugs or issues you can find
I'm planning to use this as a basis for implementing a hash-map in C that uses chaining.
slist.h
#ifndef C_SLIST_H
#define C_SLIST_H
#include <stdbool.h>
#include <stddef.h>
struct slist_node;
struct slist;
typedef bool (*func_cmp)(const void *a, const void *b);
struct slist *slist_init(func_cmp cmp);
void slist_insert(struct slist *, void *);
void slist_free(struct slist *);
bool slist_empty(const struct slist *);
void *slist_value(const struct slist_node *);
struct slist_node *slist_find(const struct slist *, void *);
void clear(struct slist *);
size_t slist_size(const struct slist *);
void slist_remove(struct slist *, void *);
#endif
slist.c
#include <stdlib.h>
#include "slist.h"
struct slist_node {
struct slist_node *next;
void *data;
};
struct slist {
struct slist_node *head, *tail;
func_cmp compare;
};
static struct slist_node *create_node(void *value)
{
struct slist_node *node = calloc(1, sizeof(*node));
if (!node) { return NULL; }
node->data = value;
return node;
}
static bool default_compare(const void *a, const void *b)
{
return a == b;
}
struct slist *slist_init(func_cmp func)
{
struct slist *l = calloc(1, sizeof(struct slist));
l->compare = func ? func : default_compare;
return l;
}
void slist_insert(struct slist *l, void *value)
{
if (!l) { return; }
if (slist_empty(l)) {
l->head = create_node(value);
if (!l->head) { return; }
l->tail = l->head;
return;
}
l->tail->next = create_node(value);
if (!l->tail->next) { return; }
if (l->tail->next) {
l->tail = l->tail->next;
}
}
void clear(struct slist *l)
{
if (!l) { return; }
struct slist_node *copy = l->head;
while (copy) {
struct slist_node *after = copy->next;
free(copy);
copy = after;
}
l->head = NULL;
l->tail = NULL;
}
void slist_free(struct slist *l)
{
clear(l);
}
bool slist_empty(const struct slist *l)
{
return l ? l->head == NULL : true;
}
void *slist_value(const struct slist_node *n)
{
return n ? n->data : NULL;
}
size_t slist_size(const struct slist *l)
{
if (!l || slist_empty(l)) { return 0; }
const struct slist_node *copy = l->head;
size_t size = 0;
while (copy) {
++size;
copy = copy->next;
}
return size;
}
static struct slist_node *slist_find_ahead(const struct slist *l, void *value)
{
struct slist_node *copy = l->head;
while (copy) {
if ((l->compare)(copy->next->data, value)) {
return copy;
} else {
copy = copy->next;
}
}
return NULL;
}
struct slist_node *slist_find(const struct slist *l, void *value)
{
if (!l || slist_empty(l)) { return NULL; }
if ((l->compare)(l->head->data, value)) { return l->head; }
const struct slist_node *found = slist_find_ahead(l, value);
return found ? found->next : NULL;
}
void slist_remove(struct slist *l, void *value)
{
if (!l || slist_empty(l)) { return; }
if (l->head->data == value) {
struct slist_node *copy = l->head;
if (l->tail == l->head) {
l->tail = copy->next;
}
l->head = copy->next;
free(copy);
return;
}
struct slist_node *ahead = slist_find_ahead(l, value);
if (!ahead) { return; }
if (ahead->next == l->tail) {
l->tail = ahead;
}
struct slist_node *to_delete = ahead->next;
ahead->next = ahead->next->next;
free(to_delete);
}