address of a statement in C

A

Arthur J. O'Dwyer

(This is a silly argument. Any bug in your code will "make a joke of"
the program's invariants, sure. But a well-used goto won't be any worse
than a well-used 'while' or 'if'. In fact, I'd argue that the average
'if' probably introduces more invariant-related bugs than the average
'goto', since the former introduces two new control paths while the
latter does not. :)
The tone here is that the effects of using 'goto' aren't that much
different from using 'break' or (presumably early) 'return'. That's
not so, for both a theoretical reason and a practical reason.

The theoretical reason is that 'goto' can lead to irreducible flow
graphs. Early returns or using 'break' to exit a loop (or a 'switch')
cannot cause irreducible flow graphs. Irreducible flows are more
difficult to reason about than reducible flow graphs.

Operative word: "can." Not "must." I don't think anyone is suggesting
the use of pathological control flow, e.g.

if (x) goto a;
do {
while (y) {
a: ++foo;
}
if (z) goto a;
} while (w);

Sure, that's hard to reason about. (Incidentally, I have no idea whether
that program has an "irreducible flow graph." I think I get the general
idea of what makes a hairy flow graph, but I don't know how to define it
precisely.)
The practical reason is that programs using 'goto' are harder to
comprehend than programs using early returns or loop exits. At least,
that was the conclusion of studies that were done looking at how the
different control mechanisms affected programmer performance. (Sorry
I don't have a citation handy; I think it was in Software Practice
and Experience, sometime in the early 1980's, or maybe late 1970's.)

I can imagine that programs using /only/ gotos would be hard to
comprehend (e.g., I'd expect a 1975 study pitting Fortran against
Ratfor to find that Ratfor wins), but I disagree with the blanket
statement. Why, just yesterday I wrote

static char buffer[1000];
char *p, *pstart;
while (fgets(buffer, sizeof buffer, in) != NULL) {
for (p=buffer; isspace(*p); ++p) continue;
if (!strncmp(p, "%%MERR", 6)) goto all_done;

where the 'goto all_done' is exactly equivalent to a 'break'. But
the end of this loop was a screen and a half away, and there were
nested loops in between. So the mnemonic value of the 'all_done'
label far outweighed the benefits of using a 'break' there, whatever
those benefits might have been.

There's another 'goto' in that same program, this time branching
back to the top of a loop but after the condition has been executed,
like this (simplified drastically):

while (fgets(buf, sizeof buf, fp) != NULL) {
top_of_loop:
process(buf);
if (buffered_input_remains) {
strcpy(buf, buffered_line());
goto top_of_loop;
}
}

My own (informal) rules for goto are: (1) only forward, never
backward, (2) never into a nested scope, and (3) target label
at the outermost level in a function body. (Yes I know 2 and
3 are not independent. :) Furthermore, it doesn't come up very
often - the most common case for code that I write is for whole
files to have not a single 'goto' in them. At the other end
of the scale, I would guess files with early returns happen
more often than not, and occurrences of 'break' being somewhere
in the middle.

This paragraph sounds much more reasonable to me. ;)

-Arthur
 
K

Keith Thompson

Ideally, any control construct in a program should correspond to
something in the problem domain. For example, if a program is dealing
with widgets, a loop might iterate over a group of widgets, and an if
or switch statement might decide what to do based on the
characteristics of a particular widget. A break statement might
denote giving up on the handling of a particular widget.

But a goto statement usually specified only an action in the context
of the program or algorithm, not something that has a meaning in the
problem domain.

Two cases where a goto is justified are breaking out of a nested loop
(something that some languages support directly), and implementing an
explicit finite state machine. In the latter case, a goto actually
does correspond to an action in the problem domain, namely switching
to a specified state.
 
D

dandelion

Michael Wojcik said:
Sheer nonsense. Using break to escape from a loop - which is its only
use, except for breaking from a switch - violates the loop invariant.

Then don't do that. Breaks are good for switch-statement. Returns belong at
the end of your function, at the top for error returns but not in the middle
of a loop.
(Or, more precisely, it adds a "hidden" invariant to the loop.) Since
control remains in the loop until the invariants no longer hold, *by
definition* encountering a break statement represents a new invariant.

And one breaking the loop invariant. Hence... Don't do that.
Ditto any return from within a loop - that is, any return in the scope
of an invariant.

Ditto. That practice, legal as it my be according to The Standard, it
usually results in "bad" code, which derives it unreadability from the
profusion of possible execution paths trough the code.
They have precisely the same effect on invariants as does goto, and for
precisely the same reason.

Ok. You provided a few good examples of what *not* to do, of (in my book)
improper use of "break" and "return".
An empty statement.

