Compare commits

...

6 Commits

14 changed files with 320 additions and 225 deletions
+9 -1
View File
@@ -5,13 +5,21 @@ A small, personal C library for learning and experimentation.
## Features
All the features listed are Thread-safe.
Most core containers use internal locks for single-object operations.
Cross-object operations can still require caller-side synchronization.
- Hashmaps
- Strings
- Circular queues
- Vectors
- Stack
- Set
### Notes
- Hashmap keys/values are copied as raw bytes (`memcpy`) using `key_size`/`value_size`.
- For C-string keys, provide a readable key buffer of at least `key_size` bytes.
- The set module is basic and intentionally minimal.
## Installation
+2
View File
@@ -100,6 +100,8 @@ void hm_free_bucket(bucket_s *bucket);
* If the key already exists, the old value is freed (if free_value_fn is provided)
* and replaced with the new value. If the key doesn't exist, a new entry is created.
* Both key and value are copied into the hashmap using memcpy.
* The caller must pass buffers that are readable for at least key_size/value_size bytes.
* For C-string keys, use a fixed-size key buffer (or set key_size to the exact copied size).
*
* @param[in] hashmap Pointer to the hash map.
* @param[in] key Pointer to the key to insert (will be copied, must not be NULL).
+8 -9
View File
@@ -14,7 +14,7 @@ add_global_arguments('-D_POSIX_C_SOURCE=200112L', language: 'c')
lib_src = files(
'hashmap/myhashmap.c',
'queue/myqueue.c',
'socket/mysocket.c',
'set/myset.c',
'stack/mystack.c',
'string/mystring.c',
'vector/myvector.c',
@@ -24,7 +24,7 @@ lib_src = files(
test_src = files(
'test/hashmap/hm1.c',
'test/queue/queue1.c',
'test/socket/socket1.c',
'test/set/set1.c',
'test/stack/stack1.c',
'test/string/str1.c',
'test/string/str2.c',
@@ -33,11 +33,8 @@ test_src = files(
'test/vector/vec1.c',
)
# Windows Socket lib
winsock_dep = cc.find_library('ws2_32', required: false)
# Include directories
inc_dir = include_directories('string', 'queue', 'hashmap', 'vector', 'stack', 'socket')
inc_dir = include_directories('string', 'queue', 'hashmap', 'vector', 'stack', 'set')
if host_machine.system() == 'windows'
win_inc_dir = include_directories('c:/include/')
else
@@ -60,17 +57,19 @@ install_headers(
'queue/myqueue.h',
'string/mystring.h',
'vector/myvector.h',
'set/myset.h',
'stack/mystack.h',
'socket/mysocket.h',
],
subdir: 'myclib',
)
# Test executable
executable(
testlib_exe = executable(
'testlib',
lib_src + test_src,
test_src,
include_directories: [inc_dir, win_inc_dir],
dependencies: winsock_dep,
link_with: myclib_lib,
)
test('testlib', testlib_exe)
+129 -6
View File
@@ -1,6 +1,30 @@
#include "myset.h"
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
static int set_find_index_locked(set_s *set, void *elem, size_t *index_out) {
size_t size = vec_size(set->data);
for (size_t i = 0; i < size; ++i) {
void *current = vec_get(set->data, i);
if (current == NULL) {
return -1;
}
int equal = (memcmp(current, elem, set->elem_size) == 0);
free(current);
if (equal) {
*index_out = i;
return 0;
}
}
*index_out = SIZE_MAX;
return 0;
}
set_s *set_new(size_t element_size) {
if (element_size == 0) {
@@ -12,17 +36,116 @@ set_s *set_new(size_t element_size) {
return NULL;
}
// TODO: think about how to deal the free_value for each element
if (mtx_init(&set->lock, mtx_plain) != thrd_success) {
free(set);
return NULL;
}
set->data = vec_new(8, element_size);
if (set->data == NULL) {
mtx_destroy(&set->lock);
free(set);
return NULL;
}
set->elem_size = element_size;
return set;
}
int set_add(set_s *set, void *elem);
int set_add(set_s *set, void *elem) {
if (set == NULL || elem == NULL) {
return -1;
}
int set_remove(set_s *set, void *elem);
if (mtx_lock(&set->lock) != thrd_success) {
return -1;
}
int set_clear(set_s *set);
size_t index;
if (set_find_index_locked(set, elem, &index) != 0) {
mtx_unlock(&set->lock);
return -1;
}
int set_contains(set_s *set, void *elem);
if (index != SIZE_MAX) {
mtx_unlock(&set->lock);
return 0;
}
void set_free(set_s *set);
int ret = vec_push(set->data, elem);
mtx_unlock(&set->lock);
return ret;
}
int set_remove(set_s *set, void *elem) {
if (set == NULL || elem == NULL) {
return -1;
}
if (mtx_lock(&set->lock) != thrd_success) {
return -1;
}
size_t index;
if (set_find_index_locked(set, elem, &index) != 0) {
mtx_unlock(&set->lock);
return -1;
}
if (index == SIZE_MAX) {
mtx_unlock(&set->lock);
return -1;
}
int ret = vec_remove(set->data, index);
mtx_unlock(&set->lock);
return ret;
}
int set_clear(set_s *set) {
if (set == NULL) {
return -1;
}
if (mtx_lock(&set->lock) != thrd_success) {
return -1;
}
int ret = vec_clear(set->data);
mtx_unlock(&set->lock);
return ret;
}
int set_contains(set_s *set, void *elem) {
if (set == NULL || elem == NULL) {
return -1;
}
if (mtx_lock(&set->lock) != thrd_success) {
return -1;
}
size_t index;
if (set_find_index_locked(set, elem, &index) != 0) {
mtx_unlock(&set->lock);
return -1;
}
mtx_unlock(&set->lock);
return (index == SIZE_MAX) ? 0 : 1;
}
void set_free(set_s *set) {
if (set == NULL) {
return;
}
vec_free(set->data);
mtx_destroy(&set->lock);
free(set);
}
+10 -4
View File
@@ -1,27 +1,33 @@
#ifndef MYCLIB_SET_H
#define MYCLIB_SET_H
// TODO: WIP
#include <stddef.h>
#include <threads.h>
#include "../hashmap/myhashmap.h"
#include "../vector/myvector.h"
typedef struct set {
hashmap_s *map;
vec_s *data;
size_t elem_size;
mtx_t lock;
} set_s;
/* Create a new set for fixed-size elements. */
set_s *set_new(size_t element_size);
/* Add a new element to the set (no-op if already present). */
int set_add(set_s *set, void *elem);
/* Remove an element from the set. */
int set_remove(set_s *set, void *elem);
/* Remove all elements from the set. */
int set_clear(set_s *set);
/* Returns 1 if present, 0 if absent, -1 on error. */
int set_contains(set_s *set, void *elem);
/* Free the set and all related resources. */
void set_free(set_s *set);
#endif
-90
View File
@@ -1,90 +0,0 @@
#include "mysocket.h"
#include <errno.h>
int sock_platform_init(void) {
#ifdef _WIN32
WSADATA wsaData;
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
return -1;
}
if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2) {
WSACleanup();
return -1;
}
#endif
return 0;
}
int sock_close(socket_t socket) {
int ret = 0;
#ifdef _WIN32
ret = closesocket(socket);
#else
ret = close(socket);
#endif
return ret;
}
int sock_platform_shutdown(void) {
#ifdef _WIN32
WSACleanup();
#endif
return 0;
}
int sock_readall(socket_t sockfd, void *buf, size_t bufsize) {
char *p = (char *)buf;
size_t total_read = 0;
while (total_read < bufsize) {
int n = recv(sockfd, p, bufsize - total_read, 0);
if (n > 0) {
/* Data received */
p += n;
total_read += n;
} else if (n == 0) {
/* Connection closed */
break;
} else {
/* Error */
if (errno == EINTR) {
/* Try again */
continue;
} else if (errno == EAGAIN || errno == EWOULDBLOCK) {
/* Socket non-blocking, no data right now */
/* Returns what has read */
return total_read;
} else {
return -1;
}
}
}
return total_read;
}
int sock_writeall(socket_t socket, const void *buf, size_t n) {
const char *p = (const char *)buf;
size_t bytes_to_write = n;
int bytes_written;
while (bytes_to_write > 0) {
bytes_written = send(socket, p, bytes_to_write, 0);
if (bytes_written >= 0) {
p += bytes_written;
bytes_to_write -= bytes_written;
} else {
if (errno == EINTR) {
continue;
} else if (errno == EAGAIN || errno == EWOULDBLOCK) {
return n - bytes_to_write;
} else {
return -1;
}
}
}
return n;
}
-36
View File
@@ -1,36 +0,0 @@
#ifndef MYCLIB_SOCKET_H
#define MYCLIB_SOCKET_H
#include <stddef.h>
#ifdef _WIN32
#include <winsock2.h>
#include <ws2tcpip.h>
typedef int socket_t;
#else
#include <sys/socket.h>
#include <unistd.h>
typedef int socket_t;
#endif
/* Initialize the socket system */
int sock_platform_init(void);
/* Close a socket */
int sock_close(socket_t socket);
/* Clean the socket system */
int sock_platform_shutdown(void);
/*
* Read 'bufsize' bytes from socket
* Returns the read bytes, -1 on failure, 0 connection closed without issues
*/
int sock_readall(socket_t sockfd, void *buf, size_t bufsize);
/*
* Writes 'n' bytes to socket
*/
int sock_writeall(socket_t socket, const void *buf, size_t n);
#endif
+10 -1
View File
@@ -21,6 +21,7 @@ stack_s *stack_new(size_t initial_capacity, size_t element_size) {
/* Allocate the vec data */
stack->data = vec_new(initial_capacity, element_size);
if (stack->data == NULL) {
mtx_destroy(&stack->lock);
free(stack);
return NULL;
}
@@ -71,7 +72,14 @@ void *stack_top(stack_s *stack) {
return NULL;
}
void *elem = vec_get(stack->data, stack->data->size - 1);
size_t size = vec_size(stack->data);
if (size == 0) {
mtx_unlock(&stack->lock);
return NULL;
}
void *elem = vec_get(stack->data, size - 1);
mtx_unlock(&stack->lock);
@@ -84,5 +92,6 @@ void stack_free(stack_s *stack) {
}
vec_free(stack->data);
mtx_destroy(&stack->lock);
free(stack);
}
+6 -4
View File
@@ -53,7 +53,7 @@ void test_hm1(void) {
/* Pass your custom hash, equal and free functions */
/* This hashmap will contain names as keys and a custom type as value */
size_t key_size = sizeof(char) * MAX_STR_LEN;
size_t value_size = sizeof(int) + sizeof(char) * MAX_STR_LEN;
size_t value_size = sizeof(struct my_custom_type);
hashmap_s *map =
hm_new(my_hash_func, my_equal_fun, my_free_key, my_free_value, key_size, value_size);
assert(map != NULL);
@@ -65,11 +65,13 @@ void test_hm1(void) {
strncpy(p1.favourite_brand, "Ferrari", sizeof(p1.favourite_brand));
/* Insert a new pair */
assert(hm_set(map, "John", &p1));
char john_key[MAX_STR_LEN] = {0};
strncpy(john_key, "John", sizeof(john_key) - 1);
assert(hm_set(map, john_key, &p1));
/* Retrieve the data */
/* Remember to free the value from the get function */
bucket_s *john = hm_get(map, "John");
bucket_s *john = hm_get(map, john_key);
assert(john != NULL);
char *name = (char *)john->key;
@@ -85,7 +87,7 @@ void test_hm1(void) {
hm_free_bucket(john);
/* Remove a key from hash map */
assert(hm_remove(map, "John"));
assert(hm_remove(map, john_key));
/* Deallocate */
hm_free(map);
+31
View File
@@ -0,0 +1,31 @@
#include "../set/myset.h"
#include <assert.h>
void test_set1(void) {
set_s *set = set_new(sizeof(int));
assert(set != NULL);
int a = 10;
int b = 20;
int another_a = 10;
int missing = 99;
assert(set_add(set, &a) == 0);
assert(set_add(set, &b) == 0);
/* Duplicate values are ignored and treated as success. */
assert(set_add(set, &another_a) == 0);
assert(set_contains(set, &a) == 1);
assert(set_contains(set, &another_a) == 1);
assert(set_contains(set, &missing) == 0);
assert(set_remove(set, &a) == 0);
assert(set_contains(set, &a) == 0);
assert(set_remove(set, &a) == -1);
assert(set_clear(set) == 0);
assert(set_contains(set, &b) == 0);
set_free(set);
}
-43
View File
@@ -1,43 +0,0 @@
#include "../socket/mysocket.h"
#include <arpa/inet.h>
#include <netdb.h>
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
void test_socket1(void) {
sock_platform_init();
struct addrinfo hints, *res, *p;
char ipstr[INET6_ADDRSTRLEN];
memset(&hints, 0, sizeof hints);
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
char hostname[1024];
printf("hostname> ");
fscanf(stdin, "%s", hostname);
if (getaddrinfo(hostname, NULL, &hints, &res) != 0) {
fprintf(stderr, "getaddrinfo() failed\n");
sock_platform_shutdown();
return;
}
for (p = res; p != NULL; p = p->ai_next) {
void *addr = 0;
char *ipver = 0;
struct sockaddr_in *ipv4;
if (p->ai_family == AF_INET) {
ipv4 = (struct sockaddr_in *)p->ai_addr;
addr = &(ipv4->sin_addr);
ipver = "IPv4";
} else {
continue;
}
inet_ntop(p->ai_family, addr, ipstr, sizeof ipstr);
printf("%s: %s\n", ipver, ipstr);
}
freeaddrinfo(res);
sock_platform_shutdown();
}
+3 -3
View File
@@ -17,7 +17,7 @@ void test_vec1(void);
void test_stack1(void);
void test_socket1(void);
void test_set1(void);
int main(void) {
puts("==== [Running Hashmap tests] ====");
@@ -47,8 +47,8 @@ int main(void) {
puts("OK!");
puts("");
puts("==== [Running Socket tests] ====");
test_socket1();
puts("==== [Running Set tests] ====");
test_set1();
puts("OK!");
puts("");
+29
View File
@@ -9,6 +9,7 @@ typedef struct my_elem {
/* Functions used to iterate for each vector's element */
static void multiply(size_t index, void *elem) {
(void)index;
my_elem_s *e = (my_elem_s *)elem;
e->age = e->age * 2;
}
@@ -65,12 +66,40 @@ void test_vec1(void) {
vec_push(v, &last);
my_elem_s *lastv = (my_elem_s *)vec_pop(v);
free(lastv);
assert(vec_get(v, vec_size(v)) == NULL);
assert(vec_remove(v, vec_size(v)) == -1);
/* Remove from the middle and validate shifted content for elem_size > 1 */
assert(vec_remove(v, 1) == 0);
my_elem_s *shifted = (my_elem_s *)vec_get(v, 1);
assert(shifted != NULL);
assert(shifted->age == 19);
free(shifted);
my_elem_s replacement = {
.age = 40,
.name = "Updated",
};
assert(vec_set(v, 1, &replacement) == 0);
assert(vec_set(v, vec_size(v), &replacement) == -1);
my_elem_s *updated = (my_elem_s *)vec_get(v, 1);
assert(updated != NULL);
assert(updated->age == 40);
free(updated);
/* Iterate for each element */
vec_foreach(v, multiply);
/* Print each element */
// vec_foreach(v, print);
/* Shrink to fit current size and ensure data is still readable */
size_t before_shrink_size = vec_size(v);
assert(vec_shrink(v) == 0);
assert(vec_cap(v) == before_shrink_size);
my_elem_s *shrink_elem = (my_elem_s *)vec_get(v, 1);
assert(shrink_elem != NULL);
free(shrink_elem);
/* Sort */
vec_sort(v, my_cmp);
+83 -28
View File
@@ -22,6 +22,10 @@ static size_t next_power_two(size_t len) {
}
vec_s *vec_new(size_t initial_capacity, size_t element_size) {
if (element_size == 0) {
return NULL;
}
vec_s *vec = (vec_s *)malloc(sizeof(vec_s));
if (vec == NULL) {
return NULL;
@@ -30,6 +34,11 @@ vec_s *vec_new(size_t initial_capacity, size_t element_size) {
vec->capacity = next_power_two(initial_capacity);
vec->elem_size = element_size;
vec->size = 0;
if (vec->capacity > SIZE_MAX / vec->elem_size) {
free(vec);
return NULL;
}
vec->data = malloc(vec->capacity * vec->elem_size);
if (vec->data == NULL) {
free(vec);
@@ -58,14 +67,21 @@ int vec_push(vec_s *vec, void *elem) {
if (vec->size + 1 > vec->capacity) {
/* Reallocate buffer */
vec->capacity = next_power_two(vec->size + 1);
void *tmp = realloc(vec->data, vec->capacity * vec->elem_size);
size_t new_capacity = next_power_two(vec->size + 1);
if (new_capacity > SIZE_MAX / vec->elem_size) {
mtx_unlock(&vec->lock);
return -1;
}
void *tmp = realloc(vec->data, new_capacity * vec->elem_size);
if (tmp == NULL) {
mtx_unlock(&vec->lock);
return -1;
}
vec->data = tmp;
vec->capacity = new_capacity;
}
/* Add the new element */
@@ -86,16 +102,18 @@ void vec_free(vec_s *vec) {
free(vec->data);
}
mtx_destroy(&vec->lock);
free(vec);
}
size_t vec_size(vec_s *vec) {
if (vec == NULL) {
return (size_t)-1;
return 0;
}
if (mtx_lock(&vec->lock) != thrd_success) {
return (size_t)-1;
return 0;
}
size_t size = vec->size;
@@ -107,11 +125,11 @@ size_t vec_size(vec_s *vec) {
size_t vec_cap(vec_s *vec) {
if (vec == NULL) {
return (size_t)-1;
return 0;
}
if (mtx_lock(&vec->lock) != thrd_success) {
return (size_t)-1;
return 0;
}
size_t cap = vec->capacity;
@@ -122,7 +140,7 @@ size_t vec_cap(vec_s *vec) {
}
void *vec_get(vec_s *vec, size_t index) {
if (vec == NULL || index > vec->size) {
if (vec == NULL) {
return NULL;
}
@@ -130,8 +148,16 @@ void *vec_get(vec_s *vec, size_t index) {
return NULL;
}
if (index >= vec->size) {
mtx_unlock(&vec->lock);
return NULL;
}
void *elem = malloc(vec->elem_size);
if (elem == NULL) {
mtx_unlock(&vec->lock);
return NULL;
}
@@ -151,7 +177,14 @@ int vec_shrink(vec_s *vec) {
return -1;
}
void *tmp = realloc(vec->data, vec->size);
size_t new_capacity = (vec->size == 0) ? 1 : vec->size;
if (new_capacity > SIZE_MAX / vec->elem_size) {
mtx_unlock(&vec->lock);
return -1;
}
void *tmp = realloc(vec->data, new_capacity * vec->elem_size);
if (tmp == NULL) {
mtx_unlock(&vec->lock);
@@ -159,7 +192,7 @@ int vec_shrink(vec_s *vec) {
}
vec->data = tmp;
vec->capacity = vec->size;
vec->capacity = new_capacity;
mtx_unlock(&vec->lock);
@@ -175,7 +208,7 @@ int vec_clear(vec_s *vec) {
return -1;
}
memset(vec->data, 0, vec->size);
memset(vec->data, 0, vec->size * vec->elem_size);
vec->size = 0;
mtx_unlock(&vec->lock);
@@ -188,15 +221,23 @@ void *vec_pop(vec_s *vec) {
return NULL;
}
if (vec->size == 0) {
return NULL;
}
if (mtx_lock(&vec->lock) != thrd_success) {
return NULL;
}
if (vec->size == 0) {
mtx_unlock(&vec->lock);
return NULL;
}
void *e = malloc(vec->elem_size);
if (e == NULL) {
mtx_unlock(&vec->lock);
return NULL;
}
vec->size--;
memcpy(e, (char *)vec->data + (vec->size * vec->elem_size), vec->elem_size);
@@ -210,29 +251,39 @@ int vec_insert(vec_s *vec, size_t index, void *value) {
return -1;
}
if (index > vec->size) {
if (mtx_lock(&vec->lock) != thrd_success) {
return -1;
}
if (mtx_lock(&vec->lock) != thrd_success) {
if (index > vec->size) {
mtx_unlock(&vec->lock);
return -1;
}
if (vec->size + 1 > vec->capacity) {
/* No space, realloc */
void *tmp = realloc(vec->data, next_power_two(vec->size + 1));
size_t new_capacity = next_power_two(vec->size + 1);
if (new_capacity > SIZE_MAX / vec->elem_size) {
mtx_unlock(&vec->lock);
return -1;
}
void *tmp = realloc(vec->data, new_capacity * vec->elem_size);
if (tmp == NULL) {
mtx_unlock(&vec->lock);
return -1;
}
vec->data = tmp;
vec->capacity = new_capacity;
}
/* Shift memory and copy the new value */
size_t tmp_size = vec->size - index + 1;
memmove((char *)vec->data + (index * vec->elem_size),
(char *)vec->data + ((index + 1) * vec->elem_size), tmp_size * vec->elem_size);
size_t elements_to_move = vec->size - index;
memmove((char *)vec->data + ((index + 1) * vec->elem_size),
(char *)vec->data + (index * vec->elem_size), elements_to_move * vec->elem_size);
memcpy((char *)vec->data + (index * vec->elem_size), value, vec->elem_size);
vec->size++;
@@ -246,18 +297,20 @@ int vec_remove(vec_s *vec, size_t index) {
return -1;
}
if (index > vec->size) {
return -1;
}
if (mtx_lock(&vec->lock) != thrd_success) {
return -1;
}
size_t size = vec->size - index;
if (index >= vec->size) {
mtx_unlock(&vec->lock);
return -1;
}
size_t size = vec->size - index - 1;
/* Overwrite bytes */
memmove((char *)vec->data + (index * vec->elem_size),
(char *)vec->data + ((index + 1) * vec->elem_size), size);
(char *)vec->data + ((index + 1) * vec->elem_size), size * vec->elem_size);
vec->size--;
mtx_unlock(&vec->lock);
@@ -270,11 +323,13 @@ int vec_set(vec_s *vec, size_t index, void *value) {
return -1;
}
if (index > vec->size) {
if (mtx_lock(&vec->lock) != thrd_success) {
return -1;
}
if (mtx_lock(&vec->lock), thrd_success) {
if (index >= vec->size) {
mtx_unlock(&vec->lock);
return -1;
}