Skip to content

embedded c

Marcel Schmalzl edited this page Jun 12, 2024 · 9 revisions

(Embedded) C/C++ cheat sheet


Types

For peripheral access use volatile for register definitions in order to assure the compiler generates the access correctly.

Casting

C-style cast

Scheme:

(dest)src

New style casts

Scheme:

x_cast<dest>(src)
  • static_cast: Most cases
    • No overhead (memory/run-time) -> compile time
  • dynamic_cast: virtual functions -> safe down-cast
    • Inheritance and using virtual functions must be enabled/available
    • ~+ 2kB
  • dynamic_pointer_cast: for smart pointers (dynamic cast does not work here)
  • const_cast: casts const + volatile away
    • No overhead (memory/run-time) -> compile time
  • reinterpret_cast: complete free address-based cast, casts of function pointers, data, memory mangaement, ...

Forward declarations

Performance

Prefer forward declaration over includes

Pointer & references

  • *a
    • In declarations: "pointer to a"
    • In expressions: "contents of a"
  • &a
    • In declarations: "reference to a"
    • In expressions: "address of a"

const references

Pass by reference (to avoid copying big objects) but assure that it will not be changed.

void sort(const vector<int>& a);

Function Objects and Callbacks

std::function and std::bind

// std::function can store, copy, and invoke any callable (func, lambda, expression, bind expression, func obj, ...)
#include <functional>
void someFunc(int num, int b) { /* ... */ }
std::function<void(int, int)> func = someFunc;  // Assign function to std::function object

func = { /* some lambda */ };                   // redefine std::function with constexpr
func(10, 5);


// std::bind creates a new function object that can adapt its function parameters (usually you bind one or more arguments to placeholders)
auto func = std::bind(someFunc, std::placeholders::_1, 5);      // First argument (-> placeholder) still as argument but second we bind to `5`
func(10);       // num = 10

Memory management

  • Set pointer to nullptr/NULL after

malloc & free

MyObj* myObjPtr = (MyObj*)malloc(sizeof(MyObj));
// 
  • malloc() returns non-typed pointer (void*) -> type cast to typed pointer
  • Memory allocated via malloc() is not initialised (in contrast to new)

new & delete

// Typically you would not do one try-catch block per `new` but place it wisely where you can handle the error
try {
    MyObj* myObjPtr = new MyObj;
    // ...
    myObjPtr->foo();
}
catch(std::bad_alloc& e) {                      // Check if new was successfull
    // Handle error
}
myObjPtr = nullptr;

// or (without exception handling)
MyObj* myObjPtr = new (std::nothrow) MyObj;     // Returns a `nullptr`/`NULL` if not successfull
if (!myObjPtr) {                                // or better: `if(myObjPtr == nullptr)`
    // Handle error
}

myObjPtr = nullptr;
delete myObjPtr;                                // Safe on `nullptr`
  • new returns typed pointer
  • Constructor gets automatically called (-> memory is initialised)
  • new without delete leads to memory fragmentation
  • Embedded: pre-allocate all (via new) during boot -> Better run-time performance
  • "placement new" (#inlude <new>): Store object at arbitrary position (register, heap, ...) -> Ptr* objPtr = new(ptr) Obj(); //... objPtr->~Obj(); -> no delete possible! -> deallocate + call destructor manually

Arrays

#include <cstring>                              // strlen()
#include <string>                               // std::char_traits<char>::length()

const char* cStr = "blub";
char* charArray = new char[strlen(cStr) + 1];   // C str + `\0` OR `std::char_traits<char>::length(cStr)` -> C++
int* intArray = new int [4];                    // int array with 
delete[] charArray;                             // `[]` otherwise only the first Element gets deleted
delete[] intArray;

struct

Define a struct:

struct myStruct {
    unsigned long DATA;
    unsigned long FLAG;
};

Declare a struct variable named var:

struct myStruct var;

Use struct myStruct as a type:

  1. typedef struct myStruct UART_TypeDef ("define UART_TypeDef as the type name for struct myStruct")
  2. typedef struct myStruct { ... } UART_TypeDef; (same as above)

Same as anonymous struct:

typedef struct { 
    <...>
} UART_TypeDef;

Access a struct

Through a pointer (dot operator):

UART_TypeDef *ptr_uart

// Assign an address of a struct to the pointer
// &addr = 0x40000000 (struct named `addr` at add address 0x40000000)
ptr_uart = &addr;
// or
ptr_uart = 0x40000000;

// Assign a value
ptr_uart.DATA = 40;

Directly (arrow/membership operator):

// Assign address to pointer explicitely
#define UART0 ((UART_TypeDef *) 0x40000000)

// Assign a value
UART0->DATA = 40;

extern

Tells the compiler that the function or variable is defined somewhere else (=> external linkage). Thus, extern extends the visibility to a global scope of those variables/functions.

// aVar is global now
extern int aVar;

extern "C" & extern "C++": Means that C/C++ linkage will be used.

  • extern "C": do not mangle names in C++ code (when omitted the ISR possibly cannot find the compiled function name due to name mangling (for example extern "C" void SystemInit(void);)
  • `extern "C++": do name mangling in C code (use C++ features in C code)
extern "C" {
// Use C linkage for these functions
void func1();
void func2();
}

// or

extern "C" void func3();

// or
// To use C++ features (namespaces) in C code
extern "C++" void func4(const std::string &);

Due to the possibility of overloading functions in C++, C++ modifies the function names (adds argument type information in the name for linkage) during the compilation process to keep function names unique. This is not done with C code.

Linkage

Weak linkage

Say you want to create some kind of framework containing some stubs. You want the user to later define this function without modifying the code of your framework (obviously).

weak_func.c:

__WEAK int func(){
    return 1;
}

This function is called in main.c:

int func();

int main(){
    func();
    while(1);
}

The user adds in a new file his implementation of the function ("strong" function) - so he "overwrites" the stub:

func.c:

int func(){
// user implementation
return aValue;
}

Normally you would get a linker error since you have defined func() multiple times. __WEAK prevents that by throwing away the as __WEAK defined function.

-> For GCC it is __attribute__((weak)) instead of __WEAK

Namespaces

Global and local scope

#include "Counter.h"

void count() { /* ... */ }          // global count()
int main(void) {
    using SomeCounter::count;
    // ...
    aCounter.count();               // count() of SomeCounter
    ::count();                      // global count()
}

Aliases

namespace sc = SCout;               // Make SCout available under a different name

sc::print()

Annonymous

Annonymous namespaces act like a static keyword within its scope.

// blah.h
namespace {
    int x = 42;                     // Only visible in `blah.h`
}
int askMe() { return x; }           // Access to `x` since it is within the same compilation unit

// main.cpp
#include "blah.h"

int main(void) {
    int answer = askMe();           // answer = 42
}

Performance

No additional run-time + memory costs -> handling during compile time.

Inheritance

  • Inheritance is mostly not the way to go for code reuse
  • Composition is for code reuse, inheritance for flexibility
  • Class containing one or more pure virtual operatorations are not instantiable
  • Diamond (A<-(B C)<-D): If B and C implement the same method you have to use the scope operator to access it (otherwise compiler error): B::myFunc(), C::myFunc(), but not myFunc().
  • Best practice: 1 actual class, 1-n interfaces

Constructors

Make constructor protected if class is abstract -> only callable from derived class (using delete in this case is not useful)

Destructors

class Blah {
    public:
        Blah() {}
        ~Blah() = delete;           // Memory reductions without destructor:
                                    // ~ 44 Bytes Code, ~ 12 Bytes RO

        virtual void func() = 0;    // Pure virtual (=0)
}

*Destructor in derived classes*: Compiler only sees the base class pointer and, hence, calls only the base class destructor. **Solution: make destructor `virtual` (-> `virtual ~Blah() { /* ... */ }`)** => **If using virtual *methods* in your base class** (this is optional for derived classes) **always make the destructor virtual too**.

### `private` inheritance
Alternative form of aggregation.

```c++
class Derived: private Base {
    public:
        // ...
        using Base::aFunc();        // Make privately inherited function publicly available
}

virtual inheritance

  • Only memory for a base class object is allocated
  • Each instance of two derived classes share the object they inherited from (= base class, only one object). Non-virtual would result in independent base class attributes and methods (-> base class = two objects).
  • Solves the problem if you inherit from multiple classes which have the same base class (ambiguity since you two times the "base base class" attributes and methods)
class DerivedClass : virtual public BaseClass {
    public:
        DerivedClass() : BaseClass() {}
}

Templates

template <class T>
T func(T par) {
    // ...
}
  • class == typename

Template specialisation

// A generic templated function
template <class T> 
void foo(T bar) { 
	// ...
} 

// Template specialisation
template <> 
void foo<char>(char bar) {          // foo's implementation is different for `char`s
	// ...
} 

Variadic templates

<!–– TODO: Add efficiency (run-time + memory) examples TODO: Add exception handling TODO: Add + complete memory management TODO: Add dyn. polymorphism + RTTI TODO: Add + extend templates ––>

RTOS

Clone this wiki locally