Reorganize files to base/ folder (#1)
This commit was merged in pull request #1.
This commit is contained in:
+580
@@ -0,0 +1,580 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdalign.h>
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
#ifdef __has_feature
|
||||
#if __has_feature(address_sanitizer)
|
||||
#define USE_ASAN 1
|
||||
#endif
|
||||
#endif
|
||||
#if defined(__SANITIZE_ADDRESS__)
|
||||
#define USE_ASAN 1
|
||||
#endif
|
||||
|
||||
// Types
|
||||
typedef int8_t S8;
|
||||
typedef int16_t S16;
|
||||
typedef int32_t S32;
|
||||
typedef int64_t S64;
|
||||
typedef uint8_t U8;
|
||||
typedef uint16_t U16;
|
||||
typedef uint32_t U32;
|
||||
typedef uint64_t U64;
|
||||
typedef float F32;
|
||||
typedef double F64;
|
||||
#if defined(__FLT128_MANT_DIG__)
|
||||
typedef _Float128 F128;
|
||||
#elif defined(__SIZEOF_FLOAT128__)
|
||||
typedef __float128 F128;
|
||||
#endif
|
||||
#ifdef __SIZEOF_INT128__
|
||||
typedef __int128 S128;
|
||||
typedef unsigned __int128 U128;
|
||||
#endif
|
||||
|
||||
#define U8_Max ((U8) 0xff)
|
||||
#define U16_Max ((U16)0xffff)
|
||||
#define U32_Max ((U32)0xffffffff)
|
||||
#define U64_Max ((U64)0xffffffffffffffffULL)
|
||||
|
||||
// Duh ..
|
||||
#define U8_Min ((U8)0x0)
|
||||
#define U16_Min ((U16)0x0)
|
||||
#define U32_Min ((U32)0x0)
|
||||
#define U64_Min ((U64)0x0)
|
||||
|
||||
#define S8_Max ((S8) 0x7f)
|
||||
#define S16_Max ((S16)0x7fff)
|
||||
#define S32_Max ((S32)0x7fffffff)
|
||||
#define S64_Max ((S64)0x7fffffffffffffffLL)
|
||||
|
||||
#define S8_Min ((S8) 0x80)
|
||||
#define S16_Min ((S16)0x8000)
|
||||
#define S32_Min ((S32)0x80000000)
|
||||
#define S64_Min ((S64)0x8000000000000000LL)
|
||||
// asserts
|
||||
|
||||
#define Trap() __builtin_trap()
|
||||
#define Abort() __builtin_abort()
|
||||
|
||||
|
||||
#define Assert(x) do { if (!(x)) { Trap(); }} while(0)
|
||||
#define NotImplemented Assert(!"Not Implemented")
|
||||
#define ShouldNotHappen Assert(!"This should not happen")
|
||||
|
||||
#define StaticAssert(expr) typedef int STATIC_ASSERT_FAILED[(expr) ? 1 : -1]
|
||||
|
||||
// misc utilities
|
||||
#define Max(a, b) \
|
||||
({ \
|
||||
__typeof__(a) _amx = (a); \
|
||||
__typeof__(b) _bmx = (b); \
|
||||
_amx > _bmx ? _amx : _bmx;\
|
||||
})
|
||||
|
||||
#define Min(a, b) \
|
||||
({ \
|
||||
__typeof__(a) _amn = (a); \
|
||||
__typeof__(b) _bmn = (b); \
|
||||
_amn < _bmn ? _amn : _bmn; \
|
||||
})
|
||||
|
||||
#define ArraySize(a) (sizeof(a) / sizeof((a)[0]))
|
||||
|
||||
#define Swap(a, b) \
|
||||
do { \
|
||||
__typeof__(a) tmp_ = (a); \
|
||||
(a) = (b); \
|
||||
(b) = tmp_; \
|
||||
} while (0)
|
||||
|
||||
#define ContainerOf(ptr, type, member) \
|
||||
({ \
|
||||
typeof(((type *)0)->member) *__mptr = (ptr); \
|
||||
(type *)((void *)(unsigned char *)__mptr - offsetof(type, member)); \
|
||||
})
|
||||
|
||||
#define ConstContainerOf(ptr, type, member) \
|
||||
({ \
|
||||
const typeof(((const type *)0)->member) *__mptr = (ptr); \
|
||||
(const type *)((const void *)(const unsigned char *)__mptr - offsetof(const type, member)); \
|
||||
})
|
||||
|
||||
// `align` must be a power of two.
|
||||
//
|
||||
// These keep the result in the *same integer type as the first argument*
|
||||
#define AlignUp(val, align) \
|
||||
({ \
|
||||
__typeof__(val) AlignUp_v_ = (val); \
|
||||
__typeof__(val) AlignUp_a_ = (__typeof__(val))(align); \
|
||||
(__typeof__(val))((AlignUp_v_ + (AlignUp_a_ - 1)) & \
|
||||
~(__typeof__(val))(AlignUp_a_ - 1)); \
|
||||
})
|
||||
|
||||
#define AlignDown(val, align) \
|
||||
({ \
|
||||
__typeof__(val) AlignDown_v_ = (val); \
|
||||
__typeof__(val) AlignDown_a_ = (__typeof__(val))(align); \
|
||||
(__typeof__(val))(AlignDown_v_ & ~(__typeof__(val))(AlignDown_a_ - 1));\
|
||||
})
|
||||
|
||||
#define IsPow2(x) \
|
||||
({ \
|
||||
__typeof__(x) IsPow2_x_ = (x); \
|
||||
IsPow2_x_ && !(IsPow2_x_ & (__typeof__(x))(IsPow2_x_ - 1)); \
|
||||
})
|
||||
|
||||
#define IsPow2OrZero(x) \
|
||||
({ \
|
||||
__typeof__(x) IsPow2OrZero_x_ = (x); \
|
||||
!(IsPow2OrZero_x_ & (__typeof__(x))(IsPow2OrZero_x_ - 1)); \
|
||||
})
|
||||
|
||||
#define IsAlignedTo(x, align) \
|
||||
({ \
|
||||
__typeof__(x) IsAlignedTo_x_ = (x); \
|
||||
__typeof__(x) IsAlignedTo_a_ = (__typeof__(x))(align); \
|
||||
IsPow2(IsAlignedTo_a_) && \
|
||||
!(IsAlignedTo_x_ & (__typeof__(x))(IsAlignedTo_a_ - 1)); \
|
||||
})
|
||||
|
||||
#define GetBit(val, idx) (((val) >> (idx)) & 1)
|
||||
// Get n_bits starting at idx going left
|
||||
#define GetBits(val, n_bits, idx) \
|
||||
(((n_bits) == 0 ? 0 : ((value) >> (idx)) & ((((__typeof__(val))(1) << (n_bits)) - 1)))
|
||||
|
||||
#define STRINGIFY(x) STRINGIFY_HLP(x)
|
||||
#define STRINGIFY_HLP(x) #x
|
||||
|
||||
#define SRC_LOCATION __FILE__ ":" STRINGIFY(__LINE__)
|
||||
|
||||
#ifdef DEBUG
|
||||
#define TRACEF(fmt, ...) \
|
||||
do { \
|
||||
fprintf(stdout, "%s (%s)\t" fmt, SRC_LOCATION, __FUNCTION__, \
|
||||
##__VA_ARGS__); \
|
||||
fflush(stdout); \
|
||||
} while (0)
|
||||
#else
|
||||
#define TRACEF(fmt, ...) \
|
||||
do { \
|
||||
} while (0)
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Ceiling of X / y , or round up x / y
|
||||
* (e.g 100/9 == 12, 3/2 == 2)
|
||||
* x + y must not overflow.
|
||||
*
|
||||
* @param x numerator, must be positive
|
||||
* @param y denominator
|
||||
* @return x/y rounded up
|
||||
*/
|
||||
#define CeilDiv(x, y) (((x) + ((y) - 1)) / (y))
|
||||
|
||||
/**
|
||||
* Round x up to nearest multiple of y:
|
||||
* (((x) + (y) - 1) - (((x) + (y) - 1) % (y)))
|
||||
* e.g. RoundUp(30,48) == 48
|
||||
* e.g. RoundUp(49,48) == 96
|
||||
*
|
||||
* @param x numerator, must be positive
|
||||
* @param y denominator
|
||||
* @return x rounded up to nearest multiple of y
|
||||
*/
|
||||
#define RoundUpToMultiple(x, y) \
|
||||
({ \
|
||||
__typeof__(x) _x = (x); \
|
||||
__typeof__(y) _y = (y); \
|
||||
Assert(_y != 0); \
|
||||
__typeof__(x) _tmp = _x + _y - 1; \
|
||||
_tmp - _tmp % _y; \
|
||||
})
|
||||
|
||||
|
||||
/**
|
||||
* Round x down to nearest multiple of y
|
||||
* e.g. RoundDown(30,48) == 0
|
||||
* e.g. RoundDown(49,48) == 48
|
||||
*
|
||||
* @param x numerator, must be positive
|
||||
* @param y denominator
|
||||
* @return x rounded down nearest multiple of y
|
||||
*/
|
||||
#define RoundDownToMultiple(x, y) ((x) / (y) * (y))
|
||||
#define Clamp(val, min, max) (((val) < (min)) ? (min) : ((val) > (max)) ? (max) : (val))
|
||||
|
||||
#ifdef __GNUC__
|
||||
#define INLINE inline __attribute__((always_inline, flatten))
|
||||
#else
|
||||
#define INLINE inline
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
#define C_LINKAGE_BEGIN extern "C" {
|
||||
#define C_LINKAGE_END }
|
||||
#define C_LINKAGE extern "C"
|
||||
#else
|
||||
#define C_LINKAGE_BEGIN
|
||||
#define C_LINKAGE_END
|
||||
#define C_LINKAGE
|
||||
#endif
|
||||
|
||||
/** Indicate the variable is unused.
|
||||
*/
|
||||
#define UNUSED(x) x __attribute__((unused))
|
||||
|
||||
// sizes
|
||||
#define Bytes(n) ((U64)(n))
|
||||
#define KB(n) (((U64)(n)) << 10)
|
||||
#define MB(n) (((U64)(n)) << 20)
|
||||
#define GB(n) (((U64)(n)) << 30)
|
||||
#define TB(n) (((U64)(n)) << 40)
|
||||
|
||||
#define Thousand(n) (((U64)(n) * 1000))
|
||||
#define Million(n) (((U64)(n) * 1000000))
|
||||
#define Billion(n) (((U64)(n) * 1000000000))
|
||||
|
||||
// memory
|
||||
#define MemSet(ptr, val, len) memset(ptr, val, len)
|
||||
#ifdef USE_ASAN
|
||||
#define MemoryZero(ptr, len) memset(ptr, 0, len)
|
||||
// explicity bzero is usually not being intercepted
|
||||
#else
|
||||
#define MemoryZero(ptr, len) explicit_bzero(ptr, len)
|
||||
#endif
|
||||
#define MemMove(dst, src, len) memmove(dst, src, len)
|
||||
#define MemCopy(dst, src, len) memcpy(dst, src, len)
|
||||
#define MemAppend(dst, src, len) mempcpy(dst, src, len)
|
||||
#define MemCompare(first, second, len) memcmp(first, second, len)
|
||||
|
||||
static INLINE U8 *MemFind(U8 *mem, U64 mem_len, U8 *needle, U64 needle_len)
|
||||
{
|
||||
if (needle_len > mem_len) {
|
||||
return NULL;
|
||||
}
|
||||
for (U64 i = 0 ; i + needle_len <= mem_len; i++) {
|
||||
if (MemCompare(&mem[i], needle, needle_len) == 0) {
|
||||
return &mem[i];
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
// atomics
|
||||
#define Atomic(type) _Atomic(type)
|
||||
#define AtomicSub(ptr, val) __atomic_fetch_sub((ptr), (val), __ATOMIC_SEQ_CST)
|
||||
#define AtomicAdd(ptr, val) __atomic_fetch_add((ptr), (val), __ATOMIC_SEQ_CST)
|
||||
#define AtomicDec(ptr) AtomicSub((ptr), 1)
|
||||
#define AtomicInc(ptr) AtomicAdd((ptr), 1)
|
||||
#define AtomicGet(ptr) __c11_atomic_load((ptr), __ATOMIC_SEQ_CST)
|
||||
#define AtomicCAS(ptr, oldval, newval) \
|
||||
__atomic_compare_exchange((ptr), (oldval), (newval),0, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST)
|
||||
|
||||
#define MemoryBarrier() __atomic_thread_fence(UC_ORDER_CST)
|
||||
#define CompilerBarrier() __asm__ __volatile__("": : :"memory")
|
||||
|
||||
// Saturating operations
|
||||
#define SaturatingAdd(a, b, type, max_val)\
|
||||
({type res ;\
|
||||
if (__builtin_add_overflow(a, b, &res)) {\
|
||||
res = max_val;}\
|
||||
res;})
|
||||
|
||||
|
||||
#define SaturatingSub(a, b, type)\
|
||||
({type res ;\
|
||||
if (__builtin_sub_overflow(a, b, &res)) {\
|
||||
res = 0;}\
|
||||
res;})
|
||||
|
||||
#define SaturatingMult(a, b, type, max_val)\
|
||||
({type res ;\
|
||||
if (__builtin_mul_overflow(a, b, &res)) {\
|
||||
res = max_val;}\
|
||||
res;})
|
||||
|
||||
static INLINE U8 SaturatingAddU8(U8 a, U8 b)
|
||||
{
|
||||
return SaturatingAdd(a, b, U8, U8_Max);
|
||||
}
|
||||
|
||||
static INLINE U8 SaturatingSubU8(U8 a, U8 b)
|
||||
{
|
||||
return SaturatingSub(a, b, U8);
|
||||
}
|
||||
|
||||
static INLINE U8 SaturatingMult8(U8 a, U8 b)
|
||||
{
|
||||
return SaturatingMult(a, b, U8, U8_Max);
|
||||
}
|
||||
|
||||
static INLINE U16 SaturatingAdd16(U16 a, U16 b)
|
||||
{
|
||||
return SaturatingAdd(a, b, U16, U16_Max);
|
||||
}
|
||||
|
||||
static INLINE U16 SaturatingSubU16(U16 a, U16 b)
|
||||
{
|
||||
return SaturatingSub(a, b, U16);
|
||||
}
|
||||
|
||||
static INLINE U16 SaturatingMultU16(U16 a, U16 b)
|
||||
{
|
||||
return SaturatingMult(a, b, U16, U16_Max);
|
||||
}
|
||||
|
||||
static INLINE U32 SaturatingAdd32(U32 a, U32 b)
|
||||
{
|
||||
return SaturatingAdd(a, b, U32, U32_Max);
|
||||
}
|
||||
|
||||
static INLINE U32 SaturatingSub32(U32 a, U32 b)
|
||||
{
|
||||
return SaturatingSub(a, b, U32);
|
||||
}
|
||||
|
||||
static INLINE U32 SaturatingMult32(U32 a, U32 b)
|
||||
{
|
||||
return SaturatingMult(a, b, U32, U32_Max);
|
||||
}
|
||||
|
||||
static INLINE U64 SaturatingAdd64(U64 a, U64 b)
|
||||
{
|
||||
return SaturatingAdd(a, b, U64, U64_Max);
|
||||
}
|
||||
|
||||
static INLINE U64 SaturatingSubU64(U64 a, U64 b)
|
||||
{
|
||||
return SaturatingSub(a, b, U64);
|
||||
}
|
||||
|
||||
static INLINE U64 SaturatingMult64(U64 a, U64 b)
|
||||
{
|
||||
return SaturatingMult(a, b, U64, U64_Max);
|
||||
}
|
||||
|
||||
// Allocators
|
||||
typedef enum AllocOperation AllocOperation;
|
||||
enum AllocOperation {
|
||||
Alloc_Op_Aligned = 1,
|
||||
Alloc_Op_Free,
|
||||
Alloc_Op_Free_All,
|
||||
};
|
||||
|
||||
typedef struct Allocator Allocator;
|
||||
typedef void *(*AllocOpFunc)(Allocator *allocator, AllocOperation operation, void *memory, U32 size, U32 align);
|
||||
struct Allocator {
|
||||
AllocOpFunc op_func_ptr;
|
||||
void *data;
|
||||
};
|
||||
|
||||
static INLINE void *allocator_alloc_aligned(Allocator *allocator, U32 size, U32 align)
|
||||
{
|
||||
return allocator->op_func_ptr(allocator, Alloc_Op_Aligned, NULL, size, align);
|
||||
}
|
||||
|
||||
static INLINE void allocator_free(Allocator *allocator, void *memory)
|
||||
{
|
||||
allocator->op_func_ptr(allocator, Alloc_Op_Free, memory, 0, 0);
|
||||
}
|
||||
|
||||
static INLINE void allocator_free_all(Allocator *allocator)
|
||||
{
|
||||
allocator->op_func_ptr(allocator, Alloc_Op_Free_All, NULL, 0, 0);
|
||||
}
|
||||
|
||||
|
||||
#define allocator_push_type(allocator, type_)\
|
||||
(allocator)->op_func_ptr((allocator), Alloc_Op_Aligned, NULL, sizeof(type_), _Alignof(type_))
|
||||
|
||||
#define allocator_push_array(allocator, type_, nr_elements)\
|
||||
(allocator)->op_func_ptr((allocator), Alloc_Op_Aligned, NULL, sizeof(type_) * (nr_elements), _Alignof(type_) )
|
||||
|
||||
#define allocator_push(allocator, size, align)\
|
||||
(allocator)->op_func_ptr((allocator), Alloc_Op_Aligned, NULL, size, align)
|
||||
|
||||
// address-sanitizer helpers (-fsanitize=address)
|
||||
#ifdef USE_ASAN
|
||||
#include <sanitizer/asan_interface.h>
|
||||
#define ASAN_POISON_MEMORY_REGION(addr, size) \
|
||||
__asan_poison_memory_region((addr), (size))
|
||||
#define ASAN_UNPOISON_MEMORY_REGION(addr, size) \
|
||||
__asan_unpoison_memory_region((addr), (size))
|
||||
#else
|
||||
#define ASAN_POISON_MEMORY_REGION(addr, size) \
|
||||
((void)(addr), (void)(size))
|
||||
#define ASAN_UNPOISON_MEMORY_REGION(addr, size) \
|
||||
((void)(addr), (void)(size))
|
||||
#endif
|
||||
|
||||
|
||||
// linked lists
|
||||
|
||||
/*
|
||||
SList - singly linked lists
|
||||
"list" struct must have a "*first" and "**last" member to entrys
|
||||
"first"/"last" structs must have a "*next" member
|
||||
*/
|
||||
|
||||
/**
|
||||
* Initialize a list
|
||||
*
|
||||
* @param list struct pointer with a *first and a **last member to an entry struct
|
||||
*/
|
||||
#define SListInit(list) \
|
||||
do { \
|
||||
(list)->first = NULL; \
|
||||
(list)->last = &(list)->first; \
|
||||
} while (0)
|
||||
|
||||
/**
|
||||
* Adds entry to the head of a list
|
||||
*
|
||||
* @param list struct pointer with a *first and a **last member to an entry struct
|
||||
* @param entry struct pointer with a *next member to an entry struct
|
||||
*/
|
||||
#define SListAddFirst(list, entry) \
|
||||
do { \
|
||||
(entry)->next = (list)->first; \
|
||||
if ((entry)->next == NULL) { \
|
||||
(list)->last = &(entry)->next; \
|
||||
} \
|
||||
(list)->first = (entry); \
|
||||
} while (0)
|
||||
|
||||
/**
|
||||
* Adds entry to the tail of a list
|
||||
*
|
||||
* @param list struct pointer with a *first and a **last member to an entry struct
|
||||
* @param entry struct pointer with a *next member to an entry struct
|
||||
*/
|
||||
#define SListAddLast(list, entry) \
|
||||
do { \
|
||||
(entry)->next = NULL; \
|
||||
*(list)->last = (entry); \
|
||||
(list)->last = &(entry)->next; \
|
||||
} while (0)
|
||||
|
||||
/**
|
||||
* Remove head from list, list MUST not be empty
|
||||
*
|
||||
* @param entry struct pointer with a *next member to an entry struct
|
||||
*/
|
||||
#define SListRemoveFirst(list) \
|
||||
do { \
|
||||
(list)->first = (list)->first->next; \
|
||||
if ((list)->first == NULL) { \
|
||||
(list)->last = &(list)->first; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#define SListIsEmpty(list) ((list)->first == NULL)
|
||||
#define SListResetNode(node) ((node)->next = NULL)
|
||||
|
||||
/*
|
||||
DList - doubly linked lists
|
||||
"list" struct must have a "*first" and "*last" member to entrys
|
||||
"first"/"last" structs must have a "*next" and a "*prev" member
|
||||
*/
|
||||
|
||||
/**
|
||||
* Initialize a list
|
||||
*
|
||||
* @param list struct pointer with a *first and a *last member to an entry struct
|
||||
*/
|
||||
#define DListInit(list) \
|
||||
do { \
|
||||
(list)->first = NULL; \
|
||||
(list)->last = NULL; \
|
||||
} while (0)
|
||||
|
||||
/**
|
||||
* Adds entry to the head of a list
|
||||
*
|
||||
* @param list struct pointer with a *first and a *last member to an entry struct
|
||||
* @param entry struct pointer with a *next and *prev member to an entry struct
|
||||
*/
|
||||
#define DListAddFirst(list, entry) \
|
||||
do { \
|
||||
(entry)->next = (list)->first; \
|
||||
(entry)->prev = NULL; \
|
||||
if ((list)->first != NULL) { \
|
||||
(list)->first->prev = (entry); \
|
||||
} else { \
|
||||
(list)->last = (entry); \
|
||||
} \
|
||||
(list)->first = (entry); \
|
||||
} while (0)
|
||||
/**
|
||||
* Adds entry to the tail of a list
|
||||
*
|
||||
* @param list struct pointer with a *first and a *last member to an entry struct
|
||||
* @param entry struct pointer with a *next and *prev member to an entry struct
|
||||
*/
|
||||
#define DListAddLast(list, entry) \
|
||||
do { \
|
||||
(entry)->prev = (list)->last; \
|
||||
(entry)->next = NULL; \
|
||||
if ((list)->last != NULL) { \
|
||||
(list)->last->next = (entry); \
|
||||
} else { \
|
||||
(list)->first = (entry); \
|
||||
} \
|
||||
(list)->last = (entry); \
|
||||
} while (0)
|
||||
|
||||
/**
|
||||
* Remove first entry from list, list MUST not be empty
|
||||
*
|
||||
* @param entry struct pointer with a *next and *prev member to an entry struct
|
||||
*/
|
||||
#define DListRemoveFirst(list) \
|
||||
do { \
|
||||
(list)->first = (list)->first->next; \
|
||||
if ((list)->first != NULL) { \
|
||||
(list)->first->prev = NULL; \
|
||||
} else { \
|
||||
(list)->last = NULL; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
/**
|
||||
* Remove last entry from list, list MUST not be empty
|
||||
*
|
||||
* @param entry struct pointer with a *next and *prev member to an entry struct
|
||||
*/
|
||||
#define DListRemoveLast(list) \
|
||||
do { \
|
||||
(list)->last = (list)->last->prev; \
|
||||
if ((list)->last != NULL) { \
|
||||
(list)->last->next = NULL; \
|
||||
} else { \
|
||||
(list)->first = NULL; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
/**
|
||||
* Remove last entry from list, entry must be in the list
|
||||
*
|
||||
* @param entry struct pointer with a *next and *prev member to an entry struct
|
||||
*/
|
||||
#define DListRemove(list, entry) \
|
||||
do { \
|
||||
if ((entry)->next != NULL) { \
|
||||
(entry)->next->prev = (entry)->prev; \
|
||||
} else { \
|
||||
(list)->last = (entry)->prev; \
|
||||
} \
|
||||
if ((entry)->prev != NULL) { \
|
||||
(entry)->prev->next = (entry)->next; \
|
||||
} else { \
|
||||
(list)->first = (entry)->next; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#define DListIsEmpty(list) ((list)->first == NULL)
|
||||
#define DListResetNode(node) ((node)->next = (node)->prev = NULL)
|
||||
Reference in New Issue
Block a user