Cpp Coding Guide

User Manual:

Open the PDF directly: View PDF PDF.
Page Count: 32

DownloadCpp Coding Guide
Open PDF In BrowserView PDF
Member of the P ICANOL GROUP

µ C & DSP T EAM

C++ Coding Guide

Jens Jonckheere

September 30, 2018

Writing clean code is what you must do in order to call yourself a professional.
There is no reasonable excuse for doing anything less than your best.
– Robert C. Martin [5]

Contents

Foreword

v

1

Coding Style

1

1.1

Formatting . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

1

1.1.1

Module Files . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

1

1.1.2

Class Files . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

2

1.1.3

Order of Includes [1] . . . . . . . . . . . . . . . . . . . . . . . . . . . .

3

1.1.4

Functions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

3

1.1.5

If statements . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

4

1.1.6

Switch statements

. . . . . . . . . . . . . . . . . . . . . . . . . . . . .

4

1.1.7

Loops . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

5

1.1.8

Array Initialization . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

5

1.1.9

Preprocessor Directives . . . . . . . . . . . . . . . . . . . . . . . . . . .

6

Initialization . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

6

1.2.1

Fundamental Types . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

6

1.2.2

Arrays . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

7

1.2.3

Classes and Structs . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

7

1.2.4

Classes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

8

1.2.5

Templates . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

8

Naming Conventions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

8

1.3.1

Arrays . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

8

1.3.2

Enums . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

8

Miscellaneous . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

9

1.2

1.3

1.4

ii

2

3

Clean Code

10

2.1

Performance and Safety . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

10

2.1.1

Comparisons . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

10

2.2

Variables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

11

2.3

Functions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

11

2.4

Classes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

12

2.4.1

Small . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

12

2.4.2

Cohesion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

12

2.5

Refactoring . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

12

2.6

State-Machines . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

12

2.7

Horizontal Alignment . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

12

2.8

Vertical Whitespace . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

12

Essential C ++ Knowledge

13

3.1

Type Conversion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

13

3.1.1

Implicit Conversion . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

13

3.1.2

Explicit Conversion . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

14

Initialization . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

14

3.2.1

Fundamental Types . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

14

3.2.2

Arrays . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

15

3.2.3

Classes and Structs . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

15

Classes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

17

3.2

3.3
4

Multi-Threading

18

4.1

Instruction Reordering . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

18

4.2

Statically Allocated Memory . . . . . . . . . . . . . . . . . . . . . . . . . . . .

19

4.3

Inter-Task Communication . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

19

4.4

Protecting Shared Resources . . . . . . . . . . . . . . . . . . . . . . . . . . . .

19

4.5

Atomics . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

19

4.6

Thread Safety . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

20

4.7

Reentrancy [3, Reentrancy (computing)] . . . . . . . . . . . . . . . . . . . . . .

20

4.8

Usage of Modules . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

20

5

Debugging

21

6

Test-Driven Development

22

6.1

22

Benefits of TDD [4, p. 32] . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
iii

7

Design Patterns

23

7.1

Single-Instance Module . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

23

7.2

Multiple-Instance Module . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

23

8

Code Smells

24

9

TouchGFX

25

Bibliography

26

iv

Foreword
This document presents a common coding style for our team. It also serves as a collection of
important programming concepts we all should know.

v

Chapter 1

Coding Style
1.1

Formatting

1.1.1

Module Files

A module is a self-contained part of a system that has a well-defined interface. Modules normally
coincide with compilation units.
1.1.1.1
1
2

Header

# i f n d e f MODULE_H
# d e f i n e MODULE_H

3
4
5

/ / includes
/ / forward d e c l a r a t i o n s

6
7
8

namespace o p t i o n a l _ n a m e s p a c e
{

9
10
11
12
13

//
//
//
//

exported
exported
exported
exported

typedefs
global variable declarations
function declarations
inline function definitions

14
15
16

} / / namespace o p t i o n a l _ n a m e s p a c e
# e n d i f / / MODULE_H

Listing 1.1: Module header format

1.1.1.2
1
2
3
4

//
//
//
//

Source

includes
forward d e c l a r a t i o n s
using d i r e c t i v e s
using d e c l a r a t i o n s

5

1

6
7
8
9

/ / typedefs
/ / s t a t i c function declarations
/ / s t a t i c global variable definitions
/ / s t a t i c function definitions

10
11
12

namespace o p t i o n a l _ n a m e s p a c e
{

13
14
15

/ / exported global variable d e f i n i t i o n s
/ / exported function d e f i n i t i o n s

16
17

} / / namespace o p t i o n a l _ n a m e s p a c e

Listing 1.2: Module source format

1.1.2

Class Files

Every class should have its own header and source file. The files give a convenient overview of all
the used classes. In contrast: having to scroll trough endless files to find class definitions is not so
convenient.
1.1.2.1
1
2

Header

# i f n d e f CLASS_H
# d e f i n e CLASS_H

3
4
5

/ / includes
/ / forward d e c l a r a t i o n s

6
7
8

namespace o p t i o n a l _ n a m e s p a c e
{

9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

/ / c l a s s documentation
class Class
{
public :
/ / public typedefs
/ / p u b l i c s t a t i c member v a r i a b l e s
/ / public s t a t i c functions
/ / p u b l i c non s t a t i c member v a r i a b l e s
/ / p u b l i c s p e c i a l member f u n c t i o n s
/ / public pure v i r t u a l f u n c t i o n s
/ / public virtual functions
/ / public v i r t u a l overridden functions
/ / public virtual final functions
/ / p u b l i c non s t a t i c member f u n c t i o n s

24
25
26

protected :
/ / see public

27
28
29

private :
/ / see public

30
31

// friend declarations

2

32

}

33
34
35

} / / namespace o p t i o n a l _ n a m e s p a c e
# e n d i f / / CLASS_H

Listing 1.3: Class header format

1.1.2.2
1
2
3
4

//
//
//
//

Source

includes
forward d e c l a r a t i o n s
using d i r e c t i v e s
using d e c l a r a t i o n s

5
6
7

namespace o p t i o n a l _ n a m e s p a c e
{

8
9
10
11
12
13
14
15

/ / s t a t i c member v a r i a b l e s
/ / p u b l i c s t a t i c member f u n c t i o n s
/ / s p e c i a l member f u n c t i o n s
// virtual functions
/ / v i r t u a l overridden functions
// virtual final functions
/ / non s t a t i c member f u n c t i o n s

16
17

} / / namespace o p t i o n a l _ n a m e s p a c e

Listing 1.4: Class source format

1.1.3

1.
2.
3.
4.
5.
6.
7.

Dir/File.h
A blank line
C system files
C++ system files
A blank line
Other libraries’ .h files
Your project’s .h files

1.1.4
1

Order of Includes [1]

Functions

R e t u r n T y p e FunctionName ( Type par_name1 , Type par_name2 ) ;

Listing 1.5: Function declaration format 1

1
2

R e t u r n T y p e FunctionName (
Type par_name1 , Type par_name2 , Type par_name3 ) ;

Listing 1.6: Function declaration format 2

3

1
2
3

R e t u r n T y p e FunctionName ( Type par_name1 ,
Type par_name2 ,
Type par_name3 ) ;

Listing 1.7: Function declaration format 3

1
2
3
4

R e t u r n T y p e FunctionName (
Type par_name1 ,
Type par_name2 ,
Type par_name3 ) ;

Listing 1.8: Function declaration format 4

Function definition and function call formatting is equivalent.
1.1.5

If statements

When a line is getting too long, prefer to put the logical AND and OR at the end of the line, instead
of on the next line.
1
2
3
4
5
6
7
8
9

i f ( t h i s _ o n e _ t h i n g > t h i s _ o t h e r _ t h i n g &&
a _ t h i r d _ t h i n g == a _ f o u r t h _ t h i n g )
{
// ...
}
else
{
// ...
}

Listing 1.9: If statement formatting

1.1.6

Switch statements

Don’t indent the cases.
1
2
3
4
5

switch ( var )
{
case 0:
// ...
break ;

6
7
8
9

case 1:
// ...
break ;

10
11
12
13
14

default :
// ...
break ;
}

Listing 1.10: Swich statement formatting

4

1.1.7

Loops

In a for loop, make sure the comparison is done between variables of the same type. In the
following example, i and ARRAY_LENGTH are both of type uint8_t.
1
2
3
4
5

s t a t i c c o n s t e x p r u i n t 8 _ t ARRAY_LENGTH = 1 0 ;
f o r ( u i n t 8 _ t i = 0 ; i < ARRAY_LENGTH; ++ i )
{
// ...
}

Listing 1.11: For loop formatting

1
2
3
4

w h i l e ( v a r == t r u e )
{
// ...
}

Listing 1.12: While loop formatting

1
2
3
4
5

do
{
// ...
}
w h i l e ( v a r < SOME_NUMBER) ;

Listing 1.13: Do while loop formatting

1.1.8
1

Array Initialization

u i n t 8 _ t some_numbers = { 0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 } ;

Listing 1.14: Array initialization format 1

1
2
3

const char ∗ vegetable_names = {
" c a r r o t s " , " c a u l i f l o w e r " , " peas " , " onions " , " spinach " , " w i t l o o f "
};

Listing 1.15: Array initialization format 2

1
2
3

const char ∗ fruit_names = {" apple " ,
" orange " ,
" strawberry " };

Listing 1.16: Array initialization format 3

1
2
3
4

const char ∗ array_of_long_strings = {
" I f the elements of the array are very long " ,
" we c a n p u t e v e r y e l e m e n t on a d i f f e r e n t l i n e l i k e t h i s
};

Listing 1.17: Array initialization format 4

5

1.1.9

Preprocessor Directives

Preprocessor directives are normally placed one indent to the left.
1
2
3
4
5
6
7
8
9

c l a s s MyClass
{
void Function ( )
{
# i f DEBUGGING == 1
DebugPrint ( ) ;
# endif
}
}

Listing 1.18: Preprocessor directive indentation

Indentation of preprocessor directives is allowed.
1
2
3
4
5
6
7
8
9
10
11
12
13

# i f DEBUGGING == 1
# i f d e f i n e d ( PIC18F2480 )
# d e f i n e FLASH_MEMORY_END 0x3DC0
#else
# e r r o r " Can ’ t s e t up f l a s h memory end ! "
# endif
#else
# i f d e f i n e d ( PIC18F2480 )
# d e f i n e FLASH_MEMORY_END 0 x4000
#else
# e r r o r " Can ’ t s e t up f l a s h memory end ! "
# endif
# endif

Listing 1.19: Preprocessor directive indentation

1.2
1.2.1
1
2
3
4
5

Initialization
Fundamental Types

uint8_t
uint8_t
uint8_t
uint8_t
uint8_t

uninitialized_var ;
zeroed_var1 (0) ;
zeroed_var2 = 0;
zeroed_var3 {0};
zeroed_var4 = {0};

//
//
//
//
//

default intialization
direct initialization
copy i n i t i a l i z a t i o n => p r e f e r r e d
d i r e c t −l i s t −i n i t i a l i z a t i o n
copy− l i s t − i n i t i a l i z a t i o n

Listing 1.20: Initialization of fundamental type objects

This style guide prefers copy initialization for fundamental type objects because it also works in
C, it’s what we are accustomed to.
Note that for fundamental types default initialization actually means uninitialized.
By leaving the parentheses empty, objects are value initialized. For fundamental types this means
that all bits are made zero.

6

1
2
3

uint8_t zeroed_var5 () ;
uint8_t zeroed_var6 {};
uint8_t zeroed_var7 = {};

Listing 1.21: Value initialization of fundamental type objects

1.2.2
1
2
3

Arrays

uint8_t uninitialized_array [3];
uint8_t array1 [3]{0 , 1 , 2};
u i n t 8 _ t a r r a y 2 [ 3 ] = {0 , 1 , 2};

// default initialization
/ / d i r e c t −l i s t −i n i t i a l i z a t i o n
/ / copy− l i s t − i n i t i a l i z a t i o n => p r e f e r r e d

Listing 1.22: Initialization of arrays

Default initialization of an array results in default initialization of every element.
This style guide prefers copy-list-initialization for arrays over direct-list-initialization because it
also works in C.
Array1 and array2 are initialized using aggregate initialization (a form of list initialization). The
individual array elements are initialized by copy-initialization from the initializers specified in the
braced-init-list. If the number of initializer clauses is less than the number of elements, or the
initializer list is completely empty, the remaining elements are value initialized.

1
2

uint8_t zeroed_array1 [3]{};
uint8_t zeroed_array2 [3] = {};

Listing 1.23: Value initialization of arrays

The preferred way of zeroing an array is shown in listing 3.5.

1

uint8_t zeroed_array3 [3] = {0};

Listing 1.24: Zeroing arrays

1.2.3
1.2.3.1

Classes and Structs
Aggregates

A class type is an aggregate if it has
•
•
•
•

no private or protected non-static data members;
no user-declared or inherited constructors;
no virtual, private, or protected base classes;
no virtual member functions.

Informall: a class type is an aggregate if it looks like the good old structs that we know from C.

7

1
2
3
4
5

s t r u c t GoodOldCStruct
{
uint32_t an_integer ;
float
a_float ;
}

6
7
8
9
10
11
12
13

GoodOldCStruct
GoodOldCStruct
GoodOldCStruct
GoodOldCStruct
GoodOldCStruct
GoodOldCStruct
GoodOldCStruct

uninitialized_struct
s t r u c t 1 {10 , 3 . 1 4 } ;
s t r u c t 2 = {10 , 3 . 1 4 } ;
struct3 ( struct1 ) ;
struct4 = struct1 ;
struct5 { struct1 } ;
struct6 = { struct1 };

//
//
//
//
//
//
//

default initialization
d i r e c t −l i s t −i n i t i a l i z a t i o n
copy− l i s t − i n i t i a l i z a t i o n => p r e f e r r e d
direct initialization
copy i n i t i a l i z a t i o n => p r e f e r r e d
d i r e c t −l i s t −i n i t i a l i z a t i o n
copy− l i s t − i n i t i a l i z a t i o n

Listing 1.25: Initialization of aggregates

Default initialization of an aggregate results in default initialization of every non-static member.
When using aggregate initialization (struct1 and struct2), this guide prefers copy-list-initialization
over direct-list-initialization because it also works in C. Each non-static class member, in order
of appearance in the class definition, is copy-initialized from the corresponding clause of the
initializer list. If the number of initializer clauses is less than the number of members or the
initializer list is completely empty, the remaining members are value-initialized.
When initializing from another struct (struct3, struct4, struct5 and struct6), this guide prefers copy
initialization (struct4).
The preferred way of zeroing aggregate types is shown in listing 3.7.

1

GoodOldCStruct z e r o e d _ a g g r e g a t e = {0};

Listing 1.26: Zeroing aggregates

1.2.4

Classes

1.2.5

Templates

1.3
1.3.1

Naming Conventions
Arrays

The name of an array should make it clear that it contains multiple objects, for example by making
the name a plural noun.
1.3.2

Enums

Names of enums should be a singular noun because the enum name is the type of the enum values,
e.g. RED is a value of type Color.
The enumerators are constant expressions, therefore they are all caps.

8

1
2
3
4
5
6

enum C o l o r
{
RED,
GREEN,
BLUE
}

Listing 1.27: Enum naming convention

1.4

Miscellaneous

Do not put private static member variables in the class definition (in the header file). Make them
static global variables in the .cpp file.
Use enum typedefs to give related constants a specific type.
An RTOS is only needed if there are long tasks. Long tasks can block short tasks, and so they can
make the short tasks feel unresponsive.
When referring to the number of elements in a collection, prefer the term count over size.
Prefer #if over #ifdef.
Functions should only do what their name says they do.
Namespaces should start with a small letter to distinguish them from classes.
https://gist.github.com/lefticus/10191322

9

Chapter 2

Clean Code

Figure 2.1: The only valid measurement of code quality: WTFs / minute [5]

This chapter contains guidelines for writing clean code. I made a separate chapter for this because
these guidelines are universal, it does not only apply to PsiControl style..

2.1
2.1.1

Performance and Safety
Comparisons

Comparisons should be done on variables of the same type. The usual arithmetic conversions (see
3.1.1.2) can convert an int to an unsigned int, hereby turning small negative integers into large
positive integers.

1
2

# i n c l u d e < s t d i o . h>
# i n c l u d e < s t d i n t . h>

3
4

s t a t i c c o n s t e x p r u i n t 8 _ t ARRAY_LENGTH = 5 ;

5
6

i n t main ( )

10

7

{
i n t 3 2 _ t i n t _ a r r a y [ARRAY_LENGTH] = { 0 , 1 , −5, 6 , 9 } ;

8
9

f o r ( u i n t 8 _ t i = 0 ; i < ARRAY_LENGTH; ++ i )
{
uint32_t ten = 10;

10
11
12
13

const char ∗ larger_than_10 = i n t _ a r r a y [ i ] > ten ? " true " : " f a l s e " ;
p r i n t f ( "%2d > 10 = %s \ n " , i n t _ a r r a y [ i ] , l a r g e r _ t h a n _ 1 0 ) ;

14
15

}

16
17

getchar () ;

18
19

}

Listing 2.1: Comparing different types: code

Notice that i and ARRAY_LENGTH, which are compared in the for loop condition, have the same
type: uint8_t.
0
1
−5
6
9

>
>
>
>
>

10
10
10
10
10

=
=
=
=
=

false
false
true
false
false

Listing 2.2: Comparing different types: output

2.2

Variables

Starting the name of a boolean member variable with is can improve readability, e.g. this->is_initialized.

2.3

Functions

Functions should be small. They should not be longer than 20 lines. If a function does several
distinct things, place these distinct subroutines in separate functions, the added abstraction will
make the code easier to read.
The interface functions of a software module should check their arguments for valid input. These
checks shouldn’t be assertions (they freeze your program) tough, as you can’t know what arguments the calling code will use.
Prefer to enumerate a list of functions than to pass an enumerated type. It’s safer and more descriptive [4].
Starting the name of a member function returning bool with Is can improve readability, e.g.
my_module.IsInitialized().

11

2.4
2.4.1

Classes
Small

Classes should be small. Group related variables and member functions of a large class in a
separate class. Subdividing code in small classes and modules makes the structure of the software
clearer.
2.4.2

Cohesion

Cohesion is a measurement of the connection between member variables and member functions. If
all member functions use all member variables, the class has maximum cohesion. Classes should
be as cohesive as possible. A class with low cohesion should probably be split in separate classes.

2.5

Refactoring

Refactoring is the activity of changing a program’s structure without changing its behavior. The
purpose is to make less work by creating code that is easy to understand, easy to evolve, and easy
to maintain by others and ourselves.
Small messes are easy to create. Unfortunately, they are also easy to ignore. The mess will never
be easier to clean up than right after, ahem, you make it. Clean the mess while it’s fresh.

2.6

State-Machines

Functions for which the output does not solely depend on the input, depend on a state. Implicit
states, e.g. using bool variables, can make the code complex. Prefer to make the states explicit by
defining an enum with the different states and implementing a state machine.
State machines can be especially useful in periodic tasks and TouchGfx handleTickEvent() functions.

2.7

Horizontal Alignment

Don’t over-use horizontal alignment. One noteworthy case where horizontal alignment benefits
readability is in the definition of enums.

2.8

Vertical Whitespace

Use blank lines sparingly. Blank lines can be used to visually group related lines of code. Do not
put a blank line after every line of code if you think that all the lines are unrelated!

12

Chapter 3

Essential C ++ Knowledge
3.1

Type Conversion

3.1.1

Implicit Conversion

3.1.1.1

Integral Promotion [2, Implicit conversions]

Arithmetic operators do not accept types smaller than int as arguments, and integral promotions
are automatically applied after lvalue-to-rvalue conversion 1 , if applicable. This conversion always
preserves the value.
The following implicit conversions are classified as integral promotions:
• signed char or signed short can be converted to int;
• unsigned char or unsigned short can be converted to int if it can hold its entire value range,
and unsigned int otherwise;
• char can be converted to int or unsigned int depending on the underlying type: signed char or
unsigned char (see above);
• wchar_t, char16_t, and char32_t can be converted to the first type from the following list able
to hold their entire value range: int, unsigned int, long, unsigned long, long long, unsigned
long long;
• an unscoped enumeration type whose underlying type is not fixed can be converted to the
first type from the following list able to hold their entire value range: int, unsigned int, long,
unsigned long, long long, or unsigned long long, extended integer types (in size order, signed
given preference over unsigned). If the value range is greater, no integral promotions apply;
• an unscoped enumeration type whose underlying type is fixed can be converted to its underlying type, and, if the underlying type is also subject to integral promotion, to the promoted
underlying type. Conversion to the unpromoted underlying type is better for the purposes of
overload resolution;
• a bit field type can be converted to int if it can represent entire value range of the bit field,
otherwise to unsigned int if it can represent entire value range of the bit field, otherwise no
integral promotions apply;
• the type bool can be converted to int with the value false becoming 0 and true becoming 1.
1 lvalue-to-rvalue

conversion models the act of reading a value from a memory location into a CPU register.

13

3.1.1.2

Usual Arithmetic Conversions [2, Arithmetic operators]

If the operand passed to an arithmetic operator is integral or unscoped enumeration type, then
before any other action (but after lvalue-to-rvalue conversion, if applicable), the operand undergoes
integral promotion. If an operand has array or function type, array-to-pointer and function-topointer conversions are applied.
For the binary operators (except shifts), if the promoted operands have different types, additional
set of implicit conversions is applied, known as usual arithmetic conversions with the goal to
produce the common type (also accessible via the std::common_type type trait).
• If either operand has scoped enumeration type, no conversion is performed: the other operand
and the return type must have the same type.
• Otherwise, if either operand is long double, the other operand is converted to long double.
• Otherwise, if either operand is double, the other operand is converted to double.
• Otherwise, if either operand is float, the other operand is converted to float.
• Otherwise, the operand has integer type (because bool, char, char16_t, char32_t, wchar_t, and
unscoped enumeration were promoted at this point) and integral conversions are applied to
produce the common type, as follows:
– If both operands are signed or both are unsigned, the operand with lesser conversion rank
is converted to the operand with the greater integer conversion rank.
– Otherwise, if the unsigned operand’s conversion rank is greater or equal to the conversion
rank of the signed operand, the signed operand is converted to the unsigned operand’s type.
– Otherwise, if the signed operand’s type can represent all values of the unsigned operand,
the unsigned operand is converted to the signed operand’s type.
– Otherwise, both operands are converted to the unsigned counterpart of the signed operand’s
type.
The conversion rank above increases in order bool, signed char, short, int, long, long long. The
rank of any unsigned type is equal to the rank of the corresponding signed type. The rank of char is
equal to the rank of signed char and unsigned char. The ranks of char16_t, char32_t, and wchar_t
are equal to the ranks of their underlying types.
3.1.2

3.2
3.2.1
1
2
3
4
5

Explicit Conversion

Initialization
Fundamental Types

uint8_t
uint8_t
uint8_t
uint8_t
uint8_t

uninitialized_var ;
zeroed_var1 (0) ;
zeroed_var2 = 0;
zeroed_var3 {0};
zeroed_var4 = {0};

//
//
//
//
//

default intialization
direct initialization
copy i n i t i a l i z a t i o n => p r e f e r r e d
d i r e c t −l i s t −i n i t i a l i z a t i o n
copy− l i s t − i n i t i a l i z a t i o n

Listing 3.1: Initialization of fundamental type objects

Default initialization of a fundamental types actually leaves the object uninitialized.

14

This style guide prefers copy initialization for fundamental type objects because it also works in
C, it’s what we are accustomed to.
By leaving the parentheses empty, objects are value initialized. For fundamental types this means
that all bits are made zero.

1
2
3

uint8_t zeroed_var5 () ;
uint8_t zeroed_var6 {};
uint8_t zeroed_var7 = {};

Listing 3.2: Value initialization of fundamental type objects

3.2.2
1
2
3

Arrays

uint8_t uninitialized_array [3];
uint8_t array1 [3]{0 , 1 , 2};
u i n t 8 _ t a r r a y 2 [ 3 ] = {0 , 1 , 2};

// default initialization
/ / d i r e c t −l i s t −i n i t i a l i z a t i o n
/ / copy− l i s t − i n i t i a l i z a t i o n => p r e f e r r e d

Listing 3.3: Initialization of arrays

Default initialization of an array results in default initialization of every element.
This style guide prefers copy-list-initialization for arrays over direct-list-initialization because it
also works in C.
Array1 and array2 are initialized using aggregate initialization (a form of list initialization). The
individual array elements are initialized by copy-initialization from the initializers specified in the
braced-init-list. If the number of initializer clauses is less than the number of elements, or the
initializer list is completely empty, the remaining elements are value initialized.

1
2

uint8_t zeroed_array1 [3]{};
uint8_t zeroed_array2 [3] = {};

Listing 3.4: Value initialization of arrays

The preferred way of zeroing an array is shown in listing 3.5.

1

uint8_t zeroed_array3 [3] = {0};

Listing 3.5: Zeroing arrays

3.2.3
3.2.3.1

Classes and Structs
Aggregates

A class type is an aggregate if it has
• no private or protected non-static data members;
• no user-declared or inherited constructors;
• no virtual, private, or protected base classes;
15

• no virtual member functions.
Informally: a class type is an aggregate if it looks like the good old structs that we know from C.

1
2
3
4
5

s t r u c t GoodOldCStruct
{
uint32_t an_integer ;
float
a_float ;
}

6
7
8
9
10
11
12
13

GoodOldCStruct
GoodOldCStruct
GoodOldCStruct
GoodOldCStruct
GoodOldCStruct
GoodOldCStruct
GoodOldCStruct

uninitialized_struct
s t r u c t 1 {10 , 3 . 1 4 } ;
s t r u c t 2 = {10 , 3 . 1 4 } ;
struct3 ( struct1 ) ;
struct4 = struct1 ;
struct5 { struct1 } ;
struct6 = { struct1 };

//
//
//
//
//
//
//

default initialization
d i r e c t −l i s t −i n i t i a l i z a t i o n
copy− l i s t − i n i t i a l i z a t i o n => p r e f e r r e d
direct initialization
copy i n i t i a l i z a t i o n => p r e f e r r e d
d i r e c t −l i s t −i n i t i a l i z a t i o n
copy− l i s t − i n i t i a l i z a t i o n

Listing 3.6: Initialization of aggregates

Default initialization of an aggregate results in default initialization of every non-static member.
When using aggregate initialization (struct1 and struct2), this guide prefers copy-list-initialization
over direct-list-initialization because it also works in C. Each non-static class member, in order
of appearance in the class definition, is copy-initialized from the corresponding clause of the
initializer list. If the number of initializer clauses is less than the number of members or the
initializer list is completely empty, the remaining members are value-initialized.
When initializing from another struct object (struct3, struct4, struct5 and struct6), this guide
prefers copy initialization (struct4).
The preferred way of zeroing aggregate types is shown in listing 3.7.

1

GoodOldCStruct z e r o e d _ a g g r e g a t e = {0};

Listing 3.7: Zeroing aggregates

3.2.3.2
1
2
3
4
5

Non-Aggregate Classes

class Rectangle
{
public :
R e c t a n g l e ( i n t 3 2 _ t width , i n t 3 2 _ t h e i g h t ) ;
i n t Area ( v o i d ) ;

6
7
8
9
10

private :
i n t 3 2 _ t width
int32_t height ;
};

11
12
13
14
15

Rectangle
Rectangle
Rectangle
Rectangle

uninitialized_rect ; // default initialization
r e c t 1 (10 , 5) ;
/ / d i r e c t i n i t i a l i z a t i o n => p r e f e r r e d
r e c t 2 {10 , 5};
/ / d i r e c t −l i s t −i n i t i a l i z a t i o n
r e c t 3 = {10 , 5};
/ / copy− l i s t − i n i t i a l i z a t i o n

16

16
17
18
19

Rectangle
Rectangle
Rectangle
Rectangle

rect4 ( rect1 ) ;
rect5 = rect1 ;
rect6 { rect1 } ;
rect7 = { rect1 };

// direct initialization
/ / copy i n i t i a l i z a t i o n => p r e f e r r e d
/ / d i r e c t −l i s t −i n i t i a l i z a t i o n
/ / copy− l i s t − i n i t i a l i z a t i o n

Listing 3.8: Initialization of class objects

The uninitialized_rect is initialized using default initialization. Default initialization calls the default constructor (constructor with no parameters), but since there is no default constructor, the
struct is left uninitialized. A default constructor that inits the width and height to zero could be
added to avoid uninitialized Rectangles.
Rect1, rect2 and rect3 are all initialized using the constructor. Direct initialization is preferred
because it visually indicates that a constructor (= function) is called.
When initializing from another class object (struct4, struct5, struct6 and struct7), this guide prefers
copy initialization (struct5).

3.3

Classes

A class is a user-defined type that can be viewed as:
• a collection of variables, with functions that can access these variables;
• a collection of functions, with variables that can be accessed by all of these functions.

17

Chapter 4

Multi-Threading
When writing software modules that need to be accessed from different threads, extreme care
must be taken to ensure thread safety; otherwise data races can occur. An ISR can also be seen as
a different thread of execution, and therefore the same rules apply to ISRs.

4.1
1
2

Instruction Reordering

bool i n i t i a l i z e d ;
u i n t 3 2 _ t number ;

3
4
5
6
7
8
9
10

void thread1 ( void )
{
// ...
number = 6 6 6 ;
initialized = true ;
// ...
}

/ / i n s t r u c t i o n r e o r d e r i n g c a n do t h i s :
// initialized = true ;
/ / number = 6 6 6 ;

11
12
13
14
15
16
17
18
19
20

void thread2 ( void )
{
// ...
if ( intialized )
{
Use ( number )
}
// ...
}

Listing 4.1: Instruction reordering

You might have thought that no critical section is needed inside thread1 because a store to an
aligned variable (smaller or equal to a word) is an atomic operation. Wrong! The compiler can
reorder these instructions so that initialized becomes true before number gets initialized! And it
gets worse. The code generated by the compiler can even be executed in a different order by the
hardware. Special instructions called memory barriers exist to prohibit instruction reordering. So
the need for a critical section here is certain. Let’s hope your RTOS calls the memory barrier
instructions when handling mutexes.
For your information, the C++ standard library has 2 memory barrier functions:
18

• Compiler memory barrier: std::atomic_signal_fence(std::memory_order order)
• Hardware memory barrier: std::atomic_thread_fence(std::memory_order order)
std::atomic_signal_fence is equivalent to std::atomic_thread_fence, except no CPU instructions
for memory ordering are issued. Only reordering of the instructions by the compiler is suppressed.

4.2

Statically Allocated Memory

Unprotected statically allocated (non-const) memory is not thread safe. The handling of these
variables needs to be done in a critical section (mutex or disabling IRQs). This does not apply to
constants as their value never changes.
Examples of statically allocated memory:
• Global variables (extern and static): If the module is used in different threads, the global variables need to be handled inside a critical section.
• Static member variables: Try to avoid these. If really needed, like when implementing a singleton, put them inside a critical section.
• Static local variables: Try to avoid these. When inside a member fucntion: check if the local
variable needs to be static, maybe it can/should be a data member of the class?

4.3

Inter-Task Communication

Use a queue.

4.4

Protecting Shared Resources

Shared resources (e.g. memory) need to be protected by mutexes.

4.5

Atomics

Normally, a store to and a load from an aligned variable (smaller or equal to a word) are atomic
operations. But this is not guaranteed on all CPU architectures. Use std::atomic to make the compiler enforce atomic operations. Furthermore, the compiler will then guarantee that all instructions
before the atomic operation really are executed before the atomic operation; and that all instructions after the atomic operation really are executed after the atomic operation. This precludes the
need for a critical section.

19

4.6

Thread Safety

4.7

Reentrancy [3, Reentrancy (computing)]

4.8

Usage of Modules

Modules that are used in different threads need to make their API functions thread safe by
• using critical sections (or atomics) to protect shared data; or
• using a "module only thread", the API functions then need to send a message to the task (e.g.
with a queue) to execute something.
If modules may only be used in one thread, maybe this should be made clear in the module name?

20

Chapter 5

Debugging
Before trying to fix a bug, you should have a thorough understanding of the problem.

21

Chapter 6

Test-Driven Development
6.1

Benefits of TDD [4, p. 32]

Fewer bugs
Small and large logic errors, which can have grave consequences in the field, are found
quickly during TDD. Defects are prevented.
Less debug time
Having fewer bugs means less debug time. That’s only logical, Mr. Spock.
Fewer side effect defects
Tests capture assumptions, constraints, and illustrate representative usage. When new code
violates a constraint or assumption, the tests holler.
Documentation that does not lie
Well-structured tests become a form of executable and unambiguous documentation. A working example is worth 1,000 words.
Peace of mind
Having thoroughly tested code with a comprehensive regression test suite gives confidence.
TDD developers report better sleep patterns and fewer interrupted weekends.
Improved design
A good design is a testable design. Long functions, tight coupling, and complex conditionals
all lead to more complex and less testable code. The developer gets an early warning of design
problems if tests cannot be written for the envisioned code change. TDD is a code-rot radar.
Progress monitor
The tests keep track of exactly what is working and how much work is done. It gives you
another thing to estimate and a good definition of done.
Fun and rewarding
TDD is instant gratification for developers. Every time you code, you get something done,
and you know it works.

22

Chapter 7

Design Patterns
7.1

Single-Instance Module

An Abstract Data Type.
[4, p. 51]

7.2

Multiple-Instance Module

An Abstract Data Type.
[4, p. 51]

23

Chapter 8

Code Smells
A code smell is an indication of bad coding style which you should develop a nose for.
Reference and pointer parameters should be declared as pointing to const if the pointed-to-object
doesn’t have to be changed.
Pointers are dangerous, they should be avoided. Try to use references instead.
Do not return NULL pointers to indicate an error. They can become the source of bugs.
Built-in type member variables do not get initialized by default. It is better to initialize them
always.

24

Chapter 9

TouchGFX
Keep status variables in the presenter. Access them from widgets in the view.

25

Bibliography
[1] Google C++ Style Guide. https://google.github.io/styleguide/cppguide.html.
[2] C++ reference. https://en.cppreference.com/w/, 2018.
[3] Wikipedia. https://en.wikipedia.org/wiki/Main_Page, 2018.
[4] James W. Grenning. Test-Driven Development for Embedded C. The Pragmatic Bookshelf,
2011.
[5] Robert C. Martin. Clean Code. Prentice Hall, 2009.

26



Source Exif Data:
File Type                       : PDF
File Type Extension             : pdf
MIME Type                       : application/pdf
PDF Version                     : 1.5
Linearized                      : No
Page Count                      : 32
Page Mode                       : UseOutlines
Author                          : 
Title                           : 
Subject                         : 
Creator                         : LaTeX with hyperref package
Producer                        : pdfTeX-1.40.19
Create Date                     : 2018:09:30 08:57:29+02:00
Modify Date                     : 2018:09:30 08:57:29+02:00
Trapped                         : False
PTEX Fullbanner                 : This is MiKTeX-pdfTeX 2.9.6668 (1.40.19)
EXIF Metadata provided by EXIF.tools

Navigation menu