Question

I'm developing on C99 for an embedded environment using GCC. I made a small library to deal with circular buffers and queues. It implements and works on instances of a basic struct which contains a buffer and the needed metadata.

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

Sometimes the struct is used as a volatile global, since it is used for communication between an interrupt routine which generates data and the main loop which consumes the data.

But sometimes the struct is used as a non-volatile local, for example when one part of the main loop generates data to be consumed later on the same main loop.

Having the struct being sometimes volatile and sometimes not means that any function dealing with the struct needs two versions, for the volatile and non-volatile parameter. That is a maintenance problem: any change made in one of the versions has to be repeated in the other version. For example,

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

I could just go with the volatile version of everything, since that will be always correct. But that also implies a pessimization for the non-volatile case that I would like to avoid.

So, a possible solution would be to declare a union with volatile/non volatile members, and declare the struct members to have the type of that union.

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

That would help to get rid of the 2-versions-of-each-function problem. But, does it really help? What semantics would (could) such a function have? My understanding is that the compiler will have to use the strictest semantics needed by the union, so in fact this would just be a needlessly complicated version of the everything-volatile option, with the same pessimization.

So, the questions are:

  • am I right that the union won't help?
  • is there then any way to avoid the function duplication and the pessimization?
  • would C++ help in this kind of situation? (would function overloading respect the volatile and non-volatile semantics? or maybe using generics?)

(looking at the compiler output is not a valid answer; I'm looking for a standard-based justification that can be relied upon)

EDIT: I removed the C++ tag, since the C++ part of the question was more out of curiosity. And of course C11's generics make the problem easier to solve, but the goal is to solve this in C99.

Was it helpful?

Solution 3

(Self-answering because of lack of non-C++ answers)

In C99 the only way to have the overloaded-function-like behaviour like this is through preprocessor tricks. The union wouldn't work, at the very least because the semantics of such a union are undecided in GCC itself, have been for some years and don't seem to be changing. In that bug report and discussion looks like GCC developers aren't really sure about what to interpret from the standard… so I took that as a very strong suggestion to abandon this whole angle of attack, even if I changed compilers.

So finally I went with generalized preprocessor "templates" which declared and initialized functions, structures, etc with parameterized types - for example, a uint16 in one case and a volatile uint8 in another. For example, for the circular buffer:

#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;

And then invoked like so:

CIRCBUF_TYPE_DECLARE(uint8_t, _u8, volatile, _v)

For this kind of thing, Jens Gustedt's P99 preprocessor library for C was a great inspiration. Later I found that this kind of "preprocessor templating" is also used in Linux's kernel source code… So even though I rolled my own versions probably with time I would have tried using one of the existing implementations. However, I switched jobs before I had the time for that.

For those interested, I wrote a blog post with the full discussion on the C99-standard-based justification of why the volatile+non-volatile union is legal and yet how the semantics are still not clear.

OTHER TIPS

In C++, you could certainly do this using templates:

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

Of course, this function template would be callable with any type (but likely fail instantiation), so you might want to limit its use like this:

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);
}

EDIT

An even more C++ way would be this:

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
    }
};

Mat his response that you can overload on volatileness in C++ is correct, but if I understand correctly it is not what you want.

#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;
}

Output:

P1A 
PV1A

Under the hood, the compiler is emitting two versions of foo.

To solve this in C, you could use macros. Even better, if you can use C11, you could use generics.

Using macros:

#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)

Then use which ever you need. This means you don't need to duplicate all of that code you were talking about.

And if you use C11, like I mentioned, you could sweeten this further:

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)

Which adds a nice little bit of syntactic sugar.


Every C++ answer so far has used some sort of object-orientedness, so I thought I'd put my 2 cents worth in. You could make a templated function that will only instantiate for specific types, using std::enable_if, std::is_same and std::remove_cv.

If you can use C++14, we also have the useful std::enable_if_t and std::remove_cv_t, which saves some typing.

The template function:

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){
    // ...
}

which can be simplified down to:

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){
    // ...
}

would suit your needs.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top