免责声明:我以前在堆栈溢出上问过这个问题,并得到了一个答复,这个地方会更适合我,所以我在这里复制粘贴这个问题。
我已经提出了两种在C中实现通用算法和数据结构的不同方法,在决定哪种方法更好时,我需要您的帮助。
在过去的几个星期里,我一直在这两者之间讨价还价,试图决定哪种方法更适合在公司环境中使用。哪种方法产生更高的代码质量,更容易调试,更未来的证明,更易读,更易维护,更容易为新的人所掌握。更好的方法没有必要超越上述所有的品质。
下面,我将用代码示例展示这两个实现以及我观察到的一些优点和弱点。这些样本仅仅是实际通用数据结构所包含的一小部分。
#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#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;
}#define FIFO_STATIC_DEFINE(handle)和#define FIFO_DYNAMIC_DEFINE() )。#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_ */#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)和逗号运算符结构,这有其缺点,特别是在返回值时(这对于函数来说非常容易)。对于一个心理节选,试着想象这两种方法将如何使用逐元素复制或标准memcpy()实现从FIFO/Queue中添加和获取多个元素。FIFO只实现一组函数:FIFO_AddMultiple()、FIFO_TakeMultiple(),而队列必须实现两组函数:Queue_PushMultipleElementWise()、Queue_PushMultipleMemoryWise()、Queue_PopMultipleElementWise()、Queue_PopMultipleMemoryWise()。
你会用哪一个?为什么?
我倾向于#1,但也担心如果我们的代码库会大量使用第一种方法,那么对于新员工来说,掌握和维护代码就不容易了。而且,更重要的是,我不知道第一种方法是如何与测试、静态分析和汽车标准相结合和发挥作用的。
欢迎对这两种方法的任何改进、更改或评论。
发布于 2023-03-28 10:11:56
)。
第一种方法进行编译,但我的编译器显示了许多警告,包括const-correctness问题和传递不正确类型的指针。
更重要。
当涉及到库和实用程序时,主要的目标是使它们的使用安全、简单和可靠。它的实施方式是次要的问题。因此,如果我必须在这两种方法之间进行选择,我肯定会选择第二种方法,主要是因为它不太容易出错:我不可能忘记#define或#include "FIFO.h",也不会意外地传递错误的标记。
在你的优点和弱点分析中,有几件事是不正确的。例如
您仍然依赖宏来调用这些函数。此外,函数不一定比宏更安全。您可以使用函数、使用空指针并键入不安全。
这是错误的。宏是在调用的地方展开的,但这并不意味着编译器不能看到它一遍又一遍地展开相同的代码,并决定不对其进行内联(有时称为“大纲”)。此外,编译器现在的内联功能很大,因为它使其他优化工作得更好。
不可能在宏中放置断点。但是,函数有一个明确的定义,而且在C中没有函数重载,因此为给定函数名设置断点应该是明确的。例如,在第一种方法中,没有函数FIFO_Add(),但是会有一个可以设置断点的函数FIFO_MyFIFO_Add()。
为什么不行?您可以轻松地在struct Queue_Control中放置指向这些函数的指针,并创建一个将这些函数指针作为参数存储在控制块中的QUEUE_DEFINE_STATIC()版本。
用C语言编写类型安全的通用代码是困难的或不可能的。然而,现在几乎没有理由不考虑迁移到C++:编译器非常好,而且很容易将C移植到C++。C++标准库甚至附带了一个队列类型,所以您可以这样写:
import std;
int main()
{
std::queue<int> gQueue;
for (int i = 0; i < 10; i++)
{
gQueue.push(i);
}
while (!gQueue.empty())
{
std::print("{}\n", gQueue.front());
gQueue.pop();
}
}这里没有宏,一切都是类型安全的,gQueue.front()返回一个值,因此不需要传递指向变量的指针。(我在这里使用C++23来展示import和std::print(),但是std::queue部分从C++98开始就已经出现了。)
std::queue在引擎盖下使用一个动态数组,因此您不需要指定大小,而且它可以无限增长。您还可以将自己静态大小的队列类型实现为模板化类:
template<typename T, std::size_t Size>
class Queue
{
size_t Head = 0;
size_t Tail = 0;
size_t TotalPushed = 0;
size_t TotalPopped = 0;
T Buffer[Size];
static std::size_t AdvanceIndex(const std::size_t index, const std::size_t n)
{
return ((index < (Size - n)) ? index + n : index - (Size - n));
}
void AdvanceHead(const std::size_t n = 1)
{
Head = AdvanceIndex(Head, n);
TotalPushed += n;
}
…
public:
void Push(const T& element)
{
Buffer[Head] = element;
AdvanceHead();
}
…
};请注意,您在C版本中拥有的所有函数都在那里,只是没有任何宏和几乎没有指针,从而产生了更清晰的代码。你会像这样使用它:
Queue<int, 10> gQueue;
for (int i = 0; i < 10; i++)
{
gQueue.Push(i);
}
…https://codereview.stackexchange.com/questions/284176
复制相似问题