andrewducker: (Cutest Kitten)
[personal profile] andrewducker
A few days ago I was waiting for something or other to get sorted before I could proceed with the current round of bugfixing (the latest project is very nearly done) when I got into a discussion with [livejournal.com profile] channelpenguin about the syntax of the for statement in C#. She was complaining that it felt wrong - it didn't really fit with any other statement in C#, what with having semicolons inside brackets. In fact, the for statement was the one I kept having to check the syntax of when I first started using C#, so I could sympathise with her.

The syntax is:
for (initialisers; expression;iterators)
{
    Stuff();
}


for instance:

for(int myInt = 0;myInt < 5;myInt++)
{
	Console.WriteLine(myInt);
}

meaning "Create an integer. Set it to zero. While said integer is less than 5, output it to the console and then increment it by one."

Now, I thought about how you could write the for statement differently, and I couldn't think of one that was any clearer without expanding it out entirely into its equivalent while statement. Which looks like this:

int myInt = 0;
while (myInt < 5)
{
	Console.WriteLine(myInt);
	myInt++;
}

In fact, the more I thought about it, the more it seemed likely that the for statement was just a macro - and at compile time the for statement was translated into the equivalent while statement, and _that_ was compiled - so that the original syntax statement above would become:

{	
	initialisers;
	While (expression)
	{
		Stuff();	
		iterators;
	}
}

(the braces around the entire of the expanded statement are to make sure that any variables created in the initialisers are local to that code segment)

Fortunately, Visual Studio comes with a neat little disassembler (ILDASM), so I compiled the two above bits of code and they both came out exactly the same, as:

 .locals init ([0] int32 myInt)
  IL_0000:  ldc.i4.0
  IL_0001:  stloc.0
  IL_0002:  br.s       IL_000e
  IL_0004:  ldloc.0
  IL_0005:  call       void [mscorlib]System.Console::WriteLine(int32)
  IL_000a:  ldloc.0
  IL_000b:  ldc.i4.1
  IL_000c:  add
  IL_000d:  stloc.0
  IL_000e:  ldloc.0
  IL_000f:  ldc.i4.5
  IL_0010:  blt.s      IL_0004
  IL_0012:  ret

which gave me a warm glow inside. It's nice to be right about something, especially when you're just stabbing in the dark.

Of course, I'm now curious about .NET's Intermediate Language (which is what that stuff at the end is). So possibly a look into _that_ is in order next.

[Poll #705445]

Date: 2006-04-06 06:49 pm (UTC)
wychwood: archaeology: computer, trowel, books (Arch - archaeology)
From: [personal profile] wychwood
...for given values of "understood", at least :)

Date: 2006-04-06 07:00 pm (UTC)
From: [identity profile] jccw.livejournal.com
Yes, C style for loops are counterintuitive... better than "goto", but not much.

Does C# have C-style break and continue? If so, I think the translation no longer works since "continue" in a for loop runs the iterators again but in a while loop they will be skipped. (The fix is to translate "continue;" to "{iterators;continue;}"

Date: 2006-04-06 07:31 pm (UTC)
From: [identity profile] figg.livejournal.com
In dylan for instance, for is implemented as a macro, but you coudn't implement this as a macro in C due to the preprocessor being crap.

Also, the reverse translator for the Psion 3a (REVTRAN) had an option to turn p-code back into while loops or for loops.

