4

我正在使用 GCC 在 C99 上为嵌入式环境开发。我做了一个小库来处理循环缓冲区和队列。它实现并处理包含缓冲区和所需元数据的基本结构的实例。

struct circbuf_u8_st {
    const uint8_t buffer_num_elems; 
    uint8_t head;
    uint8_t tail;
    uint8_t * buffer;   
};

有时它struct被用作volatile全局,因为它用于生成数据的中断例程和消耗数据的主循环之间的通信。

但有时它struct被用作非volatile本地的,例如当主循环的一部分生成数据以供稍后在同一个主循环上使用时。

有时struct有时volatile不存在意味着任何处理该参数的函数都struct需要两个版本,用于参数volatile和非volatile参数。这是一个维护问题:在一个版本中所做的任何更改都必须在另一个版本中重复。例如,

void circbufu8_reset(struct circbuf_u8_st *cb);
void circbufu8_v_reset(struct circbuf_u8_st volatile *cbv);

我可以选择volatile一切的版本,因为那总是正确的。但这也意味着对volatile我想避免的非案例的悲观。

因此,一种可能的解决方案是声明 a unionwith volatile/nonvolatile成员,并将struct成员声明为具有 that 的类型union

union un_dual_volatile_u8 {
    uint8_t volatile v;
    uint8_t nv;
};

这将有助于摆脱每个功能的 2 个版本问题。但是,真的有用吗?这样的功能将(可能)具有什么语义?我的理解是编译器必须使用 . 所需的最严格的语义union,所以实际上这只是volatileall-option 的一个不必要的复杂版本,具有相同的悲观化。

所以,问题是:

  • 我说工会不帮忙是对的吗?
  • 那么有什么办法可以避免功能重复悲观化呢?
  • C++ 在这种情况下会有所帮助吗?(函数重载会尊重volatile和非volatile语义吗?或者可能使用泛型?)

(查看编译器输出不是一个有效的答案;我正在寻找可以依赖的基于标准的理由)

编辑:我删除了 C++ 标签,因为问题的 C++ 部分更多是出于好奇。当然 C11 的泛型使问题更容易解决,但目标是在 C99 中解决这个问题。

4

4 回答 4

3

在 C++ 中,你当然可以使用模板来做到这一点:

template <class T>
void circbufu8_reset(T &cb)
{
  // code here
}

当然,这个函数模板可以用任何类型调用(但可能无法实例化),所以你可能想像这样限制它的使用:

class Helper
{
  friend void circbufu8_reset(circbuf_u8_st &);
  friend void circbufu8_reset(volatile circbuf_u8_st &);
private:
  template <class T>
  static void reset(T &cb)
  {
    // use cb here, guaranteed to be circbuf_u8_st & or volatile circbuf_u8_st &
  }
};

inline void circbufu8_reset(circbuf_u8_st &cb)
{
  Helper::reset(cb);
}

inline void circbufu8_reset(volatile circbuf_u8_st &cb)
{
  Helper::reset(cb);
}

编辑

一个更 C++ 的方式是这样的:

struct circbuf_u8_st {
    const uint8_t buffer_num_elems; 
    uint8_t head;
    uint8_t tail;
    uint8_t * buffer;   

    void reset() { resetImpl(*this); }
    void reset() volatile { resetImpl(*this); }
private:
    template <class T>
    static void resetImpl(T &cb) {
      //code with cb
    }
};
于 2013-09-13T14:05:13.357 回答
1

Mat 他的回答是,您可以在 C++ 中超载易失性是正确的,但如果我理解正确,这不是您想要的。

#include <iostream>
#include <typeinfo>

struct A
{
    int a;
};

//specializing the template function still works, in case you need to do something different in the volatile case
//void foo( A volatile* ptr )
//{
//  std::cout << "specialization: " << typeid(ptr).name() << "\n";
//}

template< typename T >
void foo( T* ptr )
{
    std::cout << typeid(ptr).name() << "\n";
}

