根据参数是否易变,C函数是否可以双重使用?

时间:2013-09-13 13:17:57

标签: c language-lawyer params volatile unions

我正在使用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>

所以,问题是:

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

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

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

4 个答案:

答案 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_ifstd::is_same

如果你可以使用C ++ 14,我们还有一个有用的std::enable_if_tstd::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){
    // ...
}

符合您的需求。