function composition, sequence point, and unsuspected side effects

S

Stephen Sprunk

The key is to notice that the sequence points establish that
10 is before x = 1 is before 30 and that
20 is before x = 2 is before 40
but that there is NO sequence points between the first set and the
second, and no rule to prevent interleaving, so on possible execution
sequence is:

10, 20; x=1, x=2; 30, 40
where here , separates items without a sequence point, and the ; mark
sequence points. Thus the x=1 and the x=2 are not separated by a
sequence point.

Ah. I had assumed that the unspecified ordering between (10,x=1,30) and
(20,x=2,40) meant that one had to be evaluated and then the other, i.e.
they couldn't be interleaved like you show, even if we don't know which
of the two will be evaluated first.
Again, the points could occur in the order:
Sequence point before calling set_x(1)
Sequence point before calling set_x(2)
calling set_x(1) and set_x(2)
Sequence point after calling set_x(1)
Sequence point after calling set_x(2)

You need some further specification to make sure that the functions
don't overlap in execution.

Similarly, I had assumed that evaluating either function, including its
sequence points, was indivisible. In fact, I thought that was the
entire purpose of specifying there were sequence points before/after a
function call!

(All of the above being subject to the as-if rule, of course.)

S
 
S

Stephen Sprunk

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.

[ very helpful explanation ]

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.

I think this is where I got turned around. I thought there _was_ a
total ordering, at least to the extent that anything separated by
sequence points happens in the specified order and anything between
sequence points happens in an unspecified order. (All subject to the
as-if rule, of course.)

Following that (apparently flawed) logic, I concluded that since there
was a sequence point before and after a function call, nothing outside
the function call could overlap with it because that would violate the
function's own internal ordering, even if the ordering of its parent
expression was unspecified.
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).

Well, at least I'm not alone in my confusion.

Could it be said that it was always _intended_ to work how I thought it
did, but the Standard didn't actually say so until C11?

S
 
S

Stephen Sprunk

I had been using the port 80 server; switching to the port 119 server
was my work-around. If you got it work without having to switch servers,
then my "success" may have simply been a matter of changing something
irrelevant at the same time that the real problem got solved.

I always use the port 119 server; it seems to have been fixed sometime
in the last ~16 hours.

S
 
S

Stephen Sprunk

ES had a problem. All OK now. Incidentally, if it's the same as I had
I think the error code was 441 (which doesn't really tell us
anything). Code 220 is just for indicating the message.

In theory, NNTP response code 441 is supposed to be followed by a
human-readable explanation of what went wrong. However, ES's servers
respond with "441 220" when they're not accepting posts, which some
newsreaders then present as "NNTP error: 220". It has nothing to do
with NNTP response code 220.

S
 
R

Rosario1903

// THIS IS AN VAR IN THE PROGRAM
// STACK SPACE!!!
BAD STYLE! DO YOU MEAN RETURN IN
VAL FOR NON-BUILTIN TYPES?

i remember as you, but if one do some try
with code one think can be ok....

--------------------------
#include <stdio.h>

typedef struct { unsigned first, second; } Pair;

Pair MakePair(unsigned first, unsigned second)
{Pair pair;

pair.first = first;
pair.second= second;
return pair;
}

void printPair(Pair* a)
{if(a==0) {printf("Pair=null\n"); return ;}
printf("Pair=%x, %x\n", a->first, a->second);
}

int main(void)
{Pair vv;
vv=MakePair(0xFFFFFFFB, 0xFFFFFFFA);
printPair(&vv);
return 0;
}

--------------------------

_TEXT segment dword public use32 'CODE'
_MakePair proc near
;
; Pair MakePair(unsigned first, unsigned second)
;
push ebp
mov ebp,esp
add esp,-8
mov eax,dword ptr [ebp+8]
;
; {Pair pair;
;
; pair.first = first;
;
?live1@16: ; EAX = return
@1:
mov edx,dword ptr [ebp+12]
mov dword ptr [ebp-8],edx
;
; pair.second= second;
;
mov ecx,dword ptr [ebp+16]
mov dword ptr [ebp-4],ecx
;
; return pair;

; this write in the mem pointed from eax [&vv] the result

mov edx,dword ptr [ebp-8]
mov dword ptr [eax],edx
mov edx,dword ptr [ebp-4]
mov dword ptr [eax+4],edx
;
; }
@3:
@2:
pop ecx
pop ecx
pop ebp
ret
_MakePair endp
----------------------------

;
; int main(void)
;
push ebp
mov ebp,esp
add esp,-8

