我想出了两种不同的方法来在 C 中实现通用算法和数据结构。我需要你的帮助来决定哪个更好。
过去几周我一直在两者之间权衡,试图决定哪种方法更适合在公司环境中使用。哪种方法产生更高的代码质量、更容易调试、更适合未来、更易读、更可维护并且更容易让新手掌握。更好的方法不一定在上述所有品质上都表现出色。
下面我将通过代码示例和我观察到的一些优点和缺点来展示这两个实现。这些示例只是实际通用数据结构所包含内容的一小部分。
对于脑力练习,请尝试想象这两种方法将如何使用逐元素复制或标准 memcpy() 来实现从 FIFO/队列中添加和获取多个元素。 FIFO 将只实现一组功能:
FIFO_AddMultiple()
、FIFO_TakeMultiple()
,而队列必须实现两组功能:Queue_PushMultipleElementWise()
、Queue_PushMultipleMemoryWise()
、Queue_PopMultipleElementWise()
、Queue_PopMultipleMemoryWise()
.
FIFO.h
#if !defined(FIFO_H_)
#define FIFO_H_
#include <stdbool.h>
#include <stddef.h>
#define ARRAY_SIZE(array) (sizeof(array) / sizeof(array[0]))
#define PREPROC_PASTE_TWO(_1, _2) PREPROC_PASTE_TWO_(_1, _2)
#define PREPROC_PASTE_TWO_(_1, _2) _1##_2
#define PREPROC_PASTE_THREE(_1, _2, _3) PREPROC_PASTE_THREE_(_1, _2, _3)
#define PREPROC_PASTE_THREE_(_1, _2, _3) _1##_2##_3
/**
* FIFO control.
*/
typedef struct FIFO_Control
{
size_t Head; /**< Head index. */
size_t Tail; /**< Tail index. */
size_t NofAdds; /**< Total number of elements added to FIFO. */
size_t NofTakes; /**< Total number of elements taken from FIFO. */
} FIFO_Control_s;
#define FIFO_t(tag) struct PREPROC_PASTE_TWO(FIFO_, tag)
#define FIFO_STATIC_DEFINE(handle) {.Control = {0}, .Size = ARRAY_SIZE((handle).Buffer)}
#define FIFO_Occupied(tag, pHandle) PREPROC_PASTE_THREE(FIFO_, tag, _Occupied)(pHandle)
#define FIFO_Free(tag, pHandle) PREPROC_PASTE_THREE(FIFO_, tag, _Free)(pHandle)
#define FIFO_IsFull(tag, pHandle) PREPROC_PASTE_THREE(FIFO_, tag, _IsFull)(pHandle)
#define FIFO_IsEmpty(tag, pHandle) PREPROC_PASTE_THREE(FIFO_, tag, _IsEmpty)(pHandle)
#define FIFO_Add(tag, pHandle, pElement) PREPROC_PASTE_THREE(FIFO_, tag, _Add)(pHandle, pElement)
#define FIFO_Take(tag, pHandle, pElement) PREPROC_PASTE_THREE(FIFO_, tag, _Take)(pHandle, pElement)
#define FIFO_Seek(tag, pHandle, offset) PREPROC_PASTE_THREE(FIFO_, tag, _Seek)(pHandle, offset)
#define FIFO_Peek(tag, pHandle, offset) PREPROC_PASTE_THREE(FIFO_, tag, _Peek)(pHandle, offset)
static inline void FIFO_AdvanceIndex(size_t *const pIndex, const size_t size, const size_t n)
{
*pIndex = (*pIndex < (size - n)) ? *pIndex + n : *pIndex - (size - n);
}
static inline void FIFO_AdvanceHead(FIFO_Control_s *const pControl, const size_t size, const size_t n)
{
FIFO_AdvanceIndex(&pControl->Head, size, n);
pControl->NofAdds += n;
}
static inline void FIFO_AdvanceTail(FIFO_Control_s *const pControl, const size_t size, const size_t n)
{
FIFO_AdvanceIndex(&pControl->Tail, size, n);
pControl->NofTakes += n;
}
static inline size_t FIFO_Head(const FIFO_Control_s *const pControl)
{
return pControl->Head;
}
static inline size_t FIFO_Tail(const FIFO_Control_s *const pControl)
{
return pControl->Tail;
}
static inline size_t FIFO_NofOccupied(const FIFO_Control_s *const pControl)
{
return (pControl->NofAdds - pControl->NofTakes);
}
static inline size_t FIFO_NofFree(const FIFO_Control_s *const pControl, const size_t size)
{
return (size - FIFO_NofOccupied(pControl));
}
static inline bool FIFO_IsBufferFull(const FIFO_Control_s *const pControl, const size_t size)
{
return (FIFO_NofOccupied(pControl) == size);
}
static inline bool FIFO_IsBufferEmpty(const FIFO_Control_s *const pControl)
{
return (FIFO_NofOccupied(pControl) == 0);
}
#endif /* FIFO_H_ */
/* Instance specific compile-time code generation from here to end of file. */
#if !defined(FIFO_CONFIG_ADD_LOCK) && !defined(FIFO_CONFIG_ADD_UNLOCK)
#define FIFO_CONFIG_ADD_LOCK() (void)0
#define FIFO_CONFIG_ADD_UNLOCK() (void)0
#endif
#if !defined(FIFO_CONFIG_TAKE_LOCK) && !defined(FIFO_CONFIG_TAKE_UNLOCK)
#define FIFO_CONFIG_TAKE_LOCK() (void)0
#define FIFO_CONFIG_TAKE_UNLOCK() (void)0
#endif
/**
* Instance specific FIFO.
*/
FIFO_t(FIFO_CONFIG_TAG)
{
FIFO_Control_s Control; /**< FIFO control. */
const size_t Size; /**< Buffer size. */
FIFO_CONFIG_TYPE Buffer[FIFO_CONFIG_SIZE]; /**< Statically allocated buffer. */
};
static inline size_t FIFO_Occupied(FIFO_CONFIG_TAG, const FIFO_t(FIFO_CONFIG_TAG) *const pHandle)
{
return FIFO_NofOccupied(&pHandle->Control);
}
static inline size_t FIFO_Free(FIFO_CONFIG_TAG, const FIFO_t(FIFO_CONFIG_TAG) *const pHandle)
{
return FIFO_NofFree(&pHandle->Control, pHandle->Size);
}
static inline bool FIFO_IsFull(FIFO_CONFIG_TAG, const FIFO_t(FIFO_CONFIG_TAG) *const pHandle)
{
return FIFO_IsBufferFull(&pHandle->Control, pHandle->Size);
}
static inline bool FIFO_IsEmpty(FIFO_CONFIG_TAG, const FIFO_t(FIFO_CONFIG_TAG) *const pHandle)
{
return FIFO_IsBufferEmpty(&pHandle->Control);
}
static bool FIFO_Add(FIFO_CONFIG_TAG, FIFO_t(FIFO_CONFIG_TAG) *const pHandle, const FIFO_CONFIG_TYPE *const pElement)
{
#if !defined(FIFO_CONFIG_OVERWRITE_FULL)
if (FIFO_IsFull(FIFO_CONFIG_TAG, pHandle))
{
return false;
}
#endif
FIFO_CONFIG_ADD_LOCK();
pHandle->Buffer[FIFO_Head(&pHandle->Control)] = *pElement;
FIFO_AdvanceHead(&pHandle->Control, pHandle->Size, 1);
FIFO_CONFIG_ADD_UNLOCK();
return true;
}
static bool FIFO_Take(FIFO_CONFIG_TAG, FIFO_t(FIFO_CONFIG_TAG) *const pHandle, FIFO_CONFIG_TYPE *const pElement)
{
if (FIFO_IsEmpty(FIFO_CONFIG_TAG, pHandle))
{
return false;
}
FIFO_CONFIG_TAKE_LOCK();
*pElement = pHandle->Buffer[FIFO_Tail(&pHandle->Control)];
FIFO_AdvanceTail(&pHandle->Control, pHandle->Size, 1);
FIFO_CONFIG_TAKE_UNLOCK();
return true;
}
static void FIFO_Seek(FIFO_CONFIG_TAG, FIFO_t(FIFO_CONFIG_TAG) *const pHandle, const size_t offset)
{
FIFO_CONFIG_TAKE_LOCK();
FIFO_AdvanceTail(&pHandle->Control, pHandle->Size, offset);
FIFO_CONFIG_TAKE_UNLOCK();
}
static FIFO_CONFIG_TYPE * FIFO_Peek(FIFO_CONFIG_TAG, const FIFO_t(FIFO_CONFIG_TAG) *const pHandle, const size_t offset)
{
size_t index = FIFO_Tail(&pHandle->Control);
FIFO_AdvanceIndex(&index, pHandle->Size, offset);
return &pHandle->Buffer[index];
}
#undef FIFO_CONFIG_TAG
#undef FIFO_CONFIG_TYPE
#undef FIFO_CONFIG_SIZE
#undef FIFO_CONFIG_OVERWRITE_FULL
#undef FIFO_CONFIG_ADD_LOCK
#undef FIFO_CONFIG_ADD_UNLOCK
#undef FIFO_CONFIG_TAKE_LOCK
#undef FIFO_CONFIG_TAKE_UNLOCK
main.c
#include <stdio.h>
#define FIFO_CONFIG_TAG MyFIFO
#define FIFO_CONFIG_TYPE int
#define FIFO_CONFIG_SIZE 10
#include "FIFO.h"
static FIFO_t(MyFIFO) gFIFO = FIFO_STATIC_DEFINE(gFIFO);
static FIFO_t(MyFIFO) gFIFO2 = FIFO_STATIC_DEFINE(gFIFO2); // Same FIFO instance
// By reconfiguring and reincluding FIFO.h another different FIFO instance can be created
#define FIFO_CONFIG_TAG OtherFIFO
#define FIFO_CONFIG_TYPE float
#define FIFO_CONFIG_SIZE 3
#include "FIFO.h"
static FIFO_t(OtherFIFO) gFIFO3 = FIFO_STATIC_DEFINE(gFIFO3);
int main(void)
{
for (int i = 0; i < 10; i++)
{
FIFO_Add(MyFIFO, &gFIFO, &i);
}
int x;
while (FIFO_Take(MyFIFO, &gFIFO, &x))
{
printf("%d\n", x);
}
FIFO_Add(OtherFIFO, &gFIFO3, &x);
FIFO_Take(OtherFIFO, &gFIFO3, &x);
printf("%d\n", x);
return 0;
}
好处
弱点
Queue.h
#if !defined(QUEUE_H_)
#define QUEUE_H_
#include <stdbool.h>
#include <stddef.h>
#define ARRAY_SIZE(array) (sizeof(array) / sizeof(array[0]))
/**
* Queue control structure.
*/
typedef struct Queue_Control
{
size_t Head; /**< Head index. */
size_t Tail; /**< Tail index. */
size_t TotalPushed; /**< Total number of elements pushed to queue. */
size_t TotalPopped; /**< Total number of elements popped from queue. */
} Queue_Control_s;
/**
* Declare statically allocated queue.
*/
#define QUEUE_DECLARE_STATIC(type, size) \
struct \
{ \
Queue_Control_s Control; /**< Queue control. */ \
const size_t Size; /**< Queue size (number of elements). */ \
type Buffer[size]; /**< Statically allocated element buffer. */ \
}
/**
* Define statically allocated queue.
*/
#define QUEUE_DEFINE_STATIC(handle) {.Control = {0}, .Size = ARRAY_SIZE((handle).Buffer)}
inline static size_t Queue_Generic_AdvanceIndex(const size_t index, const size_t size, const size_t n)
{
return ((index < (size - n)) ? index + n : index - (size - n));
}
inline static void Queue_Generic_AdvanceHead(Queue_Control_s *const pControl, const size_t size, const size_t n)
{
pControl->Head = Queue_Generic_AdvanceIndex(pControl->Head, size, n);
pControl->TotalPushed += n;
}
inline static void Queue_Generic_AdvanceTail(Queue_Control_s *const pControl, const size_t size, const size_t n)
{
pControl->Tail = Queue_Generic_AdvanceIndex(pControl->Tail, size, n);
pControl->TotalPopped += n;
}
inline static size_t Queue_Generic_Count(const Queue_Control_s control)
{
return (control.TotalPushed - control.TotalPopped);
}
inline static size_t Queue_Generic_Free(const Queue_Control_s control, const size_t size)
{
return (size - Queue_Generic_Count(control));
}
inline static bool Queue_Generic_IsFull(const Queue_Control_s control, const size_t size)
{
return (Queue_Generic_Count(control) == size);
}
inline static bool Queue_Generic_IsEmpty(const Queue_Control_s control)
{
return (Queue_Generic_Count(control) == 0);
}
#define Queue_Count(handle) Queue_Generic_Count((handle).Control)
#define Queue_Free(handle) Queue_Generic_Free((handle).Control, (handle).Size)
#define Queue_IsFull(handle) Queue_Generic_IsFull((handle).Control, (handle).Size)
#define Queue_IsEmpty(handle) Queue_Generic_IsEmpty((handle).Control)
#define Queue_Push(handle, element) \
do \
{ \
(handle).Buffer[(handle).Control.Head] = (element); \
Queue_Generic_AdvanceHead(&(handle).Control, (handle).Size, 1); \
} while(0)
#define Queue_Pop(handle, pElement) \
do \
{ \
*(pElement) = (handle).Buffer[(handle).Control.Tail]; \
Queue_Generic_AdvanceTail(&(handle).Control, (handle).Size, 1); \
} while (0)
#define Queue_Seek(handle, offset) Queue_Generic_AdvanceTail(&(handle).Control, (handle).Size, offset)
#define Queue_Peek(handle, offset) (&(handle).Buffer[Queue_Generic_AdvanceIndex(Queue_Generic_Tail(handle), size, offset)])
#endif /* QUEUE_H_ */
main.c
#include <stdio.h>
static QUEUE_DECLARE_STATIC(int, 10) gQueue = QUEUE_DEFINE_STATIC(gQueue);
static QUEUE_DECLARE_STATIC(float, 3) gQueue2 = QUEUE_DEFINE_STATIC(gQueue2);
int main(void)
{
for (int i = 0; i < 10; i++)
{
Queue_Push(gQueue, i);
}
int x;
while (!Queue_IsEmpty(gQueue))
{
Queue_Pop(gQueue, &x);
printf("%d\n", x);
}
return 0;
}
好处
#define
配置并包含标题弱点
do{} while(0)
和逗号运算符构造,它们有其缺点,尤其是在返回值时(这对于函数来说真的很容易)你会使用哪一个,为什么?
我倾向于#1,但也担心如果我们的代码库大量使用方法#1,那么新员工将不容易掌握和维护代码。此外,更重要的是,我不确定方法 #1 如何与测试、静态分析和汽车标准集成并很好地发挥作用。
欢迎对任何一种方法进行任何改进、更改或评论。