A basic question question about volatile use

A

Ark Khasin

I have a memory-mapped peripheral with a mapping like, say,
struct T {uint8_t read, write, status, forkicks;};

If I slap a volatile on an object of type struct T, does it guarantee
that all accesses to the members are byte-wide, or is the compiler free
to read or read-modify-write in any data width it chooses?

Is slapping a volatile on each member of the struct definition any
different? better? worse?

Thank you,
Ark
 
F

Flash Gordon

Ark Khasin wrote, On 31/07/08 06:23:
I have a memory-mapped peripheral with a mapping like, say,
struct T {uint8_t read, write, status, forkicks;};

Be aware that the compiler is allowed to put in padding. Since this is
obviously for non-portable code it might be sufficient to just put in a
comment in the code stating that you are relying on the compiler not
putting in any padding.
If I slap a volatile on an object of type struct T, does it guarantee
that all accesses to the members are byte-wide, or is the compiler free
to read or read-modify-write in any data width it chooses?

The requirements on the compiler for volatile are quite lax and even
leave is up to the implementation to define what counts as accessing a
variable. So you should read your compiler documentation.

Imagine a processor which can physically only read/write in chunks of 64
bits but which nevertheless has a compiler with a "normal" 8 bit byte.
On such an implementation if the structure did not have any padding the
compiler would not be able to prevent all bytes being added.

Having said that, I would *expect* the compiler to "do the right
thing"(tm) in your case. I would expect this because by not doing so on
what I'm guessing is a compiler targeting a small embedded device would
make it hard to use.
Is slapping a volatile on each member of the struct definition any
different? better? worse?

It could be inconvenient. One thing I've sometimes wanted to do was use
a structure of that general type both for some in-normal-memory stuff
(without the volatile qualifier) and for the memory-mapped device.
volatile-qualifying the members would make this awkward.

There is also an argument that you should not use a struct for this
purpose *because* it could have padding, but if your code for
interfacing to the HW is nicely isolated and the compiler documentation
says it will work, then as it is highly system specific anyway I don't
see a real problem.

Sorry that the best advise is to read the compiler documentation, but it
really is your best chance to ensure it works correctly. The next best
advice would be asking in a group dedicated to your specific
implementation or in comp.arch.embedded where there are a lot more
people who are regularly doing this kind of thing than there are in this
group.

Note your question *is* topical here since volatile *is* part of the
standard language, it's just that to achieve your aims you need to go
beyond the guarantees of the language to things guaranteed by your
specific implementation.
 
A

Ark Khasin