; {Pair vv;
; vv=MakePair(0xFFFFFFFB, 0xFFFFFFFA);
@9:
push -6
push -5
lea eax,dword ptr [ebp-8]
; here eax point to the space of mem of vv
push eax

; this call _MakePair with 3 arg
; 1: is the address that can contain the result [&vv]
; 2: is the argument 0xFFFFFFFB
; 3: is the arguemtn 0xFFFFFFFA
call _MakePair
add esp,12
; so this free the 3 dword stack [4*3=12]
 
T

Tim Rentsch

you don't have a link to a C89 definition/draft (I suppose you'd call
it C90) do you?.

I don't always distinguish, but when I do usually I use C89 to
mean the ANSI version and C90 to mean the ISO version. In any
case here are the links I have for (what I believe is) the
pre-ANSI draft, one a text document, the other html --

http://flash-gordon.me.uk/ansi.c.txt
http://web.archive.org/web/20050207005628/http://dev.unicals.com/papers/c89-draft.html

Note that these use different section numbering than the ISO
C90 document. Other than that I believe they are essentially
identical.
PDF would be nice...

Yes, wouldn't it? :)
 
T

Tim Rentsch

Stephen Sprunk said:
.
. [semantics of function call evaluation sequencing]
.
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).

Well, at least I'm not alone in my confusion.

Could it be said that it was always _intended_ to work how I thought
it did, but the Standard didn't actually say so until C11?

Assuming you're asking about function calls being "indivisible", I
believe the answer to the first part is Yes, that is always how the
standardization group(s) expected they would work, although I'm not
sure how conscious that expectation was.

The second part of the question, about when the Standard actually
said that, is harder to answer. As far as the committee is
concerned, the response to Defect Report 87 may constitute an
explicit statement that resolves the question. I think responses
to Defect Reports have some sort of official status but I'm not
sure just what. Furthermore, even in C90 there is the statement
that "Calling a function suspends but does not end execution of the
block containing the call." This statement could be taken to mean
that the sequence point before the function call acts as a sequence
point for all evaluations that have been started in the calling
function up to that point, in which case the call to some other
function (or any other expression for that matter) could not be "in
flight" while the called function body was being evaluated. This
reading of that sentence comes close to, or may even match exactly,
the behavior described more precisely in C11. So the people who
wrote the C90 standard may consider that document to have already
put forth the "indivisibleness" of function calls, just in a rather
indirect form.

One further note of possible historical interest - it wasn't until
C99 that the Standard specified that there is a sequence point
before the return of a library function (as opposed to a function
defined in regular source). I found it interesting to look through
Annex C (which summarizes sequence points) in each of C90, C99, and
C11, and see how the list has evolved over time.
 
R

regis

On 11/17/13 19:49, 88888 Dihedral wrote:
C99 introduced compound literals that seem to encourage this style.

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

by the way, starting from C99,
<stdlib.h> has some functions returning a structure, such as:
div_t div (int numer, int denom);
which has a behavior semantically equivalent to:

div_t
div (int numer, int denom)
{
return (div_t) { .quot= numer / denom, .rem= numer % denom };
}
 
E

Eric Sosman

[...]

by the way, starting from C99,
<stdlib.h> has some functions returning a structure, such as:
div_t div (int numer, int denom);

div() and ldiv() (and div_t and ldiv_t) were not C99
innovations; they are in the original ANSI Standard of 1989.
 
T

Tim Rentsch

Malcolm McLean said:
On Fri, 15 Nov 2013 01:29:52 -0800 (PST), Malcolm McLean

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.

If you implement threads, you've got to suppose that there is some
mechanism which prevents them stepping over each other's memory
accesses. If this isn't provided, then indeed you can't say
anything about the behaviour of the program. [snip example] So
it's exactly the same as undefined behaviour.

Apparently you don't understand what question is being asked, or
what the term 'undefined behavior' means, or both.

The question under consideration (lost to unmarked snipping) is
about a code fragment like

foo( rand(), rand() )

and is asking what ISO C specifies for the behavior of such
an expression when run under a conforming implementation (the
only arena where the ISO C standard says anything at all).

The answer to this is that such expressions are specified to have
'unspecified behavior' (assuming foo() is declared appropriately,
etc), not 'undefined behavior'. This answer is not affected by
the presence or absence of threads, either in the implementation
or in the execution environment.

The term 'undefined behavior' refers to a specification. It does
not refer to actual behavior. The actual behavior of any program
execution is never 'undefined behavior', 'unspecified behavior",
'implementation-defined behavior', or 'defined behavior'; rather
it is some particular behavior, which might or might not meet the
specified requirements in each particular case.

If you mean to address the question originally posed, the
statement you make is wrong, because what is specified in such
cases is unspecified behavior, not undefined behavior.

If you mean to address a question outside the realm of what
the ISO C standard specifies, your statement is nonsensical,
because the term 'undefined behavior' has meaning only in the
context of what is specified by ISO C. It also would have no
bearing on the original question.
 
R

regis

[...]

