1. Introduction

C++ was developed as an extension to C. It adds man few features to the C language, and tis perhaps best through of as a superset of C.

Step 1: Define the problem that you would like to solve

  • I want to write a program that will …

Step 2: Determine how you are going to solve the problem Determine how we are going to solve the problem you came up with in step 1.

  • They are straightforward (not overly complicated or confusing).

Step 3: Write the program

#include <iostream>

int main()
{
    std::cout << "Here is some text.";
    return 0;
}

Step 4: Compiling your source code

  • We use a C++ compiler: MinGW/GCC, Clang, … for many different OS.
  • The C++ compiler sequentially goes through each source code file and does two important tasks:
    • checks your C++ code to make sure it follows the rules of the C++ language.
    • translates your C++ code into machine language instructions. These instructions are stored in an intermediate file called an object file.

Step 5: Linking object files and libraries

  • After the compiler has successfully finished, another program called the linker kicks in: ar,ld, …
  • Linking is to combine all the object files and produce the desired output file (.exe, .elf, .hex ..)

NOTE: Building refer to the full process of converting source code files into an executable that can be run. For complex project, build automation tools such as make, cmake are often used.

Steps 6 & 7: Testing and Debugging


2. Memory

2.1. Memory Types

  • RAM (Random Access Memory):

    • Read/write memory
    • High-speed memory used during program execution
    • Data is lost when power is turned off
  • ROM (Read-Only Memory):

    • Read-only
    • Data persists after power off
  • Flash Memory:

    • Non-volatile memory (data persists without power)
    • Slower than RAM
    • Can be erased and rewritten

2.2. Memory Layout

[High Address]  +-----------------------------+
                |            Stack            | => Local variables, Function calls, Return addresses, Grows downward:
                |                             |
                |                             |     void f(){ int val = 0};              ///< Local variable
                |                             |     ...
                |                             |
                |-----------------------------|
                v                             v
                            Free Space
                ^                             ^
                |-----------------------------|
                |             Heap            | => Dynamic memory new / malloc, Grows upward:
                |                             |
                |                             |     int val = new int;
                |                             |     delete val;
                +-----------------------------+
                |     Uninitialized Data      | => Global/static variables are not initialized or initialized with `0`:
                |           .bss              |
                |                             |     int global_val;                             ///< Global variable has initialized
                |                             |     int global_val_zr = 0;
                |                             |     void f(){ static int val};
                +-----------------------------+
                +-----------------------------+
                |      Initialized Data       | => Global/static variables are initialized with values:
                |           .data             |
                |                             |     int global_val = 100;                       ///< Global variable has initialized
                |                             |     void f(){ static int val = 0};              ///< Static variable has initialized
                +-----------------------------+
                +-----------------------------+
                |          Text/Code          | => Read only data: 
                |           .text             |     
                |                             |     printf("Hello World");                      ///< String literals
                |                             |     const uint32_t BACCATE = 115200             ///< const global variables
                |                             |     __asm__ volatile("nop"); void f(){};        ///< Program instructions & Compiled machine code
[Low Address]   +-----------------------------+

2.3. Analyze

  • List all sections:

    $ size ./build/cpp-lab # cpp
    $ arm-none-eabi-size firmware.elf #arm
    text    data     bss     dec     hex    filename
    14791     792     280   15863           ./build/cpp-lab
    12456     124    2048   14628    3924   firmware.elf
     # RAM usage = bss + data
     # Flash usage = text + data
    
    • .text: Text/Code Segment: the executable code size, including: complied function, inline, template, constants, string literals
    • .data: Initialized Data Segment
      • stores initialized global and static variables. The initial values are stored in the .bin / .hex file during compilation
      • copied from Flash to RAM at startup
      • remain alive for the entire program lifetime.
    • .bss: Uninitialized Data Segment
      • Stores global and static variables that are not initialized or initialized with 0.
      • Flash memory only stores the required size, not the actual zero values.
      • The system automatically initializes the .bss section to 0 during startup.
      • Helps reduce .bin / .hex file size and speed up start up.
    • Stack Segment:
      • Example: main() -> function_A(int x) -> function_B(int y) -> function_C(int z)

            ┌─────────────────────────┐
             main()                  
            ├─────────────────────────┤
             Return to main()        
             function_A()            
             x = 1                   
             local_a = 10            
            ├─────────────────────────┤
             Return to function_A()  
             function_B()            
             y = 2                   
             local_b = 20            
            ├─────────────────────────┤
             Return to function_B()  
             function_C()            
             z = 3                   
             local_c = 30            
             buffer[16]               <-> Stack Pointer (SP)
            └─────────────────────────┘
        

2.4. Optimize

/// @brief Use bit-fields for flags
struct Status {
    uint8_t is_ready : 1;   // 1 bit
    uint8_t is_error : 1;   // 1 bit
    uint8_t mode     : 3;   // 3 bits (0-7)
    uint8_t reserved : 3;   // 3 bits
}; // Total: 1 byte

static_assert(sizeof(Status) == 1, "Status must be 1 byte");

/// @brief Memory layout (with padding)
struct StructNormal {
    uint32_t b;     // 4 bytes
    uint8_t a;      // 1 byte
    uint8_t c;      // 1 byte
}; // Total: 8 bytes (2 bytes padding)

