function composition, sequence point, and unsuspected side effects

R

regis

Greetings,

my question relates to function calls with hidden side effects,
that are used as arguments of another function, particularly
in regard to the two following sections found in the draft

[3.3.2.2 Function calls] "The order of evaluation of the function
designator, the arguments, and subexpressions within the arguments is
unspecified."

[2.1.2.3 Program execution] "... at sequence points, all side effects of
previous evaluations shall be complete and no side effects of subsequent
evaluations shall have taken place."



For the sake of the exposition, let assume the type Pair,
and a function MakePair() returning a Pair:

typedef struct { int first, second; } Pair;

Pair MakePair (int first, int second) {
Pair pair;
pair.first= first;
pair.second= second;
return pair;
}

Assume 666 and 777 are the next two integers returned by rand(),
and consider the call: Pair rand_pair= MakePair (rand(), rand());

rand() probably does some side-effects on some global variables
to prepare for its next call, which leads me to wonder what to
think about the behavior or the call:

1) side-effects of both rand() calls may interleave, behavior is
undefined and program may break ?

2) rand_pair is either (Pair){ 666, 777 } or (Pair){ 777, 666 }
at the discretion of the implementation, i.e., the order of evaluation
of both rand() calls is unspecified, but we end with a random pair,
and all run smoothly in all rand() calls ?

3) any other scenario ?

Similar scenarios could be made with functions unsuspectingly touching
errno through other functions deeper in their code, or with functions
unsuspectingly using static data through deeper calls to, say, ctime().
 
G

glen herrmannsfeldt

regis said:
my question relates to function calls with hidden side effects,
that are used as arguments of another function, particularly
in regard to the two following sections found in the draft
[3.3.2.2 Function calls] "The order of evaluation of the function
designator, the arguments, and subexpressions within the arguments is
unspecified."
[2.1.2.3 Program execution] "... at sequence points, all side effects of
previous evaluations shall be complete and no side effects of subsequent
evaluations shall have taken place."
(snip)

Pair MakePair (int first, int second) {
(snip)

Assume 666 and 777 are the next two integers returned by rand(),
and consider the call: Pair rand_pair= MakePair (rand(), rand());
rand() probably does some side-effects on some global variables
to prepare for its next call, which leads me to wonder what to
think about the behavior or the call:
1) side-effects of both rand() calls may interleave, behavior is
undefined and program may break ?

In actual use, the most likely result is that the two random numbers
come out in unspecified order. Still, the general rule on undefined
behavior is that anything can happen.
2) rand_pair is either (Pair){ 666, 777 } or (Pair){ 777, 666 }
at the discretion of the implementation, i.e., the order of evaluation
of both rand() calls is unspecified, but we end with a random pair,
and all run smoothly in all rand() calls ?
3) any other scenario ?
Similar scenarios could be made with functions unsuspectingly touching
errno through other functions deeper in their code, or with functions
unsuspectingly using static data through deeper calls to, say, ctime().

The usual random number generators keep their state in static variables,
so, yes, it is a similar problem.

On a multithreaded system, you could imagine both calls to rand() at the
same time, such that the internal state was not self consistent.
I don't know that any systems do that, though.

-- glen
 
P

Philip Lantz

glen said:
regis said:
my question relates to function calls with hidden side effects,
that are used as arguments of another function, particularly
in regard to the two following sections found in the draft
[3.3.2.2 Function calls] "The order of evaluation of the function
designator, the arguments, and subexpressions within the arguments is
unspecified."
[2.1.2.3 Program execution] "... at sequence points, all side effects of
previous evaluations shall be complete and no side effects of subsequent
evaluations shall have taken place."
(snip)

Pair MakePair (int first, int second) {
(snip)

Assume 666 and 777 are the next two integers returned by rand(),
and consider the call: Pair rand_pair= MakePair (rand(), rand());
rand() probably does some side-effects on some global variables
to prepare for its next call, which leads me to wonder what to
think about the behavior or the call:
1) side-effects of both rand() calls may interleave, behavior is
undefined and program may break ?

In actual use, the most likely result is that the two random numbers
come out in unspecified order. Still, the general rule on undefined
behavior is that anything can happen.

Why do you think the behavior is undefined?
 
M

Malcolm McLean

