18

I have this code:

typedef struct {
    int test;
} SensorData_t;
volatile SensorData_t sensorData[10];

SensorData_t getNextSensorData(int i)
{
    SensorData_t data = sensorData[i];
    return data;
}


int main(int argc, char** argv) {

}

It compiles with gcc version 8.3, but not with g++. Error message:

main.c: In function ‘SensorData_t getNextSensorData(int)’:
main.c:8:34: error: no matching function for call to ‘SensorData_t(volatile SensorData_t&)’
  SensorData_t data = sensorData[i];
                                  ^
main.c:3:3: note: candidate: ‘constexpr SensorData_t::SensorData_t(const SensorData_t&)’ <near match>
 } SensorData_t;
   ^~~~~~~~~~~~
main.c:3:3: note:   conversion of argument 1 would be ill-formed:
main.c:8:34: error: binding reference of type ‘const SensorData_t&’ to ‘volatile SensorData_t’ discards qualifiers
  SensorData_t data = sensorData[i];
                      ~~~~~~~~~~~~^
main.c:3:3: note: candidate: ‘constexpr SensorData_t::SensorData_t(SensorData_t&&)’ <near match>
 } SensorData_t;
   ^~~~~~~~~~~~
main.c:3:3: note:   conversion of argument 1 would be ill-formed:
main.c:8:34: error: cannot bind rvalue reference of type ‘SensorData_t&&’ to lvalue of type ‘volatile SensorData_t’
  SensorData_t data = sensorData[i];

I'm not sure if I need to add volatile as well for the data variable and the return type, shouldn't be needed because it is copied. But I do access the sensorData array from an interrupt as well (on an embedded system), so I think I need volatile for the top level variable sensorData.

user3386109
  • 34,287
  • 7
  • 49
  • 68
Frank Buss
  • 714
  • 7
  • 14
  • 2
    FYI: "typedef struct" is not required in C++. Also, one question concerning the code here: You could use a simple `int` array (or, better, `std::array`), do you have the struct here because it contains more members? In any case, you may need actual synchronization with the interrupt, even if it's just one `int`, so plan your critical section where only one of the two can access the data. – Ulrich Eckhardt May 11 '22 at 05:11
  • 1
    Related: https://stackoverflow.com/q/17217300/951890 – Vaughn Cato May 14 '22 at 16:47

2 Answers2

14

Your program is trying to copy a SensorData_t object. The compiler supplies a copy constructor with the following signature:

SensorData_t(const SensorData_t &)

This copy constructor will not work with volatile arguments, hence the compilation error.

You can write your own copy constructor which works with volatile SensorData_t objects (as well as non-volatile SensorData_t objects):

struct SensorData_t {
    SensorData_t() = default;

    SensorData_t(const volatile SensorData_t &other)
        : test(other.test) {
    }

    int test;
};
strager
  • 88,763
  • 26
  • 134
  • 176
5

As an alternative to the accepted answer, you may consider avoid copying the data from the array altogether, if the getNextSensorData is a simple getter function for the global sensorData:

// Intorduce type alias
using SD = const volatile SensorData_t&;

SD getNextSensorData(int i)
{
    return sensorData[i];
}

This way (if it is suitable for the design), you'll keep the definitions code more concise. But I have to note that the usage is a bit less direct than in the copy case:

int main() {
    // Type alias
    SD sd1 = getNextSensorData(0);
    // Deduced type
    auto&& sd2 = getNextSensorData(1);
    // Explicit type
    const volatile SensorData_t& sd3 = getNextSensorData(2);
}
rawrex
  • 4,044
  • 2
  • 8
  • 24
  • 5
    This is a bad idea, since it moves the actual access to the volatile object from the "get" abstraction to some unknown point later in the code. `volatile` strongly suggests that either the timing or order of access is critical. – Ben Voigt May 11 '22 at 15:19