5

Lets suppose it is embedded development of some ARM controller. Lets suppose we have some variable, which can be assigned from an interrupt or "mainThread" - (is it a "main loop" or RTOS thread). In C world volatile keyword should be used in this case and code may look like this:

/* Some subsystem .C file */
static volatile uint8_t state;

void on_main_thread(void) {
    state = 1;     /* Changing state in this context */
}

void on_interrupt(void) {
    state = 0;     /* Changing state from interrupt */
}

uint8_t get_state(void) {
    return state;  /* Getting the state in whatever context */
}

volatile keyword is essential in this situation. Now our company rewrites some code to C++ and the same subsystem example looks like this (I use enum here to emphasize the problem)

class SomeSubsystem
{
public:
    enum class States
    {
        Off,
        Idle,
        Up,
        Down,
    };


    States getState() const { return mState; }

    void onMainThread(void) {
           mState = States::Idle;     // Changing state in this context
    }

    // Somehow this function is called from the interrupt
    void onInterrupt(void) {
           mState = States::Up;     // Changing state from interrupt
    }
private:
    States mState;   // <-- Here! Volatile? 
//...
};

Now States mState should be volatile because it is shared among different contexts. But If one sets it as volatile... Then volatile works like plague for C++ class and one have to volatilize everything around. Like volatile enum class States, getState() volatile etc. Which doesn't look good for me (am I wrong?)

So. What is the right way to handle this situation in C++?

P.S. I would try to define "this situation" as: "possible usage of a class members from different contexts like interrupts and normal code execution"

MajesticRa
  • 13,770
  • 12
  • 63
  • 77
  • Having a member state besides the global is redundant. If you want that, omit volatile there. Otherwise you introduced another plaque. –  Jul 27 '16 at 11:09
  • "besides the global" - what do you mean by global? – MajesticRa Jul 27 '16 at 11:13
  • making the member variable volatile is fine but is only guaranteed to do what you require if it's referring to an I/O memory address. If you want cross-thread communications, use std::atomic<> – Richard Hodges Jul 27 '16 at 11:18
  • @MajesticRa ... the `static volatile uint8_t state;` –  Jul 27 '16 at 11:19
  • @Richard Hodges std::atomic<> - While using C++11 code, we can't use standard library in the development. Unfortunately have this restriction. – MajesticRa Jul 27 '16 at 11:23
  • @Dieter Lücking ```static volatile uint8_t state``` - was in C. Now there is "State mState". onInterrupt is called from the interrupt itself. – MajesticRa Jul 27 '16 at 11:25
  • @MajesticRa Then, you miss a static variable and/or some static callbacks in you class. –  Jul 27 '16 at 11:27
  • @Dieter Lücking - hm... We have some global gApplication variable. It is global and static. When the interrupt is called, the function above is called like ```gApp.oneSubsystem.anotherSubsystem.onInterrupt()```. Do you mean making gApplication volatile? But in this case wouldn't it just make whole the code volatile? (except small C HAL layer) Because like 90% of code is in classes like above, which are part of gApplication. – MajesticRa Jul 27 '16 at 11:31
  • Not sure about C++, but the C standard allows `stdatomic.h` for freestanding implementations, too. It is a header-only part of the stdlib like `stdint.h`, nothing to link. I don't know any coding style whioch forbids using the header-only parts of the stdlib - **especially** for embedded systems where fixed-width types are a must-have. – too honest for this site Jul 27 '16 at 12:22
  • 3
    @Olaf First, stdatomic is C11 standard. For example Keil uVision compiller doesn't use it http://www.keil.com/support/man/docs/armcc/armcc_chr1359124192674.htm. Second " I don't know any coding style whioch forbids " - that is cool, you don't know anybody who is working by one or another set of standards, but it doesn't answer anything in my question. – MajesticRa Jul 27 '16 at 12:36
  • Can you cast variables to volatile? As in `(volatile States &)mState = ...`? Not sure if that helps with anything. – melpomene Jul 28 '17 at 13:50

2 Answers2

1

This could work if you only need a single instance of SomeSubsystem in your program (which I assume as per the c code you posted.

If you need multiple instances, then maybe you could modify mState to be a States array or some similar structure instead.

class SomeSubsystem
{
public:
    enum class States
    {
        Off,
        Idle,
        Up,
        Down,
    };


    States getState() const { return mState; }

    void onMainThread(void) {
           mState = States::Idle;     // Changing state in this context
    }

    // Somehow this function is called from the interrupt
    void onInterrupt(void) {
           mState = States::Up;     // Changing state from interrupt
    }
// Make mState public in order to access it from the rest of your code
// Otherwise, keep it private and create static set/get functions
public:
    static volatile States mState;   // <-- Here! Volatile? 
//...
};

Then define mState somewhere (eg. in SomeSubsystem.cpp)

volatile SomeSubsystem::States SomeSubsystem::mState = SomeSubsystem::States::Off;

Now you are able to access mState from anywhere in your code like this

SomeSubsystem::mState = SomeSubsystem::States::Off;
Santos
  • 37
  • 7
1

I believe you only need to volatile-qualify the methods if the class object itself is volatile. However, I don't think you should have any issues if you just make the relevant member variables volatile. I have done this successfully (i.e. it compiled/ran) on something similar to what you are trying to achieve.. E.g.:

class SomeSubsystem
{
public:
    ...
    void onMainThread(void); // no volatile qualification necessary
    void onInterrupt(void);  // "
private:
    States volatile mState;  // only make the relevant member variables volatile
}

SomeSubsystem aSubsystem;    // don't make the object volatile
...
aSubsystem.onMainThread();
aSubsystem.onInterrupt();
abc
  • 212
  • 3
  • 14