Why do you think the behavior is undefined?
The C standard doesn't attempt to define what happens when two threads contest
for a resource. Assuming the state is 8 bytes, and words are four bytes, the
compiler could emit load high byte, load low byte, store high byte store low
byte, or the reverse, leading to different behaviour when a store interleaves
between the two loads. Or the operating system could detect this situation and
terminate the program.
So it's classic undefined behaviour.
 
T

Tim Rentsch

regis said:
my question relates to function calls with hidden side effects,
that are used as arguments of another function, particularly
in regard to the two following sections found in the draft

[3.3.2.2 Function calls] "The order of evaluation of the function
designator, the arguments, and subexpressions within the arguments
is unspecified."

[2.1.2.3 Program execution] "... at sequence points, all side
effects of previous evaluations shall be complete and no side
effects of subsequent evaluations shall have taken place."

Based on the numbering it looks like you are using an ANSI (ie,
pre-ISO) document. For reference here are some pointers to
drafts of later versions of the ISO C standard:

Pre-C99
http://www.open-std.org/jtc1/sc22/wg14/www/docs/n843.pdf
http://www.open-std.org/jtc1/sc22/wg14/www/docs/n843.htm
http://www.open-std.org/jtc1/sc22/wg14/www/docs/n869/n869.pdf.gz
http://www.open-std.org/jtc1/sc22/wg14/www/docs/n869/n869.txt.gz
Post-C99
http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1124.pdf
http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1256.pdf
Pre-C11
http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1570.pdf

Sometimes it is useful to compare different drafts to see how
the text has evolved over time.

For the sake of the exposition, let assume the type Pair,
and a function MakePair() returning a Pair:

typedef struct { int first, second; } Pair;

Pair MakePair (int first, int second) {
Pair pair;
pair.first= first;
pair.second= second;
return pair;
}

Assume 666 and 777 are the next two integers returned by rand(),
and consider the call: Pair rand_pair = MakePair (rand(), rand());

rand() probably does some side-effects on some global variables
to prepare for its next call, which leads me to wonder what to
think about the behavior or the call:

1) side-effects of both rand() calls may interleave, behavior is
undefined and program may break ?

2) rand_pair is either (Pair){ 666, 777 } or (Pair){ 777, 666 } at
the discretion of the implementation, i.e., the order of evaluation
of both rand() calls is unspecified, but we end with a random pair,
and all run smoothly in all rand() calls ?

3) any other scenario ?

Similar scenarios could be made with functions unsuspectingly
touching errno through other functions deeper in their code, or
with functions unsuspectingly using static data through deeper
calls to, say, ctime().

This is an excellent question. Let me rephrase it slightly
to simplify the discussion. With this function definition:

int
stateful(){
static int t;
t = 1 - t;
return t;
}

will the result of evaluating

stateful() - stateful()

be (a) undefined, (b) unspecified but always either 1 or -1, or
(c) something else? For what you're asking about the simpler
question is the same as the original - the outer function call to
MakePair doesn't change anything, and using stateful() rather
than a library function avoids the question of whether library
functions are somehow special in this regard (which they are not,
in case you were wondering, but it still helps to start with the
simpler formulation).

In C11, the answer is clearly (b), because C11 changed the
wording for how expression sequencing is done. Looking in n1570,
section 6.5.2.2, paragraph 10, we see that evaluation of function
bodies is /indeterminately sequenced/ with respect to other
evaluations in the calling function, which means they are done
either entirely before or entirely after, but never interleaved.
Speaking informally, calling a function is done "atomically"
relative to any other evaluation, including in particular another
function call.

Now what about C99 and C90? Here the answer is also (b), but
this is not evident looking just at what is written in the
two Standard documents (or the various drafts). This question
was addressed in an early Defect Report

http://www.open-std.org/jtc1/sc22/wg14/www/docs/dr_087.html

for C90. Apparently either this was forgotten, or the committee
thought the answer given in DR 87 was sufficient, because C99
doesn't make any changes regarding this issue. The question came
up again, in a slightly different form, after C99 in this DR:

http://www.open-std.org/jtc1/sc22/wg14/www/docs/dr_287.htm

which contains this sentence in its offcial response:

As noted in the response to DR 087, function calls in the
same expression do not overlap. This has not changed for
C99.

So this should answer your question, and also explain why
the answer isn't evident from what is said in the C90 or
C99 standards.
 
T

Tim Rentsch

