Reputation: 4846
I have a basic string structure:
typedef struct String {
size_t size;
union {
char *ptr, buf[sizeof(char *)];
};
} String;
The idea is to be able to extract a char *
from a String
like so:
extern String string;
char *str = string.size >= sizeof(char *) ? string.ptr : string.buf;
I have a problem. What if I have a const
static
/global String
struct that has to be initialized at compile time? What if I do not want to just hardcode the assumption that char *
is of a given size?
static const String string = {
sizeof(CONSTANT_STRING)-1,
#if sizeof(CONSTANT_STRING) > sizeof(char *)
.name
#else
.buf
#endif
= CONSTANT_STRING
But as we know sizeof()
happens after preprocess time so cannot be used in an #if
.
How can I conditionally assign either one member of my string or another to a given value at compile time so the resulting expression is a compile time constant?
Upvotes: 4
Views: 161
Reputation: 4846
In order for the compound literals to be guarinteed compile-time constants they have to be specified as constexpr
which is a C23-specific feature. Otherwise without constexpr
this may or may not work depending on the compiler and its interpretation of the standard.
This could be done but I had to redesign the String
structure. Instead of having an unnamed nested union, I could have a named union member and conditionally initialize it to different compound literals using the ?:
operator:
typedef union Data {
char *ptr, buf[sizeof(char *)];
} Data;
typedef struct String {
size_t size;
Data data;
} String;
#define CONSTANT_STRING "Hello"
static constexpr String string = {
.size = sizeof(CONSTANT_STRING)-1,
.data = sizeof(CONSTANT_STRING) > sizeof(char *)
? (constexpr Data){.ptr = CONSTANT_STRING}
: (constexpr Data){.buf = CONSTANT_STRING}
};
Upvotes: 2
Reputation: 19221
I would add that there's more room than you're actually using in the struct
, as a numeral value between 1-8 doesn't need a size_t
type...
The following incomplete example would add room for 7 more bytes to your small string optimization when used on a 64 bit system:
typedef struct my_string_s {
unsigned char len[sizeof(size_t)];
char * ptr;
} my_string_s;
Here's an example for "the wild".
The facil.io C STL String module has better optimizations, but this is from the CLI module and it shows this approach at work. It's designed for 64 bit systems and might add an extra 4 byte overhead on 32 bit systems (but these 4 bytes will be used by short strings):
/* Note: requires some macros and includes from the facil.io C STL core header */
/** string container */
typedef struct {
uint8_t em; /* embedded? const? how long? */
uint8_t pad[3]; /* padding - embedded buffer starts here */
uint32_t len; /* if not embedded, otherwise see `em` */
char *str; /* if not embedded, otherwise see `pad` */
} fio_cli_str_s;
/* cli string free / destroy by context */
FIO_SFUNC void fio_cli_str_destroy(fio_cli_str_s *s) {
if (!s || s->em || !s->str)
return;
FIO_LEAK_COUNTER_ON_FREE(fio_cli_str);
FIO_MEM_FREE_(s->str, s->len);
*s = (fio_cli_str_s){0};
}
/* cli string info */
FIO_IFUNC fio_buf_info_s fio_cli_str_buf(fio_cli_str_s *s) {
fio_buf_info_s r = {0};
if (s && (s->em || s->len))
r = ((s->em) & 127) ? (FIO_BUF_INFO2((char *)s->pad, (size_t)s->em))
: (FIO_BUF_INFO2(s->str, (size_t)s->len));
return r;
}
/* cli string copy */
FIO_SFUNC fio_cli_str_s fio_cli_str_copy(fio_buf_info_s s) {
fio_cli_str_s r = {0};
if (s.len < sizeof(r) - 2) {
r.em = s.len;
FIO_MEMCPY(r.pad, s.buf, s.len);
return r;
}
r.len = (uint32_t)s.len;
r.str = (char *)FIO_MEM_REALLOC_(NULL, 0, s.len + 1, 0);
FIO_ASSERT_ALLOC(r.str);
FIO_LEAK_COUNTER_ON_ALLOC(fio_cli_str);
FIO_MEMCPY(r.str, s.buf, s.len);
r.str[r.len] = 0;
return r;
}
/* cli string tmp copy */
FIO_SFUNC fio_cli_str_s fio_cli_str(fio_buf_info_s s) {
fio_cli_str_s r = {0};
if (s.len < sizeof(r) - 2) {
r.em = s.len;
FIO_MEMCPY(r.pad, s.buf, s.len);
return r;
}
r.em = 128; /* mark as const, memory shouldn't be freed */
r.len = (uint32_t)s.len;
r.str = s.buf;
return r;
}
Upvotes: 2