Can *common* struct-members of 2 different struct-types, that are thesame for the first common membe

L

Lanarcam

Le 03/05/2012 21:16, James Kuyper a écrit :
Why should that be necessary?

IMO, volatile is useful when and only when the variable
can be modified by a piece of code out of scope of
the compiler or by a separate IC, for instance:

- global variable modifiable by several threads in a
multithread OS.

- Global variable modifiable by an ISR.

- Memory mapped IC registers.

Is that correct?
 
J

James Kuyper

On 05/03/2012 03:56 PM, Lanarcam wrote:
....
IMO, volatile is useful when and only when the variable
can be modified by a piece of code out of scope of
the compiler or by a separate IC, for instance:

- global variable modifiable by several threads in a
multithread OS.

- Global variable modifiable by an ISR.

- Memory mapped IC registers.

Is that correct?

You left out another important context: signal handlers. In fact, until
C2011 added working about threading, signal handlers were the only
purely internal C reason for needing volatile; all of the other possible
reasons were outside the scope of the standard.
 
J

Jens Gustedt

Am 05/03/2012 09:16 PM, schrieb James Kuyper:
Why not? In the abstract machine those statements must be executed in
sequence. There's a sequence point at the end of each statement, which
means that side effects such as the change in value of a in the second
statement, must be complete by the time the value of 'a' is read by the
third statement.

ah, right, I missed the fact that this was a pointer of the same (and
char) type.
Why should that be necessary?

so it should only be necessary if you'd do a modification through a
pointer of different type.

Thinking of it, I wonder if the change would be necessarily visible if
we had

unsigned a = 1;
unsigned two = 2;
memcpy(&a, &two, sizeof a);
printf("%u\n", a);

Jens
 
J

John Reye

On 05/03/2012 03:56 PM, Lanarcam wrote:
...






You left out another important context: signal handlers. In fact, until
C2011 added working about threading, signal handlers were the only
purely internal C reason for needing volatile; all of the other possible
reasons were outside the scope of the standard.

Volatile is also useful, if you have a dummy variable for debugging
purposes. To prevent it from getting optimized away (as could occur if
you never read from that variable, but just look at it through the
debugger), just declare it volatile.
 
J

James Kuyper

Hmmm... I think that would be one heck of a rubbish compiler (or more
precisely *optimizing* compiler)! ;)
If the standard allows that kind of stuff, then it simply is not
bullet-proof enough.

Many people think so; C is not the language to use if you want
bullet-proof code.
Because tmp occurs in both lines. Every compiler should notice that!

The rule violated by your code is written to cover situations where that
is not the case, without creating a special exception for when it is the
case. Type punning is very fundamentally a hack; if you really want to
write for bullet-proof code, you want a language where such a hack
constitutes a syntax error or a constraint violation; ideally, a
language which doesn't even present you with a mechanism for describing
such behavior. For bullet-proof code, you don't want a language which
actually defines the behavior that C leaves undefined.
Even if I "obfuscate" like this:
tmp.c1 = 1;
char *cp = ((struct b*)&tmp);
printf("%d\n", cp->c1);

I'd expect any compiler that gets it wrong, to be complete rubbish.
Why? Because it's an optimizer BUG.
Why?
Because any simple compiler, that does not optimize... get's it right!
And if any simple compiler get's it right, then any optimization must
guarantee to get it right as well.

I mean: if the C standard allows one to create optimizers that result
in such ... ummm... surprises (read: "rubbish"), then the standard is
faulty in my eyes.

OK: here's an example of the kind of code for which the relevant rules
were defined, which may give you a better understanding of why they
exist. It's enormously simplified from any realistic example, but the
key features of this example that are relevant to the aliasing rules are
also commonplace features of much real-world code:

struct department {
int department_id;
// ...
};

struct employee {
int employee_id;
int department_id;
// ...
};

// The key point is that employee_id and department_id are both at the
// start of their structs, and have the same type.

static void lay_off_employees(
struct department *dept,
struct employee employees[],
int num
){
for(int emp = 0; emp<num; emp++)
{
if(employees[emp].department_id == dept->department_id)
{
// Deal with lots of other issues.
// We're done now!
employees[emp].employee_id = -1;
}
}
// ...
}

Key point to notice: dept->department_id is not changed through an
lvalue of type "struct department" anywhere inside the lay_off_employees
loop. It can therefore be treated as a loop invariant, which need only
actually be evaluated once, before the loop begins. In other words, the
implementation is free to compile that function as if it were written:

