Featured image of post Compile C++ Program

Compile C++ Program

Understanding how a C++ program goes from source code to an executable is fundamental for writing efficient, portable, and well-debugged code. Yet, the compilation process often feels like a black box to many developers.

In this post, I’ll break down the C++ build pipeline step-by-step — from preprocessing and compilation to linking and execution. Whether you’re revisiting the basics or deepening your systems-level knowledge, this guide will help demystify what actually happens when you hit “build”.


Initial Program

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
// square.cpp

#define INPUT 5

int square(int n) {
    return n * n;
}

// Comment: This code is used for experiment
int main() {
    return square(INPUT);
}

Step 1: Preprocessing

  • Purpose

    • Preprocessor expands macros (#define INPUT 5)
    • It would also process: #include headers, #ifdef, #pragma, etc.
    • Comments are removed
  • Run clang++ -E square.cpp -o square.i

Result

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
// square.i
# 1 "square.cpp"
# 1 "<built-in>" 1
# 1 "<built-in>" 3
# 384 "<built-in>" 3
# 1 "<command line>" 1
# 1 "<built-in>" 2
# 1 "square.cpp" 2



int square(int n) {
    return n * n;
}

int main() {
    return square(5);
}

Step 2: Compile to Assembly

  • Purpose:

    • Compiler translates C++ code into assembly code (human-readable, low-level instructions).
    • This is architecture-specific (e.g., x86_64 or ARM).
  • Run clang++ -S square.i -o square.s

Result

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
	.section	__TEXT,__text,regular,pure_instructions
	.build_version macos, 14, 0	sdk_version 14, 2
	.globl	__Z6squarei                     ## -- Begin function _Z6squarei
	.p2align	4, 0x90
__Z6squarei:                            ## @_Z6squarei
	.cfi_startproc
## %bb.0:
	pushq	%rbp
	.cfi_def_cfa_offset 16
	.cfi_offset %rbp, -16
	movq	%rsp, %rbp
	.cfi_def_cfa_register %rbp
	movl	%edi, -4(%rbp)
	movl	-4(%rbp), %eax
	imull	-4(%rbp), %eax
	popq	%rbp
	retq
	.cfi_endproc
                                        ## -- End function
	.globl	_main                           ## -- Begin function main
	.p2align	4, 0x90
_main:                                  ## @main
	.cfi_startproc
## %bb.0:
	pushq	%rbp
	.cfi_def_cfa_offset 16
	.cfi_offset %rbp, -16
	movq	%rsp, %rbp
	.cfi_def_cfa_register %rbp
	subq	$16, %rsp
	movl	$0, -4(%rbp)
	movl	$5, %edi
	callq	__Z6squarei
	addq	$16, %rsp
	popq	%rbp
	retq
	.cfi_endproc
                                        ## -- End function
.subsections_via_symbols
  • You’ll now have a file square.s, which contains assembly instructions.

Step 3: Assemble to Object Code

  • Purpose:

    • The assembler turns the assembly code into binary machine code
    • This is stored in an object file (.o), which can’t run on its own
  • Run clang++ -c square.s -o square.o

  • Disassemble with objdump -SC square.o > square.lst to see what’s going on with square.o since we cannot open a binary file

Result

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

square.o:	file format mach-o 64-bit x86-64

Disassembly of section __TEXT,__text:

0000000000000000 <square(int)>:
       0: 55                           	pushq	%rbp
       1: 48 89 e5                     	movq	%rsp, %rbp
       4: 89 7d fc                     	movl	%edi, -4(%rbp)
       7: 8b 45 fc                     	movl	-4(%rbp), %eax
       a: 0f af 45 fc                  	imull	-4(%rbp), %eax
       e: 5d                           	popq	%rbp
       f: c3                           	retq

0000000000000010 <_main>:
      10: 55                           	pushq	%rbp
      11: 48 89 e5                     	movq	%rsp, %rbp
      14: 48 83 ec 10                  	subq	$16, %rsp
      18: c7 45 fc 00 00 00 00         	movl	$0, -4(%rbp)
      1f: bf 05 00 00 00               	movl	$5, %edi
      24: e8 00 00 00 00               	callq	0x29 <_main+0x19>
      29: 48 83 c4 10                  	addq	$16, %rsp
      2d: 5d                           	popq	%rbp
      2e: c3                           	retq
  • Purpose:

    • The linker takes your object file, startup/runtime code (such as _start), and any required standard library functions (e.g., std::cout if used), and links them together into a single executable.
  • Run clang++ square.o -o square

Result

1
2
3
(base) apple@Macbook Compilation % ./square
echo $?
25
Built with Hugo
Theme Stack designed by Jimmy