Search icon CANCEL
Subscription
0
Cart icon
Your Cart (0 item)
Close icon
You have no products in your basket yet
Save more on your purchases now! discount-offer-chevron-icon
Savings automatically calculated. No voucher code required.
Arrow left icon
Explore Products
Best Sellers
New Releases
Books
Videos
Audiobooks
Learning Hub
Conferences
Free Learning
Arrow right icon
Arrow up icon
GO TO TOP
Embedded Programming with Modern C++ Cookbook

You're reading from   Embedded Programming with Modern C++ Cookbook Practical recipes to help you build robust and secure embedded applications on Linux

Arrow left icon
Product type Paperback
Published in Apr 2020
Publisher Packt
ISBN-13 9781838821043
Length 412 pages
Edition 1st Edition
Languages
Tools
Arrow right icon
Author (1):
Arrow left icon
Igor Viarheichyk Igor Viarheichyk
Author Profile Icon Igor Viarheichyk
Igor Viarheichyk
Arrow right icon
View More author details
Toc

Table of Contents (17) Chapters Close

Preface 1. Fundamentals of Embedded Systems 2. Setting Up the Environment FREE CHAPTER 3. Working with Different Architectures 4. Handling Interrupts 5. Debugging, Logging, and Profiling 6. Memory Management 7. Multithreading and Synchronization 8. Communication and Serialization 9. Peripherals 10. Reducing Power Consumption 11. Time Points and Intervals 12. Error Handling and Fault Tolerance 13. Guidelines for Real-Time Systems 14. Guidelines for Safety-Critical Systems 15. Microcontroller Programming 16. Other Books You May Enjoy

Working with different architectures

Developers of desktop applications usually pay little attention to the hardware architecture. First, they often use high-level programming languages that hide these complexities at the cost of some performance drop. Second, in most cases, their code runs on x86 architecture and they often take its features for granted. For example, they may assume that the size of int is 32 bits, which is not true in many cases.

Embedded developers deal with a much wider variety of architectures. Even if they do not write code in assembly language native to the target platform, they should be aware that all C and C++ fundamental types are architecture-dependent; the standard only guarantees that int is at least 16 bits. They should also know the traits of particular architectures, such as endianness and alignment, and take into account that operations with floating point or 64-bit numbers, which are relatively cheap on x86 architecture, may be much more expensive on other architectures.

Endianness

Endianness defines the order in which bytes that represent large numerical values are stored in memory.

There are two types of endianness:

  • Big-endian: The most significant byte is stored first. The 0x01020304 32-bit value is stored at the ptr address as follows:

    Offset in memory Value
    ptr 0x01
    ptr + 1 0x02
    ptr + 2 0x03
    ptr + 3 0x04

Examples of big-endian architectures are AVR32 and Motorola 68000.

  • Little-endian: The least significant byte is stored first. The 0x01020304 32-bit value is stored at the ptr address as follows:

    Offset in memory Value
    ptr 0x04
    ptr + 1 0x03
    ptr + 2 0x02
    ptr + 3 0x01

The x86 architecture is little-endian.

  • Bi-endian: Hardware supports switchable endianness. Some examples are PowerPC, ARMv3, and the preceding examples.

Endianness is particularly essential when exchanging data with other systems. If a developer sends the 0x01020304 32-bit integer as is, it may be read as 0x04030201 if the endianness of the receiver does not match the endianness of the sender. That is why data should be serialized.

This C++ snippet can be used to determine the endianness of a system:

#include <iostream>
int main() {
union {
uint32_t i;
uint8_t c[4];
} data;
data.i = 0x01020304;
if (data.c[0] == 0x01) {
std::cout << "Big-endian" << std::endl;
} else {
std::cout << "Little-endian" << std::endl;
}
}

Alignment

Processors don't read and write data in bytes but in memory words—chunks that match their data address size. 32-bit processors work with 32-bit words, 64-bit processors with 64-bit words, and so on.

Reads and writes are most efficient when words are aligned—the data address is a multiple of the word size. For example, for 32-bit architectures, the 0x00000004 address is aligned, while 0x00000005 is unaligned.

Compilers align data automatically to achieve the most efficient data access. When it comes to structures, the result may be surprising for developers who are not aware of alignment:

 struct {

uint8_t c;

uint32_t i;

} a = {1, 1};

std::cout << sizeof(a) << std::endl;

What is the output of the preceding code snippet? The size of uint8_t is 1 and the size of  uint32_t is 4. A developer may expect that the size of the structure is the sum of the individual sizes. However, the result highly depends on the target architecture.

For x86, the result is 8. Let's add one more uint8_t field before i:

struct {

uint8_t c;

uint8_t cc;

uint32_t i;

} a = {1, 1};

std::cout << sizeof(a) << std::endl;

The result is still 8! The compiler optimizes the placement of the data fields within a structure according to alignment rules by adding padding bytes. The rules are architecture-dependent and the result may be different for other architectures. As a result, structures cannot be exchanged directly between two different systems without serializationwhich will be explained in more depth in Chapter 8, Communication and Serialization.

Besides the CPU, access data alignment is also crucial for efficient memory mapping through hardware address translation mechanisms. Modern operating systems operate 4 KB memory blocks or pages to map a process virtual address space to physical memory. Aligning data structures on 4 KB boundaries can lead to performance gain.

Fixed-width integer types

C and C++ developers often forget that the size of fundamental data types, such as char, short, or int, is architecture-dependent. To make the code portable, embedded developers often use fixed-size integer types that explicitly specify the size of a data field.

The most commonly used data types are as follows:

Width Signed Unsigned
8-bit int8_t uint8_t
16-bit int16_t uint16_t
32-bit int32_t uint32_t

 

The pointer size also depends on the architecture. Developers often need to address elements of arrays and since arrays are internally represented as pointers, the offset representation depends on the pointer size. size_t is a special data type to represent the offset and data sizes in an architecture-independent way.

lock icon The rest of the chapter is locked
Register for a free Packt account to unlock a world of extra content!
A free Packt account unlocks extra newsletters, articles, discounted offers, and much more. Start advancing your knowledge today.
Unlock this book and the full library FREE for 7 days
Get unlimited access to 7000+ expert-authored eBooks and videos courses covering every tech area you can think of
Renews at €18.99/month. Cancel anytime