0
votes

I am trying to use a volatile unsigned char as a pointer array argument to a function. I created the function:

static void queue_to_serial(unsigned char *queue)

I'm using "static" for the function, because it is only to be used in the current source file. Then I call the function and supply the address:

queue_to_serial(&queue);

And I get an error:

Error[Pe167]: argument of type "unsigned char volatile (*)[8]" is incompatible with parameter of 
type "unsigned char *"

The array is declared as a volatile but from what I know that means it shall not be optimized out as it can change asynchronously

static volatile unsigned char queue[MAX_NUM_CONNECTED_VALVES];  

The array is also declared to be static because I would like to retain the value assigned to the variable. Unless the array is updating at the exact time the function is run which should not happen this should in my mind work because the memory used by a volatile unsigned char is the same as an unsigned char. How does making a variable volatile change it? Or does declaring it as a pointer cause it to occupy memory at a different location? Thank you

2
You just need to do queue_to_serial(queue); queue is already a pointer. The volatile flag is unrelated to that error. - yhyrcanus
I actually tried that and the error persisted *a and a[] are equivalent from what I know. Thanks! - William Dussault
*a and a[] are "equivalent" (not really but it depends on context), but you're passing &queue when queue is already an array of char. The error message shows this. Since you get a similar error message when you pass queue, you should update your question with that code and error message, which is closer to correct. - trentcl

2 Answers

3
votes

When you write a function such as

int foo(char* x);

the compiler will generate some code for this function, which does not know anything about what kind of parameters you are going to cast to char* and pass to it. So it will assume x is a pointer to some regular memory that can be be optimized in usual way, and for example something like:

int foo(char* x) 
{
    a = x[0];
    b = x[0];
    return a + b;
}

could be optimized to something like return 2 * x[0]; - that is performing a single read from x[0].

Now consider

int bar(volatile char* x) 
{
    a = x[0];
    b = x[0];
    return a + b;
}

This time the compiler must generate two reads from x.

Why does it matter? Think that x is pointing to some hardware counter register, which will increment each time it is being read. So assuming it's initial value is 0, the foo(x) call will return 0 and the value of the counter will be 1. bar(x) will return 1 and the value of the counter will be 2.

1
votes

volatile was standardized in 1989 ANSI C at the same time as const, and was lumped into the same category as a type qualifier, following the same rules.

If you have a

void function(char *ptr);

then for obvious reasons, there is a diagnostic if you pass in the address of a const char object:

{ const char c = 'A'; function(&c); } /* diagnostic required */

How that works, formally, is that a pointer to a more qualified type cannot be implicitly converted into a pointer to a less qualified type.

Since volatile is in the same lexical category as const (it's a qualifier), it gets treated the same way.

Furthermore, if an object defined volatile is accessed through an lvalue that doesn't carry the volatile qualifier, the behavior is undefined. (Note that this is a looser requirement area than const: const objects are accessible through a non-const-lvalue; only modification is undefined.) I think this was added to the language to justify the treatment of the volatile concept as a qualifier similar to const; it doesn't actually make a whole lot of sense. It's just "the way it is".