我正在使用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
和非{{1}参数。这是一个维护问题:其中一个版本中的任何更改都必须在另一个版本中重复。例如,
volatile
我可以使用所有内容的void circbufu8_reset(struct circbuf_u8_st *cb);
void circbufu8_v_reset(struct circbuf_u8_st volatile *cbv);
版本,因为这将始终是正确的。但这也意味着我希望避免的非volatile
案件的悲观情绪。
因此,一个可能的解决方案是声明volatile
成员union
/非volatile
成员,并声明volatile
成员的类型为struct
1}}。
union
这将有助于摆脱每个功能的2个版本问题。但是,它真的有帮助吗?这样的函数有什么语义(可能)?我的理解是编译器必须使用union un_dual_volatile_u8 {
uint8_t volatile v;
uint8_t nv;
};
所需的最严格的语义,所以实际上这只是一切不必要的复杂版本 - union
选项,具有相同的悲观性。 / p>
所以,问题是:
volatile
和非volatile
语义吗?或者可能使用泛型?)(查看编译器输出不是一个有效的答案;我正在寻找可以依赖的基于标准的理由)
编辑:我删除了C ++标签,因为问题的C ++部分更多是出于好奇。当然,C11的泛型使问题更容易解决,但目标是在C99中解决这个问题。答案 0 :(得分: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
}
};
答案 1 :(得分:1)
他的回答是你可以在C ++中对volatile的重载是正确的,但如果我理解正确,那就不是你想要的了。
#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。
答案 2 :(得分:1)
(由于缺乏非C ++答案而自我回答)
在C99中,像这样具有类似重载功能的行为的唯一方法是通过预处理器技巧。工会不会起作用,至少因为这种联盟的语义是undecided in GCC itself,已经存在了几年而且似乎并没有改变。在那个错误报告和讨论看起来像GCC开发人员并不确定从标准中解释什么...所以我认为这是一个非常强烈的建议,即使我改变了编译器,也放弃了整个攻击角度。
所以最后我选择了通用预处理器&#34;模板&#34;使用参数化类型声明和初始化函数,结构等 - 例如,一个案例中uint16
和另一个案例中volatile uint8
。例如,对于循环缓冲区:
#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 Gustedt&#39; P99 preprocessor library for C是一个很好的灵感。后来我发现这种&#34;预处理器模板&#34;也用在Linux的内核源代码中......所以尽管我可能会随着时间的推移推出自己的版本,但我会尝试使用其中一个现有的实现。但是,在我有时间之前,我换了工作。
对于那些感兴趣的人,我写了一篇blog post,其中充分讨论了基于C99标准的理由,说明为什么volatile + non-volatile union是合法的,但语义仍然不清楚。
答案 3 :(得分: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美分。你可以创建一个模板化的函数,只使用{{3来实例化特定类型},std::enable_if
和std::is_same
。
如果你可以使用C ++ 14,我们还有一个有用的std::enable_if_t
和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){
// ...
}
符合您的需求。