Not at all, as your examples indicate.
And I claim that your explanation and "'proper' usage" are bogus.

Your post shows otherwise.
 
D

dandelion

Michael Wojcik said:
A pointless statement.

Not at all. And you merely slip into a semantics argument, for which i have
neither time, nor patience.

Good day.
 
D

dandelion

Kenny McCormack said:
Thomas Stegen said:
John Bode wrote:
[snip]

It is nice to see how you and Dandelion manage to completely
miss the point.

Glad you like it. Besides, i haven't missed your point. I just do not agree,
certainly not in the terms you put it.

Code using goto's without having a good reason is (in my view) bad code.
For

This is, of course, true for all language features. For example:

Code using printf without having a good reason is bad code.

However, there ren't many alternatives to printf, while there are (usually)
many alternatives to goto. Goto's can be used to replace almost every loop
construct, and (with newbiew) usually do.

Let me illustrate:

label:
printf("Hello World\n");
goto label

or

while(1)
printf("Hello World\n");

or

do
printf("Hello World\n");
while(1)

or

for(;;)
printf("Hello World\n");

Functionally, there is no difference. All four will produce the same output.
So there's a good reason to use a looping construct, but no good reason to
employ goto, although the solution would work.
Note that using printf increases the size of your executable.

Which is sometimes a problem, but by no means always. The extra output could
help debugging, though, or simply reassure the user..
It should, therefore, not be used w/o a good reason.

Correct, up to a point. If there's no shortage of space, i'd rather have one
printf too many, than one too little. But that is not what I meant.

And this conclused my reactions to semantic "arguments".
 
D

Dave Thompson

hi all,

Is there is any way in the C language by which I can get the

address of a statement? For eg,consider the following simple program:

1. #include<stdio.h>
2.
3. int main(void){
4. int variable;
5. return 0;
6. }

Well is there is any way by which I can know the address of the
statement which is in the line 4?
Formally line 4 is a declaration not a statement. And more imporant,
on typical contemporary machines, there is no executable code
generated specifically for it.
Also,if I know the address of a statement then can I

transfer the control to that statement by equating program

counter equal to that address? Well,I can access as well as

modify the program counter with the help of the

setjmp(jmp_buf env) function.
You can't do any such thing portably with setjmp (and longmp); the
contents of jmp_buf are entirely implementation-dependent, often
opaque, and even compiler-version or target(-version) dependent.

C doesn't even require that an implementation have a program counter.
I don't see much likelihood of a return of the serial-memory effect of
the old drum machines, like that IIRC in The Story of Mel, but some
microarchitectures are explicit-next aka n+1-address and it might
conceivably make sense for a C implementation to target one of them at
least partially (e.g. for computational cores).

Even if you meant an actual statement like line 5, or a declaration
that in fact generates specific code (like an initialized automatic on
at least the vast majority of current systems), on a "normal" (general
purpose (pseudo)sequential) machine, there is no requirement that
there be a singular address; it is entirely permissible for code from
multiple statements (etc.) to be duplicated, interleaved and/or
reordered, as long as _externally_ visible effects (basically only I/O
and volatile accesses if supported and used) occur "as if" the code
had actually been executed in an order consistent with the abstract
semantics -- and even then it need not be _stored_ in order. And
modern optimizing compilers often do such reordering; they also break,
and combine, source statements (etc.) into "basic blocks" at different
boundaries for purposes of code generation, placement and alignment.

So in short, no.

In GCC as an extension you can set a (void) pointer to point to a
label, and goto that pointer. That pointer value is in effect _an_
address for the labelled statement, not necessarily the only one.
And valid only within the same function invocation (stack frame).

- David.Thompson1 at worldnet.att.net
 
T

Thomas Stegen

Tim said:
Note that the quote says "The UNBRIDLED use" of goto.

True, I didn't really notice. It is a non statement in other words.
When the use of
goto is truly "unbridled", the quoted statement is a lot closer to
true than it is to false. Wouldn't you agree?

In so far that it approaches a tautology, yeah. It does apply to almost
any construct in any language if we want to apply this interpretation
to it.

If the intended interpretation (by the author), is to say that goto
is more powerful than most when it comes to cooking spaghetti then
I can still agree.
 
J

John Bode

Stephen Sprunk said:
For the code fragment above, there's not enough information. Try this:

i = 1;
do {
x = 2 * i;
printf("%d", x);
...
} while (...);

Can you say with any confidence what the value of x will be at any given
time? Not until you've accounted for every if and loop and traced every
possible execution path.

