How SerenityOS declares ssize_t
This post explores one of my favorite hacks in SerenityOS. I don’t recommend doing this in your codebase, but it has worked for us so far. :^)
Background
size_t
and ssize_t
are common types used in many POSIX APIs. According to POSIX, they are used as follows:
size_t
: Used for sizes of objects.ssize_t
: Used for a count of bytes or an error indication.
In practice, ssize_t
is essentially a “signed size_t
”.
Since we’re building the whole operating system, including the standard C library ourselves, we’re also responsible for declaring all the common system types, including size_t
and ssize_t
.
How we declare them
To declare size_t
, we leverage the C preprocessor’s predefined __SIZE_TYPE__
macro:
typedef __SIZE_TYPE__ size_t;
However, there is no __SSIZE_TYPE__
macro for ssize_t
, so I decided to get a little creative:
#define unsigned signed
typedef __SIZE_TYPE__ ssize_t;
#undef unsigned
Here’s what’s happening: The C preprocessor expands “__SIZE_TYPE__
” to “unsigned long
” or something similar. We trick it by temporarily defining a macro that replaces “unsigned
” with “signed
”, and so ssize_t
is declared as a signed version of whatever the size_t
type is!
How others declare them
Other C libraries typically use more careful techniques, such as wrapping the declarations in architecture-specific #ifdef
s:
#ifdef __i386__
typedef uint32_t size_t;
typedef int32_t ssize_t;
#endif
#ifdef __x86_64__
typedef uint64_t size_t;
typedef int64_t ssize_t;
#endif
That’s obviously a better approach if you’re building with compilers that don’t predefine __SIZE_TYPE__
, or in an environment where unsigned
has already been redefined.
In our case, we haven’t had any issues with our approach yet, so I’m inclined to hold on to the hack as it’s just so cute. :^)