Table of Contents
- Getting Started
- Documentation
- Toggling Features
- Version Number Generator
- Architecture Detection
- Compiler Detection
- OS Detection
- Platform Detection
- X_EXPORT, X_IMPORT
- X_KEY
- x_assert
- x_buffer_valid
- x_bit
- x_count
- x_fail
- x_free
- x_Pi
- x_succ
- x_cpu_count
- x_duration
- x_file_size
- x_full_path
- x_getch
- x_now
- x_path_exists
- x_split_path
- x_sleep
- x_strmty
- x_timestamp
- x_log
- x_err
- Concurrency
- x_cks
- x_pkt
- x_iov
- x_skt
- x_node
- x_deque
- x_lfque
- x_tlque
- x_tictoc
- LICENSE
Put x.h
into your project.
x.h
is a single-header library which mean users can simply use its
functionalities by including it anywhere:
// in some foo.h file
#include "x.h"
// in some foo.c file
#include "x.h"
Several macros are provided for toggling features, some of which may be used to avoid redundant build dependencies in some cases.
#ifndef X_ENABLE_ATOMIC
#define X_ENABLE_ATOMIC (0)
#endif
#ifndef X_ENABLE_CONCURRENCY
#define X_ENABLE_CONCURRENCY (0)
#endif
#ifndef X_ENABLE_CUDA
#define X_ENABLE_CUDA (0)
#endif
#ifndef X_ENABLE_SOCKET
#define X_ENABLE_SOCKET (0)
#endif
#ifndef X_ENABLE_STRUCT_FUNCTION
#define X_ENABLE_STRUCT_FUNCTION (1)
#endif
X_VER: Combines major
, minor
, patch
numbers into a version number. The
ranges of each component is:
- major: [0, 99]
- minor: [0, 99]
- patch: [0, 99999]
A big range of patch allow the result version number specifically work with _MSC_FULL_VER.
- X_32BIT: Defined as 1 if the architecture is 32-bit, 0 otherwise.
- X_64BIT: Defined as 1 if the architecture is 64-bit, 0 otherwise.
- X_ARM: Defined as 1 if the architecture is ARM, 0 otherwise.
- X_ARM64: Defined as 1 if the architecture is 64-bit ARM, 0 otherwise.
- X_X86: Defined as 1 if the architecture is 32-bit x86, 0 otherwise.
- X_X64: Defined as 1 if the architecture is 64-bit x86, 0 otherwise.
- X_CLANG: Defined as the version number of Clang if it is being used, 0 otherwise.
- X_GCC: Defined as the version number of GCC if it is being used, 0 otherwise.
- X_MSVC: Defined as the version number of MSVC if it is being used, 0 otherwise.
The version number is generated by X_VER
. For instance, X_VER(15, 0, 6)
(version 15.0.6
) results in 150000006
.
- X_CYGWIN: Defined as the version number of Cygwin if it is the target OS, 0 otherwise.
- X_LINUX: Defined as 1 if Linux is the target OS, 0 otherwise.
- X_MACOS: Defined as the version number if MACOS is the target OS, 0 otherwise.
- X_WINDOWS: Defined as 1 if Windows is the target OS, 0 otherwise.
- X_ANDROID: Defined as 1 if Android is the target platform, 0 otherwise.
- X_MINGW: Defined as 1 if MinGW32/64 is the target platform, 0 otherwise.
- X_MINGW32: Defined as the version number if MinGW32 is the target platform, 0 otherwise.
- X_MINGW64: Defined as the version number if MinGW64 is the target platform, 0 otherwise.
#ifndef X_EXPORT
#if X_WINDOWS
#define X_EXPORT __declspec(dllexport)
#else
#define X_EXPORT __attribute__ ((visibility("default")))
#endif
#endif
#ifndef X_IMPORT
#if X_WINDOWS
#define X_IMPORT __declspec(dllimport)
#else
#define X_IMPORT __attribute__ ((visibility("hidden")))
#endif
#endif
See Microsoft Docs and GCC Wiki for more details.
#define X_KEY_ESC (0x1B)
#define X_KEY_A (0x41)
#define X_KEY_B (0x42)
#define X_KEY_C (0x43)
#define X_KEY_D (0x44)
#define X_KEY_Q (0x51)
#if X_WINDOWS
#define X_KEY_LEFT (0x4B)
#define X_KEY_UP (0x48)
#define X_KEY_RIGHT (0x4D)
#define X_KEY_DOWN (0x50)
#else
#define X_KEY_LEFT (-1)
#define X_KEY_UP (-2)
#define X_KEY_RIGHT (-3)
#define X_KEY_DOWN (-4)
#endif
Several virtual key macros, most of which are defined as their corresponding ASCII code. However, the arrow keys are handled differently among OSs (please tell me if this statement is not accurate), they are defined as negative integers to work around. Please checkout x_getch for their usage.
An macro enables the assertion functionality for both debug and release builds. For debug build, it is the same as the assertion macro in assert.h/cassert header.
x_assert(expr)
expr
: The assertion expression.
x_assert(1 == 1);
x_assert(1 != 1);
A macro used to generate an integer with only the n-th bit set to 1. This is
useful when one needs enumerations like 0b0001
, 0b0010
, 0b0100
to perform
the &
, |
, ~
operations.
T x_bit(bit) // In this document, T indicats a return type from a macro.
bit
: Specification for which bit of the integer should be set to 1.
- The generated integer, may be
int
,long
, etc.
enum
{
FLAG_NONE = 0,
FLAG_READ = x_bit(1),
FLAG_WRITE = x_bit(2),
};
int read_and_write = FLAG_READ | FLAG_WRITE;
The definition of this macro should have explained itself well:
#define x_buffer_valid(buffer, size) \
(((buffer) == NULL && (size) == 0) || ((buffer) != NULL && (size) != 0))
It is not implemented as function since size
can be int
, size_t
etc. So
please pass arguments carefully.
buffer
: The pointer to a buffer.size
: The size of the contents in the pointer.
true
if the buffer is valid.
int read_file(const char* file, void* buffer, size_t size)
{
if (!x_buffer_valid(buffer, size)) {
return EINVAL;
}
// operations
return 0;
}
Get length of an array. This macro only works with static arrays.
T x_count(array)
For C++, it is implemented as a templated function:
template<class T, size_t N>
size_t x_count(const T (&array)[N])
{
return N;
}
array
: The array of which the size to be queried.
- The length (count of elements) of the input array.
int a[5] = {1, 2, 3, 4, 5};
for (size_t i = 0; i < x_count(a); ++i)
{
printf("%d", a[i]);
}
A macro to check if an error number indicates a failure. It only works with
an error handling system that defines 0
as a success, e.g. POSIX errno.
bool x_fail(err)
err
: An error number.
Boolean value indicating if the error number means a failure.
int err = EINVAL;
if (x_fail(err))
{
printf("%s\n", strerror(err));
}
A macro to free a memory block allocated on heap and set it to NULL.
void x_free(ptr)
ptr
: A pointer was allocated with memory previously.
int *a = (int*)malloc(5 * sizeof(int));
x_free(a);
A macro for retrieving the constant Pi. The input argument is used to specify the target data type.
T x_Pi(T)
In C++, one may use the following template:
template<class T>
static constexpr T x_Pi = (T)3.141592653589793238462643383279502884197169399375;
// Usage: x_Pi<float>, x_Pi<double>, etc.
T
: Target type, e.g.float
,double
,long
.
- Pi of specified type.
float a = x_pi(float);
double b = x_pi(double);
A macro to check if an error number indicates a success. It only works with
an error handling system that defines 0
as a success, e.g. POSIX errno.
bool x_succ(err)
err
: An error number.
Boolean value indicating if the error number means a success.
int err = 0;
if (x_succ(err))
{
printf("%s\n", strerror(err));
}
size_t x_cpu_count()
- Count of CPU of current host.
double x_duration(const struct timespec start, const struct timespec end, const char* unit)
start
: The start time point.end
: The end time point.unit
: The unit of the outcome.
- The duration of the two time points in specified unit.
long long x_file_size(const char* file)
file
: File name of which the size to be exam.
- Size of the file in bytes.
const char* x_full_path(char* dst, const char* src)
dst
: The full/absolute path to the input file. It should be allocated with sufficient memory before passed into this function, otherwiseNULL
is returned.src
: The (maybe relative) path to a file.
- The same value contained in
dst
, orNULL
when the operation failed.
const char* file = "./foo/bar.txt";
char buf[X_PATH_MAX] = {0};
x_full_path(buf, file);
A cross-platform version of Win32's _getch function.
int x_getch()
- The pressed key.
This function only works with console programs. One may use it to break a infinity loop manually:
int main(int argc, char** argv)
{
while (true)
{
if (x_getch() == X_KEY_ESC)
{
break; // When the 'Esc' key is pressed, the loop breaks.
}
x_sleep(10);
}
return 0;
}
struct timespec x_now();
- A
struct timespec
instance indicating the current time.
bool x_path_exists(const char* path)
path
: The path to a querying file/directory.
true
if the file/directory exists.
A cross-platform version of Win32's _splitpath function.
int x_spli_tpath(
const char *path,
char *root, const size_t rsz, char *dir, const size_t dsz,
char *file, const size_t fsz, char *ext, const size_t esz)
void x_sleep(const unsigned long ms)
ms
: The length of the sleep duration in milliseconds.
bool x_strmty(const char* string)
string
: The string.
true
if the string is NULL or '\0' is the first character it contains.
const char* x_timestamp(char* buffer, const size_t size)
buffer
: Buffer to store timestamp string.size
: Size of the buffer.
- Same content of the input buffer. A empty string ("") will be returned if the operation failed.
void x_log(char level, FILE* file, const char* format, ...)
level
: Logging level specification, available values areP
(Public),F
(Fatal),E
(Error),W
(Warning),I
(Info),D
(Debug) and they are case insensitive.file
: File stream for saving the loggings. IfNULL
provided, no loggings will be saved.format
: Same as that in printf....
: Same as that in printf.
int a = 0;
x_log('P', NULL "hello");
x_log('D', NULL "%s", "world");
x_log('I', NULL "%d", 10);
x_log('W', NULL "%d", ++a);
x_log('E', pf, "%d", ++a); // e.g. FILE* pf = fopen("log.txt", "w")
x_log('F', pf, "%s", "fatal");
The formats of each level are almost identical. What differentiate them are the level prefixes and the colors.
Level | Color | Debug Build Format | Release Build Format |
---|---|---|---|
P | [level timestamp | file | function | line] message | [level timestamp] message | |
F | |||
E | |||
W | |||
I | |||
D |
// Enumerations of error categories.
enum
{
x_err_custom = 0, // Custom errors.
x_err_posix = 1, // POSIX errors.
x_err_win32 = 2, // Win32 API errors, which also includes WSA errors.
x_err_socket = 3, // WSA errors (on Windows) or POSIX errors.
#if X_ENABLE_CUDA
x_err_cuda = 4, // CUDA errors.
#endif
#if X_WINDOWS
x_err_system = x_err_win32, // System errors, for Windows OS.
#else
x_err_system = x_err_posix, // System errors, for POSIX OS.
#endif
};
// Enumerations of error message querying method.
enum
{
x_err_msg_literal = 0,
x_err_msg_lookup = 1,
};
typedef struct _x_err_
{
int32_t cat; // Category of the error.
int32_t val; // Numeric value of the error.
} x_err;
// Error message lookup function API.
typedef const char* (*x_err_msg_fn)(char* msg, const size_t msz, const int32_t val);
// Retrieves the last error of specific category, via GetLastError() or
// WSAGetLastError() from Win32 API or errno from POSIX.
x_err x_err_get(const int32_t cat);
// If err.cat is x_err_custom, two additional arguments are required for
// specifying the error message querying method and the method itself. Please
// checkout test/test_x_err.c, which should be self explained.
//
// Otherwish, the description will be retrieved according to the value of the
// error via FormatMessage from Win32 API or strerror from POSIX.
const char* x_err_msg(char* msg, const size_t msz, const x_err err, ...
/* const int method, const char* generator*/);
// Constructs an x_err instance.
x_err x_err_set(const int32_t cat, const int32_t val);
// Can be used to initialize an x_err instance.
x_err x_ok();
Please checkout test/test_x_err.c and test/test_x_err.cu.
These are pretty much similar to those provided by C11 concurrency support library
- x_cnd: Condition variable utilities.
- x_mtx: Mutex utilities.
- x_sem: Semaphore utilities.
- x_thd: Thread utilities.
- x_atomic_B: Atomic utilities of 8-bit, 16-bit, 32-bit, 64-bit, bool,
pointer (
void*
) and flag. The structs and functions are generated by macros during compilation.
Available atomic types:
- x_atomic_8: contains a
uint8_t
data. - x_atomic_16: contains a
uint16_t
data. - x_atomic_32: contains a
uint32_t
data. - x_atomic_64: contains a
uint64_t
data. - x_atomic_ptr: contains a
uint32_t
oruint64_t
(sizeof(void*)
, depends on architecture) data. - x_atomic_bool: contains a
_Bool
data. - x_atomic_flag: contains a
_Bool
data.
Available atomic functions (where B
is one of 8
, 16
, 32
, 64
, ptr
,
bool
, T
is one of uint8_t
, uint16_t
, uint32_t
, uint64_t
, void*
,
'bool'):
void x_atomic_init_B(volatile x_atomic_B* obj, T desired)
void x_atomic_store_B(volatile x_atomic_B* obj, T desired, x_memory_order order)
T x_atomic_load_B(volatile x_atomic_B* obj, x_memory_order order)
T x_atomic_exchange_B(volatile x_atomic_B* obj, T desired, x_memory_order order)
bool x_atomic_compare_exchange_B(
volatile x_atomic_B* obj, T* expected, T desired,
x_memory_order succ, x_memory_order fail)
T x_atomic_fetch_add_B(volatile x_atomic_B* obj, T desired, x_memory_order order)
T x_atomic_fetch_sub_B(volatile x_atomic_B* obj, T desired, x_memory_order order)
T x_atomic_fetch_Or_B(volatile x_atomic_B* obj, T desired, x_memory_order order)
T x_atomic_fetch_Xor_B(volatile x_atomic_B* obj, T desired, x_memory_order order)
T x_atomic_fetch_And_B(volatile x_atomic_B* obj, T desired, x_memory_order order)
#define x_kill_dependency(x) (x)
#define x_atomic_is_lock_free(T) _x_is_size_lock_free(sizeof(T))
bool x_atomic_flag_test_and_set(volatile x_atomic_flag* obj, x_memory_order order)
void x_atomic_flag_clear(volatile x_atomic_flag* obj, x_memory_order order)
void x_atomic_signal_fence(const x_memory_order order)
void x_atomic_thread_fence(const x_memory_order order)
Function pointers inside x_atomic_B
are provided to unified APIs so that
different atomic object can be replaced easily during code refactor. For
example:
// before
x_atomic_32 obj = X_ATOMIC_VAR_INIT(32); // was 32-bit data
obj.init(&obj, 0);
obj.fetch_add(&obj, 1);
// after
x_atomic_64 obj = X_ATOMIC_VAR_INIT(64); // refactor to 64-bit
obj.init(&obj, 0); // no need to change this
obj.fetch_add(&obj, 1); // this too, woohoo!
Definition of x_memory_order
:
typedef enum _x_memory_order_
{
#if X_CLANG || X_GCC
x_mo_relaxed = __ATOMIC_RELAXED,
x_mo_consume = __ATOMIC_CONSUME,
x_mo_acquire = __ATOMIC_ACQUIRE,
x_mo_release = __ATOMIC_RELEASE,
x_mo_acq_rel = __ATOMIC_ACQ_REL,
x_mo_seq_cst = __ATOMIC_SEQ_CST
#else
x_mo_relaxed = 0,
x_mo_consume = 1,
x_mo_acquire = 2,
x_mo_release = 3,
x_mo_acq_rel = 4,
x_mo_seq_cst = 5
#endif
} x_memory_order;
Except x_atomic_bool
and x_atomic_flag
, x_atomic_B
objects only care
about how many bits of the internal data has. Therefore, one should perform
proper data type casting when storing to or loading from these atomic objects.
uint32_t x_cks_crc32(const void* data, const size_t size, const uint32_t* prev)
data
: Source data of the checksum.size
: Size of the data in bytes.prev
: Optional previous checksum result. Can beNULL
.
- A CRC-32 checksum.
uint16_t x_cks_rfc1071(const void* data, const size_t size)
data
: Source data of the checksum.size
: Size of the data in bytes.
- An IPv4 header checksum. Checkout RFC 1071 for more details.
uint8_t x_cks_xor(const void* data, const size_t size)
data
: Source data of the checksum.size
: Size of the data in bytes.
- A checksum calculated by perform XOR of all data in
data
.
Packet utilities. The source code should have explained itself.
IO vector utilities. The source code should have explained itself.
Socket utilities.
Nodes for linked queues and lists.
Simple double ended queue without synchronization.
Lock-free queue.
Two-lock queue.
Utilities for recording code execution time.
typedef struct _x_tictoc_
{
struct timespec start; // Timestamp when the recording starts.
double elapsed; // Execution time of last cycle.
struct
{
bool ready; // Set to `true` once targe execution cycles are reached.
size_t cyc; // Total cycles to be record.
double sum; // Total elapsed time.
double avg; // Average execution time per cycle.
struct
{
size_t idx; // The index of the cycle that max/min execution time happened.
double val; // The execution time of `idx`th cycle.
} max, min; // Most (max) and least (min) execution time information.
} report;
} x_tictoc;
X_INLINE int x_tictoc_init(x_tictoc* tictoc);
// Start point of timing.
X_INLINE int x_tic(x_tictoc* tictoc, const bool echo);
// End point of timing.
X_INLINE int x_toc(x_tictoc* tictoc, const char* unit, const bool echo);
// End point of timing in a multi-cycle recording operation.
X_INLINE int x_toc_ex(
x_tictoc* tictoc,
const char* unit, const long long cycle, const char* title,
char* echo, size_t* size);
Distributed under the Mulan PSL v2 license. See LICENSE for more information.