In your example, there is one and only one way to enter the loop, and
in absence of a label, you *know* that I will be initialized to 1
before x = 2 * i is executed the first time. Any effect on x and i
within that loop will be limited to the scope of that loop, and you
can narrow down any problems to the body of that loop and any of its
called functions. Flow of control can be determined rather easily by
inspection. You have a starting point for debugging, if nothing else.

With my example, you simply *don't know*. The statement i = 1 may be
executed once before x is computed, or it may be skipped over. The
code may initialize i to a different value elsewhere and then branch
to label. Or something else might happen. You don't know if the
branch is coming from above or below; indeed, it could come from both
directions. To make sure, you will have to grovel through the whole
function body line by line and find every instance of "goto label",
and then trace the flow from there. For a 100-line function, no
problem. For something that spans a couple of pages, it can get
problematic.
One can do equally perverse things with enough loops and ifs. Bad code is
bad code regardless of the constructs used.

IME, bad structured code is quantitatively easier to debug and repair
than bad unstructured code.
Goto makes bad code easier to
write, but their presence is not proof that the code is bad.

S

I never said it was *proof* of bad code, but it *is* a strong
indicator that the code has problems. It's like using gets() IMO; the
potential for mayhem outweighs any benefit, and its presence indicates
that the author is either inexperienced or unaware of the problems
associated with it.
 
M

Michael Wojcik

The tone here is that the effects of using 'goto' aren't that much
different from using 'break' or (presumably early) 'return'. That's
not so, for both a theoretical reason and a practical reason.

I fear you've misread. (I also believe you're overestimating the
degree of subtlety possible to convey by tone. That's a rather
specific concept you claim to have identified in it.)

My thesis was simply that break and return have just as much
potential for affecting invariants as goto does. dandelion had
identified affects on invariants as "the main reason" for avoiding
goto; that was the argument I was concerned with.

While your subsequent comments on goto are perfectly relevant to the
larger discussion (if not particularly new), they are not directly
relevant to my post.

--
Michael Wojcik (e-mail address removed)

This is a "rubbering action game," a 2D platformer where you control a
girl equipped with an elastic rope with a fishing hook at the end.
-- review of _Umihara Kawase Shun_ for the Sony Playstation
 
M

Michael Wojcik

Not at all.

It's tautological, as I and others have noted. As such it has no
meaningful consequence.
And you merely slip into a semantics argument, for which i have
neither time, nor patience.

Ah, the "semantics" defense, typically the final resort of those who
cannot formulate a coherent argument in support of their position.
 
M

Michael Wojcik

Then don't do that. Breaks are good for switch-statement. Returns belong at
the end of your function, at the top for error returns but not in the middle
of a loop.

Ah, so you are indeed urging us to eschew break (except in switch
cases) and return (except at the end of a function), which is
essentially what I claimed in my original response.

Note that you claimed goto's effect on invariants is "inherent".
Of course this is only true if a goto occurs in the scope of some
invariant. It is equally true if a (non-switch) break or return
occurs in such scope. Thus my claim that your claim is just as
true of the latter two as it is of goto stands.

For the record, I too prefer to avoid using break to escape from
loops with non-trivial conditions or bodies (that is, in any case
where the "hidden invariant" created by break isn't readily apparent
to the reader), and I prefer a single point of return from non-
trivial functions. However, I do not use those preferences to
justify sweeping generalizations about what control structures are or
are not problematic, and in particular I do not discriminate against
one such control structure when it has the same consequences as the
others.
 
T

Tim Rentsch

Arthur J. O'Dwyer said:
The theoretical reason is that 'goto' can lead to irreducible flow
graphs. Early returns or using 'break' to exit a loop (or a 'switch')
cannot cause irreducible flow graphs. Irreducible flows are more
difficult to reason about than reducible flow graphs.

Operative word: "can." Not "must." I don't think anyone is suggesting
the use of pathological control flow, e.g.

['goto' example code snipped]

The difference is, when reading code, on seeing 'break' or 'return' I
don't need to think about anomalous control flow, because it can't
happen. But if I see a 'goto' then I need to check; even if the
control flow isn't screwed up, reading the code takes more effort
because it might be. Not so with 'break' or 'return'.

I can imagine that programs using /only/ gotos would be hard to
comprehend (e.g., I'd expect a 1975 study pitting Fortran against
Ratfor to find that Ratfor wins), but I disagree with the blanket
statement.

My recollection is that a 'goto' statement required more effort even
if it was used in the context of an otherwise "structured" program.
The study seemed fairly well done. Of course that's just my
recollection - sorry again for the lack of a specific reference.

Why, just yesterday I wrote