Robert Wessel said:
Greetings,

my question relates to function calls with hidden side effects,
that are used as arguments of another function, particularly
in regard to the two following sections found in the draft

[3.3.2.2 Function calls] "The order of evaluation of the function
designator, the arguments, and subexpressions within the arguments is
unspecified."

[2.1.2.3 Program execution] "... at sequence points, all side effects of
previous evaluations shall be complete and no side effects of subsequent
evaluations shall have taken place."



For the sake of the exposition, let assume the type Pair,
and a function MakePair() returning a Pair:

typedef struct { int first, second; } Pair;

Pair MakePair (int first, int second) {
Pair pair;
pair.first= first;
pair.second= second;
return pair;
}

Assume 666 and 777 are the next two integers returned by rand(),
and consider the call: Pair rand_pair= MakePair (rand(), rand());

rand() probably does some side-effects on some global variables
to prepare for its next call, which leads me to wonder what to
think about the behavior or the call:

1) side-effects of both rand() calls may interleave, behavior is
undefined and program may break ?

2) rand_pair is either (Pair){ 666, 777 } or (Pair){ 777, 666 }
at the discretion of the implementation, i.e., the order of evaluation
of both rand() calls is unspecified, but we end with a random pair,
and all run smoothly in all rand() calls ?

3) any other scenario ?

Similar scenarios could be made with functions unsuspectingly touching
errno through other functions deeper in their code, or with functions
unsuspectingly using static data through deeper calls to, say, ctime().


#2 is correct in this case. All of the parameters are evaluated
before the call to the function, but the order in which they're
evaluated is not defined. In any event he cannot interleave function
calls, since there are sequence points before and after function calls
too.

The conclusion is right but the reasoning isn't. If f() and g()
modify the same global variable x, then the expression

(10, f(), 20) + (10, g(), 20)

is okay, but

(10, x = 1, 20) + (10, x = 2, 20)

isn't, even though in both cases there are sequence points before
and after the modification of x. (Two sequence points together
have the same effect as one.) Function calls are special, as
explained elsethread.
IOW, if you had "MakePair(f(), g())" he could call either f() or g()
first, but whichever one got called first would have to complete and
return before the second one could return.

As for touching a global like errno, it leads to errno being undefined
if both f() and g() use it. If both set it, you won't know which
value will be left at the end, if one sets it and the other checks it,
you won't know if the checking routine will see the value from before
or after the setting routine.

What I think you mean is that the value of errno is unspecified
(which is indeed the case) rather than undefined.
 
T

Tim Rentsch

glen herrmannsfeldt said:
regis said:
[can execution of two function bodies interleave?]

On a multithreaded system, you could imagine both calls to rand()
at the same time, such that the internal state was not self
consistent.

This kind of reasoning is sometimes a good way to understand why
something /should/ be undefined behavior, but it isn't a good
way to decide if something /is/ undefined behavior. The result
of an an expression like f() + g() is unspecified, not undefined.
 
F

Fuseblower

The C standard doesn't attempt to define what happens when two threads contest
for a resource.
(snip)

So it's classic undefined behaviour.

I don't think so. The Standard defines what it considers undefined
behaviour.

It also defines the order in which arguments of a function are
evaluated as "unspecified".

Here's the snipped part again :
Assuming the state is 8 bytes, and words are four bytes, the
compiler could emit load high byte, load low byte, store high byte store low
byte, or the reverse, leading to different behaviour when a store interleaves
between the two loads.

The actual code that the compiler emits isn't defined in the Standard.
It depends on the target CPU. A simple integer division would be 1
machine code instruction on an x86 but might be a couple of
instructions on a Z80 (which doesn't have a division instruction).

Even a simple assignment might result in several instructions, like in
your example.

And all these "strings of machine code instructions" can be
"interrupted" by another thread in such a way that the result is
undefined.

But it's something else to suggest the Standard defines C operations
as "undefined behaviour" just because they can be interrupted by
another thread.

Unless the Standard states somewhere that when one uses
multi-threading, all objects become volatile. In such a case sequence
points become meaningless and we no longer have an abstract machine.

In that case, every single operation involving operands would invoke
undefined behaviour.

The case of the OP, IMO, is a clear case of "unspecified behaviour".
Or the operating system could detect this situation and
terminate the program.

Possible, but very unlikely :)
 
T

