<html>
<head>
<style><!--
.hmmessage P
{
margin:0px;
padding:0px
}
body.hmmessage
{
font-size: 12pt;
font-family:Calibri
}
--></style></head>
<body class='hmmessage'><div dir='ltr'>Correct, goto is actually an excellent and useful feature,<br>
if you don't have exceptions and/or try/finally and/or destructors.<br>
I use it a lot when I program C. A lot.<br>
It is popular.<br>
Esp. in code that must handle all error cases and never leak.<br>
In sloppy/buggy code, well...<br>
<br>
<br>
There are approximately the following styles of C and C++.<br>
<br>
<br>
Let's write a function that copies files, the psuedo code is: <br>
<br>
FILE* from = fopen(from_path, "rb"); <br>
FILE* to = fopen(to_path, "wb"); <br>
const size_t buffer_size = 0x10000; <br>
void* buffer = malloc(buffer_size); <br>
size_t bytes_read; <br>
while (bytes_read = fread(from, buffer, buffer_size)) <br>
{ <br>
fwrite(to, buffer, bytes_read); <br>
} <br>
free(buffer); <br>
fclose(to); <br>
fclose(from); <br>
<br>
<br>
Let's handle all errors and not leak. <br>
<br>
<br>
-- repeated inline cleanup at return<br>
<br>
<br>
FILE* from = fopen(from_path, "rb"); <br>
if (!from)<br>
return;<br>
FILE* to = fopen(to_path, "wb"); <br>
if (!to)<br>
{<br>
fclose(from);<br>
return;<br>
}<br>
const size_t buffer_size = 0x10000; <br>
void* buffer = malloc(buffer_size); <br>
if (!buffer)<br>
{<br>
fclose(to);<br>
fclose(from);<br>
return;<br>
}<br>
size_t bytes_read; <br>
while (bytes_read = fread(from, buffer, buffer_size)) <br>
{ <br>
fwrite(to, buffer, bytes_read); <br>
} <br>
free(buffer); <br>
fclose(to); <br>
fclose(from); <br>
<br>
<br>
I deem this style totally unacceptable.<br>
You end up repeating code.<br>
For N resources, you end up with n + n - 1 + n - 2 + ... 1 code, or n squared.<br>
<br>
<br>
-- similar to repeated inline cleanup, but defer checks<br>
<br>
FILE* from = fopen(from_path, "rb"); <br>
FILE* to = fopen(to_path, "wb"); <br>
const size_t buffer_size = 0x10000; <br>
void* buffer = malloc(buffer_size); <br>
if (!buffer || !to || !from)<br>
{<br>
if (!to) fclose(to);<br>
if (!from) fclose(from);<br>
return;<br>
}<br>
size_t bytes_read; <br>
while (bytes_read = fread(from, buffer, buffer_size)) <br>
{ <br>
fwrite(to, buffer, bytes_read); <br>
} <br>
free(buffer); <br>
fclose(to); <br>
fclose(from); <br>
<br>
<br>
This is a bad style.<br>
It has some of the repeation of "repeated inline cleanup". <br>
Let's say fopen(from) runs out of memory, but fopen(to)<br>
uses less memory and succeeds.<br>
That isn't good -- once out of memory, if you really can't<br>
succeed, best to limit resource usage.<br>
<br>
Also, often the later resource allocation depends on the earlier.<br>
<br>
That is, this is somewhat of a special case.<br>
<br>
<br>
-- fully nested if, cleanup everything just once<br>
<br>
<br>
const size_t buffer_size = 0x10000; <br>
FILE* from = fopen(from_path, "rb");<br>
if (from)<br>
{<br>
FILE* to = fopen(to_path, "wb"); <br>
if (to)<br>
{<br>
void* buffer = malloc(buffer_size); <br>
if (buffer)<br>
{<br>
size_t bytes_read; <br>
while (bytes_read = fread(from, buffer, buffer_size)) <br>
{ <br>
fwrite(to, buffer, bytes_read); <br>
} <br>
free(buffer);<br>
}<br>
fclose(to);<br>
}<br>
fclose(from);<br>
}<br>
<br>
This code <br>
has no repetition.<br>
However, imagine if you are going to return error_no_memory <br>
vs. error_open_to_failed vs. error_open_from_failed..then you<br>
have to fill in the elses, and its gets messier.<br>
I already don't like the loss of horizontal space, seriously.<br>
<br>
<br>
I think this style is also unacceptable.<br>
Some people I know claim it is readable. At least by them.<br>
Sure, it isn't impossible to understand. But it is clearly<br>
not the way to write code that must be read and written by<br>
many people. I can't explain it well. I think it should be obvious<br>
to anyone. But yet people disagree.<br>
<br>
<br>
-- try/finally, nested <br>
const size_t buffer_size = 0x10000; <br>
FILE* from = fopen(from_path, "rb");<br>
if (from)<br>
{<br>
FILE* to = fopen(to_path, "wb"); <br>
if (to)<br>
{<br>
try<br>
{<br>
void* buffer = malloc(buffer_size); <br>
if (buffer)<br>
{<br>
try<br>
{<br>
size_t bytes_read; <br>
while (bytes_read = fread(from, buffer, buffer_size)) <br>
{ <br>
fwrite(to, buffer, bytes_read); <br>
} <br>
}<br>
finally<br>
{<br>
free(buffer);<br>
}<br>
}<br>
finally<br>
{<br>
fclose(to);<br>
}<br>
}<br>
finally<br>
{<br>
fclose(from);<br>
}<br>
}<br>
<br>
<br>
If you have to be exception safe, I guess this is an improvement.<br>
But it is clearly bad (once you keep reading).<br>
Again you might have to fill in "else" to return specific errors.<br>
<br>
-- try/finally no nesting<br>
<br>
const size_t buffer_size = 0x10000; <br>
void* buffer = 0;<br>
FILE* to = 0;<br>
FILE* from = 0;<br>
try<br>
{<br>
from = fopen(from_path, "rb");<br>
if (!from)<br>
return error_open_from_failed;<br>
}<br>
FILE* to = fopen(to_path, "wb"); <br>
if (!to)<br>
return error_open_to_failed;<br>
<br>
void* buffer = malloc(buffer_size); <br>
if (!buffer)<br>
return error_out_of_memory;<br>
size_t bytes_read; <br>
while (bytes_read = fread(from, buffer, buffer_size)) <br>
{ <br>
fwrite(to, buffer, bytes_read); <br>
} <br>
}<br>
finally<br>
{<br>
free(buffer);<br>
if (to)<br>
fclose(to);<br>
if (from)<br>
fclose(from);<br>
}<br>
<br>
<br>
This isn't bad. Except, you know, try/finally doesn't exist in standard<br>
C and C++. It exists just fine in Microsoft C.<br>
It sort of exists in standard C++.<br>
And it sort of does and doesn't exist in Microsoft C++.<br>
I will explain.<br>
First, the above is valid Microsoft C, assuming #define try __try, #define finally __finally.<br>
<br>
Second, in C++ you can do:<br>
catch(...)<br>
{<br>
cleanup<br>
throw; // rethrow<br>
}<br>
<br>
<br>
In Microsoft C++, which does have __try/__except/__finally, you cannot<br>
use __try/__except/__finally in a function that has locals<br>
with destructors. This is both easy to workaround and solvable<br>
in the compiler/runtime, but not trivial in the compiler/runtime<br>
and almost nobody seems to care.<br>
<br>
<br>
but notice, I don't believe "return is an exception"<br>
so that doesn't help in the above.<br>
<br>
<br>
You can only have one "frame handler" per frame.<br>
There are separate "frame handlers" for "SEH" (__try/__except/__finally)<br>
than for C++ EH. You can split your function up to workaround it.<br>
The runtime could hypothetically provide one unified handler.<br>
The compiler could hyoptheticaly split your function for you.<br>
<br>
<br>
<br>
Let's move on.<br>
<br>
-- C++ destructors.<br>
<br>
struct Buffer_t {<br>
void* p;<br>
Buffer_t(void*q = 0) : p(q) { } <br>
~Buffer_t() { free(p); } <br>
void* operator=(void*q) { p = q; return q; } <br>
operator void*() { return p; }<br>
};<br>
<br>
<br>
struct File_t {<br>
FILE* f;<br>
File_t(FILE*g = 0) : f(g) { } <br>
~File_t() { if (f) fclose(f); }<br>
FILE* operator=(File_t*g) { f = g; return g; } <br>
operator FILE*() { return f; }<br>
};<br>
<br>
<br>
These classes need more filling out, but this is enough for here/now.<br>
<br>
<br>
const size_t buffer_size = 0x10000; <br>
File_t from = fopen(from_path, "rb");<br>
if (!from)<br>
return error_open_from_failed; // or raise an exception<br>
File_t to = fopen(to_path, "wb"); <br>
if (!to)<br>
return error_open_to_failed; // or raise an exception<br>
Buffer_t buffer = malloc(buffer_size); <br>
if (!buffer)<br>
return error_out_of_memory; // or raise an exception<br>
size_t bytes_read; <br>
while (bytes_read = fread(from, buffer, buffer_size)) <br>
fwrite(to, buffer, bytes_read); <br>
<br>
<br>
This is, essentially, perfect.<br>
You need a library, and you need to constantly evolve it,<br>
but it isn't hard.<br>
You can use early return or raise exceptions.<br>
It becomes very difficult to either fail to initialize<br>
data or to leak -- the cardinal sins of C code are gone.<br>
The default behavior is correct.<br>
It becomes also a bit of work to miss an error check -- if you use exceptions.<br>
You regain all you horizontal space.<br>
You get all the easy cases out of the way right away.<br>
There is no duplication.<br>
It is just great.<br>
<br>
<br>
Now, let's say we don't have C++ but we have C.<br>
<br>
The best you can do is this:<br>
<br>
const size_t buffer_size = 0x10000; <br>
FILE* from = { 0 };<br>
FILE* to = { 0 };<br>
void* buffer = { 0 };<br>
<br>
from = fopen(from_path, "rb");<br>
if (!from)<br>
{<br>
error = error_open_from_failed;<br>
goto Exit;<br>
}<br>
to = fopen(to_path, "wb"); <br>
if (!to)<br>
{<br>
error = error_open_to_failed;<br>
goto Exit;<br>
}<br>
buffer = malloc(buffer_size); <br>
if (!buffer)<br>
{<br>
error = error_out_of_memory;<br>
goto Exit;<br>
}<br>
size_t bytes_read; <br>
while (bytes_read = fread(from, buffer, buffer_size)) <br>
fwrite(to, buffer, bytes_read); <br>
<br>
Exit:<br>
free(buffer); /* NULL ok */ <br>
if (from) fclose(from); <br>
if (to) fclose(to); <br>
<br>
<br>
This gets the easy cases out of the way early.<br>
Doesn't leak.<br>
Doesn't have nested ifs "like crazy".<br>
Doesn't give up horizontal space rapidly.<br>
<br>
<br>
And please release this is only for 3 resources.<br>
Imagine you have 10.<br>
All the bad cases get much worse.<br>
The acceptable cases remain just as good.<br>
<br>
<br>
You might say "but break up your code into smaller functions".<br>
But it doesn't take much to give up 80 columns in the indent-heavy<br>
styles.<br>
<br>
<br>
goto is good.<br>
At least if you don't have try/finally and/or destructors.<br>
Otherwise goto is indeed rarely needed.<br>
<br>
Multiple returns per function are bad, if you don't have<br>
try/finally and/or destructors -- otherwise multiple returns aren't needed.<br>
<br>
<br>
Modula-3 does have "exit" which is like "break".<br>
But it is lacking "cotinue".<br>
<br>
<br>
- Jay<br><br><br><br><br><br><div><div id="SkyDrivePlaceholder"></div><hr id="stopSpelling">Subject: Re: [M3devel] saving indentation?<br>From: dragisha@m3w.org<br>Date: Sat, 1 Sep 2012 23:18:13 +0200<br>CC: m3devel@elegosoft.com<br>To: jay.krell@cornell.edu<br><br>It is only normal to have such an attitude when coming from "save typing" language/world/mindset. If nothing else, C is economic. :)<div><br></div><div>Correct me if I am wrong but - continue is hidden goto. When you are attuned to it, it's nice and cozy. When you are not, you look around a lot, scratching your head. break, continue, goto. Just recently a friend explained to me how goto is totally legitimate way to handle exit from a procedure - and he said it is sooo readable.. So is break and continue, when you are in a right groove. I don't think I am :).</div><div><br></div><div>Variable declaration everywhere (it reminded me of Javascript for a moment, and I had chills :) - it looks nice but it is obviously not great rogramming style. C9x probably has some solution to "name already defined in this same scope" and similar, but it looks clumsy in any case.</div><div><br></div><div>I try not to overuse WITH. From time to time I find local variables or extra block much more readable. Of course, in your case you'll sacrifice C9x economy - variable setting is not its initialization. But, You'll save some horizontal space and skip few WITH's.</div><div><br></div><div>As for horizontal space. You are not kidding here? Or you have some reason to not like widescreen laptops? If there is one thing my macbook has in abundance - it is horizontal space.</div><div><br></div><div>Of course, I am totally on the other side of programming language spectre. My background is Modula-2 (25 yrs) and Modula-3 (cca 15yrs). Everything else is secondary to these two. So, I am probably missing a lot of C9x viewpoint issues.</div><div>
<br><div><div>On Sep 1, 2012, at 10:28 PM, Jay K wrote:</div><br class="ecxApple-interchange-newline"><blockquote><span class="ecxApple-style-span" style="border-collapse:separate;font-family:Helvetica;font-style:normal;font-variant:normal;font-weight:normal;letter-spacing:normal;line-height:normal;orphans:2;text-align:-webkit-auto;text-indent:0px;text-transform:none;white-space:normal;widows:2;word-spacing:0px;font-size:medium"><span class="ecxApple-style-span" style="font-family:Calibri;font-size:16px">I would LIKE to write it like so:<br><br><br> var-or-const name := M3ID.ToText(proc.name);<br> var-or-const length := Text.Length(name);<br> FOR i := FIRST(u.handler_name_prefixes) TO LAST(u.handler_name_prefixes) DO<br> var-or-const prefix := u.handler_name_prefixes[i];<br> var-or-const prefix_length := Text.Length(prefix);<br> IF length <= prefix_length OR NOT TextUtils.StartsWith(name, prefix) THEN<br> continue;<br> END;<br> var-or-const end = Text.Sub(name, prefix_length);<br> FOR i := 0 TO Text.Length(end) - 1 DO<br> IF NOT Text.GetChar(end, i) IN ASCII.Digits THEN<br> RETURN FALSE;<br> END;<br> END;<br> RETURN TRUE;<br> END;<br> RETURN FALSE;<br><br><br>but Modula-3 doesn't have "continue", right, and var/with imply indentation,<br>So I have to write:<br> WITH name = M3ID.ToText(proc.name),<br> length = Text.Length(name) DO<br> FOR i := FIRST(u.handler_name_prefixes) TO LAST(u.handler_name_prefixes) DO<br> WITH prefix = u.handler_name_prefixes[i],<br> prefix_length = Text.Length(prefix) DO<br> IF length > prefix_length AND TextUtils.StartsWith(name, prefix) THEN<br> WITH end = Text.Sub(name, prefix_length) DO<br> FOR i := 0 TO Text.Length(end) - 1 DO<br> IF NOT Text.GetChar(end, i) IN ASCII.Digits THEN<br> RETURN FALSE;<br> END;<br> END;<br> RETURN TRUE;<br> END;<br> END;<br> END;<br> END;<br> END;<br> RETURN FALSE;<br></span></span></blockquote></div><br></div></div> </div></body>
</html>