C is generally compiled to assembly language first, and then an assembler compiles the assembly language to actual machine code. Typically the intermediate assembly language code is deleted.
If you are very curious about what the compiler does to your code, you can run gcc with the -S option to tell it to stop after creating the assembly file; for example, the command gcc -S program.c will create a file program.s containing the compiled code. What this code will look like depends both on what your C code looks like and what other options you give the C compiler. You can learn how to read (or write) assembly for the Intel i386 architecture by following this link (but see these notes for an explanation of the variant of i386 assembly language supported by gas, the GNU assembler used by gcc), or you can just jump in and guess what is going on by trying to expand the keywords (hint: movl stands for "move long," addl for "add long," etc., numbers preceded by dollar signs are constants, and things like %ebx are the names of registers, high-speed memory locations built in to the CPU).
For example, here's a short C program that implements a simple loop in two different ways, the first using the standard for loop construct and the second building a for loop by hand using goto (don't do this).
1 #include <stdio.h>
2
3 int
4 main(int argc, char **argv)
5 {
6 int i;
7
8 /* normal loop */
9 for(i = 0; i < 10; i++) {
10 printf("%d\n", i);
11 }
12
13 /* non-standard implementation using if and goto */
14 i = 0;
15 start:
16 if(i < 10) {
17 printf("%d\n", i);
18 i++;
19 goto start;
20 }
21
22 return 0;
23 }
and here is the output of the compiler using gcc -S loops.c:
.file "loops.c" .section .rodata .LC0: .string "%d\n" .text .globl main .type main, @function main: pushl %ebp movl %esp, %ebp subl $24, %esp andl $-16, %esp movl $0, %eax subl %eax, %esp movl $0, -4(%ebp) .L2: cmpl $9, -4(%ebp) jle .L5 jmp .L3 .L5: movl -4(%ebp), %eax movl %eax, 4(%esp) movl $.LC0, (%esp) call printf leal -4(%ebp), %eax incl (%eax) jmp .L2 .L3: movl $0, -4(%ebp) .L6: cmpl $9, -4(%ebp) jg .L7 movl -4(%ebp), %eax movl %eax, 4(%esp) movl $.LC0, (%esp) call printf leal -4(%ebp), %eax incl (%eax) jmp .L6 .L7: movl $0, %eax leave ret .size main, .-main .section .note.GNU-stack,"",@progbits .ident "GCC: (GNU) 3.3.4 (Debian)"
Note that even though the two loops are doing the same thing, the structure is different. The first uses jle (jump if less than or equal to) to jump over the jmp that skips the body of the loop if it shouldn't be executed, while the second uses jg (jump if greater than) to skip it directly, which is closer to what the C code says to do. This is not unusual; for built-in control structures the compiler will often build odd-looking implementations that still work, for subtle and mysterious reasons of its own.
We can make them even more different by turning on the optimizer, e.g. with gcc -S -O3 -funroll-loops loops.c:
.file "loops.c" .section .rodata.str1.1,"aMS",@progbits,1 .LC0: .string "%d\n" .text .p2align 4,,15 .globl main .type main, @function main: pushl %ebp xorl %ecx, %ecx movl %esp, %ebp pushl %ebx subl $20, %esp movl $2, %ebx andl $-16, %esp movl %ecx, 4(%esp) movl $.LC0, (%esp) call printf movl $.LC0, (%esp) movl $1, %edx movl %edx, 4(%esp) call printf movl %ebx, 4(%esp) movl $5, %ebx movl $.LC0, (%esp) call printf movl $.LC0, (%esp) movl $3, %ecx movl %ecx, 4(%esp) call printf movl $.LC0, (%esp) movl $4, %edx movl %edx, 4(%esp) call printf movl %ebx, 4(%esp) xorl %ebx, %ebx movl $.LC0, (%esp) call printf movl $.LC0, (%esp) movl $6, %ecx movl %ecx, 4(%esp) call printf movl $.LC0, (%esp) movl $7, %edx movl %edx, 4(%esp) call printf movl $.LC0, (%esp) movl $8, %eax movl %eax, 4(%esp) call printf movl $.LC0, (%esp) movl $9, %edx movl %edx, 4(%esp) .p2align 4,,15 .L31: call printf .L7: cmpl $9, %ebx jg .L8 movl %ebx, 4(%esp) incl %ebx movl $.LC0, (%esp) jmp .L31 .L8: movl -4(%ebp), %ebx xorl %eax, %eax leave ret .size main, .-main .section .note.GNU-stack,"",@progbits .ident "GCC: (GNU) 3.3.4 (Debian)"
Now the first loop is gone, replaced by ten separate calls to printf. Some of the arguments are loaded up in odd places; for example, 2 and 5 are stored in their registers well before they are used. Again, the compiler moves in mysterious ways, but guarantees that the resulting code will do what you asked. The second loop is left pretty much the same as it was, since the C compiler doesn't have enough information to recognize it as a loop.
The moral of the story: if you write your code using standard idioms, it's more likely that the C compiler will understand what you want and be able to speed it up for you. The odds are that using a non-standard idiom will cost you more in time by confusing the compiler than it will gain you in slight code improvements.