How to cast volatile typedef struct to non-volatile typedef struct as argument of the function

111 Views Asked by At

I'm writing a C library for STM32 and ran into a problem.

I have typedef struct:

typedef struct foo {
    // some struct elements
} foo;

Volatile variable with foo type:

volatile foo bar;

And function with foo-typed argument:

int foobar(foo* baz) {
    // some operations with baz
    return 0;
}

When I'm trying to call foobar(&bar);, I get an error: error: invalid conversion from ‘volatile foo*’ to ‘foo*’ [-fpermissive]

Will it work if I cast volatile foo* to foo* ( foobar((foo*)&bar);)?

I tried to cast volatile foo* to foo* but I don't know if it will work without bugs.

2

There are 2 best solutions below

3
0___________ On BEST ANSWER

It is not only cast. If you need to cast this way it means that your code has a issue.

If you want to use a function which does not take a volatile parameter with a volatile object it means that this object needs to be treated as not *side effects prone. Your function does not have to assume that something not visible to the compiler can change it and apply optimizations which are not possible for volatile objects. The most important is that all accesses to the volatile object have to be applied to its permanent storage place.

If you have this problem then:

  1. You overuse the volatile keyword and your volatile object does not have to be volatile (as you want to use it as non volatile). You should rethink your program data types.

  2. You can declare the function as taking volatile - but it will prevent many possible optimizations.

  3. You can have different functions handling volatile and non volatile data.

Will it work if I cast volatile foo* to foo* ( foobar((foo*)&bar);)?

No. It will only silence the warnings but it can be very dangerous.

struct  x
{
    bool dangerous;
    float data;
};

int dangerous(struct x *ptr)
{
    if(!ptr -> dangerous)
    {
        /* do something */
        if(!ptr -> dangerous) never_call_if_dangerous_is_true();
    }
}

int dangerous_volatile(volatile struct x *ptr)
{
    if(!ptr -> dangerous)
    {
        /* do something */
        if(!ptr -> dangerous) never_call_if_dangerous_is_true();
    }
}

and resulting code:

dangerous:
        cmp     BYTE PTR [rdi], 0
        jne     .L2
        xor     eax, eax
        jmp     never_call_if_dangerous_is_true
.L2:
        ret
dangerous_volatile:
        mov     al, BYTE PTR [rdi]
        test    al, al
        jne     .L5
        mov     al, BYTE PTR [rdi]
        test    al, al
        jne     .L5
        jmp     never_call_if_dangerous_is_true
.L5:
        ret

in non volatile version skips the second check. If something (signal, interrupt) changes the dangerous member it will call the function - killing someone for example

6
Pepijn Kramer On

NOTE the tag C++ was removed after I created my answer.

  You can solve this with a template function, the std::enable_it_t is an extra (SFINAE) check to avoid accepting anything other than a foo_t. Note I use refences not pointers since I expect the input to be a valid instance (with pointers you always have to do the extra nullptr check)

#include <type_traits>
#include <iostream>

/*typedef*/ struct foo_t  // <== typedef is "C" syntax, not needed
{
};

// any form of foo_t is ok (with or without const, volatile or reference)
template<typename type_t>
constexpr bool is_foo_t()
{
    return std::is_same_v<foo_t,std::remove_cvref_t<type_t>>;
}

template<typename type_t>
auto foobar(const type_t& foo) -> std::enable_if_t<is_foo_t<type_t>(),int>
{
    // reusable code here 
    // just a demo to show both arguments are accepted
    if constexpr(std::is_volatile_v<type_t>)
    {
        std::cout << "Called with volatile foo_t\n";
    }
    else
    {
        std::cout << "Called with non-volatile foo_t\n";
    }

    return 0;
}


int main()
{
    volatile foo_t volatile_foo;
    foobar(volatile_foo);

    foo_t foo;
    foobar(foo);
}