Date: 2006-04-06 08:01 pm (UTC)
From: [identity profile] figg.livejournal.com
Here is the annotated dissasembly of the .net program. (I was curious about what the instructions were, and so looked them up, and I thought i'd share the below)

To cut down on words: #0,#1,... refer to the local variables 0,1,...
                      $0,$1,... refer to the first,second,third items
                      in the stack

.locals init ([0] int32 myInt)

# initialisation, set up counter in #0 at 0.
        IL_0000:  ldc.i4.0              # Push 0 (int32) onto the stack
        IL_0001:  stloc.0               # Pop $0 off stack (0) and store in #0
        IL_0002:  br.s       IL_000e    # Jump to TEST

# loop body. Increment #0 by 1, and call WriteLine.
LOOP:   IL_0004:  ldloc.0               # 
        IL_0005:  call       void [mscorlib]System.Console::WriteLine(int32)
                                        # Write stuff!
        IL_000a:  ldloc.0               # Push #0 onto the stack
        IL_000b:  ldc.i4.1              # Push 1 (int32) onto the stack
        IL_000c:  add                   # Pop two numbers, add, push result
        IL_000d:  stloc.0               # Store result (top of stack) in #0

# Test for exit condition.
TEST:   IL_000e:  ldloc.0               # Push #0 (counter) onto stack
        IL_000f:  ldc.i4.5              # Push 5 (int32) onto stack
        IL_0010:  blt.s      IL_0004    # If counter ($1) < 5 ($0) 
                                        # jump to LOOP:
                                        
        IL_0012:  ret                   # return to caller

Date: 2006-04-06 09:24 pm (UTC)
From: [identity profile] call-waiting.livejournal.com
If the compiler is anything like halfway decent, so long as your programs have the same semantics, it should produce the same object code. Which I guess is to say that the object code is a result of the semantics of the code rather than the syntax, so you can't necessarily infer anything about the syntax (or meta-syntax, in the case of 'X is a macro of Y') of the language from the object code.

In this case it's not quite a macro, for reasons pointed out elsewhere. Unless C# happens to have python-style 'continue' blocks, in which case it'd map to a macro that spans the entire for loop and places a 'continue' block at the end.

The reality of the situation is more likely to be that the compiler recognises the syntax of the for (;;) loop independently of the syntax of the while() loop and translates them both to equivalent structures in the compiler's intermediate representation.

The real thing that's abhorrent about the C 'for' construct, from my point of view, is that (syntactically) there are three statements, with entirely different usage semantics, but visually entirely undistinguished from each other except by position. You can remove the code inside the '()' and drop it elsewhere in a program and it'll be a syntactically valid sequence of statements. Same code, entirely different semantics. A sensible syntax would identify them independently in some way.

The only thing that 'for(;;)' has going for it over a plain and simple 'while ()' is that it lets you put that S3 up at the top of the loop body rather than at the end. If it wasn't for that... its days would be numbered.

Date: 2006-04-07 12:30 am (UTC)
From: [identity profile] figg.livejournal.com
for (S1;S2;S3) { }
in a while, you have to put S3 (the increment.) at the end.

Date: 2006-04-07 01:34 pm (UTC)
From: [identity profile] call-waiting.livejournal.com
Bingo. I could have sworn I typed 'for (S1; S2; S3)' somewhere in that comment...

Date: 2006-04-06 10:17 pm (UTC)
moniqueleigh: (Titans)
From: [personal profile] moniqueleigh
For vague values of "read"... OK, actually, I realized it was C# (which I've never bothered to learn), realized it was a bit of code that looks suspiciously like most of the other languages I have bothered to learn, and then read the rest of the post. And realized that it still made sense in the translation. Yes, yes, I am a geek. *nods*

Date: 2006-04-06 11:33 pm (UTC)
From: [identity profile] 0olong.livejournal.com
For years, I never ever used for in C or Java for more-or-less the reasons you talked about. The whole syntax just seems out of place, it's very odd. I still use while statements almost every time.

Date: 2006-04-07 08:27 am (UTC)
From: [identity profile] drdoug.livejournal.com
This is directly inherited behaviour from the granddaddy language C (which happens to be my first language). I'm pretty sure that K & R* says that for and while loops are entirely equivalent in just the manner you describe, with the exception of the behaviour of continue.

(IIRC, continue in a for loop means 'skip to the iterator part' but in a while loop it means 'skip to the test-expression part' - I imagine it compiles to the equivalent of 'goto the closing curly bracket of the loop'... but I hardly ever use continue anyway.)

Interesting to see that for and while loops compile exactly the same in C#, as they should. Fancy a quick test of continue too to see if I'm right?

* Kernighan & Ritchie, "The C Programming Language", the definitive tome on C. Although it is a mere pamphlet by comparison with definitive tomes for other languages.

Date: 2006-04-10 08:21 am (UTC)
From: [identity profile] drdoug.livejournal.com
Cool - thanks for that, interesting to know. I still don't like continue much, though.

Date: 2006-04-10 09:05 am (UTC)
From: [identity profile] drdoug.livejournal.com
Partly - but more a vicious circle of unfamiliarity. I don't know it well enough to be totally certain of how it works without double-checking, so I tend not to use it, so I don't get to know it well.

I'm not entirely averse to goto. I've seen some hairy deeply-nested loops where a judicious goto to just get the hell out of there would not be as bad as the morass of continues, breaks and flag variables you need to get round it. Mind you, probably better to turn that section in to a function and just 'return' from the depths, which would transform a mess in to what's widely accepted as perfectly good style - which I think it what I'd tend to do when others might use continue.

Date: 2006-04-10 09:33 am (UTC)
From: [identity profile] drdoug.livejournal.com
I'm sure that's the Right Answer these days. I had a slight nervousness at the performance overhead of a function call - which is so last century.

Date: 2006-04-10 11:03 am (UTC)
From: [identity profile] drdoug.livejournal.com
TBH I'm surprised the difference is that large. And as you say, for most applications that's so trivial to be not worth bothering about, but if you are worried about it you should optimise for it.

So if, say, that code was in the main loop of your Shiny New Brute-Force Sudoku Solver, an order of magnitude improvement would be well worth having. (Although you still have another half-dozen or so orders of magnitude to somehow knock off the computation time to get it complete before you die. Ah well.)

The key, as ever, is recognising the one situation from t'other.

The right sort of optimisation can be an astonishing thing to behold, particularly when the function call you're optimising away is to an API. I once wrote some code to display large 2D datasets on screen. My initial attempt simply handed each data point (suitably scaled) to the drawing API in turn and told it to draw a line to there from the previous point. It took more than 10 minutes to refresh the screen on a smallish test dataset - not good when a key point of the app was to be able to fly around in the data. So I optimised by tracking the highest and lowest point for each horizontal pixel before handing it to the API, and bingo - pretty much instant, even for the larger datasets I was using.

Date: 2006-04-07 06:06 pm (UTC)
From: [identity profile] azalemeth.livejournal.com
Ever heard of the GCC funroll-loops optimiser? (i.e. gcc --funroll-loops -M2 -[required other options] foo.cpp)

If you compile a for loop "as is", you have to have (and this is in english I'm afraid, as my x86 assembler consists of 'NULL'...) basically [initialisers] [check] [do something] [check condition] [increment] [do something] [check condition] [increment] ... [check condition] [end instruction block], which is functionally the same as what you've just discovered :). The --funroll-loops option "funrolls" the loops, so you get [initialisers] [do] [advance] [do] [advance]....[end], which is a lot faster. Or something like that.

At any rate, for, and while are just GOTO in disguise :).

Date: 2006-04-07 10:38 pm (UTC)
From: [identity profile] azalemeth.livejournal.com
Badly.

Hence, http://www.funroll-loops.org. It has a nasty tendency to break lots of stuff. Me, I stick to -O2 (second level of optimisations) in 'home' code, and -O1 for important stuff. It's fast enough that you notice the difference, and yet not so insanely optimised that you notice the need to punch in assembler from the front panel.....

March 2026

S M T W T F S
1 2 3 4 56 7
8 9 10 11 12 13 14
15161718192021
22232425262728
293031    

Most Popular Tags

Style Credit

Expand Cut Tags

No cut tags
Page generated Mar. 15th, 2026 06:54 pm
Powered by Dreamwidth Studios