Gordon said:
I don't believe that anything in the standard guarantees the right
thing for memory-mapped I/O (the vague promises aren't good enough),
What if it is not an I/O but a normal memory location with intended
byte-size members? (BTW, why the difference?)

Thanks,
Ark
 
A

Ark Khasin

Flash said:
Be aware that the compiler is allowed to put in padding. Since this is
obviously for non-portable code it might be sufficient to just put in a
comment in the code stating that you are relying on the compiler not
putting in any padding.
I was under the impression that if a struct contains only uint8_t and
arrays thereof, there is no padding. Is it wrong?
Imagine a processor which can physically only read/write in chunks of 64
bits but which nevertheless has a compiler with a "normal" 8 bit byte.
On such an implementation if the structure did not have any padding the
compiler would not be able to prevent all bytes being added.
Is such a compiler allowed to define uint8_t? Hmm... I guess it is...

Thanks,
Ark
 
S

santosh

Ark said:
I was under the impression that if a struct contains only uint8_t and
arrays thereof, there is no padding. Is it wrong?

The intN_t types are specified to contain any padding bits, but
padding between structure members of these types in still allowed.

<snip>
 
A

Ark Khasin

Flash said:
> Imagine a processor which can physically only read/write in chunks of 64
bits but which nevertheless has a compiler with a "normal" 8 bit byte.
On such an implementation if the structure did not have any padding the
compiler would not be able to prevent all bytes being added.
This is true for any implementation of any language; the peripherals
must be designed to be accessible via CPU instructions, so my
struct T {uint8_t read, write, status, forkicks;};
doesn't describe a peripheral on such a platform.
OTOH, it can be a normal memory object (+ consider a "normal" platform
with a volatile bitfield member of a struct). The closest thing to "the
right thing" would be to disable the interrupts while reading/writing.
Will a(ny) compiler do this?
 
S

santosh

santosh said:
Ark said:
I was under the impression that if a struct contains only uint8_t and
arrays thereof, there is no padding. Is it wrong?

The intN_t types are specified to contain any padding bits, but

^
add a "not" here.
 
A

Ark Khasin

santosh said:
Ark said:
I was under the impression that if a struct contains only uint8_t and
arrays thereof, there is no padding. Is it wrong?

The intN_t types are specified to contain any padding bits, but
padding between structure members of these types in still allowed.

Of course struct {uint8_t a; uint32_t b; uint8_t c;} is likely to have
padding somewhere. But uint8_t alone?..
 
S

santosh

Ark said:
santosh said:
Ark said:
I was under the impression that if a struct contains only uint8_t
and arrays thereof, there is no padding. Is it wrong?

The intN_t types are specified to contain any padding bits, but
padding between structure members of these types in still allowed.

Of course struct {uint8_t a; uint32_t b; uint8_t c;} is likely to have
padding somewhere. But uint8_t alone?..


I think you are conflating padding bits (which the intN_t types
cannot have) with padding bytes, which are allowed between any two
structure members. It is not allowed between elements of an array.
 
C

Chris Torek

For instance, the original DEC Alpha (though later versions of the
chip acquired byte load and store via the so-called "BWX" option)
Current MIPS chips, which are found in equipment being made and
sold today (many readers probably have them in their own homes),
still do 32-bit accesses as a minimum.
On such an implementation if the structure did not have any padding the
compiler would not be able to prevent all bytes being a[ccess]ed.

This is true for any implementation of any language; the peripherals
must be designed to be accessible via CPU instructions, so my
struct T {uint8_t read, write, status, forkicks;};
doesn't describe a peripheral on such a platform.

Indeed. But it shows some of the limitations the C Standard has
in setting limitations on implementations: if Standard C required
implementations to do only single-byte load and store for volatile
"uint8_t" accesses, Standard C would be impossible on old DEC Alpha
chips, and on current MIPS chips.
OTOH, it can be a normal memory object (+ consider a "normal" platform
with a volatile bitfield member of a struct). The closest thing to "the
right thing" would be to disable the interrupts while reading/writing.
Will a(ny) compiler do this?

One might. I have not seen any that would, though.

The *intent* of the volatile qualifier is clear enough. It means,
in effect: "Hey Mr Compiler, this object does not behave like
ordinary RAM, so do not assume that anything you put in it stays
there, nor that anything you read from it is still there even
nanoseconds later." What the compiler *does* with that extra bit
of information is up to the implementation. In general, if you
are accessing memory-mapped I/O hardware, using the "volatile"
qualifier is necessary, but sometimes not sufficient.

Memory-mapped I/O is of course not required, and there are systems
that do not have it (systems where "I/O" operations are separate
instructions like "inb" and "outb", for instance; of course no one
would buy a chip with such instructions today :) ). So in "100%
Standard" C, where you would not even *think* of accessing I/O
hardware :) , there are only two situations in which you need
"volatile":

- when working with a sig_atomic_t variable used in a signal
handler, and

- when using setjmp() and longjmp(), where you should qualify
all local variables as "volatile". (You can get away without
it for some local variables, but figuring out which ones can
be somewhat painful, and it is easier and safer to start by
marking them all volatile. Later, when doing optimization, if
profiling shows that some of them are causing performance
problems, you can work out which one(s) are safe.)

Personally, I wish that Standard C did not require "volatile" for
either of these situations (and I think C99 does not for the first
one, having smuggled the "volatile" into sig_atomic_t). But C is
what the Standard says it is, and as C programmers, we must either
go with that, or be very certain *why* we are not doing so. (That
is, you can abandon the Standard any time you like, but you should
*know* that you are doing this, and what you stand both to gain
and to lose in the process.)
 