by the way, starting from C99,
<stdlib.h> has some functions returning a structure, such as:
div_t div (int numer, int denom);

div() and ldiv() (and div_t and ldiv_t) were not C99
innovations; they are in the original ANSI Standard of 1989.

even better ;o)
 
F

Fuseblower

[...]

by the way, starting from C99,
<stdlib.h> has some functions returning a structure, such as:
div_t div (int numer, int denom);

div() and ldiv() (and div_t and ldiv_t) were not C99
innovations; they are in the original ANSI Standard of 1989.

And excellent functions they are (7.10.6.2 and 7.10.6.4 from C90,
BTW).

Any half decent compiler will replace the function call by the single
machine instruction that calculates both the quotient and remainder
(if the target machine has such an instruction, the x86 does : IDIV).

So, using div() might actually result in faster code than using
something like :

a = c / d;
b = c % d;

Now, all we need are new functions that use the carry flag and we're
all set to go ;)
 
G

glen herrmannsfeldt

Tim Rentsch said:
(snip)

Apparently you don't understand what question is being asked, or
what the term 'undefined behavior' means, or both.
The question under consideration (lost to unmarked snipping) is
about a code fragment like
foo( rand(), rand() )
and is asking what ISO C specifies for the behavior of such
an expression when run under a conforming implementation (the
only arena where the ISO C standard says anything at all).
The answer to this is that such expressions are specified to have
'unspecified behavior' (assuming foo() is declared appropriately,
etc), not 'undefined behavior'. This answer is not affected by
the presence or absence of threads, either in the implementation
or in the execution environment.

Just to be sure, is it 'unspecified behavior' in all ISO C versions?

Not all of us have C11 compilers, and some might still need to compile
on C89/C90 compilers.
The term 'undefined behavior' refers to a specification. It does
not refer to actual behavior. The actual behavior of any program
execution is never 'undefined behavior', 'unspecified behavior",
'implementation-defined behavior', or 'defined behavior'; rather
it is some particular behavior, which might or might not meet the
specified requirements in each particular case.

-- glen
 
8

88888 Dihedral

On 17-Nov-13 04:07, Tim Rentsch wrote:
On 16-Nov-13 19:28, Tim Rentsch wrote:

. [semantics of function call evaluation sequencing]
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).
Well, at least I'm not alone in my confusion.
Could it be said that it was always _intended_ to work how I thought
it did, but the Standard didn't actually say so until C11?



Assuming you're asking about function calls being "indivisible", I

believe the answer to the first part is Yes, that is always how the

standardization group(s) expected they would work, although I'm not

sure how conscious that expectation was.



The second part of the question, about when the Standard actually

said that, is harder to answer. As far as the committee is

concerned, the response to Defect Report 87 may constitute an

explicit statement that resolves the question. I think responses

to Defect Reports have some sort of official status but I'm not

sure just what. Furthermore, even in C90 there is the statement

that "Calling a function suspends but does not end execution of the

block containing the call." This statement could be taken to mean

that the sequence point before the function call acts as a sequence

point for all evaluations that have been started in the calling

function up to that point, in which case the call to some other

function (or any other expression for that matter) could not be "in

flight" while the called function body was being evaluated. This

reading of that sentence comes close to, or may even match exactly,

the behavior described more precisely in C11. So the people who

wrote the C90 standard may consider that document to have already

put forth the "indivisibleness" of function calls, just in a rather

indirect form.



One further note of possible historical interest - it wasn't until

C99 that the Standard specified that there is a sequence point

before the return of a library function (as opposed to a function

defined in regular source). I found it interesting to look through

Annex C (which summarizes sequence points) in each of C90, C99, and