int main() 
{
    A* aPtr;
    A volatile* aVolatilePtr;

    foo( aPtr );
    foo( aVolatilePtr );

    return 0;
}

输出:

P1A 
PV1A

在引擎盖下,编译器发出两个版本的 foo。

于 2013-09-13T14:18:24.487 回答
1

(由于缺乏非 C++ 答案而自行回答)

在 C99 中,获得类似重载函数的行为的唯一方法是通过预处理器技巧。联合不会起作用,至少因为这种联合的语义在 GCC 本身中尚未确定,已经存在了几年并且似乎没有改变。在那个错误报告和讨论中,GCC 开发人员似乎并不确定从标准中解释什么……所以我认为这是一个非常强烈的建议,即放弃整个攻击角度,即使我改变了编译器。

所以最后我使用了通用预处理器“模板”,它使用参数化类型声明和初始化函数、结构等——例如,uint16在一种情况下是 a volatile uint8,在另一种情况下是 a。例如,对于循环缓冲区:

#define CIRCBUF_TYPE_DECLARE(TYPE, TYPELABEL, QUALIF, QUALIFLABEL)          \
    struct circbuf##TYPELABEL##QUALIFLABEL##_st {                           \
        const TYPE buffer_num_elems;                                        \
        TYPE QUALIF head;                                                   \
        TYPE QUALIF tail;                                                   \
        uint8_t QUALIF * buffer;                                            \
    };                                                                      \
                                                                            \
    typedef struct circbuf##TYPELABEL##QUALIFLABEL##_st circbuf##TYPELABEL##QUALIFLABEL;

然后像这样调用:

CIRCBUF_TYPE_DECLARE(uint8_t, _u8, volatile, _v)

对于这类事情,Jens GustedtC 语言 P99 预处理器库是一个很好的启发。后来我发现这种“预处理器模板”也用在 Linux 的内核源代码中……所以即使我推出了自己的版本,可能随着时间的推移,我也会尝试使用现有的实现之一。然而,在我有时间之前我就换了工作。

对于那些感兴趣的人,我写了一篇博客文章,全面讨论了基于 C99 标准的理由,说明为什么 volatile+non-volatile 联合是合法的,但语义仍然不清楚。

于 2015-07-12T01:19:35.153 回答
0

要在 C 中解决这个问题,您可以使用宏。更好的是,如果你可以使用 C11,你可以使用泛型。

使用宏:

#define RESET_BUFF_IMPL(x){\
    /* do something with x as if it were circbuf_u8_st [volatile]* */ \
}

void reset_buff_v(struct circbuf_u8_st volatile *ptr) RESET_BUFF_IMPL(ptr)
void reset_buff(struct circbuf_u8_st *ptr) RESET_BUFF_IMPL(ptr)

然后使用你需要的。这意味着您不需要复制您正在谈论的所有代码。

如果你使用 C11,就像我提到的那样,你可以进一步完善它:

typedef struct circbuf_u8_st volatile *buff_vptr;
typedef struct circbuf_u8_st *buff_ptr;
#define RESET(x) _Generic(x, buff_vptr: reset_buff_v, buff_ptr: reset_buff)(x)

这增加了一点语法糖。


到目前为止,每个 C++ 答案都使用了某种面向对象的特性,所以我想我会投入 2 美分。您可以创建一个模板化函数,该函数只会针对特定类型进行实例化,std::enable_if使用std::is_samestd::remove_cv.

如果您可以使用 C++14,我们还有有用的std::enable_if_tand std::remove_cv_t,它可以节省一些输入。

模板功能:

template<typename T, typename = typename std::enable_if<std::is_same<typename std::remove_cv<T>::type, circbuf_u8_st>::value>::type>
void circbufu8_reset(T volatile *ptr){
    // ...
}

可以简化为:

template<typename T, typename = std::enable_if_t<std::is_same<std::remove_cv_t<T>, circbuf_u8_st>::value>>
void circbufu8_reset(T /* volatile */ *ptr){
    // ...
}

会满足你的需要。

于 2015-07-12T01:54:30.213 回答