On Linux, I compiled the code below into assembly using both g++ and clang++. For more information on how I did that, see here. (Short version: g++ -S -O3 filename.cpp
clang++ -S -O3 filename.cpp
, and some assembly comments you'll see below to help me out.)
Conclusion/TL;DR at the bottom.
First, I compared label:
and goto
vs. do {} while
. You can't compare a for () {}
loop with this (in good faith), because a for loop always evaluates the condition first. This time around, the condition is evaluated only after the loop code has been executed once.
#include <iostream>
void testGoto()
{
__asm("//startTest");
int i = 0;
loop:
std::cout << i;
++i;
if (i < 100)
{
goto loop;
}
__asm("//endTest");
}
#include <iostream>
void testDoWhile()
{
__asm("//startTest");
int i = 0;
do
{
std::cout << i;
++i;
}
while (i < 100);
__asm("//endTest");
}
In both cases, the assembly is the exact same regardless of goto
or do {} while
, per compiler:
g++:
xorl %ebx, %ebx
leaq _ZSt4cout(%rip), %rbp
.p2align 4,,10
.p2align 3
.L2:
movl %ebx, %esi
movq %rbp, %rdi
addl $1, %ebx
call _ZNSolsEi@PLT
cmpl $100, %ebx
jne .L2
clang++:
xorl %ebx, %ebx
.p2align 4, 0x90
.LBB0_1: # =>This Inner Loop Header: Depth=1
movl $_ZSt4cout, %edi
movl %ebx, %esi
callq _ZNSolsEi
addl $1, %ebx
cmpl $100, %ebx
jne .LBB0_1
# %bb.2:
Then I compared label:
and goto
vs. while {}
vs. for () {}
. This time around, the condition is evaluated before the loop code has been executed even once.
For goto
, I had to invert the condition, at least for the first time. I saw two ways of implementing it, so I tried both ways.
#include <iostream>
void testGoto1()
{
__asm("//startTest");
int i = 0;
loop:
if (i >= 100)
{
goto exitLoop;
}
std::cout << i;
++i;
goto loop;
exitLoop:
__asm("//endTest");
}
#include <iostream>
void testGoto2()
{
__asm("//startTest");
int i = 0;
if (i >= 100)
{
goto exitLoop;
}
loop:
std::cout << i;
++i;
if (i < 100)
{
goto loop;
}
exitLoop:
__asm("//endTest");
}
#include <iostream>
void testWhile()
{
__asm("//startTest");
int i = 0;
while (i < 100)
{
std::cout << i;
++i;
}
__asm("//endTest");
}
#include <iostream>
void testFor()
{
__asm("//startTest");
for (int i = 0; i < 100; ++i)
{
std::cout << i;
}
__asm("//endTest");
}
As above, in all four cases, the assembly is the exact same regardless of goto
1 or 2, while {}
, or for () {}
, per compiler, with just 1 tiny exception for g++ that may be meaningless:
g++:
xorl %ebx, %ebx
leaq _ZSt4cout(%rip), %rbp
.p2align 4,,10
.p2align 3
.L2:
movl %ebx, %esi
movq %rbp, %rdi
addl $1, %ebx
call _ZNSolsEi@PLT
cmpl $100, %ebx
jne .L2
Exception for g++: at the end of the goto2 assembly, the assembly added:
.L3:
endbr64
(I presume this extra label was optimized out of the goto
1's assembly.) I would assume that this is completely insignificant though.
clang++:
xorl %ebx, %ebx
.p2align 4, 0x90
.LBB0_1: # =>This Inner Loop Header: Depth=1
movl $_ZSt4cout, %edi
movl %ebx, %esi
callq _ZNSolsEi
addl $1, %ebx
cmpl $100, %ebx
jne .LBB0_1
# %bb.2:
In conclusion/TL;DR: No, there does not appear to be any difference whatsoever between any of the possible equivalent arrangements of label:
and goto
, do {} while
, while {}
, and for () {}
, at least on Linux using g++ 9.3.0 and clang++ 10.0.0.
Note that I did not test break
and continue
here; however, given that the assembly code generated for each of the 4 in any scenario was the same, I can only presume that they would be the exact same for break
and continue
, especially since the assembly is using labels and jumps for every scenario.
To ensure correct results, I was very meticulous in my process and also used Visual Studio Code's compare files feature.