Tim Rentsch

Malcolm McLean said:
The C standard doesn't attempt to define what happens when two
threads contest for a resource.

Threads have nothing to do with the question here.
Assuming the state is 8 bytes, and words are four bytes, the
compiler could emit load high byte, load low byte, store high byte
store low byte, or the reverse, leading to different behaviour
when a store interleaves between the two loads. Or the operating
system could detect this situation and terminate the program.
So it's classic undefined behaviour.

Irrelevant reasoning, and wrong conclusion. The result is
unspecified, not undefined, behavior.
 
R

regis

In C11, the answer is clearly (b), because C11 changed the
wording for how expression sequencing is done. Looking in n1570,
section 6.5.2.2, paragraph 10, we see that evaluation of function
bodies is/indeterminately sequenced/ with respect to other
evaluations in the calling function, which means they are done
either entirely before or entirely after, but never interleaved.
Speaking informally, calling a function is done "atomically"
relative to any other evaluation, including in particular another
function call.

Now what about C99 and C90? Here the answer is also (b), but
this is not evident looking just at what is written in the
two Standard documents (or the various drafts). This question
was addressed in an early Defect Report

http://www.open-std.org/jtc1/sc22/wg14/www/docs/dr_087.html

for C90. Apparently either this was forgotten, or the committee
thought the answer given in DR 87 was sufficient, because C99
doesn't make any changes regarding this issue. The question came
up again, in a slightly different form, after C99 in this DR:

http://www.open-std.org/jtc1/sc22/wg14/www/docs/dr_287.htm

which contains this sentence in its offcial response:

As noted in the response to DR 087, function calls in the
same expression do not overlap. This has not changed for
C99.

So this should answer your question, and also explain why
the answer isn't evident from what is said in the C90 or
C99 standards.

(So the wording in C90 and C99 was tricky enough to require a
clarification by the committee in an answer to a defect report...)

Thank you for your expertize and your in-depth answer :eek:)
 
F

Fuseblower

That's a preliminary draft of a C standard that's a couple of decades
out of date.

OT : Hehe, over a decade ago I bought the hardcopy of The Standard
from the Ansi organization itself. It cost me over 200 bucks but I
figured it would make a fine addition to all those Microsoft tomes
that spell "authorative and definitive" by their sheer size and
hardcovers.

This is what I got :

http://www.paulmesken.net/images/TheStandard.jpg

I swear it's the truth, no matter how unlikely it seems...

It was bloody photo copied on a fax machine and the pages were bound
together by a mere piece of tape (probably put there by an intern or
something).

You can't hit someone over the head with something like that!

Still, it does fit the rest of the development environment nicely :)
 
K

Keith Thompson

Fuseblower said:
OT : Hehe, over a decade ago I bought the hardcopy of The Standard
from the Ansi organization itself. It cost me over 200 bucks but I
figured it would make a fine addition to all those Microsoft tomes
that spell "authorative and definitive" by their sheer size and
hardcovers.

This is what I got :

http://www.paulmesken.net/images/TheStandard.jpg

I swear it's the truth, no matter how unlikely it seems...

It doesn't seem unlikely at all.

I bought a PDF copy of the 1990 ISO C standard (which is equivalent to
the 1989 ANSI standard, with the addition of some front matter resulting
in the renumbering of the sections). It cost me $18 from ANSI. I don't
know whether you can buy from ANSI from outside the US; if not, check
your national standards body.

Before that, I bought a copy of Schildt's "The Annotated ANSI C
Standard" (which was actually an annotation of the 1990 ISO C standard).
It was much cheaper at the time than an copy of the standard itself.
The price difference reflects the value of the annotations.
<http://www.davros.org/c/schildt.html>

Typically each new standard comes out in a grossly overpriced version,
followed a few months later by something more reasonable; I think it's
up to $30 now.

My copy of the C90 standard appears to have been scanned from a paper
copy. The C99 and C11 standard are purely digital, which makes them
easier to search and to copy-and-paste.

N1256 is the best freely available draft of the C99 standard; it
includes the official standard itself with the three Technical
Corrigenda (offical updates) merged into it.

N1570 is a version of the C11 standard from just before it was
officially published; there are a few minor differences.

