Continuing on with our libc bringup, today we’ll look at a few simple headers that we need:
memory.h
memory.h
is probably the easiest header we will write. We simply need to use it as an alias for string.h
:
#include <string.h>
stdbool.h
Next up is stdbool.h
, also a very simple header.
Since C++ has its own bool
type, we only need to define the bool
type for C. The easiest way to ensure it’s C-only is to wrap the header contents with this macro:
#ifndef __cplusplus
//...
#endif
The actual definitions are pretty simple, especially since _Bool
is a builtin type in modern C compilers.
#define true 1
#define false 0
typedef _Bool bool;
Completed stdbool.h
Here’s what the completed stdbool.h
looks like:
#ifndef __STDBOOL_H_
#define __STDBOOL_H_
#ifndef __cplusplus
/**
* Only define these for C, since C++ has its own bool support
*/
#define true 1
#define false 0
typedef _Bool bool;
#endif //__cplusplus
#endif //__STDBOOL_H_
limits.h
limits.h
is a header which requires some platform specific definitions. This header defines many min/max values for various types.
For char
, we can do a preprocessor check to determine if char is signed or unsigned and set our limits accordingly:
/* Support signed or unsigned plain-char */
#if '\0'-1 > 0
#define CHAR_MIN 0
#define CHAR_MAX 255
#else
#define CHAR_MIN (-128)
#define CHAR_MAX 127
#endif
Otherwise we can implement most of the fixed max/min definitions directly in limits.h
:
/* Some universal constants... */
#define CHAR_BIT 8
#define SCHAR_MIN (-128)
#define SCHAR_MAX 127
#define UCHAR_MAX 255
#define SHRT_MIN (-1-0x7fff)
#define SHRT_MAX 0x7fff
#define USHRT_MAX 0xffff
#define INT_MIN (-1-0x7fffffff)
#define INT_MAX 0x7fffffff
#define UINT_MAX 0xffffffffU
#define LONG_MIN (-LONG_MAX-1)
#define ULONG_MAX (2UL*LONG_MAX+1)
#define LLONG_MIN (-LLONG_MAX-1)
#define ULLONG_MAX (2ULL*LLONG_MAX+1)
LONG_MAX
and LLONG_MAX
If you look carefully, you’ll notice that LONG_MAX
and LLONG_MAX
are used but not defined. These values vary depending on architecture: 32-bit systems have a 32-bit long
type, while 64-bit systems have a 64-bit long
type.
Here are definitions for x86_32:
#ifndef __X86_LIMITS_H_
#define __X86_LIMITS_H_
#define LONG_BIT 32
#define LONG_MAX 0x7fffffffL
#define LLONG_MAX 0x7fffffffffffffffLL
#endif //__X86_LIMITS_H_
Now compare those definitions with x86_64:
#ifndef __X86_64_LIMITS_H_
#define __X86_64_LIMITS_H_
#define LONG_BIT 64
#define LONG_MAX 0x7fffffffffffffffL
#define LLONG_MAX 0x7fffffffffffffffLL
#endif //__X86_64_LIMITS_H_
Completed limits.h
Here’s what our completed header looks like. Notice that we are including the architecture-specific _limits.h
file.
#ifndef __LIMITS_H
#define __LIMITS_H
//System Specific Limits
#include <_limits.h>
/* Support signed or unsigned plain-char */
#if '\0'-1 > 0
#define CHAR_MIN 0
#define CHAR_MAX 255
#else
#define CHAR_MIN (-128)
#define CHAR_MAX 127
#endif
/* Some universal constants... */
#define CHAR_BIT 8
#define SCHAR_MIN (-128)
#define SCHAR_MAX 127
#define UCHAR_MAX 255
#define SHRT_MIN (-1-0x7fff)
#define SHRT_MAX 0x7fff
#define USHRT_MAX 0xffff
#define INT_MIN (-1-0x7fffffff)
#define INT_MAX 0x7fffffff
#define UINT_MAX 0xffffffffU
#define LONG_MIN (-LONG_MAX-1)
#define ULONG_MAX (2UL*LONG_MAX+1)
#define LLONG_MIN (-LLONG_MAX-1)
#define ULLONG_MAX (2ULL*LLONG_MAX+1)
#endif
endian.h
To wrap up today’s set of headers, we’ll take a look at endian.h
. This header also requires architecture specific definitions to determine whether your target platform is big-endian or little-endian.
We’ll start off by defining the various endian options and some convenience macros:
#define __LITTLE_ENDIAN 1234
#define __BIG_ENDIAN 4321
#define __PDP_ENDIAN 3412
#define BIG_ENDIAN __BIG_ENDIAN
#define LITTLE_ENDIAN __LITTLE_ENDIAN
#define PDP_ENDIAN __PDP_ENDIAN
#define BYTE_ORDER __BYTE_ORDER
Next we need to implement byteswap functionality. We’ll use the standard macro forms:
#define __bswap16(x) \
((uint16_t)((((uint16_t)(x) & 0xff00) >> 8) | \
(((uint16_t)(x) & 0x00ff) << 8)))
#define __bswap32(x) \
((uint32_t)((((uint32_t)(x) & 0xff000000) >> 24) | \
(((uint32_t)(x) & 0x00ff0000) >> 8) | \
(((uint32_t)(x) & 0x0000ff00) << 8) | \
(((uint32_t)(x) & 0x000000ff) << 24)))
#define __bswap64(x) \
((uint64_t)((((uint64_t)(x) & 0xff00000000000000ULL) >> 56) | \
(((uint64_t)(x) & 0x00ff000000000000ULL) >> 40) | \
(((uint64_t)(x) & 0x0000ff0000000000ULL) >> 24) | \
(((uint64_t)(x) & 0x000000ff00000000ULL) >> 8) | \
(((uint64_t)(x) & 0x00000000ff000000ULL) << 8) | \
(((uint64_t)(x) & 0x0000000000ff0000ULL) << 24) | \
(((uint64_t)(x) & 0x000000000000ff00ULL) << 40) | \
(((uint64_t)(x) & 0x00000000000000ffULL) << 56)))
Now that we have byteswap functions, we can implement the common byteswap APIs. We use the architecture’s endianness to determine whether we are actually performing endian swaps or not.
#if __BYTE_ORDER == __LITTLE_ENDIAN
#define ntohs(x) __bswap16(x)
#define htons(x) __bswap16(x)
#define ntohl(x) __bswap32(x)
#define htonl(x) __bswap32(x)
#define ntohll(x) __bswap64(x)
#define htonll(x) __bswap64(x)
#define NTOHL(x) (x) = ntohl((uint32_t)x)
#define NTOHS(x) (x) = ntohs((uint16_t)x)
#define NTOHLL(x) (x) = ntohll((uint64_t)x)
#define HTONL(x) (x) = htonl((uint32_t)x)
#define HTONS(x) (x) = htons((uint16_t)x)
#define HTONLL(x) (x) = htonll((uint64_t)x)
#else //BIG_ENDIAN
// From Apple Open Source libc
#define ntohl(x) ((uint32_t)(x))
#define ntohs(x) ((uint16_t)(x))
#define htonl(x) ((uint32_t)(x))
#define htons(x) ((uint16_t)(x))
#define ntohll(x) ((uint64_t)(x))
#define htonll(x) ((uint64_t)(x))
#define NTOHL(x) (x)
#define NTOHS(x) (x)
#define NTOHLL(x) (x)
#define HTONL(x) (x)
#define HTONS(x) (x)
#define HTONLL(x) (x)
#endif //endian check
Machine endianness
In order to set the endianness for each architecture, we will define __BYTE_ORDER
in our architecture’s _endian.h
:
#ifndef __X86_64_MACHINE_ENDIAN_H_
#define __X86_64_MACHINE_ENDIAN_H_
#define __BYTE_ORDER __LITTLE_ENDIAN
#endif //__X86_64_MACHINE_ENDIAN_H_
Completed endian.h
Here’s our final endian.h
implementation:
#ifndef __ENDIAN_H_
#define __ENDIAN_H_
#define __LITTLE_ENDIAN 1234
#define __BIG_ENDIAN 4321
#define __PDP_ENDIAN 3412
#include <_endian.h> //machine endian header
#define BIG_ENDIAN __BIG_ENDIAN
#define LITTLE_ENDIAN __LITTLE_ENDIAN
#define PDP_ENDIAN __PDP_ENDIAN
#define BYTE_ORDER __BYTE_ORDER
#include <stdint.h>
#define __bswap16(x) \
((uint16_t)((((uint16_t)(x) & 0xff00) >> 8) | \
(((uint16_t)(x) & 0x00ff) << 8)))
#define __bswap32(x) \
((uint32_t)((((uint32_t)(x) & 0xff000000) >> 24) | \
(((uint32_t)(x) & 0x00ff0000) >> 8) | \
(((uint32_t)(x) & 0x0000ff00) << 8) | \
(((uint32_t)(x) & 0x000000ff) << 24)))
#define __bswap64(x) \
((uint64_t)((((uint64_t)(x) & 0xff00000000000000ULL) >> 56) | \
(((uint64_t)(x) & 0x00ff000000000000ULL) >> 40) | \
(((uint64_t)(x) & 0x0000ff0000000000ULL) >> 24) | \
(((uint64_t)(x) & 0x000000ff00000000ULL) >> 8) | \
(((uint64_t)(x) & 0x00000000ff000000ULL) << 8) | \
(((uint64_t)(x) & 0x0000000000ff0000ULL) << 24) | \
(((uint64_t)(x) & 0x000000000000ff00ULL) << 40) | \
(((uint64_t)(x) & 0x00000000000000ffULL) << 56)))
#if __BYTE_ORDER == __LITTLE_ENDIAN
#define ntohs(x) __bswap16(x)
#define htons(x) __bswap16(x)
#define ntohl(x) __bswap32(x)
#define htonl(x) __bswap32(x)
#define ntohll(x) __bswap64(x)
#define htonll(x) __bswap64(x)
#define NTOHL(x) (x) = ntohl((uint32_t)x)
#define NTOHS(x) (x) = ntohs((uint16_t)x)
#define NTOHLL(x) (x) = ntohll((uint64_t)x)
#define HTONL(x) (x) = htonl((uint32_t)x)
#define HTONS(x) (x) = htons((uint16_t)x)
#define HTONLL(x) (x) = htonll((uint64_t)x)
#else //BIG_ENDIAN
// From Apple Open Source libc
#define ntohl(x) ((uint32_t)(x))
#define ntohs(x) ((uint16_t)(x))
#define htonl(x) ((uint32_t)(x))
#define htons(x) ((uint16_t)(x))
#define ntohll(x) ((uint64_t)(x))
#define htonll(x) ((uint64_t)(x))
#define NTOHL(x) (x)
#define NTOHS(x) (x)
#define NTOHLL(x) (x)
#define HTONL(x) (x)
#define HTONS(x) (x)
#define HTONLL(x) (x)
#endif //endian check
#endif //__ENDIAN_H_
Putting it All Together
You can find example source files in the embedded-resources
repo: