Understanding Memory and CPU Interaction in a Simple C addition Program: An In-Depth Look

Ganesh Sahu
6 min readSep 5, 2024

--

Introduction

When programming,understanding how your code interacts with memory and the CPU is crucial for optimizing performance and debugging. In this post, we’ll break down a simple C program that adds two numbers, explore the various parts of memory involved, and explain how the CPU and Process Control Block (PCB) interact during execution, including the role of essential CPU registers.

The C Program

Let’s start with a basic C program that adds two numbers:

#include <stdio.h>

int add(int a, int b) {
int result; // Local variable
result = a + b; // Perform addition
return result; // Return result
}

int main() {
int sum; // Local variable
sum = add(5, 10); // Call add function
printf("Sum: %d\n", sum); // Output result
return 0;
}

Loading the Program into Memory

When a C program is executed, it goes through several stages of loading and setup. Here’s a high-level overview of the process:

  1. Compilation: The C source code is compiled into machine code by a compiler. This machine code is often referred to as the executable file.
  2. Loading: When you run the executable, the operating system’s loader is responsible for loading the program into memory. The loader places the program’s various segments into specific areas of memory, setting up the initial execution environment.
  3. Execution: Once loaded, the CPU begins executing the program from the starting address specified by the Program Counter (PC). During execution, the CPU interacts with different parts of memory to perform the tasks defined by the program.

Memory Segments Involved (User Space)

1. Text Segment

  • Description: This segment contains the compiled machine code of the program. It includes all the executable instructions, such as the code for main() and add() functions.
  • Role: The CPU fetches instructions from the text segment to execute them. This segment is typically read-only to prevent accidental modification of instructions during execution.

2. Data Segment

  • Description: The data segment is divided into two parts:
  • Initialized Data Segment: Stores global and static variables that are initialized by the programmer (e.g., int globalVar = 10;).
  • Uninitialized Data Segment (BSS): Holds global and static variables that are not explicitly initialized (e.g., int uninitializedVar;).
  • Role: These segments store variable data that is accessed during the program’s execution. The initialized data segment is populated with values from the executable file, while the BSS segment is zeroed out by the loader.

3. Stack Segment

  • Description: The stack segment is used for managing function calls and local variables. It grows and shrinks as functions are called and return.
  • Role:
  • When main() starts, a stack frame is created for it, including space for local variables and the return address.
  • When add() is called from main(), a new stack frame is created for add() with its own local variables and parameters. This frame is pushed onto the stack.
  • After add() returns, its stack frame is removed, and control returns to main().

4. Heap Segment

  • Description: The heap segment is used for dynamic memory allocation, managed by functions such as malloc() and free().
  • Role: Although our example program does not use dynamic memory, the heap is crucial for programs that require memory to be allocated and deallocated at runtime.

Process Control Block (PCB) (Kernel Space)

The Process Control Block (PCB) is a crucial data structure maintained by the operating system that contains information about the process. It includes:

  • Process State: Indicates the current state of the process (e.g., running, waiting).
  • Program Counter (PC): Holds the address of the next instruction to be executed.
  • CPU Registers: Includes general-purpose registers, stack pointers, and others.
  • Memory Management Information: Details about memory allocation and limits.
  • Process Scheduling Information: Priority and scheduling details.

CPU Registers and Their Roles

Understanding key CPU registers helps in grasping how the CPU manages and executes a program:

1. Program Counter (PC)

  • Function: Holds the address of the next instruction to be executed. It directs the CPU to the location in memory where the next instruction resides.
  • During Execution: After fetching an instruction, the PC increments to point to the next instruction. For function calls, the PC is updated to the address of the function being called.

2. Instruction Register (IR)

  • Function: Holds the current instruction being executed. Once the instruction is fetched from memory, it is loaded into the IR for decoding and execution.
  • During Execution: The CPU decodes the instruction in the IR to determine the operation and operands required.

3. Stack Pointer (SP)

  • Function: Points to the top of the stack, managing function calls, local variables, and return addresses.
  • During Execution: When a function is called, the SP is adjusted to allocate space for the function’s stack frame. After the function returns, the SP is adjusted back to its previous position.

4. Base Register (BP) or Base Pointer (BP)

  • Function: Keeps track of the base address of the current stack frame or data segment, aiding in accessing function parameters and local variables.
  • During Execution: The BP is set to the base address of the stack frame when a function is called and restored when the function returns.

5. General Purpose Registers

  • Function: Used to store intermediate values, operands, and results of arithmetic and logic operations.
  • During Execution: Values and results are temporarily held in these registers during processing.

Execution Flow with Registers: Step-by-Step

Let’s walk through the execution of our C program, focusing on how CPU registers interact with different parts of memory:

Program Start

  • Memory Setup:
  • Text Segment: The program’s instructions (e.g., main(), add()) are loaded into the text segment.
  • Stack Segment: The initial stack frame for main() is set up, with the Stack Pointer (SP) pointing to the top of the stack.
  • Base Register (BP): Initialized to the base of the main() stack frame.
  • Registers:
  • Program Counter (PC): Set to the starting address of main().

Simplified Execution Flow with Registers: Step-by-Step

Overview of how a simple C program executes, focusing on CPU registers and memory interactions:

Program Start

  • Loading: The operating system loads the program’s instructions (from the text segment) and sets up the stack for the main() function.
  • Registers:Program Counter (PC): Points to the first instruction of main().

Executing main()

  • Instruction Fetch: The CPU fetches the instruction at the address in the PC (e.g., calling add(5, 10)).
  • Registers:
  • Instruction Register (IR): Holds the current instruction for decoding.
  • PC: Updates to point to the add() function.

Calling add()

  • Memory Setup: The stack grows to include a new frame for add(), with space for parameters and local variables.
  • Registers:
  • Stack Pointer (SP): Adjusted to point to the new top of the stack.
  • Base Register (BP): Set to the base of add()'s stack frame.

Executing add()

  • Instruction Fetch and Execution: The CPU fetches and executes instructions in add(), such as adding the parameters.
  • Registers:
  • IR: Holds instructions like result = a + b.
  • General Purpose Registers: Used to temporarily hold values for the addition.
  • SP: Points to the current top of the stack.

Returning to main()

  • Memory Cleanup: The stack frame for add() is removed.
  • Registers:
  • SP: Adjusted back to the previous stack frame for main().
  • PC: Updated to continue with the instruction after add() in main().

Continuing main()

  • Instruction Fetch: The CPU fetches the next instruction in main(), such as printf("Sum: %d\n", sum);.
  • Registers:
  • IR: Holds instructions for printing the result.
  • SP: Manages the stack frame for main().

Program End

  • Memory Cleanup: The final stack frame is cleaned up as the program finishes.
  • Registers: PC: Points to the end of the program, signalling completion.

Summary

In our simple C program, various memory segments and CPU components work together to execute the code efficiently:

  • Text Segment: Holds the compiled instructions.
  • Stack Segment: Manages local variables, function parameters, and return addresses.
  • Heap Segment: Not used in this example, but crucial for dynamic memory.
  • Data Segment: Not used in this example, but important for global/static variables.

The PCB and CPU collaborate to manage the execution of the program, handling instruction fetching, memory access, and function calls. Registers such as the PC, IR, SP, and BP play critical roles in this process.

Understanding these interactions provides valuable insights into how programs are executed and how you can optimize your code for better performance and reliability.

--

--

Ganesh Sahu

Senior engineer at VMware.Passionate about building elegant solutions to complex problems.