1

I use the following type:

/* double_buffer.h */

typedef struct
{
    uint8_t * active_buffer_p;       //< Address of active buffer
    uint8_t current_writing_index;   //< Current writing index in active buffer
    uint8_t buffer_1[BUFFER_SIZE];   //< First buffer
    uint8_t buffer_2[BUFFER_SIZE];   //< Second buffer
} double_buffer_t;


#define DoubleBuffer_init(buffers) do {                 \
        (buffers).active_buffer_p = (buffers).buffer_1; \
        (buffers).current_writing_index = 0;            \
        } while(0)

In my code, I declare an array of double buffer, using the volatile keywoard (because the buffers can be updated/read asynchronously in interrupts and in functions):

static volatile double_buffer_t m_double_buffers[NB_DOUBLE_BUFFERS];

I then initialize those buffers individually:

DoubleBuffer_init(m_double_buffers[id]);

When I compile the software (gcc), I got the following warning:

 error: assignment discards 'volatile' qualifier from pointer target type [-Werror=discarded-qualifiers]
   28 |         (buffers).active_buffer_p = (buffers).buffer_1; \

The reason why I have this warning is quite unclear to me, and I am not sure how to fix it.

Any help would be appreciated (I can update the question if something is not clear).

5
  • Note that volatile does not mean "atomic". It's impossible to tell without specifics, but what you've posted opens to door to some nasty race conditions. Commented Jan 31, 2023 at 12:17
  • port70.net/%7Ensz/c/c11/n1570.html#5.1.2.3p5: "When the processing of the abstract machine is interrupted by receipt of a signal, the values of objects that are neither lock-free atomic objects nor of type volatile sig_atomic_t are unspecified, as is the state of the floating-point environment. The value of any object modified by the handler that is neither a lock-free atomic object nor of type volatile sig_atomic_t becomes indeterminate when the handler exits, as does the state of the floating-point environment if it is modified by the handler and not restored to its original state." Commented Jan 31, 2023 at 12:17
  • 1
    Critical sections are implemented in order to avoid race conditions Commented Jan 31, 2023 at 12:31
  • 1
    The problem is that members of volatile structure also get volatile qualified. The the pointer type of double_buffer_t::active_buffer_p is missing volatile qualifier. Commented Jan 31, 2023 at 13:28
  • 1
    @AndrewHenle Who said it made anything atomic? That's not why you declare variables shared with an ISR as volatile. Every single time someone brings this up, we always get these irrelevant rants about "volatile and thread safety" in comments. Where are the threads? Who claimed volatile is there to prevent data races? Nobody! So kindly delete those off-topic comments and check out this: Using volatile in embedded C development Commented Jan 31, 2023 at 13:50

2 Answers 2

4

You get this warning because you have a volatile object, and you create a non-volatile pointer to it.

This is bad as the compiler could access the volatile object without knowing that it is volatile. E.g. it could transform two reads into a single, it could change the order etc.

One way to fix it is to define active_buffer_p to uint8_t volatile * active_buffer_p.

Sign up to request clarification or add additional context in comments.

2 Comments

Yes but members of the struct are supposed to become volatile as well (since I declare the whole variable as volatile). So I don't understand why I should explicitly tell that active_buffer_p is volatile.
@The_Average_Engineer: You're confusing a volatile pointer with a pointer to volatile data. active_buffer_p will be a uint8_t* volatile when the whole struct is volatile. That means the pointer itself can change without notice.
2

When you declare a struct variable as volatile, each member object gets volatile qualified, as if you had written the struct like this:

typedef struct
{
    uint8_t* volatile active_buffer_p; 
    volatile uint8_t current_writing_index;
    volatile uint8_t buffer_1[10];
    volatile uint8_t buffer_2[10];
} double_buffer_t;

That is, in case of a pointer member, the pointer itself turns volatile. type* volatile = the pointer's address may change at any moment. And not what it is supposed to point at. volatile type* = the pointed-at data might change at any moment.

And therefore when you assign, buffer_1 "array decays" into volatile uint8_t* and then you try to assign that to a pointer qualified as uint8_t* volatile. The pointer types aren't compatible since they have different qualifiers.

The solution is to declare the pointer member volatile uint8_t* active_buffer_p;. Then if the struct variable is declared volatile, this becomes volatile uint8_t* volatile (the pointer and what it points at may change at any moment). And we can always assign a "non qualified" pointer to one with more qualifiers, but not the other way around.

const works exactly the same way.


As a side note, that init macro is just ugly and fills no purpose but obfuscation. Consider dropping it in favour of readable code:

static volatile double_buffer_t m_double_buffers[NB_DOUBLE_BUFFERS] =
{
  [0] = { .active_buffer_p = m_double_buffers[0].buffer_1, 
          .current_writing_index = 0 },
  [1] = { .active_buffer_p = m_double_buffers[1].buffer_1, 
          .current_writing_index = 0 },
  [2] = { .active_buffer_p = m_double_buffers[2].buffer_1, 
          .current_writing_index = 0 },
};

Or 100% equivalent:

static volatile double_buffer_t m_double_buffers[NB_DOUBLE_BUFFERS] =
{
  [0] = { .active_buffer_p = m_double_buffers[0].buffer_1, },
  [1] = { .active_buffer_p = m_double_buffers[1].buffer_1, },
  [2] = { .active_buffer_p = m_double_buffers[2].buffer_1, },
};

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.