N

Nick Keighley

santosh said:
Ark Khasin wrote:
The intN_t types are specified to contain any padding bits, but
padding between structure members of these types in still allowed.


Of course struct {uint8_t a; uint32_t b; uint8_t c;} is likely to have
padding somewhere. But uint8_t alone?..


your declaration looked like this:-

struct T {uint8_t read, write, status, forkicks;};

So you don't have a lone uint8_t you have 4 of them!
I must admit I'd consider it an odd compiler that padded
between uint8_ts like this.

<muse: "how can I annoy jacob?">

I suppose a machine with 24-bit words packing 3 chars
into a word might insert padding.

ccc00c
 
B

Barry Schwarz

I have a memory-mapped peripheral with a mapping like, say,
struct T {uint8_t read, write, status, forkicks;};

If you want to insure that there is no padding between the four
members, change it to {uint8_t x[4];}. If you like, you can add
macros of the form
#define READ x[0]
#define WRITE x[1]
so that references to the members are mnemonically meaningful.
If I slap a volatile on an object of type struct T, does it guarantee
that all accesses to the members are byte-wide, or is the compiler free
to read or read-modify-write in any data width it chooses?

This is really a system specific issue. I doubt if volatile makes a
difference. A machine may perform all memory accesses in "word" sized
blocks and extract the relevant bytes from internal registers.

I would expect that since this peripheral works on your system that
your system supports byte accesses. I would further expect that
anyone writing a compiler for this system would do so also.

But you asked for a guarantee. The best we can offer is probably. If
your compiler has a newsgroup or a tech support site, that would be
the place to ask.
Is slapping a volatile on each member of the struct definition any
different? better? worse?

I don't see how it could hurt and it does emphasize your intent.
 
A

Ark Khasin

Eric said:
It might appear that by qualifying each element

struct T {
volatile uint8_t read;
volatile uint8_t write;
...
};

... one could tell the compiler not to meddle with the `read' element
while accessing the adjacent `write'. The argument might hold that
`volatile' warns the compiler that each access to the variable is a
side-effect, so that touching `read' while fiddling with `write'
would cause a side-effect in the real machine that is not present in
the abstract machine. The argument sounds pretty good, but the last
sentence of 6.7.3p6 torpedoes it: "What constitutes an access to an
object that has volatile-qualified type is implementation-defined."
This sentence -- which might as well be named the Mack Truck Clause,
because it's a hole wide enough to drive a Mack truck through -- is
the Standard's concession that C is implemented on real machines that
may not always be capable of following the abstract machine's rules
quite that scrupulously. (For example, others have mentioned real
machines that are unable to access bytes in isolation, but always
deal with larger assemblages.)

Ark's case is not hopeless, though. The MTC says "implementation-
*defined*," which means that the implementation is required to define
what it means to access a volatile object. Somewhere in his compiler's
documentation there should be a description of how volatile accesses
are carried out, and perhaps the compiler's promises -- which are
specific to that particular compiler, of course -- will suffice for
Ark's purposes. In fact, there's an excellent chance that they will,
because if the machine has arranged its I/O registers in this manner,
it's highly likely that the hardware is able to generate the accesses
they need, and there's a reasonable chance that the C compiler can
create code that generates those accesses. It's not certain -- on
some machines you may need to resort to assembly -- but the odds are
fairly good.
Thank you, Eric and all.
The code is working fine; my inquiry was about how much RTFM I need to
do if I am to replace the compiler. Turns out the whole volatile
business is implementation-dependent (perhaps actually -defined with any
luck). It would be nice at least to have a machine-defined set of atomic
types and a guarantee that an atomic volatile type is accessed
atomically and without molesting its neighbors. Alas, even that isn't
true: ARM has an instruction to access (volatile) 64-bit (and wider)
objects atomically but my compiler (IAR) doesn't seem to always bother.

- Ark
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,773
Messages
2,569,594
Members
45,125
Latest member
VinayKumar Nevatia_
Top