static char buffer[1000];
char *p, *pstart;
while (fgets(buffer, sizeof buffer, in) != NULL) {
for (p=buffer; isspace(*p); ++p) continue;
if (!strncmp(p, "%%MERR", 6)) goto all_done;

where the 'goto all_done' is exactly equivalent to a 'break'. But
the end of this loop was a screen and a half away, and there were
nested loops in between. So the mnemonic value of the 'all_done'
label far outweighed the benefits of using a 'break' there, whatever
those benefits might have been.

Just offhand, how about refactoring this way:

while( (p = next_input_nonblank( buffer, sizeof buffer, in ))
&& strncmp( p, "%%MERR", 6 ) != 0
){
...
}

where 'next_input_nonblank()' is a small function that does both the
'fgets()' and the loop skipping blanks (assuming there is input to
process). That gets rid of the need to have any intermediate loop
exit. If the variable 'p' isn't used after the loop exits, then

while( p = next_valid_input( buffer, sizeof buffer, in ) ){
...
}

is another possible refactoring.

There's another 'goto' in that same program, this time branching
back to the top of a loop but after the condition has been executed,
like this (simplified drastically):

while (fgets(buf, sizeof buf, fp) != NULL) {
top_of_loop:
process(buf);
if (buffered_input_remains) {
strcpy(buf, buffered_line());
goto top_of_loop;
}
}

Either

while( fgets( buf, sizeof buf, fp ) != NULL ){
do {
process( buf );
} while( have_buffered_input( buf ) );
}

or

while( buffered_or_next_input( buf, sizeof buf, fp ) ){
process( buf );
}

avoids the need for a 'goto', amongst numerous other possibilities.
The variable 'buffered_input_remains' looks like it's global, but if
not it's easy enough to work in explicitly as part of the do/while
condition in the first example.
 
T

Tim Rentsch

I fear you've misread. (I also believe you're overestimating the
degree of subtlety possible to convey by tone. That's a rather
specific concept you claim to have identified in it.)

Oh. Does this mean you believe that using 'goto' *is* substantially
different from using 'break' and 'return', and try to avoid using
'goto' when possible?
 
R

Richard Bos

dandelion said:
Without that dogma, programming is impossible.

*Snigger* Come tell that to the many programs I've written without them.

Lemme guess - you studied at the RUG? Nice guys, really, but just a
_tad_ hung up on their theoretical program proving dogma. Me, I prefer
an approach that works in practice as well as in theory.

Richard
 
D

dandelion

Richard Bos said:
*Snigger* Come tell that to the many programs I've written without them.

You haven't. You may think so, you may even have paid no attention to them,
but they are there. Without them, programming is impossible, after all.

For instance if on one fine line of code you write

10 a=10;

you would expect A to be 10 in the code on line 11, no?

Well... That's the assignment invariant.

and if you wrote

9 i=0;
10 while(i<10)
11 {
12 i++;
13 }

You would expect i to be 10 on line 14, would you not?

Welcome to the loop-invariant.
Lemme guess - you studied at the RUG?
Nope.

Nice guys, really, but just a _tad_ hung up on their theoretical program proving dogma. Me, I ?
prefer an approach that works in practice as well as in theory.

Then i'd suggest you read up on basic programming theory. You may even read
something on preconditions and postconditions which may help you write good,
practical, working code.

Have a nice snigger while you remember that both your CPU and your compiler
have been carefully designed to honour invariants.

dandelion.
 
R

Richard Bos

dandelion said:
You haven't.

Oh, I see. You really _are_ an idiot.

Well, have fun in your happy-happy land, and leave the programming to us
professionals, mmkay?

Richard
 
D

dandelion

Richard Bos said:
Oh, I see. You really _are_ an idiot.

Ah yes...

No more arguments so the opponent is an idiot. How *very* convincing. Well.
It seems "soc.culture.*" is not such an uncivilized place, after all.

If you call yourself a *professional* it may be worthwhile to get a clue,
sometime.

Here's a few exercises from "happy-happy land" for the "professional" you
claim to be.

http://csis.pace.edu/~wolf/CS241/Loop Invariant Exercises.htm

http://courses.cs.vt.edu/~cs3304/Spring00/notes/Chapter-3b/tsld015.htm

http://courses.cs.vt.edu/~cs3304/Spring00/notes/Chapter-3b/tsld020.htm

http://c2.com/cgi/wiki?LoopInvariantAnalysis

Now, I do not want to stoop to your level of conversation, but you can
imagine what I think of your intellectual accomplishments in this thread, i
suppose.

dandelion.
 
C

Chris Torek

