libc: Useful Headers (memory, stdbool, endian, limits)

Continuing on with our libc bringup, today we'll look at a few simple headers that we need:

  1. memory.h
  2. stdbool.h
  3. endian.h
  4. limits.h

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: