-
Notifications
You must be signed in to change notification settings - Fork 0
embedded c
- (Embedded) C/C++ cheat sheet
- RTOS
For peripheral access use volatile
for register definitions in order to assure the compiler generates the access
correctly.
Scheme:
(dest)src
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
: castsconst
+volatile
away- No overhead (memory/run-time) -> compile time
-
reinterpret_cast
: complete free address-based cast, casts of function pointers, data, memory mangaement, ...
Prefer forward declaration over includes
-
*a
- In declarations: "pointer to
a
" - In expressions: "contents of
a
"
- In declarations: "pointer to
-
&a
- In declarations: "reference to
a
" - In expressions: "address of
a
"
- In declarations: "reference to
Pass by reference (to avoid copying big objects) but assure that it will not be changed.
void sort(const vector<int>& a);
// 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
- Set pointer to
nullptr
/NULL
after
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 tonew
)
// 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
withoutdelete
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();
-> nodelete
possible! -> deallocate + call destructor manually
#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 myStruct {
unsigned long DATA;
unsigned long FLAG;
};
Declare a struct
variable named var
:
struct myStruct var;
Use struct myStruct
as a type:
-
typedef struct myStruct UART_TypeDef
("defineUART_TypeDef
as the type name forstruct myStruct
") -
typedef struct myStruct { ... } UART_TypeDef;
(same as above)
Same as anonymous struct
:
typedef struct {
<...>
} UART_TypeDef;
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;
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 exampleextern "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.
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
#include "Counter.h"
void count() { /* ... */ } // global count()
int main(void) {
using SomeCounter::count;
// ...
aCounter.count(); // count() of SomeCounter
::count(); // global count()
}
namespace sc = SCout; // Make SCout available under a different name
sc::print()
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
}
No additional run-time + memory costs -> handling during compile time.
- 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 notmyFunc()
. - Best practice: 1 actual class, 1-n interfaces
Make constructor protected
if class is abstract -> only callable from derived class (using delete
in this case is not useful)
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
}
- 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).- Note: You have to call all constructors of all base classes manually in the derived classes constructor (otherwise the default constructor will be called):
- 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() {}
}
template <class T>
T func(T par) {
// ...
}
-
class
==typename
// 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
// ...
}
<!–– 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 ––>
This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License *.
Code (snippets) are licensed under a MIT License *.
* Unless stated otherwise