int dept_id = dept->department_id;
for(int emp=0; emp<num; emp++)
{
if(employee[emp].department_id == dept_id)
...

This is one of the simplest and oldest of loop optimization strategies.
Now, consider the following ridiculous code:

employees[523].employee_id = human_resources.department_id;
lay_off_employees((struct department*)(employees+523),
employees, 600);

The possibility of writing such code means that dept->department_id is
not actually a loop invariant. If it weren't for C's anti-aliasing
rules, the behavior of such code would be defined, and as soon as
emp==523, the value of dept->department_id would have to change to -1.

The simplest approach to making that work would be to accept the
performance hit of repeatedly re-evaluating dept->department_id during
every pass through the loop, just in case it might change as a result of
something that happens inside the loop. There are more complicated
approaches can be used to reduce the performance hit, by retrieving it
only once at the start of the loop, and a second time after emp==523.
However, those ARE more complicated approaches, and they don't scale
well when the number of different expressions that might alias each
other gets as large as 5 or 10 different expressions (in typical
real-world code the number of potentially-aliasing expressions is much
larger than that, particularly if there's a lot of pointers floating
around).

Because it is undefined behavior for *dept to refer to the same object
as employees[emp], an implementation is allowed to not worry about the
possibility that someone would write such ridiculous code, so they can
treat dept->department_id as if it actually were a loop invariant that
could be moved out of the loop. Only code that has undefined behavior
would be broken by treating it as a loop invariant.

This seems like a trivial issue in this simplified context, but keep in
mind that, without the anti-aliasing rules, any lvalue derived from a
pointer could potentially alias any other lvalue, if that pointer had
the right value and compatible alignment requirements. The anti-aliasing
rules guarantee that a compiler only needs to worry about aliasing
between lvalues of certain types, greatly reducing the problem, with
corresponding increases in the feasibility of performing such optimizations.
 
K

Keith Thompson

John Reye said:
Thanks!!!
So it would seem that good C coders sprinkle a lot of volatile. ;)

volatile is completely unnecessary in this case; the original code is
well defined.
 
N

Nobody

You left out another important context: signal handlers. In fact, until
C2011 added working about threading, signal handlers were the only
purely internal C reason for needing volatile; all of the other possible
reasons were outside the scope of the standard.

Not quite; volatile is also meaningful in conjunction with setjmp/longjmp.
7.13.2.1p3 says:

[#3] All accessible objects have values as of the time
longjmp was called, except that the values of objects of
automatic storage duration that are local to the function
containing the invocation of the corresponding setjmp macro
that do not have volatile-qualified type and have been
changed between the setjmp invocation and longjmp call are
indeterminate.
 
T

Tim Rentsch

Barry Schwarz said:
Assume identical common top struct members:

struct a {
int i1;
char c1;
short sa1[3];
};

struct b {
int i2;
char c2;
short sa2[3];

int differ_here;
};


struct a tmp;



Are the following 2 line always equivalent (as in: yielding the same
lvalue) and allowed:

tmp.c1
((struct b*)&tmp)->c1

Since struct b does not contain a member c1, the second line should
produce a diagnostic.

I appear to be in the minority. If you change the second line to
((struct b*)&tmp)->c2
and if the two structures are guaranteed to have the same alignment,
then I believe the requirement in 6.5.2.3-5 (which technically only
applies if the two structures are members of a union) would force the
compiler to generate the appropriate code to yield the same lvalue.
This would probably work everywhere except the DS9000.

An accident waiting to happen. The important question isn't
whether it works today, but whether it has to keep working in the
future. As compilers get better at optimizing, the likelihood
that code like this will have problems becomes non-negligible.
Since the Standards allows them to take advantage of such
cases, compiler writers inevitably will, and then... kaboom!
 
T

Tim Rentsch

Jens Gustedt said:
Am 05/03/2012 08:45 PM, schrieb John Reye:

no

for that you'd have to declare them

char volatile a;
char volatile *p = &a;

This suggestion is dangerously wrong. Not only is using
'volatile' irrelevant in this case, it makes no difference in any
other case (ie, of type punning). The effective type rules delimit
what types of accesses are permitted, and adding volatile doesn't
change that -- using 'volatile double *' to access a value stored
using a 'volatile long *' is undefined behavior just as much as
'double *' and 'long *' would be. In practical terms the
'volatile' variants may have a better chance of working, but as
far as the Standard goes they are equally undefined behavior.
 

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,769
Messages
2,569,579
Members
45,053
Latest member
BrodieSola

Latest Threads

Top