tags:

views:

218

answers:

2

When a continue statement is used inside a loop in C code, GCC creates a new label with a nop instruction right before the end of the loop block and jumps to it, rather than jump to the end of the loop block itself. For instance, the following C code

for (i=0; i<10; i++) {
    puts("blah\n");
    if (i < 10) continue;
    puts("This shouldn't be printed.\n");
}

produces the following ASM equivalent (using gcc -S ):

    movl    $0, 28(%esp)
    jmp L2
L5:
    movl    $LC0, (%esp)
    call    _puts
    cmpl    $9, 28(%esp)
    jle L7
L3:
    movl    $LC1, (%esp)
    call    _puts
    jmp L4
L7:
    nop
L4:
    incl    28(%esp)
L2:
    cmpl    $9, 28(%esp)
    jle L5

(The if (i<10) part is inserted so that the compiler doesn't "optimize" the section by removing whatever follows the continue statement)

My question is, why not jump directly to L4 instead? IMO we could just as well jump to L4, am I missing something?

+4  A: 

What you're describing is an optimization. Surely enough, if you tell gcc to optimize (-O1 is enough), it'll do exactly what you describe.

Michael Foukarakis
Thanks for the quick reply! -O1 did the trick, though the program flow after the optimization seems somewhat less intuitive now -- unless I'm mistaken, the loop seems partially unrolled now. I'm curious now as to whether there is any incentive to creating the label with the nop instruction. Is there any situation where this approach would prevent something that would happen if one was to jump directly to L4?
susmits
For debugging purposes, you could set a breakpoint in the L7 label - it'd be hard to break on the continue case if it was not there.
nos
@susmits: With -O1 the assembly is less verbose, but the loop isn't unrolled (with -O3 you can see the unrolled version - notice it doesn't even include the i<10 check or the 2nd puts()). Generally, creating the label and not performing such optimizations helps a lot in debugging.
Michael Foukarakis
+1  A: 

My guess is that it's a placeholder for some kind of skipped-code fixup sequence. Perhaps the nop is sometimes replaced with instructions to store registers to the stack or some such.

But to get more evidence for this, it would help to find an example where the nop is replaced with something else.

Artelius
I think this is probably it. In this case there are so few variables involved that the set of variables in registers at the "continue" is the same as the set of variables in registers after the second puts. If that wasn't the case, then after L7 would be the code to sync them. If there were multiple "continues" in the loop, then each would still have to sync itself to the correct L7 state before jumping, of course.
Steve Jessop
On the other hand, placing such code in a block associated with a continue statement is extremely inefficient, so I'd guess it's a pretty rare sight anyway.
Michael Foukarakis
Bear in mind that susmits has implicitly requested inefficient code (by not specifying -O) ;-)
Steve Jessop