Unless you really need an *official* standard (you probably don't),
N1256 and N1570 are probably more than good enough.
 
S

Stephen Sprunk

Assume 666 and 777 are the next two integers returned by rand(),
and consider the call: Pair rand_pair= MakePair (rand(), rand());

rand() probably does some side-effects on some global variables
to prepare for its next call, which leads me to wonder what to
think about the behavior or the call:

1) side-effects of both rand() calls may interleave, behavior is
undefined and program may break ?

There are sequence points before and after each call to rand(),
regardless of which order they're called in, so there is no danger of
them interleaving.
2) rand_pair is either (Pair){ 666, 777 } or (Pair){ 777, 666 }
at the discretion of the implementation, i.e., the order of evaluation
of both rand() calls is unspecified, but we end with a random pair,
and all run smoothly in all rand() calls ?

Correct. It is unspecified which happens, but those are the only two
possibilities. If you care about the order, you need to introduce your
own sequence point between the calls to rand():

int r1 = rand();
int r2 = rand();
Pair p = MakePair(r1, r2);

Note that both right-to-left and left-to-right evaluation can be
observed on common implementations, for various reasons. Some
implementations even vary depending on the function called!

S
 
T

Tim Rentsch

regis said:

This looks like it has the same content as the C89 draft
that I sometimes use, which is at

http://flash-gordon.me.uk/ansi.c.txt

The link you posted looks like a good resource, having
in-document links in a .html setting. I do still recommend
getting the .pdf's for the later drafts. Also the ansi.c.txt
version of C89 is good if one wants to do searching.
 
T

Tim Rentsch

Stephen Sprunk said:
There are sequence points before and after each call to rand(),
regardless of which order they're called in, so there is no danger of
them interleaving.

Having a sequence point before and after each common access does
not prevent undefined behavior if there is no other ordering
between those sequence points. For example

int x;
...

(10, x = 1, 30) + (20, x = 2, 40)

is still undefined behavior, despite there being a sequence point
both before and after each access to x.

What matters here is not the sequence points but the accesses
taking place inside a called function body. Evaluation of
function bodies does not overlap with the evalution of other
expressions outside the called function (including expressions
in other called functions). So this

int x;

void set_x( int new_x; ){ x = new_x; }

...

(10, set_x( 1 ), 30) + (20, set_x( 2 ), 40)

is defined (unspecified) behavior rather than undefined behavior,
despite the accesses to x being the same as in the assignment
example as far as sequence points go. This rule is spelled out
more precisely in C11, with evaluation of function bodies being
'indeterminately sequenced' with respect to all other expressions
in the calling function. The same "non-overlap" rule for function
calls holds in C90 and C99, as detailed in the Defect Reports
mentioned elsethread.
 
S

Stephen Sprunk

Having a sequence point before and after each common access does
not prevent undefined behavior if there is no other ordering
between those sequence points. For example

int x;
...

(10, x = 1, 30) + (20, x = 2, 40)

is still undefined behavior, despite there being a sequence point
both before and after each access to x.

I trust that you are correct, but I don't understand why. I thought
those sequence points would establish that x=1 and x=2 were ordered,
even if that order is unspecified.
What matters here is not the sequence points but the accesses
taking place inside a called function body. Evaluation of
function bodies does not overlap with the evalution of other
expressions outside the called function (including expressions
in other called functions).

So this

int x;

void set_x( int new_x; ){ x = new_x; }

...

(10, set_x( 1 ), 30) + (20, set_x( 2 ), 40)

is defined (unspecified) behavior rather than undefined behavior,
despite the accesses to x being the same as in the assignment
example as far as sequence points go.

That seems strange to me. I thought the sequence points before and
after the function calls here:

set_x(1) + set_x(2)

would be enough to establish that one call must finish before the other
could start, even if it's unspecified which order they happen in. To
interpret it that way doesn't require extra rules about non-overlap of
function bodies.

S
 
T

Tim Rentsch

Stephen Sprunk said:
I trust that you are correct, but I don't understand why. I thought
those sequence points would establish that x=1 and x=2 were ordered,
even if that order is unspecified.