You haven't. You may think so, you may even have paid no attention
to them, but they are there. Without [invariants], programming is
impossible, after all.

I think you really need to take a leaf from Knuth here:

"Beware of bugs in the above code; I have only proved it
correct, not tried it."

As I believe David Parnas has noted as well, program proofs usually
embed deliberate simplifications, lest the proof be as complex as
(or more complex than) the program itself.
... and if you wrote

9 i=0;
10 while(i<10)
11 {
12 i++;
13 }

You would expect i to be 10 on line 14, would you not?

That depends -- on what is on line 14, among other things. Even
if line 14 is something like:

p = &zog;

the value that one "sees" in i might change. In a debugger, for
instance, I might well find that i suddenly becomes 0xa0c9e7d4,
because it shares a register with variable "p" in the underlying
(optimized) machine code.

On the other hand, if line 14 is:

printf("i is now %d\n", i);

I *would* expect to see "i is now 10" in the output, and if I did
not, I could use whatever additional information I had about the
system to try to find out why not.
Then i'd suggest you read up on basic programming theory. You may even read
something on preconditions and postconditions which may help you write good,
practical, working code.

These *are* good things -- but remember:
Have a nice snigger while you remember that both your CPU and your compiler
have been carefully designed to honour invariants.

Sometimes designs fail. As Bradley Sherman asked:

Can you prove that electromigration will not cause a fault in
the processor or gamma radiation will not alter a bit [...]?

(We used to have memory chips with alpha-particle problems. Bits
stored in memory would change. Tim May is fond of telling the
story of how he figured this out. "Hot electron migration" remains
a problem in semiconductor design, too. I have stories about timing
problems in various CPUs, such as the carry-chain propagation bug
in the VAX, or the bug in the Ultrasparc CPU where the forwarding
logic would sometimes write an ALU computation into the wrong
register.)
 
M

Michael Mair

Chris said:
Then i'd suggest you read up on basic programming theory. You may even read
something on preconditions and postconditions which may help you write good,
practical, working code.


These *are* good things -- but remember:

Have a nice snigger while you remember that both your CPU and your compiler
have been carefully designed to honour invariants.


Sometimes designs fail. As Bradley Sherman asked:

Can you prove that electromigration will not cause a fault in
the processor or gamma radiation will not alter a bit [...]?

(We used to have memory chips with alpha-particle problems. Bits
stored in memory would change. Tim May is fond of telling the
story of how he figured this out. "Hot electron migration" remains
a problem in semiconductor design, too. I have stories about timing
problems in various CPUs, such as the carry-chain propagation bug
in the VAX, or the bug in the Ultrasparc CPU where the forwarding
logic would sometimes write an ALU computation into the wrong
register.)

That reminds me of an article about long-term simulation results
(cannot remember title nor authors, sorry) where the authors
wrote that they run the program several times and take the
average values as they get different results every time they
run the program due to one or two bits that flip during the run
time of several weeks... Sounds like a convenient excuse first but
might be possible.


Cheers
Michael
 
M

Michael Wojcik

Oh. Does this mean you believe that using 'goto' *is* substantially
different from using 'break' and 'return', and try to avoid using
'goto' when possible?

I'd avoid the term "substantially different", as I'm not sure it's
meaningful without specifying context. Clearly, by definition,
goto is different from break and return; they have different
semantics, and return can perform an operation which neither goto
nor break provide (assigning the function's return value).

By the same token, I don't think "try to avoid ... when possible" is
an interesting claim. It's *always* possible to avoid goto, and
under what conditions would such an attempt fail? Advocating that
is tantamount to advocating no use of goto under any conditions
whatsoever.

I use the construct I feel is appropriate in the situation at hand.
In practice, I use goto almost exclusively for single-point-of-
return, which means nearly all the goto's in my code are of the form
"goto done", where the "done" label refers to "epilog" code at the
end of the function, just before the return, which performs any
necessary final cleanup. (If C had an exception-handling mechanism
with a "finally" block, that would be a fine alternative.)

There are some excellent arguments to be made against the use of goto
in general (has anyone in this thread mentioned its deleterious
effects on peephole optimization yet?). I've yet to see one that
applies in every case, so I adopt no mottoes about "trying to avoid".

--
Michael Wojcik (e-mail address removed)

"Well, we're not getting a girl," said Marilla, as if poisoning wells were
a purely feminine accomplishment and not to be dreaded in the case of a boy.
-- L. M. Montgomery, _Anne of Green Gables_
 

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

No members online now.

Forum statistics

Threads
473,888
Messages
2,569,964
Members
46,293
Latest member
BonnieHamb

Latest Threads

Top