static_assert(sizeof(StructNormal) == 8,
              "StructNormal must be 8 bytes");

/// @brief Packed structure (no padding)
#pragma pack(push, 1)
struct StructPacked {
    uint32_t b;     // 4 bytes
    uint8_t a;      // 1 byte
    uint8_t c;      // 1 byte
}; // Total: 6 bytes
#pragma pack(pop)

static_assert(sizeof(StructPacked) == 6,
              "StructPacked must be 6 bytes");

/// @brief Use const for read-only data, stored in Flash/ROM instead of RAM
const uint8_t gamma_table[256] = {
    /* read-only lookup table */
};

3. Setup Environment, IDE (Integrated Development Environment):

We need to installing IDE or st that comes with a compiler that supports at least C++17: GCC/G++7, Clang++ 8,… Some of the options typically does:

  • Build: compiles all modified code files in the project or workspace/solution, and then links the object files into an executable. If no code files have been modified since the last build, this option does nothing.

  • Clean: removes all cached objects and executables so the next time the project is built, all files will be recompiled and a new executable produced.

  • Rebuild: does a “clean”, followed by a “build”.

  • Compile: recompiles a single code file (regardless of whether it has been cached previously). This option does not invoke the linker or produce an executable.

  • Run/start: executes the executable from a prior build. Some IDEs (e.g. Visual Studio) will invoke a “build” before doing a “run” to ensure you are running the latest version of your code. Otherwise (e.g. Code::Blocks) will just execute the prior executable.

  • Examples:

    1. Create main.c
    #include <iostream>
    #include <limits>
    
    int main(){
    	std::cout << "Hello world";
    	std::cin.get(); // get one more char from the user
    	return 0;	
    }
    
    1. Run following commands
    $ ls
    main.cpp
    
    $ g++ --version
    g++ (MinGW.org GCC-6.3.0-1) 6.3.0
    Copyright (C) 2016 Free Software Foundation, Inc.
    This is free software; see the source for copying conditions.  There is NO
    warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
    
    $ g++ main.cpp -o main.o
    
    $ g++ main.o -o main.exe
    
    $ main.exe
    Hello world
    

4. Configuring The Compiler

4.1. Build Configurations

  • It is a collection of project settings that determines how the project will be built.
  • Debug configurations: for debugging, turns off all optimizations, larger and slow, but ease
  • Release configurations: for releasing, optimized for size and performance.
  • For gcc/clang, the -0# option is used to control optimize settings.

4.2. Compiler Extensions

  • Many compilers implement their own changes to the language, often to enhance compatibility with other versions of the language
  • For gcc/clang, the -pedantic-errors option is used to disable the compiler extension.

4.3. Warning/Error Level

  • When compile the program, the compiler will check the rules of languages/compiler extension, and emit diagnostic messages.
  • For gcc users: the -Wall -Weffc++ -Wextra -Wconversion -Wsign-conversion options is used to enable the warning levels.

4.4. Language standard

  • C++98, C++03, C++11, C++14, C++17, C++20, C++23,...
  • For gcc/g++/clang, the -std=c++17 option is used to set the language standard.

5. Notes

5.1. printf/snprintf Cheat Sheet {C / C++}

Integer

Data type Specifier
int8_t / signed char %hhd
uint8_t / unsigned char %hhu
int16_t / short %hd
uint16_t / unsigned short %hu
int32_t / long %ld
uint32_t / unsigned long %lu
int64_t / long long %lld
uint64_t / unsigned long long %llu

Floating point

Data type Specifier
float %f
double %f
long double %Lf
  • %e -> scientific notation
  • %g -> auto select %f or %e

Char / String

Data type Specifier Notes
char %c single character
char* / String %s null-terminated string

Pointer / Address

Data type Specifier Notes
void* %p memory address, hex

Hex / Octal / Binary

Data type Specifier Notes
unsigned int %x / %X hexadecimal
unsigned int %o octal
Arduino only %b binary

Flags, Width, Precision

  • %-10d -> left-justify, width 10
  • %010d -> pad with zeros, width 10
  • %.2f -> 2 decimal digits
  • %*d -> dynamic width

5.2. Command Line

  • Command line arguments are optional string arguments that are passed by the operating system to the program when it launch.
  • Passing command line arguments: we simply list the command line arguments right after the executable name.
  • Using command line arguments: by using different form of main(): main(int argc, char* argv[]) / main(int argc, char** argv)
    • argc: argument count, always be at least 1, because the first argument argv[0] is always the name of the program itself.
    • argv: is where the actual argument values are stored (think: argv = argument values)
    • If an argument contains spaces, wrap it in quotes
    • Example:
      ./executable_app input.txt
      ./executable_app "hello world" 1 2 3
      

5.3. Performance

  • Things that can impact program performance:

    • CPU speed
    • Memory usage
    • Disk and file access
    • Network speed
    • Compiler optimizations
    • Algorithm efficiency
    • Too much logging/output
    • Running too many tasks/threads
    • Debug vs release build
    • TBD
  • Measuring performance:

    • Gather at least 3 test results.
    • Run the program long enough to get meaningful results (e.g. 10 seconds or more).
    • Use the same test conditions for each run.
    • Compare average execution times.
    • TBD