The rules for evaluation sequencing in C90 suffer from an
unfortunate choice of phrasing, which turned out to be misleading
in some cases. This confusion was noted fairly soon after C90 was
published, the Defect Report asking about it being submitted in
1993. Around the time C99 was being done, several attempts were
made to define a formal model for sequencing rules, to remove
ambiguities and define the rules more precisely. However these
efforts did not result in any changes in C99. Finally when C11
was being done a suitable formal model was arrived at, and these
changes were incorporated into C11. It is worth reading what C11
says about sequencing relationships, expecially if one is familar
with how those rules are expressed in C90/C99 - even though it
seems like the new rules are different, reportedly the semantics
of the C11 rules is what was intended all along for C90 and C99.

To look at the particular case, here is a diagrammatic view. The
"at sign" in '@x' means lvalue as opposed to rvalue. Statement
boundaries are semicolons.

-- @x --
/ \
- 10 --> , -- = --> , -- 30 --
/ \ / \
/ --- 1 -- \
--> ; -- + --> ; --
\ -- @x -- /
\ / \ /
- 20 --> , -- = --> , -- 40 --
\ /
--- 2 --

Here the lines show ordering for value computations, and the
arrows -> show sequence points, which also order storing of values
for operations going backwards from the ->. The undefined
behavior for this expression can be seen by the absence of a line
connecting the two assignment operators, both of which modify x.
In fact, since both are modifications, not only would there need
to be a line between the two assignments, but there would need to
be an arrow -> between them on that line, to prevent destructive
interference (ie, undefined behavior). But there is no such line.
A sequence point imposes an ordering between two modifications
only when one modification is somewhere on the "forward line" and
the other is somewhere on the "backward line". (The rule for one
modification and one read access is a little more complicated,
involving operators as well as sequence points.)

Unfortunately the Standard expresses these rules by talking about
an interval "Between the previous and next sequence point". This
sounds like such intervals are uniquely determined (which they
aren't), or perhaps like there is an unspecified total ordering of
sequence points (which there isn't). What's intended is a partial
ordering determined by the operators in an expression (and in fact
two partial ordering relationships, one for 'value computations'
that involve only reading, and another for operations that store
into objects). How C90 and C99 express this is misleading at
times; it's more reliable (meaning more consistent with the C11
wording) to ask the question in terms of what the diagram looks
like.

What matters here is not the sequence points but the accesses
taking place inside a called function body. Evaluation of
function bodies does not overlap with the evalution of other
expressions outside the called function (including expressions
in other called functions).

So this

int x;

void set_x( int new_x; ){ x = new_x; }

...

(10, set_x( 1 ), 30) + (20, set_x( 2 ), 40)

is defined (unspecified) behavior rather than undefined behavior,
despite the accesses to x being the same as in the assignment
example as far as sequence points go. [ADDED: probably I should
have mentioned that 'set_x(1) + set_x(2)' is also defined
(unspecifed) behavior and not undefined behavior.]

That seems strange to me. I thought the sequence points before and
after the function calls here:

set_x(1) + set_x(2)

would be enough to establish that one call must finish before the other
could start, even if it's unspecified which order they happen in. To
interpret it that way doesn't require extra rules about non-overlap of
function bodies.

The diagram for 'set_x(1) + set_x(2)' looks like this (simplifying
a bit):

--> [set_x(1)] -- x = 1 --> ; --> [return] --
/ \
--> ; -- + --> ; --
\ /
--> [set_x(2)] -- x = 2 --> ; --> [return] --

There are plenty of sequence points, but still no ordering line
between the two assignments. If all we have is the same rule for
sequence points that is used within expressions, this case is
still undefined behavior. I agree with what Richard Damon says,
that the more stringent rule for how function calls work cannot
be derived from what C90 or C99 says about sequencing (or at
least if it can I don't know how). Obviously it's what people
expect, but it doesn't really follow from the phrasing used in
C90 or C99.

(Unfortunately there is no simple way to explain the rule for
function calls in terms of the diagrams. This difficulty may help
explain why the earlier attempts at defining formal models weren't
used in C99.)

I realize this answer may not be completely satisfactory, because it
doesn't directly respond to your intuition about how sequence points
work. The best I can think of to say is that other people have had
reactions much like yours, and it's taken the better part of 20
years to figure out how to say how evaluation sequencing is meant to
work (or not, in the cases where there is undefined behavior). The
good news is that now there is new phrasing in C11, and it is much
better at delineating the different cases unambiguously. So I hope
that either the explanation above or the new C11 description will
help bridge the gap.
 

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,744
Messages
2,569,484
Members
44,906
Latest member
SkinfixSkintag

Latest Threads

Top