(no title)
sparkie | 1 month ago
Hmm, I found a solution and it was easier than expected. GCC has `__attribute__((designated_init))` we can stick on the struct which prevents positional initializers and requires the field names to be used (assuming -Werror). Since those names are poisoned, we won't be able to initialize except through functions defined in our library. We can similarly use a macro and #undef it.
Full encapsulation of a struct defined in a header:
#ifndef FOO_STRING_H
#define FOO_STRING_H
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#if defined __has_include
# if __has_include("config.h")
# include "config.h"
# endif
#endif
typedef size_t string_length_t;
#ifdef CONFIG_STRING_LENGTH_MAX
#define STRING_LENGTH_MAX CONFIG_STRING_LENGTH_MAX
#else
#define STRING_LENGTH_MAX (1 << 24)
#endif
typedef struct __attribute__((designated_init)) {
const string_length_t _internal_string_length;
const char *const _internal_string_chars;
} string_t;
#define STRING_CREATE(len, ptr) (string_t){ ._internal_string_length = (len), ._internal_string_chars = (ptr) }
#define STRING_LENGTH(s) (s._internal_string_length)
#define STRING_CHARS(s) (s._internal_string_chars)
#pragma GCC poison _internal_string_length _internal_string_chars
constexpr string_t error_string = STRING_CREATE(0, nullptr);
constexpr string_t empty_string = STRING_CREATE(0, "");
inline static string_t string_alloc_from_chars(const char *chars) {
if (__builtin_expect(chars == nullptr, false)) return error_string;
size_t len = strnlen(chars, STRING_LENGTH_MAX);
if (__builtin_expect(len == 0, false)) return empty_string;
if (__builtin_expect(len < STRING_LENGTH_MAX, true)) {
char *mem = malloc(len + 1);
strncpy(mem, chars, len);
mem[len] = '\0';
return STRING_CREATE(len, mem);
} else return error_string;
}
inline static const char *string_to_chars(string_t string) {
return STRING_CHARS(string);
}
inline static string_length_t string_length(string_t string) {
return STRING_LENGTH(string);
}
inline static void string_free(string_t s) {
free((char*)STRING_CHARS(s));
}
inline static bool string_is_valid(string_t string) {
return STRING_CHARS(string) != nullptr;
}
// ... other string function
#undef STRING_LENGTH
#undef STRING_CHARS
#undef STRING_CREATE
#endif /* FOO_STRING_H */
Aside from horrible pointer aliasing tricks, the only way to create a `string_t` is via `string_alloc_from_chars` or other functions defined in the library which return `string_t`. #include <stdio.h>
int main() {
string_t s = string_alloc_from_chars("Hello World!");
if (string_is_valid(s))
puts(string_to_chars(s));
string_free(s);
return 0;
}
No comments yet.