C11, and see how the list has evolved over time.
Pair MakePair (int first, int second) {

Pair pair;
// THIS IS AN VAR IN THE PROGRAM
// STACK SPACE!!!
pair.first= first;

pair.second= second;

return pair;
BAD STYLE! DO YOU MEAN RETURN IN
VAL FOR NON-BUILTIN TYPES?

i remember as you, but if one do some try
with code one think can be ok....

--------------------------
#include <stdio.h>

typedef struct { unsigned first, second; } Pair;

Pair MakePair(unsigned first, unsigned second)
{Pair pair;

pair.first = first;
pair.second= second;
return pair;
}

I'll give my version of this kind
trival settings

#define MAKEPAIR(p, x, y) { p.first=x;p.second=y;}
 
8

88888 Dihedral

It also means that when you call f (i++) the value of i is incremented before the call to f. Which would be relevant if i is a static variable that is accessible to f. So not only is the call itself undivided, but it doesn't divide any operator from its side effects. (Of course evaluating parameters does _not_ involve any sequence points).

Only idiots can't distingush
the call-by-name macro in CAPTALS
to supply with name arguments
such as i++, --i and etc. to
the right macro in the caller part.
 
K

Keith Thompson

Your quoting is messed up. Tim didn't write the text starting with "BAD
STYLE!", you did.
i remember as you, but if one do some try
with code one think can be ok....

--------------------------
#include <stdio.h>

typedef struct { unsigned first, second; } Pair;

Pair MakePair(unsigned first, unsigned second)
{Pair pair;

pair.first = first;
pair.second= second;
return pair;
}

Yes, that's essentially the same as what Stephen (or whoever it was)
posted, and it's perfectly valid and safe. You asserted that it's bad
style, but you haven't explained why, after repeated attempts to coax
you to do so.
I'll give my version of this kind
trival settings

#define MAKEPAIR(p, x, y) { p.first=x;p.second=y;}

That could work too, though I'd definitely make some changes in the way
the macro is defined:
#define MAKEPAIR(p, x, y) ( (p).first = (x), (p).second = (y) )
to allow it to be used in any context where an expression is permitted.

But how is
MAKEPAIR(p, x, y);
better than
p = MakePair(x, y);
?

Here's my answer: Returning a struct value from a function is
valid and safe, and is in no way poor style for a small struct.
(It makes sense to manipulate larger structs via pointers, to
avoid unnecessary copying.) Returning a struct value may be a
problem if you're using a non-conforming compiler, but *anything*
could be a problem if you're using a non-conforming compiler; in
particular, pre-C89 compilers are nearly irrelevant these days.
You've incorrectly asserted that it's "BAD STYLE" and refused to
admit that you were mistaken.

I invite you to explain what you meant.
 
S

Seebs

Do you push so often in the C program
stack for such trivial things?

Sure, why not?
The caller is responsible
to allocate the structure either
in the heap or the program stack
in my version of C programs.

Why? As you said, it's a trivial thing. Do you also require the
caller to do allocation for long doubles? Maybe just plain old doubles?

You made a comment about "portability", but the more you talk about this,
the less it seems to me like you have a broad experience with different
architectures and how they do or don't handle this.

-s
 
8

88888 Dihedral

88888 Dihedral said:
Stephen Sprunk <[email protected]> writes:
[...]
Pair MakePair (int first, int second) {

Pair pair;

// THIS IS AN VAR IN THE PROGRAM
// STACK SPACE!!!
pair.first= first;

pair.second= second;

return pair;

BAD STYLE! DO YOU MEAN RETURN IN
VAL FOR NON-BUILTIN TYPES?

}



Your quoting is messed up. Tim didn't write the text starting with "BAD

STYLE!", you did.


i remember as you, but if one do some try
with code one think can be ok....


#include <stdio.h>

typedef struct { unsigned first, second; } Pair;

Pair MakePair(unsigned first, unsigned second)
{Pair pair;

pair.first = first;
pair.second= second;
return pair;



Yes, that's essentially the same as what Stephen (or whoever it was)

posted, and it's perfectly valid and safe. You asserted that it's bad

style, but you haven't explained why, after repeated attempts to coax

you to do so.


I'll give my version of this kind
trival settings

#define MAKEPAIR(p, x, y) { p.first=x;p.second=y;}



That could work too, though I'd definitely make some changes in the way

the macro is defined:

#define MAKEPAIR(p, x, y) ( (p).first = (x), (p).second = (y) )

to allow it to be used in any context where an expression is permitted.



But how is

MAKEPAIR(p, x, y);

better than

p = MakePair(x, y);

?



Here's my answer: Returning a struct value from a function is

valid and safe, and is in no way poor style for a small struct.

(It makes sense to manipulate larger structs via pointers, to

avoid unnecessary copying.) Returning a struct value may be a

problem if you're using a non-conforming compiler, but *anything*

could be a problem if you're using a non-conforming compiler; in

particular, pre-C89 compilers are nearly irrelevant these days.

You've incorrectly asserted that it's "BAD STYLE" and refused to

admit that you were mistaken.



I invite you to explain what you meant.



--

Keith Thompson (The_Other_Keith) (e-mail address removed) <http://www.ghoti.net/~kst>

Working, but not speaking, for JetHead Development, Inc.

"We must do something. This is something. Therefore, we must do this."

-- Antony Jay and Jonathan Lynn, "Yes Minister"

Do you push so often in the C program
stack for such trivial things?

The caller is responsible
to allocate the structure either
in the heap or the program stack
in my version of C programs.

Of course, in C++ or Java
things are different.
 
R

regis

#include <stdio.h>

typedef struct { unsigned first, second; } Pair;

Pair MakePair(unsigned first, unsigned second)
{Pair pair;

pair.first = first;
pair.second= second;
return pair;
}

I'll give my version of this kind
trival settings

#define MAKEPAIR(p, x, y) { p.first=x;p.second=y;}

It does not allow function composition
since you have to use a named variable.
 

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