首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >实例特定代码生成

实例特定代码生成
EN

Code Review用户
提问于 2023-03-26 16:17:11
回答 1查看 93关注 0票数 4

免责声明:我以前在堆栈溢出上问过这个问题,并得到了一个答复,这个地方会更适合我,所以我在这里复制粘贴这个问题。

我已经提出了两种在C中实现通用算法和数据结构的不同方法,在决定哪种方法更好时,我需要您的帮助。

在过去的几个星期里,我一直在这两者之间讨价还价,试图决定哪种方法更适合在公司环境中使用。哪种方法产生更高的代码质量,更容易调试,更未来的证明,更易读,更易维护,更容易为新的人所掌握。更好的方法没有必要超越上述所有的品质。

下面,我将用代码示例展示这两个实现以及我观察到的一些优点和弱点。这些样本仅仅是实际通用数据结构所包含的一小部分。

#1实例特定编译时代码生成

FIFO.h

代码语言:javascript
复制
#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

代码语言:javascript
复制
#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;
}

福利

  • 使用函数而不是宏可以提供类型安全。
  • 因为使用的是函数而不是宏,所以我们可以清晰地返回和传递变量。
  • 函数不需要在调用位置内联,从而减少了整个代码大小。
  • 使用宏简单地集成任何用户提供的函数(参见FIFO_CONFIG_ADD_LOCK、FIFO_CONFIG_ADD_UNLOCK示例)
  • 与特定配置无关的代码可以退出编译

弱点

  • 调试有点笨拙,因为在函数的内部放置断点将导致断点被放置在随机生成的函数中之一或所有函数中(这是我的猜测)。
  • 需要在FIFO标记中传递的函数(或调用标记函数)
  • 非常规的(?我没见过有人使用这个)C代码练习,觉得有点不合适
  • 需要为每个实例包括FIFO报头(非常规的C编码实践)
  • 宏魔术很难理解
  • 需要单独的宏来定义实例(例如,如果实例类型不同,需要不同的初始化,则为#define FIFO_STATIC_DEFINE(handle)#define FIFO_DYNAMIC_DEFINE() )。
  • 不能在函数中包含报头,这意味着所有FIFO必须在全局范围内被忽略。

#2类函数宏代码实现

Queue.h

代码语言:javascript
复制
#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

代码语言:javascript
复制
#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;
}

福利

  • 不管在单个源文件中使用了多少个队列实例(传统的C编码实践),报头只包含一次。
  • 与#1方法相比,更传统的C代码
  • 直接传递到类似函数的宏队列句柄(没有指针)
  • 为所有类型的队列定义一个函数(不需要标签)
  • 队列配置(类型、大小)在队列声明中,因此不需要#define配置并包含报头。

弱点

  • 会导致长宏。
  • 调试宏可能是痛苦的。
  • 由于宏被使用而不是函数,它们有时需要依赖于do{} while(0)和逗号运算符结构,这有其缺点,特别是在返回值时(这对于函数来说非常容易)。
  • 宏总是在调用的位置内联,这样代码大小就会变大。
  • 与宏相关的编译器警告和错误有时会显示为不连贯和不可理解的。
  • 即使特定实例不使用标头,也必须为不同实例配置提供所有可能的函数,这意味着必须提供多个函数--比如宏(名称不同),以不同的方式实现某些功能(添加/接受本文开头描述的多个元素)。
  • 根据配置,无法拥有易于访问的用户提供的函数(如上一示例中的线程锁定和解锁)

对于一个心理节选,试着想象这两种方法将如何使用逐元素复制或标准memcpy()实现从FIFO/Queue中添加和获取多个元素。FIFO只实现一组函数:FIFO_AddMultiple()FIFO_TakeMultiple(),而队列必须实现两组函数:Queue_PushMultipleElementWise()Queue_PushMultipleMemoryWise()Queue_PopMultipleElementWise()Queue_PopMultipleMemoryWise()

你会用哪一个?为什么?

我倾向于#1,但也担心如果我们的代码库会大量使用第一种方法,那么对于新员工来说,掌握和维护代码就不容易了。而且,更重要的是,我不知道第一种方法是如何与测试、静态分析和汽车标准相结合和发挥作用的。

欢迎对这两种方法的任何改进、更改或评论。

EN

回答 1

Code Review用户

回答已采纳

发布于 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++。C++标准库甚至附带了一个队列类型,所以您可以这样写:

代码语言:javascript
复制
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来展示importstd::print(),但是std::queue部分从C++98开始就已经出现了。)

std::queue在引擎盖下使用一个动态数组,因此您不需要指定大小,而且它可以无限增长。您还可以将自己静态大小的队列类型实现为模板化类:

代码语言:javascript
复制
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版本中拥有的所有函数都在那里,只是没有任何宏和几乎没有指针,从而产生了更清晰的代码。你会像这样使用它:

代码语言:javascript
复制
Queue<int, 10> gQueue;

for (int i = 0; i < 10; i++)
{
    gQueue.Push(i);
}
…
票数 3
EN
页面原文内容由Code Review提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://codereview.stackexchange.com/questions/284176

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档