0% found this document useful (0 votes)
86 views251 pages

Vostokov Dmitry Memory Thinking For C and C++ Windows Diagnostics

Uploaded by

Odiljon Djamalov
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
86 views251 pages

Vostokov Dmitry Memory Thinking For C and C++ Windows Diagnostics

Uploaded by

Odiljon Djamalov
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 251

Memory Thinking for C & C++

Windows Diagnostics
Slides with Descriptions Only

Dmitry Vostokov
Software Diagnostics Services

OpenTask
2

Memory Thinking for C & C++ Windows Diagnostics: Slides with Descriptions
Only

Published by OpenTask, Republic of Ireland

Copyright © 2023 by OpenTask

Copyright © 2023 by Dmitry Vostokov

Copyright © 2023 by Software Diagnostics Services

Copyright © 2023 by Dublin School of Security

All rights reserved. No part of this book may be reproduced, stored in a


retrieval system, or transmitted in any form or by any means without the
prior written permission of the publisher.

OpenTask books are available through booksellers and distributors


worldwide. For further information or comments, send requests to
[email protected].

Product and company names mentioned in this book may be trademarks of


their owners.

A CIP catalog record for this book is available from the British Library.

ISBN-13: 978-1912636730 (Paperback)

Revision 1.00 (November 2023)


3

Table of Contents

Table of Contents 3

Preface 15

About the Author 16

Introduction 17

Original Training Course Name 17

Prerequisites 18

Training Goals 19

Training Principles 20

Schedule 21

Training Idea 22

General C & C++ Aspects 23

What We Do Not Cover 25

Windows C & C++ Aspects 26

Why C & C++? 27

Which C & C++? 29

My History of C & C++ 30

C and C++ Mastery Process 32

Thought Process 33
4

Philosophy of Pointers 34

Pointer 35

Pointer Dereference 36

Many to One 37

Many to One Dereference 38

Invalid Pointer 39

Invalid Pointer Dereference 40

Wild (Dangling) Pointer 41

Pointer to Pointer 42

Pointer to Pointer Dereference 43

Naming Pointers and Entities 44

Names as Pointer Content 45

Pointers as Entities 46

Memory and Pointers 47

Mental Exercise 48

Debugger Memory Layout 49

Memory Dereference Layout 50

Names as Addresses 51

Addresses and Entities 52


5

Addresses and Structures 53

Pointers to Structures 54

Arrays 55

Arrays and Pointers to Arrays 56

Strings and Pointers to Strings 57

Basic Types 58

ASCII Characters and Pointers 59

Bytes and Pointers 60

Wide Characters and Pointers 61

Integers 62

Long Integers 63

Little-Endian System 64

Short Integers 65

Long Long Integers 66

Signed and Unsigned Integers 67

Fixed Size Integers 68

Booleans 69

Bytes 70

Size 71
6

Alignment 72

LLP64 73

Nothing and Anything 74

Automatic Type Inference 75

Entity Conversion 76

Pointer Conversion (C-Style) 77

Numeric Promotion/Conversion 78

Numeric Conversion 79

Incompatible Types 80

Forcing 81

Structures, Classes, and Objects 83

Structures 84

Access Level 85

Classes and Objects 86

Structures and Classes 87

Pointer to Structure 88

Pointer to Structure Dereference 89

Many Pointers to One Structure 90

Many to One Dereference 91


7

Invalid Pointer to Structure 92

Invalid Pointer Dereference 93

Wild (Dangling) Pointer 94

Pointer to Pointer to Structure 95

Pointer to Pointer Dereference 96

Memory and Structures 97

Addresses and Structures 98

Structure Field Access 99

Pointers to Structures 100

Pointers to Structure Fields 101

Structure Inheritance 102

Structure Slicing 103

Inheritance Access Level 105

Structures and Classes II 106

Internal Structure Alignment 107

Static Structure Fields 108

Uniform Initialization 109

Old Initialization Ways 110

New Way {} 111


8

Uniform Structure Initialization 112

Static Field Initialization 113

Macros, Types, and Synonyms 114

Macros 115

Old Way 116

New Way 117

Memory Storage 118

Overview 119

Thread Stack Frames 120

Local Variable Value Lifecycle 121

Stack Allocation Pitfalls 123

Explicit Local Allocation 125

Dynamic Allocation (C-style) 126

Dynamic Allocation (C++) 127

Memory Operators 128

Memory Expressions 129

Local Pointers (Manual) 130

In-place Allocation 132

Source Code Organisation 133


9

Logical Layer (Translation Units) 134

Physical Layer (Source Files) 135

Inter-TU Sharing 136

Classic Static TU Isolation 137

Namespace TU Isolation 138

Declaration and Definition 139

TU Definition Conflicts 140

Fine-grained TU Scope Isolation 141

Conceptual Layer (Design) 142

Incomplete Types 143

References 144

Type& vs. Type* 145

Values 146

Value Categories 147

Constant Values 148

Constant Expressions 149

Functions 150

Pointers to Functions 151

Function Pointer Types 153


10

Reading Declarations 154

Structure Function Fields 155

Structure Methods 156

Structure Methods (Inlined) 157

Structure Methods (Inheritance) 158

Structure Virtual Methods 160

Structure Pure Virtual Methods 162

Structure as Interface 163

Function Structure 164

Structure Constructors 165

Structure Copy Constructor 166

Structure Copy Assignment 167

Structure Destructor 168

Structure Destructor Hierarchy 169

Structure Virtual Destructor 170

Destructor as a Method 171

Conversion Operators 172

Parameters by Value 174

Parameters by Pointer/Reference 175


11

Parameters by Ptr/Ref to Const 176

Possible Mistake 177

Function Overloading 178

Immutable Objects 179

Static Structure Functions 180

Lambdas 181

x64 CPU Registers 182

Instructions and Registers 183

Memory and Stack Addressing 184

Memory Cell Sizes 185

Memory Load Instructions 186

Memory Store Instructions 187

Flow Instructions 188

Function Parameters 189

Struct Function Parameters 190

this 191

Function Objects vs. Lambdas 192

Captures and Closures 193

Lambdas as Parameters 195


12

Lambda Parameter Optimization 197

Lambdas as Unnamed Functions 199

std::function Lambda Parameters 201

auto Lambda Parameters 203

Lambdas as Return Values 205

Virtual Function Call 207

VTBL Memory Layout 208

VPTR and Struct Memory Layout 209

Templates: A Planck-length Introduction 210

Why Templates? 211

Reusability 212

Types of Templates 214

Types of Template Parameters 215

Type Safety 216

Flexibility 218

Metafunctions 219

Iterators as Pointers 220

Containers 221

Iterators 222
13

Constant Iterators 223

Pointers as Iterators 224

Algorithms 225

Memory Ownership 226

Pointers as Owners 227

Problems with Pointer Owners 228

Smart Pointers 229

Basic Design 230

Unique Pointers 231

Handles as Unique Pointers 232

Shared Pointers 233

RAII 234

RAII Definition 235

RAII Advantages 236

Handle RAII 237

Threads and Synchronization 238

Threads in C/C++ 239

Threads in C++ Proper 240

Synchronization Problems 241


14

Synchronization Solution 242

Resources 243

C and C++ 244

Reading Windows-based Code 245

Windows (C and C++) 247

Training (Windows C and C++) 249


15

Preface

This full-color reference book is a part of the Accelerated C & C++ for
Windows Diagnostics training course organized by Software Diagnostics
Services (www.patterndiagnostics.com). The text contains slides, brief notes
highlighting particular points, and replicated source code fragments that are
easy to copy into your favorite IDE. The book's detailed Table of Contents
makes the usual Index redundant. We hope this reference is helpful for the
following audiences:

• C and C++ developers who want to deepen their knowledge;


• Software engineers developing and maintaining products on
Windows platforms;
• Technical support, escalation, DevSecOps, cloud and site reliability
engineers dealing with complex software issues;
• Quality assurance engineers who test software on Windows
platforms;
• Security and vulnerability researchers, reverse engineers,
malware and memory forensics analysts.

If you encounter any error, please use the contact form on the Software
Diagnostics Services web site or, alternatively, via Twitter @DumpAnalysis.

Facebook group:

http://www.facebook.com/groups/dumpanalysis

LinkedIn page and group:

https://www.linkedin.com/company/software-diagnostics-institute/
https://www.linkedin.com/groups/8473045/
16

About the Author

Dmitry Vostokov is an internationally recognized


expert, speaker, educator, scientist, inventor, and
author. He founded the pattern-oriented software
diagnostics, forensics, and prognostics discipline
(Systematic Software Diagnostics) and Software
Diagnostics Institute (DA+TA: DumpAnalysis.org +
TraceAnalysis.org). Vostokov has also authored
over 50 books on software diagnostics, anomaly detection and analysis,
software and memory forensics, root cause analysis and problem solving,
memory dump analysis, debugging, software trace and log analysis, reverse
engineering, and malware analysis. He has over 25 years of experience in
software architecture, design, development, and maintenance in various
industries, including leadership, technical, and people management roles.
Dmitry also founded Syndromatix, Anolog.io, BriteTrace, DiaThings,
Logtellect, OpenTask Iterative and Incremental Publishing (OpenTask.com),
Software Diagnostics Technology and Services (former Memory Dump
Analysis Services) PatternDiagnostics.com, and Software Prognostics. In his
spare time, he presents various topics on Debugging.TV and explores
Software Narratology, its further development as Narratology of Things and
Diagnostics of Things (DoT), Software Pathology, and Quantum Software
Diagnostics. His current interest areas are theoretical software diagnostics
and its mathematical and computer science foundations, application of
formal logic, artificial intelligence, machine learning and data mining to
diagnostics and anomaly detection, software diagnostics engineering and
diagnostics-driven development, diagnostics workflow and interaction.
Recent interest areas also include cloud native computing, security,
automation, functional programming, applications of category theory to
software development and big data, and diagnostics of artificial intelligence.
17

Introduction

Original Training Course Name

Dmitry Vostokov
Software Diagnostics Services
18

Prerequisites

Prerequisites

 Development experience

and (optional)

 Basic memory dump analysis

© 2023 Software Diagnostics Services

To get most of this training, you are expected to have basic development
experience in a programming language other than C or C++ and optional
basic memory dump analysis experience. I also included the necessary x64
review for some topics.
19

Training Goals

Training Goals

 Review common fundamentals of C and C++

 Review C++ specifics

 Use WinDbg for learning C and C++ internals

 See how C and C++ knowledge is used


during diagnostics and debugging

© 2023 Software Diagnostics Services

Our primary goal is to learn C and C++ and its internals in an accelerated
fashion. First, we review common C and C++ fundamentals necessary for
software diagnostics. Then, we learn various C++ features with a focus on
memory and internals. We also see examples of how the knowledge of C
and C++ helps in diagnostics and debugging.
20

Training Principles

Training Principles

 Talk only about what I can show

 Lots of pictures

 Lots of examples

 Original content and examples

© 2023 Software Diagnostics Services

There were many training formats to consider, and I decided that the best
way is to concentrate on slides and code examples you can verify.
21

Schedule

Schedule

 std::vector<Session> sessions;

 assert(sessions.size() == 12);

 assert(sessions.capacity() > 12);

© 2023 Software Diagnostics Services

I originally planned the training to have only 5 one-hour sessions, but I


gradually extended it to 12 sessions to fit all necessary material in sufficient
detail.
22

Training Idea

Training Idea

 Reading Windows-based Code training

 Memory dump analysis training

 Reversing training

 Windows API training

© 2023 Software Diagnostics Services

For years, people asked me to resume my very old training in reading


Windows-based code (see the References part). Finally, I gave in. Attendees
of memory dump analysis and reversing training courses asked questions
related to C and C++, and I realized that they would have also benefitted if
they had this training. This training may also fill some gaps between these
courses. Finally, I recently developed the Accelerated Windows API training
course (see the References section at the end of the book), where solid
knowledge of classic C and C++ is assumed, and the current C and C++ course
may provide such knowledge.
23

General C & C++ Aspects

General C & C++ Aspects


 Philosophy of pointers  Memory and pointers
 Structures, classes, and objects  Basic types
 Promotions and conversions  Memory and structures
 Macros, types, and synonyms  Uniform initialization
 Source code organization, PImpl  Memory storage
 Pointer dereference walkthrough  References
 Functions and function pointers  Values, lvalues, rvalues
 Inheritance  Constant values and expressions
 Operators, function objects  Namespaces
 Destructors, virtual destructors  Constructors, copy, assignment
 Local stack variables and values  Virtual functions, pure methods
 Memory operators and expressions  VTBL and VPTR
 Alignment  Access levels
 Slicing  Overloading, overriding
 Iterators as pointers  Templates
 Lambdas and their internals  Memory ownership, RAII
 Threads and synchronization  Smart pointers

© 2023 Software Diagnostics Services

The general C and C++ aspects that we discuss in this course:

• Philosophy of pointers
• Structures, classes, and objects
• Promotions and conversions
• Macros, types, and synonyms
• Source code organization, PImpl
• Pointer dereference walkthrough
• Functions and function pointers
• Inheritance
• Operators, function objects
• Destructors, virtual destructors
• Local stack variables and values
• Memory operators and expressions
• Alignment
• Slicing
24

• Iterators as pointers
• Lambdas and their internals
• Threads and synchronization
• Memory and pointers
• Basic types
• Memory and structures
• Uniform initialization
• Memory storage
• References
• Values, lvalues, rvalues
• Constant values and expressions
• Namespaces
• Constructors, copy, assignment
• Virtual functions, pure methods
• VTBL and VPTR
• Access levels
• Overloading, overriding
• Templates
• Memory ownership, RAII
• Smart pointers
25

What We Do Not Cover

What We Do Not Cover*


 Enumerations
 Move constructors and assignment operators
 Deleted and default members
 Universal references
 Concepts
 Coroutines
 Modules
 Tasks
 Ranges
 Container and algorithm semantics and pragmatics
 Container allocators
 Polymorphic allocators

* We promise to include these topics in the second edition


© 2023 Software Diagnostics Services

There are some C++ topics that we did not include:

• Enumerations
• Move constructors and assignment operators
• Deleted and default members
• Universal references
• Concepts
• Coroutines
• Modules
• Tasks
• Ranges
• Container and algorithm semantics and pragmatics
• Container allocators
• Polymorphic allocators

We promise to include these topics in the second edition of this course.


26

Windows C & C++ Aspects

Windows C & C++ Aspects


 Windows-specific type aliases and macros
 Desktop application walkthrough
 Desktop application improvement
 Service walkthrough
 Command-line application walkthrough
 LLP64
 COM
 Necessary x64 disassembly
 Parameter passing
 Implicit parameter

© 2023 Software Diagnostics Services

In addition, we also discuss related Windows aspects, including:

• Windows-specific type aliases and macros


• LLP64
• Necessary x64 disassembly
• Parameter passing
• Implicit parameter
27

Why C & C++?

Why C & C++?


 Interfacing
 Malware analysis
 Vulnerability analysis and exploitation
 Reversing
 Diagnostics
 Low-level debugging
 OS Monitoring
 Memory forensics
 Crash and hang analysis
 Secure coding
 Static code analysis
 Trace and log analysis
© 2023 Software Diagnostics Services

First, why did we create this course? Even if you don’t develop in C and C++,
the knowledge of C and C++ and their internals is necessary for many
software construction and post-construction activities:

• Interfacing
• Malware analysis
• Vulnerability analysis and exploitation
• Reversing
• Diagnostics
• Low-level debugging
• OS Monitoring
• Memory forensics
• Crash and hang analysis
• Secure coding
• Static code analysis
• Trace and log analysis
28

In this training, we mostly look at C and C++ from a software diagnostics


perspective. This perspective includes memory dump analysis and, partially,
trace and log analysis. The knowledge of C and C++ is tacitly assumed in my
other courses, where most abnormal software behavior modeling exercises
are written in C and C++. Of course, there is an intersection of what we learn
with other areas.
29

Which C & C++?

Which C & C++?

 C

 C++ as a better C

 Proper C++ (legacy and modern)

 Windows specifics

© 2023 Software Diagnostics Services

Which C and C++? We look at a unified presentation approach combining all


C and C++ variants. Since this course is about diagnostics and not designing
and implementing code, we do not make distinctions. It is not possible to
cover all the differences in the short time that we have. We also describe
things as they are in Windows programming, not as they ought to be from
the latest C++ standards.
30

My History of C & C++

My History of C & C++


 C from 1987 and C++ from 1989 (Old CV)
 C++ as a better C from 1991
 Implicit design patterns in 1994-1995
 C++ as proper C++ from 2000
 Explicit design patterns in 2000
 C++98/03/STL from 2001
 Windows memory dump analysis from 2003
 […]
 C++11/14 from 2016
 C++17 from 2017
 Functional programming from 2020
 C++20 from 2023
© 2023 Software Diagnostics Services

This history slide is only about C and C++ languages. Despite many years, it
is still easy to recall when I started learning C. It was shortly after I started
my university education. And although my first programming language was
FORTRAN, I read the classic K&R book in a library. C++ is harder to recall, but
most likely, it was in 1989, at least according to my old CV, which is the
source of truth. I definitely started using C++ in commercial projects around
1991 but used it as a better C, and there was no standard template library
(STL) at that time. I recall some fascinating C++ GUI frameworks for MS-DOS,
like Zinc. In 1994-1995 I designed a word processor for Windows 3.1, and in
the process, I implicitly used many design patterns I later discovered in the
GOF book in 2000. The authors also use a word processor for illustration. I
mainly understood C++ as C++ in 2000 when I read a book about CORBA
distributed object technology that used C++ and also a book about the
internals of Windows COM using C++. This also greatly improved my COM
programming and debugging skills – I used to debug COM and ActiveX
components a lot during the years 1998 – 2000. I continued learning C++ by
31

reading many books of that time and learned the merits of using STL and
also how to use it effectively. In 2001, I joined a company that developed
C++ static analysis tools, and this greatly improved my C++ knowledge up to
the expert level at that time. C++03 didn’t have major changes compared to
C++98, and this is why I included it with C++98 for the year 2001. In 2003,
things turned out unexpectedly as I moved from full-time development
using C++ to full-time memory dump analysis of C++ programs. I continued
using C++03 for writing diagnostic tools, though. In 2016, I learned that the
language completely changed to C++11/14. I came back to full-time C++
programming in late 2017, where I also started using language features from
C++17. In 2020, I moved to functional programming in Scala, which also
influenced my C++ coding for new projects. Now, I have started using C++20
- a bit late since C++23 will be available soon (at the time of this writing),
and I am switching from Scala to Rust.

Zinc
https://en.wikipedia.org/wiki/Zinc_Application_Framework

Old CV
https://opentask.com/Vostokov/CV.htm
32

C and C++ Mastery Process

C and C++ Mastery Process

Coding Mental Compiling

© 2023 Software Diagnostics Services

Despite high-level features in C++, there’s still much low-level overlap with
C, and when I program in C and C++, I mentally compile to memory. This
helps when I have a doubt about whether this or that construct is safe. And
I also believe that looking at how C and C++ constructs are implemented in
memory greatly helps in learning these languages.
33

Thought Process

Thought Process

 C and C++ Memory

 Scala/FP Functions

 Python Data

© 2023 Software Diagnostics Services

This slide about a thought process when using a programming language is


perhaps controversial. With C and C++, we think about memory; with
Scala/FP, we think about functions; and with Python, we think about data.
34

Philosophy of Pointers

Philosophy of Pointers

© 2023 Software Diagnostics Services

We start with pointers, the most important concept in C and also in C++. I
originally created this approach in 2015 but now extended it for this training.
35

Pointer

Pointer

Pointer

© 2023 Software Diagnostics Services

Conceptually, a pointer is an entity that refers (or points) to some other


entity. We say entity, not an object, so as not to confuse it with objects in
C++ or objects in object-oriented programming. This can be my finger, for
example, pointing to an apple.
36

Pointer Dereference

Pointer Dereference

Pointer

© 2023 Software Diagnostics Services

A pointer dereference is an act of getting the entity it references for further


inspection or usage. Imagine I point to an apple, and you grab it to eat.
37

Many to One

Many to One

Pointer Pointer

© 2023 Software Diagnostics Services

Several pointers can refer (or point) to the same entity. For example, two
people are pointing to the same apple. So, conceptually, pointers are
distinct from entities they point to. Should we call the latter pointees?
38

Many to One Dereference

Many to One Dereference

Pointer Pointer

© 2023 Software Diagnostics Services

Of course, if you dereference the same object, you get the same object. If
someone else grabs an apple, I point to, at the same time as you do, you
both get the same apple.
39

Invalid Pointer

Invalid Pointer

Pointer

© 2023 Software Diagnostics Services

Some pointers may be invalid; for example, I may point to an imaginary


apple.
40

Invalid Pointer Dereference

Invalid Pointer Dereference

Pointer

© 2023 Software Diagnostics Services

When you dereference an invalid pointer, you get a problem; for example,
you fail to get an imaginary apple I point to.
41

Wild (Dangling) Pointer

Wild (Dangling) Pointer

Pointer

© 2023 Software Diagnostics Services

Some pointers are called dangling – they used to point to valid entities some
time ago but not anymore, so a dereference fails. You’re reaching for an
apple that I point to, but someone snatches it a split second ago.
42

Pointer to Pointer

Pointer to Pointer

Pointer Pointer

© 2023 Software Diagnostics Services

Since a pointer is also an entity that can be pointed to, there can be a chain
of pointers. You point to me; I point to an apple.
43

Pointer to Pointer Dereference

Pointer to Pointer Dereference

Pointer Pointer

© 2023 Software Diagnostics Services

When we dereference the first pointer, we get an entity, another pointer,


which we can also dereference to get the underlying entity. You point to me,
but an alien snatches me with an apple I point to. Inside a ship, another alien
takes an apple for analysis.
44

Naming Pointers and Entities

Naming Pointers and Entities

A B C
1 2 3
fadb6810 86556810 a656ffbd

Pointer Pointer

© 2023 Software Diagnostics Services

Names are distinct from entities. Names can be programming language


identifiers or just unique numbers or IDs.
45

Names as Pointer Content

Names as Pointer Content

fadb6810 86556810 a656ffbd

86556810 a656ffbd

© 2023 Software Diagnostics Services

Pointers, as entities, may contain names, and these names may be names of
pointers, too. If a pointer contains only a name, we say the pointer value is
the name. So, the pointer value can be another pointer value, and the latter
pointer value is the name of some other entity.
46

Pointers as Entities

Pointers as Entities

fadb6810 86556810 a656ffbd

86556810 a656ffbd 00000000

© 2023 Software Diagnostics Services

Pointer dereference is an act. If we put acts aside, pointers are just entities
with some content that can be interpreted as a name if necessary. All these
dereferences happen only at runtime. The pointer content (its value) may
be invalid for all time without any problem until we use it.
47

Memory and Pointers

Memory and Pointers

© 2023 Software Diagnostics Services

Now, we look at memory representation of pointers and entities they point


to.
48

Mental Exercise

Mental Exercise
How many pointers can you count?

2ab1000 2ab1004 2ab1008 2ab100c 2ab1010

2ab1008 ffffffff 2ab1010 2ab100c 00000000

© 2023 Software Diagnostics Services

Here, in this picture, entities are the so-called memory cells. Memory cells
have addresses that start from 0 and are usually incremented by the so-
called pointer size, which is 4 on 32-bit systems and 8 on 64-bit systems.
Here, for visual clarity, we use memory cells from a 32-bit system.
49

Debugger Memory Layout

Debugger Memory Layout

2ab1000: 2ab1008

2ab1004: ffffffff
2ab1000: 2ab1008 ffffffff

2ab1008: 2ab1010
2ab1008: 2ab1010 2ab100c

2ab100c: 2ab100c
2ab1010: 00000000 00002000

2ab1010: 00000000

2ab1014: 00002000

© 2023 Software Diagnostics Services

When we use a debugger, it prints memory cell addresses and their contents
in a certain layout shown on this slide. Some debugger commands use 2-
column and some n-column layouts to print memory.
50

Memory Dereference Layout

Memory Dereference Layout

2ab1000: 2ab1008 2ab1000: 2ab1008 2ab1010

2ab1004: ffffffff 2ab1004: ffffffff ????????

2ab1008: 2ab1010 2ab1008: 2ab1010 00000000

2ab100c: 2ab100c 2ab100c: 2ab100c 2ab100c

2ab1010: 00000000 2ab1010: 00000000 ????????

2ab1014: 00002000 2ab1014: 00002000 ????????

© 2023 Software Diagnostics Services

For a 2-column format, a debugger command may interpret the second


column as a pointer. In such a case, the third column is a value from a pointer
dereference. Also, notice a case when a pointer points to itself.
51

Names as Addresses

Names as Addresses

2ab1000: 2ab1008

2ab1004: ffffffff

2ab1008: 2ab1010

2ab100c: 2ab100c

2ab1010: 00000000

2ab1014: 00002000

© 2023 Software Diagnostics Services

To repeat, for memory layout, names are interpreted as addresses, and


memory cell content (cell value) can also be interpreted as a memory
address.
52

Addresses and Entities

Addresses and Entities

2ab1000: 2ab1008 2ab1000: 2ab1008

2ab1004: ffffffff 2ab1004: ffffffff

2ab1008: 2ab1010 2ab1008: 2ab1010

2ab100c: 2ab100c 2ab100c

2ab1010: 00000000 00000000

2ab1014: 00002000 2ab1014: 00002000

© 2023 Software Diagnostics Services

Entities can be either single cells or multicells. Each part of a multicell can
be interpreted as a memory address, if necessary, even if it wasn’t meant to
be a memory address.
53

Addresses and Structures

Addresses and Structures

2ab1000: 2ab1008

2ab1004: ffffffff

2ab1008: 2ab1010

2ab100c

2ab1010: 00000000

2ab1014: 00002000

© 2023 Software Diagnostics Services

A structure in memory is a sequential collection of memory cells; some may


be multicell and themselves substructures. Each part of a structure, its
member, or structure field has its own address as well, in addition to the
overall address of the structure.
54

Pointers to Structures

Pointers to Structures

2ab1000: 2ab1008

2ab1004: ffffffff

2ab1008: 2ab1010
2ab1216: 2ab1004
2ab100c

2ab1010: 00000000

2ab1014: 00002000

© 2023 Software Diagnostics Services

A structure has its address. A pointer to a structure is a memory cell that


contains that address. It has its own address.
55

Arrays

Arrays

2ab1000: 2ab1008 2ab1000: 2ab1008

2ab1004: ffffffff ffffffff

2ab1008: 2ab1010 2ab1008: 2ab1010

2ab100c: 2ab100c 2ab100c

2ab1010: 00000000 2ab1010: 00000000

2ab1014: 00002000 00002000

© 2023 Software Diagnostics Services

An array is a contiguous sequence of n-cells in memory called array


elements. Each array element has its own address. Since the size of each
array element is fixed and the same, addressing the random element is fast.
56

Arrays and Pointers to Arrays

Arrays and Pointers to Arrays

2ab1000: 2ab1008

ffffffff

2ab1008: 2ab1010
2ab1216: 2ab1000
2ab100c

2ab1010: 00000000

00002000

© 2023 Software Diagnostics Services

The array address is the address of its first element. But a pointer to an array
is a different memory cell that contains the array address. This is similar to
structures and pointers to structures. An array can be considered as a
structure as well.
57

Strings and Pointers to Strings

Strings and Pointers to Strings

2ab1000: ‘H’

2ab1001: ‘e’

2ab1002: ‘l’
2ab1216: 2ab1000
2ab1003: ‘l’

2ab1004: ‘o’

2ab1005: 00

© 2023 Software Diagnostics Services

What about strings? An ASCII string is a zero-terminated array of one-byte


memory cells. The address of a string is the address of its first byte. Similar
to arrays, a pointer to a string is a memory cell that contains the address of
the string, the address of its first element - its first character.
58

Basic Types

Basic Types

© 2023 Software Diagnostics Services

Now, we look at a few fundamental basic types.


59

ASCII Characters and Pointers

ASCII Characters and Pointers

2ab1000: ‘H’ 2ab1216: 2ab1000

2ab1001: ‘e’
char c;
char *pstr; // pstr == 2ab1000
2ab1002: ‘l’
&pstr; // 2ab1216
2ab1003: ‘l’
c = *pstr; // c == ‘H’
++pstr; // 2ab1001
2ab1004: ‘o’ c = *pstr; // c == ‘e’
c = *(pstr+1); // c == ‘l’
2ab1005: 00

© 2023 Software Diagnostics Services

We have already looked at ASCII zero-terminated strings and pointers


conceptually using memory diagrams. Here, we look at some idiomatic C
code.

The code example corresponding to the memory diagram:

char c;
char *pstr; // pstr == 2ab1000
&pstr; // 2ab1216
c = *pstr; // c == ‘H’
++pstr; // 2ab1001
c = *pstr; // c == ‘e’
c = *(pstr+1); // c == ‘l’
60

Bytes and Pointers

Bytes and Pointers

2ab1000: 12 2ab1216: 2ab1000

2ab1001: 34
unsigned char b;
unsigned char *pb; // pb == 2ab1000
2ab1002: 56
&pb; // 2ab1216
2ab1003: 78
b = *pb; // b == 12
++pb; // 2ab1001
2ab1004: ab b = *pb; // b == 34
b = *(pb+1); // b == 56
2ab1005: cd

© 2023 Software Diagnostics Services

Characters are signed with small integer values from -128 to 127. But if we
want to work with bytes with unsigned values from 0 to 255, we need to use
unsigned characters. Later, we see what other types are available to work
with bytes.

The code example corresponding to the memory diagram:

unsigned char b;
unsigned char *pb; // pb == 2ab1000
&pb; // 2ab1216
b = *pb; // b == 12
++pb; // 2ab1001
b = *pb; // b == 34
b = *(pb+1); // b == 56
61

Wide Characters and Pointers

Wide Characters and Pointers

2ab1000: ‘H’ 2ab1216: 2ab1000

0
wchar_t wc;
wchar_t *pwstr; // pwstr == 2ab1000
2ab1002: ‘e’
&pwstr; // 2ab1216
0
wc = *pwstr; // wc == L‘H’
++pwstr; // 2ab1002
2ab1004: 00 c = *pwstr; // c == L‘e’
c = *(pwstr+1); // c == L‘\0’
00

© 2023 Software Diagnostics Services

Native Windows strings are UNICODE, and it is natural to use wide


characters that occupy two bytes each.

The code example corresponding to the memory diagram:

wchar_t wc;
wchar_t *pwstr; // pwstr == 2ab1000
&pwstr; // 2ab1216
wc = *pwstr; // wc == L‘H’
++pwstr; // 2ab1002
c = *pwstr; // c == L‘e’
c = *(pwstr+1); // c == L‘\0’
62

Integers

Integers

2ab1000: 2ab1008 2ab1216: 2ab1000

2ab1004: ffffffff
int i;
int *pi; // 2ab1000
2ab1008: 2ab1010
&pi; // 2ab1216
2ab100c: 2ab100c
i = *pi; // i == 0x2ab1008
++pi; // 2ab1004
2ab1010: 00000000 i = *pi; // i == 0xffffffff
// i == -1
2ab1014: 00002000 i = *(pi+1); // i == 0x2ab1010

© 2023 Software Diagnostics Services

The second type we look at now is integers, which occupy 4 bytes.

The code example corresponding to the memory diagram:

int i;
int *pi; // 2ab1000
&pi; // 2ab1216
i = *pi; // i == 0x2ab1008
++pi; // 2ab1004
i = *pi; // i == 0xffffffff
// i == -1
i = *(pi+1); // i == 0x2ab1010
63

Long Integers

Long Integers

2ab1000: 2ab1008 2ab1216: 2ab1000

2ab1004: ffffffff
long l;
long *pl; // 2ab1000
2ab1008: 2ab1010
&pl; // 2ab1216
2ab100c: 2ab100c
l = *pl; // l == 0x2ab1008
++pl; // 2ab1004
2ab1010: 00000000 l = *pl; // l == 0xffffffff
// l == -1
2ab1014: 00002000 l = *(pl+1); // l == 0x2ab1010

© 2023 Software Diagnostics Services

Long integers also occupy 4 bytes.

The code example corresponding to the memory diagram:

long l;
long *pl; // 2ab1000
&pl; // 2ab1216
l = *pl; // l == 0x2ab1008
++pl; // 2ab1004
l = *pl; // l == 0xffffffff
// l == -1
l = *(pl+1); // l == 0x2ab1010
64

Little-Endian System

Little-Endian System

2ab1000: 1

2ab1001: 2 char ba[4]; // { 1, 2, 3, 4 }


int i; // 0x4030201
2ab1002: 3

2ab1003: 4
MOVBE ; x86_64
2ab1004: 5 ; Move Data After Swapping Bytes

2ab1005: 6

© 2023 Software Diagnostics Services

When converting between byte sequences and number values, we need to


consider the little-endian system where the least significant digits reside at
the lowest memory addresses.

The code example corresponding to the memory diagram:

char ba[4]; // { 1, 2, 3, 4 }
int i; // 0x4030201
65

Short Integers

Short Integers

2ab1000: 2ab1008 2ab1216: 2ab1000

2ab1004: ffffffff
short s;
short *ps; // 2ab1000
2ab1008: 2ab1010
&ps; // 2ab1216
2ab100c: 2ab100c
s = *ps; // s == 0x1008
++ps; // 2ab1002
2ab1010: 00000000 s = *ps; // s == 0x2ab
s = *(ps+1); // s == 0xffff
2ab1014: 00002000

© 2023 Software Diagnostics Services

Short integers occupy 2 bytes.

The code example corresponding to the memory diagram:

short s;
short *ps; // 2ab1000
&ps; // 2ab1216
s = *ps; // s == 0x1008
++ps; // 2ab1002
s = *ps; // s == 0x2ab
s = *(ps+1); // s == 0xffff
66

Long Long Integers

Long Long Integers

2ab1000: 2ab1008 2ab1216: 2ab1000

2ab1004: ffffffff
long long ll;
long long *pll; // 2ab1000
2ab1008: 2ab1010
&pll; // 2ab1216
2ab100c: 2ab100c
ll = *pll;
// ll == 0xffffffff`02ab1008
2ab1010: 00000000 ++pll; // 2ab1008
ll = *pll;
2ab1014: 00002000 // ll == 0x2ab100c`02ab1010
ll = *(pll+1);
// ll == 0x2000`00000000
© 2023 Software Diagnostics Services

If we want 8-byte 64-bit integers, we need to use long long.

The code example corresponding to the memory diagram:

long long ll;


long long *pll; // 2ab1000
&pll; // 2ab1216
ll = *pll; // ll == 0xffffffff`02ab1008
++pll; // 2ab1008
ll = *pll; // ll == 0x2ab100c`02ab1010
ll = *(pll+1); // ll == 0x2000`00000000
67

Signed and Unsigned Integers

Signed and Unsigned Integers

 (signed) short / unsigned short

 signed / (signed) int / unsigned / unsigned int

 (signed) long (int) / unsigned long (int)

 (signed) long long (int) / unsigned long long (int)

for (unsigned i = 0xfff; i >= 0; --i)


{
// ... Spiking Thread
}

© 2023 Software Diagnostics Services

We need to be careful to use unsigned index variables in classic loops. The


following code example loops indefinitely since the loop variable is always
positive:

for (unsigned i = 0xfff; i >= 0; --i)


{
// ... Spiking Thread

Spiking Thread memory analysis pattern


https://www.dumpanalysis.org/blog/index.php/2007/05/11/crash-dump-
analysis-patterns-part-14/
68

Fixed Size Integers

Fixed Size Integers

 uint8_t b;

 uint32_t dw;

 uint64_t qw;

 uintptr_t p;

© 2023 Software Diagnostics Services

It is also possible to be precise and use portable fixed-size types.

The code example:

uint8_t b;
uint32_t dw;
uint64_t qw;
uintptr_t p;
69

Booleans

Booleans

 bool b;

 b = true;

 b = false;

© 2023 Software Diagnostics Services

C++ also includes a native type for boolean variables. If you want to use it
in pure C, you need to include the stdbool.h header.

The code example:

bool b;

b = true;
b = false;
70

Bytes

Bytes

 std::byte b;

 Not a character

 Not an integer

© 2023 Software Diagnostics Services

The latest C++ standards also include a distinct type for bytes.

The code example:

std::byte b;
71

Size

Size

 sizeof operator

 size_t size = sizeof(int);

 int i; size = sizeof i;

 size = sizeof(1 + 1);

© 2023 Software Diagnostics Services

The sizeof operator can evaluate the size of types, variables, and target
result types of expressions (without expression evaluation).

The code example:

size_t size = sizeof(int);

int i;

size = sizeof i;
size = sizeof(1 + 1);
72

Alignment

Alignment
2ab0ff8: 0

2ab0ffc: 0
 alignof operator
2ab1000: 1

 alignas specifier 2ab1004: 0

 size_t align = alignof(long long);

 long long alignas(4096) ll = 1;

© 2023 Software Diagnostics Services

Variables are usually aligned in memory at offsets divisible by their type size
value in bytes. You can get default alignment values using the alignof
operator and change the default alignment using the alignas specifier.

The code example:

size_t align = alignof(long long);

long long alignas(4096) ll = 1;


73

LLP64

LLP64

 sizeof(int) == 4

 sizeof(int *) == 8

 sizeof(long) == 4

 sizeof(long long) == 8

© 2023 Software Diagnostics Services

Windows uses the so-called LLP64 data model where only long long
integers and pointers are 64-bit.
74

Nothing and Anything

Nothing and Anything

 void foo(void);

 void *p;

© 2023 Software Diagnostics Services

Two distinct types correspond to the concepts of Nothing and Anything you
can find in other programming languages: void and void *. The latter is
a pointer to any type.

The code example:

void foo(void);
void *p;
75

Automatic Type Inference

Automatic Type Inference

auto a = "Hello";

auto func(decltype("Hello") cstr)


{
return cstr;
}

© 2023 Software Diagnostics Services

C++11 added automatic type specification, so the type is deduced from the
initializing expression.

The code example:

auto a = "Hello";

auto func(decltype("Hello") cstr)


{
return cstr;
}
76

Entity Conversion

Entity Conversion

© 2023 Software Diagnostics Services

As you anticipate, the same memory cell addresses and their values are the
basis of conversion between different entity types. So, let’s look at some
examples.
77

Pointer Conversion (C-Style)

Pointer Conversion (C-Style)

2ab1000: 12 2ab1216: 2ab1000

2ab1001: 34
unsigned char b; int i;
unsigned char *pb; // pb == 2ab1000
2ab1002: 56
&pb; // 2ab1216
2ab1003: 78
b = *pb; // b == 12
int *pi = (int *)pb; // pi == 2ab1000
2ab1004: ab &pi; // != 2ab1216
i = *pi; // i == 0x78563412
2ab1005: cd // Intel LSB
// endian

© 2023 Software Diagnostics Services

Pointers can be converted to each other freely because their value is just a
memory address. However, when we dereference them, we get the value
based on underlying memory contents, which don’t change as illustrated
here. Please also note that due to the least significant byte endian
convention, the integer value we get differs from the memory layout byte
order.

The code example corresponding to the memory diagram:

unsigned char b; int i;


unsigned char *pb; // pb == 2ab1000
&pb; // 2ab1216
b = *pb; // b == 12
int *pi = (int *)pb; // pi == 2ab1000
&pi; // != 2ab1216
i = *pi; // i == 0x78563412
// Intel LSB endian
78

Numeric Promotion/Conversion

Numeric Promotion/Conversion

 char c = 'a'; int n = c;

 short s = c;

 int n = 0x1234; char c = n;

© 2023 Software Diagnostics Services

Values from the lesser range of values can be automatically promoted to


types with a wider range of values. The opposite automatic conversion may
lose some bits of information and should be carefully reviewed.

The code example:

char c = 'a';
int n = c;

short s = c;

int n = 0x1234;
char c = n;
79

Numeric Conversion

Numeric Conversion

 (type)(expr) // C-Style

 static_cast<type>(expr)

for (unsigned i = 0xfff; (int)i >= 0; --i)


{
}

for (unsigned i = 0xfff; static_cast<int>(i) >= 0; --i)


{
}

© 2023 Software Diagnostics Services

In the absence of automatic conversion for compatible types, we can use C-


style casts or explicit, specific C++ casts.

The following code examples solve the problem with the infinite loop:

for (unsigned i = 0xfff; (int)i >= 0; --i)


{
}

for (unsigned i = 0xfff; static_cast<int>(i) >= 0; --i)


{
}
80

Incompatible Types

Incompatible Types

 (type)(expr) // C-Style

 reinterpret_cast<type>(expr)

int *p = (int *)1;

p = reinterpret_cast<int *>(1);

© 2023 Software Diagnostics Services

When types are incompatible, for example, integers and pointers to them,
we can use either C-style casts or the specific C++ type reinterpretation cast.

The code example:

int *p = (int *)1;

p = reinterpret_cast<int *>(1);
81

Forcing

Forcing
struct A
{
unsigned int u1;
unsigned int u2;
};

struct B
{
unsigned long long ull;
} b;

A a = reinterpret_cast<A>(b);

A a = *(A*)&b;
a = *reinterpret_cast<A *>(&b);

© 2023 Software Diagnostics Services

Different structures are even more incompatible with the failing direct C++
reinterpretation cast. However, we can force reinterpretation of structures
by reinterpreting a pointer to a source structure as a pointer to a target
structure and then dereferencing it. In such a case, the underlying memory
cells are reinterpreted as the target structure field values. You can review
the code example after studying the next two sections on structures and
memory:

struct A
{
unsigned int u1;
unsigned int u2;
};

struct B
{
unsigned long long ull;
} b;
82

A a = reinterpret_cast<A>(b);

A a = *(A*)&b;

a = *reinterpret_cast<A *>(&b);
83

Structures, Classes, and Objects

Structures, Classes, and Objects

© 2023 Software Diagnostics Services

Now, we cover structures, classes, and their objects.


84

Structures

Structures

struct MyStruct struct


{ {
int field; int field;
// ... // ...
}; } myOtherStruct;

struct MyStruct myStruct; struct


MyStruct myStruct2; {
int field;
struct MyStruct *pMyStruct; // ...
MyStruct *pMyStruct2; } *pMyOtherStruct;

© 2023 Software Diagnostics Services

We can view structures as collections of fields laid out in memory. Structures


may have names or can be anonymous, as on the right.

The code example:

struct MyStruct struct


{ {
int field; int field;
// ... // ...
}; } myOtherStruct;

struct MyStruct myStruct; struct


MyStruct myStruct2; {
int field;
struct MyStruct *pMyStruct; // ...
MyStruct *pMyStruct2; } *pMyOtherStruct;
85

Access Level

Access Level

struct MyStruct
{
// public:
int field1;
private:
int field2;
} myStruct;

myStruct.field1 = 1;
myStruct.field2 = 2;

© 2023 Software Diagnostics Services

Fields with the private access specified cannot be referenced from the
outside.

The code example:

struct MyStruct
{
// public:
int field1;
private:
int field2;
} myStruct;

myStruct.field1 = 1;
myStruct.field2 = 2;
86

Classes and Objects

Classes and Objects

class MyClass class


{ {
int field; int field;
// ... // ...
}; } myOtherClass;

class MyClass myClass; class


MyClass myClass2; {
int field;
class MyClass *pMyClass; // ...
MyClass *pMyClass2; } *pMyOtherClass;

© 2023 Software Diagnostics Services

Classes have the same structure.

The code example:

class MyClass class


{ {
int field; int field;
// ... // ...
}; } myOtherClass;

class MyClass myClass; class


MyClass myClass2; {
int field;
class MyClass *pMyClass; // ...
MyClass *pMyClass2; } *pMyOtherClass;
87

Structures and Classes

Structures and Classes

struct ==* class

*
struct tagStruct class tagClass
{ {
// public: public: // (private:)
int field; == int field;
// ... // ...
}; };

© 2023 Software Diagnostics Services

Both structures and classes are completely the same in C++ and can be used
interchangeably. This is why you can always see struct in good modern
C++ books. The only difference (if we ignore inheritance for now) is the field
access, which is public by default in structures and private in classes.

The code example:

struct tagStruct class tagClass


{ {
// public: public: // (private:)
int field; int field;
// ... // ...
}; };
88

Pointer to Structure

Pointer to Structure

MyStruct *pMyStruct = &myStruct;

Pointer

© 2023 Software Diagnostics Services

Here, we go again through our conceptual philosophy of pointers pictures


and annotate them with C and C++ code.

The code example:

MyStruct *pMyStruct = &myStruct;


89

Pointer to Structure Dereference

Pointer to Structure Dereference

MyStruct myStruct = *pMyStruct;

Pointer

© 2023 Software Diagnostics Services

Here, we dereference a pointer to some structure. We get the structure


value and copy-assign it to the myStruct variable.

The code example:

MyStruct myStruct = *pMyStruct;


90

Many Pointers to One Structure

Many Pointers to One Structure

MyStruct *pMyStruct = &myStruct;


MyStruct *pMyStruct2 = pMyStruct;

Pointer Pointer

© 2023 Software Diagnostics Services

Here, we assign the value of one pointer to another, and both now point to
the same structure.

The code example:

MyStruct *pMyStruct = &myStruct;


MyStruct *pMyStruct2 = pMyStruct;
91

Many to One Dereference

Many to One Dereference


assert(&*pMyStruct == &*pMyStruct2);

Pointer Pointer

© 2023 Software Diagnostics Services

If we dereference both, we get the same value with the same address.

The code example:

assert(&*pMyStruct == &*pMyStruct2);
92

Invalid Pointer to Structure

Invalid Pointer to Structure

MyStruct *pMyStruct;

Pointer

© 2023 Software Diagnostics Services

Here, we depict an uninitialized pointer that, depending on the memory


storage type, can be a NULL pointer or some random value.

The code example:

MyStruct *pMyStruct;
93

Invalid Pointer Dereference

Invalid Pointer Dereference

MyStruct myStruct = *pMyStruct;

Pointer

© 2023 Software Diagnostics Services

Dereferencing an uninitialized pointer can have undefined behavior, most


likely an access violation leading to a crash.

The code example:

MyStruct *pMyStruct;
MyStruct myStruct = *pMyStruct;
94

Wild (Dangling) Pointer

Wild (Dangling) Pointer


MyStruct *pMyStruct = new MyStruct;
delete pMyStruct;

Pointer

MyStruct myStruct = *pMyStruct;

© 2023 Software Diagnostics Services

Memory for a structure can be dynamically allocated and then deallocated,


but if a pointer is not reset to some value easy to check, such as 0, then we
have a dangling pointer with its dereferencing resulting in undefined
behavior that could lead to further corruption.

The code example:

MyStruct *pMyStruct = new MyStruct;


delete pMyStruct;

MyStruct myStruct = *pMyStruct;


95

Pointer to Pointer to Structure

Pointer to Pointer to Structure

MyStruct *pMyStruct = &myStruct;


MyStruct **ppMyStruct = &pMyStruct;

Pointer Pointer

© 2023 Software Diagnostics Services

We can also have pointers to pointers to structures and so on, with double
and more dereferences needed to get the value. We’ll see why we need
double-pointers later when we discuss passing parameters to functions.

The code example:

MyStruct *pMyStruct = &myStruct;


MyStruct **ppMyStruct = &pMyStruct;
96

Pointer to Pointer Dereference

Pointer to Pointer Dereference


MyStruct *pMyStruct = *ppMyStruct;
MyStruct myStruct = *pMyStruct;
myStruct = **ppMyStruct;

Pointer Pointer

© 2023 Software Diagnostics Services

Here, we have double dereference illustrated. Please notice an example of


Hungarian notation.

The code example:

MyStruct *pMyStruct = *ppMyStruct;


MyStruct myStruct = *pMyStruct;
myStruct = **ppMyStruct;
97

Memory and Structures

Memory and Structures

© 2023 Software Diagnostics Services

Now, we look at the memory representation of structures.


98

Addresses and Structures

Addresses and Structures

struct OuterStruct 2ab1000: 2ab1008


{
int field1;
2ab1004: ffffffff
struct InnerStruct1
{
int field1; 2ab1008: 2ab1010
int field2;
} field2;
struct InnerStruct2 2ab100c
{
int field; 2ab1010: 00000000
} field3;
} myStruct;
2ab1014: 00002000

© 2023 Software Diagnostics Services

A structure in memory is a sequential collection of memory cells; some may


be multicell and themselves substructures. Each part of a structure, its
member, or structure field has its own address as well, in addition to the
overall address of the structure.

The code example corresponding to the memory diagram:

struct OuterStruct
{
int field1;
struct InnerStruct1
{
int field1;
int field2;
} field2;
struct InnerStruct2
{
int field;
} field3;
} myStruct;
99

Structure Field Access

Structure Field Access

2ab1000: 2ab1008

&myStruct; // 2ab1004
&myStruct.field1; // 2ab1004 2ab1004: ffffffff
myStruct.field1; // ffffffff
&myStruct.field2; // 2ab1008
2ab1008: 2ab1010
&myStruct.field2.field1; // 2ab1008
myStruct.field2.field1; // 2ab1010
&myStruct.field2.field2; // 2ab100c 2ab100c
myStruct.field2.field2; // 2ab100c
&myStruct.field3; // 2ab1010
&myStruct.field3.field; // 2ab1010 2ab1010: 00000000
myStruct.field3.field; // 0
2ab1014: 00002000

© 2023 Software Diagnostics Services

This example shows field addresses and access when we have a structure
value.

The code example corresponding to the memory diagram:

&myStruct; // 2ab1004
&myStruct.field1; // 2ab1004
myStruct.field1; // ffffffff
&myStruct.field2; // 2ab1008
&myStruct.field2.field1; // 2ab1008
myStruct.field2.field1; // 2ab1010
&myStruct.field2.field2; // 2ab100c
myStruct.field2.field2; // 2ab100c
&myStruct.field3; // 2ab1010
&myStruct.field3.field; // 2ab1010
myStruct.field3.field; // 0
100

Pointers to Structures

Pointers to Structures

2ab1000: 2ab1008 2ab1216: 2ab1004

2ab1004: ffffffff

OuterStruct *pMyStruct = &myStruct;


2ab1008: 2ab1010
&myStruct; // 2ab1004
2ab100c pMyStruct; // 2ab1004
&pMyStruct; // 2ab1216

2ab1010: 00000000

2ab1014: 00002000

© 2023 Software Diagnostics Services

A structure has its address. A pointer to a structure is a memory cell that


contains that address. It has its own address.

The code example corresponding to the memory diagram:

OuterStruct *pMyStruct = &myStruct;


&myStruct; // 2ab1004
pMyStruct; // 2ab1004
&pMyStruct; // 2ab1216
101

Pointers to Structure Fields

Pointers to Structure Fields

2ab1000: 2ab1008 2ab1216: 2ab1004

2ab1004: ffffffff
pMyStruct; // 2ab1004
&pMyStruct->field1; // 2ab1004
2ab1008: 2ab1010 pMyStruct->field1; // ffffffff
&pMyStruct->field2; // 2ab1008
2ab100c &pMyStruct->field2.field1; // 2ab1008
pMyStruct->field2.field1; // 2ab1010
&pMyStruct->field2.field2; // 2ab100c
2ab1010: 00000000 pMyStruct->field2.field2; // 2ab100c
&pMyStruct->field3; // 2ab1010
&pMyStruct->field3.field; // 2ab1010
2ab1014: 00002000
pMyStruct->field3.field; // 0

© 2023 Software Diagnostics Services

This example shows field addresses and access when we have a pointer to a
structure value.

The code example corresponding to the memory diagram:

pMyStruct; // 2ab1004
&pMyStruct->field1; // 2ab1004
pMyStruct->field1; // ffffffff
&pMyStruct->field2; // 2ab1008
&pMyStruct->field2.field1; // 2ab1008
pMyStruct->field2.field1; // 2ab1010
&pMyStruct->field2.field2; // 2ab100c
pMyStruct->field2.field2; // 2ab100c
&pMyStruct->field3; // 2ab1010
&pMyStruct->field3.field; // 2ab1010
pMyStruct->field3.field; // 0
102

Structure Inheritance

Structure Inheritance
struct Base
{ 2ab1000: 2ab1008
int field;
};
2ab1004: ffffffff
struct Derived : Base
{
2ab1008: 2ab1010
int field;
int field2;
} myDerived; 2ab100c

myDerived.field;
myDerived.Base::field; 2ab1010: 00000000

Base *pMyBase = &myDerived; 2ab1014: 00002000


pMyBase->field;

© 2023 Software Diagnostics Services

Structures can inherit fields from other structures. In case of the same field
names, the derived structure hides the base structure fields, but they can
be accessed by explicit base structure name qualification:

struct Base
{
int field;
};

struct Derived : Base


{
int field;
int field2;
} myDerived;

myDerived.field;
myDerived.Base::field;

Base *pMyBase = &myDerived;


pMyBase->field;
103

Structure Slicing

Structure Slicing
struct Base
{
int field;
};

struct Derived : Base


{
int field2;
} myDerived { 0 };

Base myBase = myDerived;


myDerived = myBase;
myDerived = static_cast<Derived>(myBase);

Base *pMyBase = &myDerived;


Derived *pMyDerived = pMyBase;

Derived *pMyDerived = static_cast<Derived *>(pMyBase);

© 2023 Software Diagnostics Services

It is possible to copy a derived structure to a base structure variable, but in


this case, the former contents are sliced since the base structure occupies
less memory. The other way around, from the base structure to the derived,
is forbidden by default because the compiler doesn’t know how to fill the
new derived-only fields. However, this can be forced with a static cast where
the derived fields are filled with the existing adjacent memory content,
which can be completely random. The same downcast can be done between
pointers, but when we try to dereference a target pointer to the derived
structure later, we may get random data.

The code example:

struct Base
{
int field;
};
104

struct Derived : Base


{
int field2;
} myDerived { 0 };

Base myBase = myDerived;


myDerived = myBase;
myDerived = static_cast<Derived>(myBase);

Base *pMyBase = &myDerived;


Derived *pMyDerived = pMyBase;
Derived *pMyDerived = static_cast<Derived *>(pMyBase);
105

Inheritance Access Level

Inheritance Access Level


struct Base
{
int field;
};

struct Derived : private Base


{
int field;
int field2;
} myDerived;

myDerived.field;
myDerived.Base::field;

Base *pMyBase = &myDerived;


pMyBase->field;

© 2023 Software Diagnostics Services

It is possible to inherit privately. In such a case, the base structure fields are
inaccessible from the outside, even with the explicit qualification.

The code example:

struct Base
{
int field;
};

struct Derived : private Base


{
int field;
int field2;
} myDerived;

myDerived.field;
myDerived.Base::field;

Base *pMyBase = &myDerived;


pMyBase->field;
106

Structures and Classes II

Structures and Classes II

struct ==* class


*
struct Base class Base
{ {
// public: public:
int field; int field;
}; };

struct Derived : Base // (public) == class Derived : public Base // (private)


{ {
// ... // ...
}; };

© 2023 Software Diagnostics Services

Again, structures and classes are almost equivalent except for the default
inheritance access (and field access), which is, by default, public for
structures and private for classes. Public access needs to be specified
explicitly for classes. We do not discuss protected access in this training,
which is not really relevant for memory thinking when looking at built code.

The code example:

struct Base class Base


{ {
// public: public:
int field; int field;
}; };

struct Derived : Base class Derived : public Base


// (public) // (private)
{ {
// ... // ...
}; };
107

Internal Structure Alignment

0:000> dt wmain::__l2::Struct Dereference!wmain::__l2::Struct +0x000 field1 : Bool +0x002 field2 : Int2B +0x008 field4 : Int8B

Internal Structure Alignment

struct Struct 0:000> dt Struct


{ Struct
bool field1; +0x000 field1 : Bool
short field2; +0x002 field2 : Int2B
long long field3; +0x008 field4 : Int8B
} myStruct;

#pragma pack(1) 0:000> dt StructPacked


struct StructPacked StructPacked
{ +0x000 field1 : Bool
bool field1; +0x001 field2 : Int2B
short field2; +0x003 field4 : Int8B
long long field4;
} myStructPacked;

© 2023 Software Diagnostics Services

Fields may be aligned according to their default type alignment, which may
introduce gaps, increasing the overall structure size.

The code example corresponding to the WinDbg output:

struct Struct
{
bool field1;
short field2;
long long field3;
} myStruct;

#pragma pack(1)
struct StructPacked
{
bool field1;
short field2;
long long field4;
} myStructPacked;
108

Static Structure Fields

0:000> dt wmain::__l2::Struct Dereference!wmain::__l2::Struct +0x000 field1 : Bool +0x002 field2 : Int2B +0x008 field4 : Int8B

Static Structure Fields


struct MyStruct
{
int field;
static unsigned shared_field;
} myStruct1, myStruct2;

unsigned MyStruct::shared_field = 123;

myStruct1.field = 0;
myStruct1.shared_field = 123;
myStruct2.field = 1;

0:000> dt MyStruct
+0x000 field : Int4B
=00007ff7`ba535048 shared_field : Uint4B

© 2023 Software Diagnostics Services

Static structure field values are shared between the different objects of the
same structure type. They occupy uniquely separate memory cells from the
objects’ memory.

The code example corresponding to the WinDbg output:

struct MyStruct
{
int field;
static unsigned shared_field;
} myStruct1, myStruct2;
unsigned MyStruct::shared_field = 123;

myStruct1.field = 0;
myStruct1.shared_field = 123;
myStruct2.field = 1;
109

Uniform Initialization

Uniform Initialization

© 2023 Software Diagnostics Services

Throughout C++ history, there were several ways to initialize variables.


Finally, there is some uniform way to do it consistently.
110

Old Initialization Ways

Old Initialization Ways

 OuterStruct *pMyStruct;

 OuterStruct *pMyStruct = NULL;

 OuterStruct *pMyStruct(NULL);

 OuterStruct *pMyStruct = nullptr;

© 2023 Software Diagnostics Services

When we omit an initialization value, a variable is considered uninitialized if


its memory belongs to certain memory classes, such as stack. For static
memory, it may be default-initialized with zero memory values.

The code example:

OuterStruct *pMyStruct;
OuterStruct *pMyStruct = NULL;
OuterStruct *pMyStruct(NULL);
OuterStruct *pMyStruct = nullptr;
111

New Way {}

New Way {}

 OuterStruct *pMyStruct{};

 OuterStruct *pMyStruct{NULL};

 OuterStruct *pMyStruct{nullptr};

 OuterStruct *pMyStruct{&myStruct};

© 2023 Software Diagnostics Services

When we use the new way of initialization in modern C++, we can use empty
{} to signal default initialization even for stack memory.

The code example:

OuterStruct *pMyStruct{};
OuterStruct *pMyStruct{NULL};
OuterStruct *pMyStruct{nullptr};
OuterStruct *pMyStruct{&myStruct};
112

Uniform Structure Initialization

Uniform Structure Initialization

struct OuterStructA struct OuterStructB


{ {
int field1; int field1{1};
struct InnerStruct1 struct InnerStruct1
{ {
int field1; int field1{2};
int field2; int field2{3};
} field2; } field2;
struct InnerStruct2 struct InnerStruct2
{ {
int field; int field{4};
} field3; } field3;
} myStructA {1, {2, 3}, {4}}; } myStructB;

© 2023 Software Diagnostics Services

It is possible to uniformly initialize the structure outside or provide default


field initializers in the structure definition.

The code example:

struct OuterStructA struct OuterStructB


{ {
int field1; int field1{1};
struct InnerStruct1 struct InnerStruct1
{ {
int field1; int field1{2};
int field2; int field2{3};
} field2; } field2;
struct InnerStruct2 struct InnerStruct2
{ {
int field; int field{4};
} field3; } field3;
} myStructA {1, {2, 3}, {4}}; } myStructB;
113

Static Field Initialization

Static Field Initialization

struct MyStruct
{
int field;
inline static unsigned shared_field{123};
} myStruct1, myStruct2;

// unsigned MyStruct::shared_field = 123;

myStruct1.field = 0;
myStruct1.shared_field = 123;
myStruct2.field = 1;

© 2023 Software Diagnostics Services

The latest C++ standards allow static field initialization inside the structure
definition instead of the classic C++ ways of outside initialization (shown in
comments).

The code example:

struct MyStruct
{
int field;
inline static unsigned shared_field{123};
} myStruct1, myStruct2;

// unsigned MyStruct::shared_field = 123;

myStruct1.field = 0;
myStruct1.shared_field = 123;
myStruct2.field = 1;
114

Macros, Types, and Synonyms

Macros, Types, and Synonyms

© 2023 Software Diagnostics Services

Type names may be long or inconvenient. There are some ways to construct
easier type names.
115

Macros

Macros

 #define TRUE 1

 #define BYTE unsigned char

 #define PBYTE unsigned char *

 #define PMyStruct struct MyStruct *

© 2023 Software Diagnostics Services

The code example:

#define TRUE 1
#define BYTE unsigned char
#define PBYTE unsigned char *
#define PMyStruct struct MyStruct *
116

Old Way

Old Way

 typedef unsigned char BYTE;

 typedef unsigned char *PBYTE;

 typedef unsigned char BYTE, *PBYTE;

 typedef struct {} MyStruct,


*PMyStruct;

© 2023 Software Diagnostics Services

The code example:

typedef unsigned char BYTE;


typedef unsigned char *PBYTE;
typedef unsigned char BYTE, *PBYTE;
typedef struct {} MyStruct, *PMyStruct;
117

New Way

New Way

 using BYTE = unsigned char;

 using PBYTE = unsigned char *;

 using MyStruct = struct {};

© 2023 Software Diagnostics Services

The code example:

using BYTE = unsigned char;


using PBYTE = unsigned char *;
using MyStruct = struct {};
118

Memory Storage

Memory Storage

© 2023 Software Diagnostics Services

What memory storage is used to store values ultimately influences program


behavior and possible defects.
119

Overview

Overview
 Global (link)
 TU static (file)
 Function static
 Local (stack)
 Dynamic (heap)
 Local-dynamic (stack → heap)
 In-place (allocator)
 Polymorphic (allocator)

© 2023 Software Diagnostics Services

Here, we show the list of different storage types and talk about them in
detail later. We cover the polymorphic allocators in the next edition.
120

Thread Stack Frames

Thread Stack Frames


Before function call Inside called function After return

2ab1000: 2ab1008 2ab1000: 2ab1008 2ab1000: 2ab1008

2ab1004: ffffffff 2ab1004: ffffffff 2ab1004: ffffffff

2ab1008: 2ab1010 2ab1008: 2ab1010 2ab1008: 2ab1010

2ab100c: 2ab100c 2ab100c: 2ab100c 2ab100c: 2ab100c

2ab1010: 00000000 2ab1010: 00000000 2ab1010: 00000000

2ab1014: 00002000 2ab1014: 00002000 2ab1014: 00002000

© 2023 Software Diagnostics Services

When a function is called, a stack frame is allocated in the thread stack


memory region to hold local variables’ values.
121

Local Variable Value Lifecycle

Local Variable Value Lifecycle


int *p = foo(); int *foo() { int *p = foo(); void bar() { int *p = foo();
int i = *p; int a = 0; int i = *p; int a = 1; int i = *p;
bar(); return &a; bar(); } bar();
int j = *p; } int j = *p; int j = *p;
assert(i == j);

2ab1008 2ab1008 2ab1008 2ab1008 2ab1008

ffffffff ffffffff ffffffff ffffffff ffffffff

2ab1010 00000000 00000000 00000001 00000001

2ab100c 2ab100c 2ab100c 2ab100c 2ab100c

00000000 00000000 00000000 00000000 00000000

00002000 00002000 00002000 00002000 00002000

© 2023 Software Diagnostics Services

Since the stack frame memory values can be overwritten after the return
from the function by subsequent function calls, local variable values have
definite values only during the function call where they were initialized.

The code examples corresponding to the memory diagrams.

Before calling the foo function, the memory values below the current stack
frame are undefined:

int *p = foo();
int i = *p;
bar();
int j = *p;

When we enter the foo function, the corresponding stack frame is created.
The function code also initializes the local variable a with 0 value. The
function also returns the stack address of that local variable:
122

int *foo()
{
int a = 0;
return &a;
}

In the caller, we save that value at that address in the i variable. Then we
call the bar function:

int *p = foo();
int i = *p;
bar();
int j = *p;

When we enter the bar function, the corresponding stack frame is created.
The function code also initializes the local variable a with 1. Coincidentally,
the variable a occupies the same stack memory location as the local variable
a in the previous foo function call:

void bar()
{
int a = 1;
}

Upon the return from the bar function, we dereference the same p address
but get a different value:

int *p = foo();
int i = *p;
bar();
int j = *p;
assert(i == j);
123

Stack Allocation Pitfalls

Stack Allocation Pitfalls


foo(1); void foo(int i) {
...
...
}

void foo(int i)
2ab1008 2ab1008 {
int a = 0;

ffffffff ffffffff {
int b = 0;
}
2ab1010 2ab1010
if (i)
goto end;
2ab100c 2ab100c int c = 0x78563412;

end:
00000000 00000000
assert(c == 0x78563412);
}
00002000 00002000

© 2023 Software Diagnostics Services

Please don’t forget that stack frame memory for all function local variables
is allocated at the entrance of the function, but individual variables may be
initialized at a later time.

The code example corresponding to the memory diagram:

foo(1);

When we enter the foo function, the allocated stack frame includes the
local variable c, which is initially uninitialized:

void foo(int i)
{
...
...
}

If the value of the i function parameter is positive, the initialization of the


local variable c is skipped, and the assertion is failed:
124

void foo(int i)
{
int a = 0;
{
int b = 0;
}
if (i)
goto end;
int c = 0x78563412;
end:

assert(c == 0x78563412);
}
125

Explicit Local Allocation

Explicit Local Allocation

 Microsoft-specific

 _malloca / _freea

 _alloca (deprecated)

© 2023 Software Diagnostics Services

It is possible to explicitly allocate memory on the thread stack, for example,


for some variable-length array storage. However, be aware of the possible
stack overflow.

Microsoft-specific _malloca / _freea


https://learn.microsoft.com/en-us/cpp/c-runtime-
library/reference/malloca

deprecated _alloca
https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/alloca
126

Dynamic Allocation (C-style)

Dynamic Allocation (C-style)

 Persistent across function calls

 (m|c|re)alloc

 free

 Can be replaced

© 2023 Software Diagnostics Services

There are some advantages to a dynamic memory allocation compared to a


local stack allocation. The allocated memory and its values persist across
function calls. Since allocations are implemented by library calls, they can be
replaced with other libraries and custom code that provides debugging
capabilities for tracking memory allocations and deallocations, as well as
other checks.
127

Dynamic Allocation (C++)

Dynamic Allocation (C++)

 Persistent across function calls

 Global operators

 Structure-specific operators

 Can be replaced

© 2023 Software Diagnostics Services

C++ has its own implementation of dynamic memory that is often internally
implemented by underlying C-style calls and Windows API. However, these
high-level allocation facilities are more flexible and customizable to the
needs of structure designers. It provides replaceable operators for global
allocations for chunks of memory and structure-specific allocations.
128

Memory Operators

Memory Operators

 operator new / operator delete

 operator new[] / operator delete[]

 operator new throws std::bad_alloc


exception (do not check for nullptr)
unless told not to via std::nothrow value

© 2023 Software Diagnostics Services

When freeing globally allocated memory, always pay attention to whether


it was allocated in the array form to avoid memory leaks, crashes, and other
undefined behavior. Also, never check the allocated memory address for
nullptr as done in the C-style allocations: C++ allocation operators throw
an exception instead.
129

Memory Expressions

Memory Expressions
 Use memory operators

 new

 delete / delete[]

 new throws std::bad_alloc exception (do


not check for nullptr) unless told not to via
std::nothrow value
© 2023 Software Diagnostics Services

Memory allocation expressions are used for allocating memory for values,
structures, and their arrays. Internally, they may use memory operators. The
same advice for non-array/array deallocation and checking return addresses
is applicable here.
130

Local Pointers (Manual)

Local Pointers (Manual)


foo(); void foo() { void foo() { void foo() {
... int *p = new int; int *p = new int;
... ... ...
} } delete p;
}

2ab1008 2ab1008 2ab1008 2ab1008

ffffffff ffffffff ffffffff ffffffff

2ab1010 2ab1010 78004000 ... 78004000

2ab100c 2ab100c 2ab100c 2ab100c

00000000 00000000 00000000 00000000

00002000 00002000 00002000 00002000

© 2023 Software Diagnostics Services

When allocating memory dynamically and assigning the memory address to


a local variable, we must not forget to free/delete memory before returning
from the function to avoid a memory leak.

The code example corresponding to the memory diagram:

foo();

When we enter the foo function, the allocated stack frame includes the
local variables:

void foo()
{
...
...
}

The local variable p contains the address of the allocated memory for an
integer value:
131

void foo()
{
int *p = new int;
...
}

However, before exiting the function, we must free the memory; otherwise,
there is a memory leak. Please note that neither delete nor free change
the value of the variable p. It becomes a dangling pointer but it is ok because
it goes out of scope here and is not reused for dereferencing unless saved
somewhere else.

void foo()
{
int *p = new int;
...
delete p;
}
132

In-place Allocation

In-place Allocation
 Placement new
 #include <new>
 operator new (size_t, void*, …)
 delete must not be called

char buf[sizeof(int)];
buf: 1
int *pi = new(buf) int; pi: buf
*pi = 1;

char *pbuf = new char[sizeof(int)]; 2ab1008: 2


pi = new(pbuf) int; pbuf: 2ab1008
*pi = 2;
delete[] pbuf;

pi: 2ab1008

© 2023 Software Diagnostics Services

If we want to reuse existing memory buffers, we can use placement new.

The code examples corresponding to the memory diagrams:

char buf[sizeof(int)];
int *pi = new(buf) int;
*pi = 1;

char *pbuf = new char[sizeof(int)];


pi = new(pbuf) int;
*pi = 2;
delete[] pbuf;
133

Source Code Organisation

Source Code Organisation

© 2023 Software Diagnostics Services

We now discuss C and C++ source code organization.


134

Logical Layer (Translation Units)

Logical Layer (Translation Units)

TU TU TU

Compiler

OBJ OBJ OBJ

Linker

EXE/DLL/SYS

© 2023 Software Diagnostics Services

From a compiler perspective, it works with a translation unit as a whole and


converts the source code of a translation unit to an object file. Several object
files are combined by a linker into an executable file.
135

Physical Layer (Source Files)

Physical Layer (Source Files)


<<includes>>
common.h

client.h server.h

client.cpp server.cpp utils.cpp

Preprocessor

TU TU TU

© 2023 Software Diagnostics Services

Although one physical source code file corresponds to one translation unit,
it is passed through a preprocessor, which, among other things, looks for
special directives to include other files, and those files may also contain
directives to include other files. You can also see, as in the case of the
common.h file, by transitivity of inclusion, that the same file may be included
many times.
136

Inter-TU Sharing

Inter-TU Sharing

// TU A // TU B

int g_nCounter; extern int g_nCounter;

int g_nCounter;

Compiler

OBJ OBJ

Linker
Name Conflict

© 2023 Software Diagnostics Services

Variables in different translation units having the same name may conflict
during the linkage phase.

The code examples corresponding to the diagrams:

// TU A

int g_nCounter;

// TU B

extern int g_nCounter;

int g_nCounter;
137

Classic Static TU Isolation

Classic Static TU Isolation

// TU A // TU B

static int g_nCounter; extern int g_nCounter;

int g_nCounter;

Compiler

OBJ OBJ

Linker
EXE/DLL/SYS

© 2023 Software Diagnostics Services

To avoid name conflicts during linkage, C and classic C++ suggest using the
static specifier.

The code examples corresponding to the diagrams:

// TU A

static int g_nCounter;

// TU B

extern int g_nCounter;

int g_nCounter;
138

Namespace TU Isolation

Namespace TU Isolation

// TU A // TU B

namespace extern int g_nCounter;


{
int g_nCounter;
} int g_nCounter;

Compiler

OBJ OBJ

Linker
EXE/DLL/SYS

© 2023 Software Diagnostics Services

Modern C++ suggests using namespaces instead.

The code examples corresponding to the diagrams:

// TU A

namespace
{
int g_nCounter;
}

// TU B

extern int g_nCounter;

int g_nCounter;
139

Declaration and Definition

Declaration and Definition

 Declaration introduces a name and its type

 Multiple declarations

 Definition describes a type & its memory layout


or allocates memory & creates an entity

 One Definition Rule (ODR)

© 2023 Software Diagnostics Services

In C and C++, when reasoning about compilation, it is useful to consider the


distinction between declaration and definition. The rule of thumb is that the
latter usually describes the memory layout. Please also note that a definition
is also a declaration.
140

TU Definition Conflicts

TU Definition Conflicts

 Example:

struct S { }; // via thirdparty.h


//...
struct S { };
//...
S s;

© 2023 Software Diagnostics Services

Multiple declarations of the same entity are allowed, but only one definition
is allowed, the essence of ODR, One Definition Rule.

The code example:

struct S { }; // via thirdparty.h


//...
struct S { };
//...
S s;
141

Fine-grained TU Scope Isolation

Fine-grained TU Scope Isolation

 Named namespaces

 Example:

struct S { }; // via thirdparty.h


namespace mycode { struct S { }; }
//...
mycode::S s;
using namespace mycode;
S s2; // ambiguous
© 2023 Software Diagnostics Services

Named namespaces allow fine-grained scope isolation.

The code example:

struct S { }; // via thirdparty.h


namespace mycode { struct S { }; }
//...
mycode::S s;
using namespace mycode;
S s2; // ambiguous
142

Conceptual Layer (Design)

Conceptual Layer (Design)

api.h impl.h
<<includes>>

myapp.cpp api.cpp impl.cpp

Preprocessor

TU TU TU

© 2023 Software Diagnostics Services

In the design layer, we may want to separate implementation details.


143

Incomplete Types

Incomplete Types
struct MyStruct; // declaration

struct MyStruct *pMyStruct; // (declaration and) definition

// PImpl (Pointer to Implementation) idiom

struct Instrument // (declaration and) definition


{
int getMeasurement(); // declaration
private:
struct InstrumentImpl; // declaration
struct InstrumentImpl *pImpl; // definition
};

© 2023 Software Diagnostics Services

Such separation is achieved via incomplete types and the so-called PImpl
(Pointer to Implementation) idiom:

struct MyStruct; // declaration

struct MyStruct *pMyStruct; // (declaration and) definition

// PImpl (Pointer to Implementation) idiom

struct Instrument // (declaration and) definition


{
int getMeasurement(); // declaration
private:
struct InstrumentImpl; // declaration
struct InstrumentImpl *pImpl; // definition
};
144

References

References

© 2023 Software Diagnostics Services

Now, a slide for C++ references. We plan to extend this section in the second
edition.
145

Type& vs. Type*

Type& vs. Type*

 The same from a memory perspective

 Definition: int& ref{val}; int *ptr{&val};

 Dereference: ref; *ptr;

 Field access: rStruct.field;


pStruct->field; (*pStruct).field;

© 2023 Software Diagnostics Services

From the memory perspective, references and pointers are the same thing.
The only difference is that you cannot have a dangling reference; it must be
initialized.
146

Values

Values

© 2023 Software Diagnostics Services

Let’s now briefly discuss various categories of values. These are what is
stored in memory. A pointer value is also a value that is interpreted as a
memory address pointing to some other value elsewhere.
147

Value Categories

Value Categories
 lvalues vs. rvalues classification

 Expression: left vs. right

 Memory: lvalue is backed up by memory cell(s)

 Temporaries and literals: rvalue

int lvalue = rvalue(); lvalue = 1;


© 2023 Software Diagnostics Services

When reading serious C++ documentation, you frequently see the so-called
lvalues and rvalues mentioned. Crudely, you can think about them as left
and right values in expressions, where the right value can be temporary, and
the left value has to be backed up by some memory.

Classification
https://learn.microsoft.com/en-us/cpp/cpp/lvalues-and-rvalues-visual-cpp
148

Constant Values

Constant Values

 const int cv{1}; int v;

 const int *pc;


int * const cp{&v};
const int * const cpc{cp};

 const int& rc{v}; int& r{v};

© 2023 Software Diagnostics Services

The values can also be constant, facilitating functional programming and


code security. Please note that there can be pointers and references to
constant values, constant pointers to mutable variables, and both. The way
to read such declarations is from right to left.

The code examples:

const int cv{1}; int v;

const int *pc;


int * const cp{&v};
const int * const cpc{cp};

const int& rc{v}; int& r{v};


149

Constant Expressions

Constant Expressions

 #define myConst 1

 const int myConst = 1;

 constexpr int myConst = 1;

© 2023 Software Diagnostics Services

There are different ways to define constants for later symbolic use. The C
and classic C++ way is to use preprocessor (legacy) and const. The modern
way is to use constexpr,which is more flexible.

Code examples:

#define myConst 1

const int myConst = 1;

constexpr int myConst = 1;


150

Functions

Functions

© 2023 Software Diagnostics Services

This section is the largest in the course. We may split it up in the second
edition once it grows more.
151

Pointers to Functions

Pointers to Functions

foo (4bc2000): int (*pf) (int) {foo};


pf = &foo;
c
int (&rf) (int) {*pf};
o
d
int foo (int i) e pf (2ab1210): 4bc2000
{
// ... b
return 0; rf (2ab1218): 4bc2000
y
}
t
e
pf(10);
s
(*pf)(10);
rf(10)

© 2023 Software Diagnostics Services

Functions are code bytes and, therefore, occupy some memory locations
with their start addresses.

The code examples corresponding to the memory diagram:

int foo (int i)


{
// ...
return 0;
}

It is possible to have pointers and references to functions:

int (*pf) (int) {foo};


pf = &foo;
int (&rf) (int) {*pf};
152

When having a pointer or reference to a function, it is possible to call the


function with or without using the dereferencing syntax (*):

pf(10);
(*pf)(10);
rf(10)
153

Function Pointer Types

Function Pointer Types

 typedef int (*PF)(int);

 using PF = int (*)(int);

 PF func{foo}; func(10);

© 2023 Software Diagnostics Services

Function pointer type declarations can be done using the classic typedef
syntax or via the more modern using type alias.

The code examples:

typedef int (*PF)(int);

using PF = int (*)(int);

PF func{foo}; func(10);
154

Reading Declarations

Reading Declarations

 Right → Left, []right or ()right → Right

 Examples:

const int* const* *arr[10];

int (*(*difficult)(int (*)(int), int))(int);


using DF = PF (*)(PF, int);
DF difficult2 {difficult};

© 2023 Software Diagnostics Services

It is worth knowing the rules of reading declarations since function pointer


types can be quite complicated. The first example reads as an array of 10
elements, with each element a pointer to a pointer of constant pointers to
constant integers. The next example is a pointer to a function that accepts a
pointer to a function that accepts an integer and returns an integer, and
accepts another integer, and returns a pointer to a function that accepts an
integer and returns an integer. It can be simplified by using the common
subtype PF. We can verify the compatibility of the two descriptions by
initialization. GPT-4 is very good at deciphering such types.

const int* const* *arr[10];

int (*(*difficult)(int (*)(int), int))(int);


using DF = PF (*)(PF, int);
DF difficult2 {difficult};
155

Structure Function Fields

Structure Function Fields

struct
{
int field;
PF pFunc;
} myStruct {0, foo};

myStruct.pFunc(0);

© 2023 Software Diagnostics Services

Structures may contain fields that are pointers to functions: the obvious way
to implement OOP in C.

The code example:

struct
{
int field;
PF pFunc;
} myStruct {0, foo};

myStruct.pFunc(0);
156

Structure Methods

Structure Methods

struct MyStruct
{
int field;
PF pFunc;
int method (int i);
} myStruct {0, foo};

int MyStruct::method(int i) { return i; }

myStruct.pFunc(0);
myStruct.method(0);

© 2023 Software Diagnostics Services

C++ introduced structure or class methods. They can be defined either inside
the structure definition (next slide) or outside with the structure name
qualification.

The code example:

struct MyStruct
{
int field;
PF pFunc;
int method (int i);
} myStruct {0, foo};

int MyStruct::method(int i) { return i; }

myStruct.pFunc(0);
myStruct.method(0);
157

Structure Methods (Inlined)

Structure Methods (Inlined)

struct MyStruct
{
int field;
PF pFunc;
int method (int i) { return i; }
} myStruct {0, foo};

myStruct.pFunc(0);
myStruct.method(0);

© 2023 Software Diagnostics Services

This is an alternative way to define short functions, although it exposes users


to implementation details.

The code example:

struct MyStruct
{
int field;
PF pFunc;
int method (int i) { return i; }
} myStruct {0, foo};

myStruct.pFunc(0);
myStruct.method(0);
158

Structure Methods (Inheritance)

Structure Methods (Inheritance)


struct Base
{
int method (int i) { return i; }
};

struct Derived : Base


{
int method (int i) { return ++i; }
} myDerived;

myDerived.method(0);
myDerived.Base::method(0);

Base *pMyBase = &myDerived;


pMyBase->method(0);

© 2023 Software Diagnostics Services

In the case of inheritance, like with fields, the derived structure methods
hide methods with the same name in the base type unless explicit base type
name qualification is used. However, when we have a pointer to a base type,
then the base type method is called even if the actual object belongs to a
derived type:

struct Base
{
int method (int i) { return i; }
};

struct Derived : Base


{
int method (int i) { return ++i; }
} myDerived;

myDerived.method(0);
myDerived.Base::method(0);
159

Base *pMyBase = &myDerived;


pMyBase->method(0);
160

Structure Virtual Methods

Structure Virtual Methods


struct Base
{
int method (int i) { return i; }
virtual int vmethod (int i) { return i; }
};

struct Derived : Base


{
int method (int i) { return ++i; }
virtual int vmethod (int i) override { return ++i; }
} myDerived;

Base *pMyBase = &myDerived;


pMyBase->method(0);
pMyBase->vmethod(0);
pMyBase->Base::vmethod(0);
© 2023 Software Diagnostics Services

This previous slide problem is solved by introducing type-independent call


virtual methods. In this case, the method of derived type is called when we
have a pointer of a base type to it. The override specifier guarantees that
we override the correct base method instead of introducing the new one by
mistake:

struct Base
{
int method (int i) { return i; }
virtual int vmethod (int i) { return i; }
};

struct Derived : Base


{
int method (int i) { return ++i; }
virtual int vmethod (int i) override { return ++i; }
} myDerived;

Base *pMyBase = &myDerived;


pMyBase->method(0);
161

pMyBase->vmethod(0);
Base::vmethod(0);
162

Structure Pure Virtual Methods

Structure Pure Virtual Methods


struct Base
{
int method (int i) { return i; }
virtual int vmethod (int i) = 0;
};

struct Derived : Base


{
int method (int i) { return ++i; }
virtual int vmethod (int i) override { return ++i; }
} myDerived;

Base *pMyBase = &myDerived;


pMyBase->vmethod(0);

© 2023 Software Diagnostics Services

If we want to make sure we never define objects of the base type and make
sure we override all pure virtual methods in the derived type, we can make
the virtual function pure by using = 0.

The code example:

struct Base
{
int method (int i) { return i; }
virtual int vmethod (int i) = 0;
};

struct Derived : Base


{
int method (int i) { return ++i; }
virtual int vmethod (int i) override { return ++i; }
} myDerived;

Base *pMyBase = &myDerived;


pMyBase->vmethod(0);
163

Structure as Interface

Structure as Interface
struct Interface
{
virtual int vmethod1 (int i) = 0;
virtual int vmethod2 (int i) = 0;
};

struct Implementer : Interface


{
virtual int vmethod1 (int i) override { return ++i; }
virtual int vmethod2 (int i) override { return ++++i; }

} myObject;

Interface *pIface = &myObject;


pIface->vmethod1(0);

© 2023 Software Diagnostics Services

Pure virtual functions allow specifying abstract interfaces the derived types
have to implement.

The code example:

struct Interface
{
virtual int vmethod1 (int i) = 0;
virtual int vmethod2 (int i) = 0;
};

struct Implementer : Interface


{
virtual int vmethod1 (int i) override { return ++i; }
virtual int vmethod2 (int i) override { return ++++i; }
} myObject;

Interface *pIface = &myObject;


pIface->vmethod1(0);
164

Function Structure

Function Structure

struct MyFunction
{
int field{1};
int operator()() { return field; }
} myFunction;

myFunction();

© 2023 Software Diagnostics Services

Functions may also encapsulate state. The best way to do it is via structures
that implement function call operators.

The code example:

struct MyFunction
{
int field{1};
int operator()() { return field; }
} myFunction;

myFunction();
165

Structure Constructors

Structure Constructors

struct MyFunction
{
MyFunction(): field{1} { }
MyFunction(int _field): field{_field} { }
int field;
int operator()() { return field; }
} myFunction, myFunction2(2);

myFunction();
myFunction2();

© 2023 Software Diagnostics Services

Now, we come to traditional OOP topics in classic C++. Constructors are


methods with or without arguments for structure initialization with custom
initialization logic inside, for example, acquiring required resources.

The code example:

struct MyFunction
{
MyFunction(): field{1} { }
MyFunction(int _field): field{_field} { }
int field;
int operator()() { return field; }
} myFunction, myFunction2(2);

myFunction();
myFunction2();
166

Structure Copy Constructor

Structure Copy Constructor


struct MyFunction
{
MyFunction(): field{1} { }
MyFunction(int _field): field{_field} { }
MyFunction(const MyFunction& src):
field(src.field) { ++field; }
int field;
int operator()() { return field; }
} myFunction;

MyFunction myFunction2(myFunction);
MyFunction myFunction3 = myFunction;

© 2023 Software Diagnostics Services

When we copy objects but need complex copying logic or nontrivial memory
management copy constructor methods are quite handy. We pass the
source object reference as const if we don’t plan to modify it.

The code example:

struct MyFunction
{
MyFunction(): field{1} { }
MyFunction(int _field): field{_field} { }
MyFunction(const MyFunction& src):
field(src.field) { ++field; }
int field;
int operator()() { return field; }
} myFunction;

MyFunction myFunction2(myFunction);
MyFunction myFunction3 = myFunction;
167

Structure Copy Assignment

Structure Copy Assignment


struct MyFunction
{
MyFunction(): field{1} { }
MyFunction(int _field): field{_field} { }
MyFunction(const MyFunction& src):
field(src.field) { ++field; }
MyFunction& operator=(const MyFunction& src)
{ if (this != &src) { ++(field = src.field); }
return *this; }
int field;
int operator()() { return field; }
} myFunction;

MyFunction myFunction2;
myFunction2 = myFunction;

© 2023 Software Diagnostics Services

In the case of nontrivial assignments, we can implement an assignment


operator. We, however, should be careful not to copy to itself (this), and
we return the non-const reference to itself (*this) to allow chained copies.

struct MyFunction
{
MyFunction(): field{1} { }
MyFunction(int _field): field{_field} { }
MyFunction(const MyFunction& src):
field(src.field) { ++field; }
MyFunction& operator=(const MyFunction& src)
{ if (this != &src) { ++(field = src.field); }
return *this; }
int field;
int operator()() { return field; }
} myFunction;
MyFunction myFunction2;
myFunction2 = myFunction;
168

Structure Destructor

Structure Destructor
struct MyFunction
{
MyFunction(): field{1} { }
~MyFunction() { /* close resources */ }
MyFunction(int _field): field{_field} { }
MyFunction(const MyFunction& src):
field(src.field) { ++field; }
MyFunction& operator=(const MyFunction& src)
{ if (this != &src) { ++field = src.field; }
return *this; }
int field;
int operator()() { return field; }
};

{ MyFunction myFunction; }

© 2023 Software Diagnostics Services

What if we want some complex logic, for example, releasing resources when
the local object goes out of scope, or we delete it? Destructor is a method
that is called automatically in such a case.

struct MyFunction
{
MyFunction(): field{1} { }
~MyFunction() { /* close resources */ }
MyFunction(int _field): field{_field} { }
MyFunction(const MyFunction& src):
field(src.field) { ++field; }
MyFunction& operator=(const MyFunction& src)
{ if (this != &src) { ++field = src.field; }
return *this; }
int field;
int operator()() { return field; }
};
{
MyFunction myFunction;
}
169

Structure Destructor Hierarchy

Structure Destructor Hierarchy


struct IFunction
{
~IFunction() { }
virtual int operator()() = 0;
};

struct MyFunction : IFunction


{
~MyFunction() { /* close resources */ }
int field;
int operator()() override { return field; }
};

IFunction *pIFunction = new MyFunction;


pIFunction->operator()();
(*pIFunction)();
delete pIFunction; // ~IFunction()

© 2023 Software Diagnostics Services

Conceptually, destructors are just like a normal method, so the wrong one
may be called when we have a pointer of base type to an object of a derived
type.

struct IFunction
{
~IFunction() { }
virtual int operator()() = 0;
};

struct MyFunction : IFunction


{
~MyFunction() { /* close resources */ }
int field;
int operator()() override { return field; }
};

IFunction *pIFunction = new MyFunction;


pIFunction->operator()();
(*pIFunction)();
delete pIFunction; // ~IFunction()
170

Structure Virtual Destructor

Structure Virtual Destructor


struct IFunction
{
virtual ~IFunction() { }
virtual int operator()() = 0;
};

struct MyFunction : IFunction


{
~MyFunction() override { /* close resources */ }
int field;
int operator()() override { return field; }
};

IFunction *pIFunction = new MyFunction;


pIFunction->operator()();
(*pIFunction)();
delete pIFunction; // ~MyFunction()

© 2023 Software Diagnostics Services

To make sure that the correct destructors are called, it is recommended to


make them virtual too:

struct IFunction
{
virtual ~IFunction() { }
virtual int operator()() = 0;
};

struct MyFunction : IFunction


{
~MyFunction() override { /* close resources */ }
int field;
int operator()() override { return field; }
};

IFunction *pIFunction = new MyFunction;


pIFunction->operator()();
(*pIFunction)();
delete pIFunction; // ~MyFunction()
171

Destructor as a Method

Destructor as a Method
struct Resource
{
Resource() { /* acquire */ }
~Resource() { /* release */ }
private:
int m_hData;
};

char buf[sizeof(Resource)];
Resource *pResource = new(buf) Resource();
// ...
pResource->~Resource();

© 2023 Software Diagnostics Services

Since a destructor is also a method, it is possible to call it directly in cases


where we should not use standard delete methods, for example, when
objects are allocated using placement new:

struct Resource
{
Resource() { /* acquire */ }
~Resource() { /* release */ }
private:
int m_hData;
};
char buf[sizeof(Resource)];
Resource *pResource = new(buf) Resource();
// ...
pResource->~Resource();
172

Conversion Operators

Conversion Operators
struct A
{
unsigned int u1;
unsigned int u2;
};

struct B
{
unsigned long long ull;
operator A()
{
return A
{ (unsigned int)(ull & 0xFFFFFFFF),
(unsigned int)(ull >> 32)
};
}
} b;

A a = b;
© 2023 Software Diagnostics Services

Sources for copy constructors and copy assignment operators are of the
same type. What if we want to assign a different structure type? We can
define custom conversion operators (it is also possible to use a “conversion”
constructor):

struct A
{
unsigned int u1;
unsigned int u2;
};

struct B
{
unsigned long long ull;
operator A()
{
return A
{ (unsigned int)(ull & 0xFFFFFFFF),
(unsigned int)(ull >> 32)
};
173

}
} b;

A a = b;
174

Parameters by Value

Parameters by Value

 Original value doesn’t change


 Inefficient/efficient (basic types)
 Slicing (passing derived as base)
 Copy constructor/destructor

void func(MyStruct ms) void func(int i)


{ ms.field = 0; } { i = 0; }

func(myStruct); func(1);

© 2023 Software Diagnostics Services

When we pass parameters by values, any modifications inside functions are


lost once we return. Passing basic types by value is efficient, but passing
structures are not unless they are very simple: various functions may be
called, for example, copy constructors and destructors unless optimized by
a compiler. There is also a possibility of slicing when inheritance is used.

The code examples:

void func(MyStruct ms) void func(int i)


{ ms.field = 0; } { i = 0; }

func(myStruct); func(1);
175

Parameters by Pointer/Reference

Parameters by Pointer/Reference

 Original value may change


 Efficient/inefficient (basic types)
 Pointer/reference is passed by value
 Pointer by pointer (by pointer)

void func(MyStruct *pms) void func(int& ri)


{ pms->field = 0; } { ri = 0; }

func(&myStruct); int i{0};


func(i);

© 2023 Software Diagnostics Services

If we want efficiency for structures and also preserve changes to original


values, we need to pass by reference. Again, this may be inefficient for basic
types.

The code examples:

void func(MyStruct *pms) void func(int& ri)


{ pms->field = 0; } { ri = 0; }

func(&myStruct); int i{0};


func(i);
176

Parameters by Ptr/Ref to Const

Parameters by Ptr/Ref to Const

 Original value doesn’t change


 Efficient/inefficient (basic types)
 Pointer/reference is passed by value
 Can pass temporary values

void func(const MyStruct *pms) void func(const int& ri)


{ pms->field = 0; } { ri = 0; }

func(&myStruct); func(int(0));

© 2023 Software Diagnostics Services

If we only want efficiency for structures, we need to pass by reference to


const. In such a case, we also cannot modify the original values.

The code examples:

void func(const MyStruct *pms) void func(const int& ri)


{ pms->field = 0; } { ri = 0; }

func(&myStruct); func(int(0));
177

Possible Mistake

Possible Mistake

 Original value doesn’t change …


 … but you want to make sure
 You used languages with implicit references
 You should use const& instead

void func(const MyStruct ms) void func(const MyStruct& ms)


{ ms.field = 0; } { ms.field = 0; }

func(myStruct); func(myStruct);

© 2023 Software Diagnostics Services

If you come from languages that use implicit references, you may omit & by
mistake:

void func(const MyStruct ms) void func(const MyStruct& ms)


{ ms.field = 0; } { ms.field = 0; }

func(myStruct); func(myStruct);
178

Function Overloading

Function Overloading

 Different from overriding


 Name mangling in symbol files (cvdump)

 int funco (int i);


 int funco (int i, int j);
 int funco (long &rl);

© 2023 Software Diagnostics Services

Function overloading allows reusing the same function names for functions
with different numbers and types of parameters.

cvdump
https://github.com/microsoft/microsoft-pdb

The code example:

int funco (int i);


int funco (int i, int j);
int funco (long &rl);
179

Immutable Objects

Immutable Objects
struct MyStruct
{
MyStruct(int _field) : field{_field} { }
int get() const { return field; }
void set(int newval) { field = newval; }
private:
int field;
} myStruct(1);

const MyStruct& myCStruct{myStruct};

myCStruct.get();

myCStruct.set(2);

© 2023 Software Diagnostics Services

If you have const objects, you are only allowed to call methods that have
the const specifier in their definition.

The code example:

struct MyStruct
{
MyStruct(int _field) : field{_field} { }
int get() const { return field; }
void set(int newval) { field = newval; }
private:
int field;
} myStruct(1);

const MyStruct& myCStruct{myStruct};

myCStruct.get();

myCStruct.set(2);
180

Static Structure Functions

Static Structure Functions


// multithreading issues are ignored here
struct MyStruct
{
MyStruct(int _field) : field{_field} { ++count; }
int get() const { return field; }
void set(int newval) { field = newval; }
static auto get_count()
{
field++;
return count;
};
private:
int field;
inline static unsigned count{0};
} myStruct1(1), myStruct2(2);

MyStruct::get_count();
assert(myStruct1.get_count() == myStruct2.get_count());

© 2023 Software Diagnostics Services

Static structure functions are only allowed to access static structure fields
shared among objects:

// multithreading issues are ignored here


struct MyStruct
{
MyStruct(int _field) : field{_field} { ++count; }
int get() const { return field; }
void set(int newval) { field = newval; }
static auto get_count()
{
field++;
return count;
};
private:
int field;
inline static unsigned count{0};
} myStruct1(1), myStruct2(2);

MyStruct::get_count();
assert(myStruct1.get_count() == myStruct2.get_count());
181

Lambdas

Lambdas

 Interlude: necessary x64 disassembly

 Unnamed function objects

 A function parameter

 Optionally captures context

 A return value

© 2023 Software Diagnostics Services

Before discussing lambdas introduced in C++11 and their internals, we take


a brief tour around basic x64 disassembly.
182

x64 CPU Registers

x64 CPU Registers


 RAX  EAX  AX  {AH, AL} RAX 64-bit EAX 32-bit

 ALU: RAX, RDX

 Counter: RCX

 Memory copy: RSI (src), RDI (dst)

 Stack: RSP

 Next instruction: RIP

 New: R8 – R15, Rx(D|W|B)

© 2023 Software Diagnostics Services

There are familiar 32-bit CPU register names, such as EAX, that are extended
to 64-bit names, such as RAX. Most of them are traditionally specialized,
such as ALU, counter, and memory copy registers. Although, now they all
can be used as general-purpose registers. There is, of course, a stack pointer,
RSP, and it also takes the role of a frame pointer, which is also used to
address local variables and saved parameters. It can be used for stack
reconstruction. In Microsoft compiler code generation implementations,
RBP is also used as a general-purpose register. An instruction pointer RIP is
saved in the stack memory region with every function call, then restored on
return from the called function. In addition, the x64 platform features
another eight general-purpose registers, from R8 to R15.
183

Instructions and Registers

Instructions and Registers


 Opcode DST, SRC

 Examples:

mov rax, 10h ; RAX ← 0x10


mov r13, rdx ; R13 ← RDX
add r10, 10h ; R10 ← R10 + 0x10
imul edx, ecx ; EDX ← EDX * ECX
call rdx ; RDX already contains
; the address of func (&func)
; PUSH RIP; &func → RIP
sub rsp, 30h ; RSP ← RSP–0x30
; make room for local variables

© 2023 Software Diagnostics Services

This slide shows a few examples of CPU instructions involving operations


with registers, such as moving a value and doing arithmetic. The direction of
operands is opposite to the AT&T x64 disassembly flavor if you are
accustomed to default GDB disassembly on Linux.
184

Memory and Stack Addressing

Memory and Stack Addressing


Lower addresses Values

RSP-0x20 → [RSP-0x20]
RSP-0x18 → [RSP-0x18]
Stack grows

RSP-0x10 → [RSP-0x10]
RSP-0x8 → [RSP-0x8]
RSP → [RSP]
RSP+0x8 → [RSP+0x8]
RSP+0x10 → [RSP+0x10]
RSP+0x18 → [RSP+0x18]
RSP+0x20 → [RSP+0x20]

Higher addresses
© 2023 Software Diagnostics Services

Before we look at operations with memory, let’s look at a graphical


representation of memory addressing where for simplicity, I use 64-bit (or
8-byte) memory cells. A thread stack is just any other memory region, so
instead of RSP, any other register can be used. Please note that the stack
grows towards lower addresses, so to access the previously pushed values,
you need to use positive offsets from RSP.
185

Memory Cell Sizes

Memory Cell Sizes


RSP → BYTE PTR [RSP]

RSP → DWORD PTR [RSP]

RSP → QWORD PTR [RSP]

RSP+0x8 →

RSP+0x8 →

RSP+0x8 →

© 2023 Software Diagnostics Services

Here, each memory cell is 8-bit (or one byte). When we have a register
pointing to memory, and we want to work with the value at that address,
we need to specify the size of memory cells to work with, for example, BYTE
PTR if we want to work with a byte, DWORD PTR if we want to work with
32-bit double words, and QWORD PTR if we want to work with 64-bit quad
words. There’s also WORD PTR for 16-bit values. This notation is different
from Linux GDB, where we have bytes, half-words, words, and double
words.
186

Memory Load Instructions

Memory Load Instructions


 Opcode DST, PTR [SRC+Offset]

 Opcode DST

 Examples:

mov rax, qword ptr [rsp+10h] ; RAX ←


; 64-bit value at address RSP+0x10
mov ecx, dword ptr [20] ; ECX ←
; 32-bit value at address 0x20
pop rdi ; RDI ← value at address RSP
; RSP ← RSP + 8
lea r8, [rsp+20h] ; R8 ← address RSP+0x20

© 2023 Software Diagnostics Services

Constants are encoded in instructions, but if we need arbitrary values, we


must get them from memory. Square brackets show memory access relative
to an address stored in some register.
187

Memory Store Instructions

Memory Store Instructions


 Opcode PTR [DST+Offset], SRC

 Opcode DST|SRC

 Examples:

mov qword ptr [rbp-20h], rcx ; 64-bit value at address RBP-0x20


; ← RCX
mov byte ptr [0], 1 ; 8-bit value at address 0 ← 1
push rsi ; RSP ← RSP - 8
; value at address RSP ← RSI
inc dword ptr [rcx] ; 32-bit value at address RCX ←
; 1 + 32-bit value at address RCX

© 2023 Software Diagnostics Services

Storing is similar to loading.


188

Flow Instructions

Flow Instructions
 Opcode DST

 Opcode PTR [DST]

 Examples:

jmp 00007ff6`9ef2f008 ; RIP ← 0x7ff69ef2f008


; (goto 0x7ff69ef2f008)
jmp qword ptr [rax+10h] ; RIP ← value at address RAX+0x10
call 00007ff6`9ef21400 ; RSP ← RSP – 8
00007ff6`9ef21057: ; value at address RSP ← 0x7ff69ef21057
; RIP ← 0x7ff69ef21400
; (goto 0x7ff69ef21400)

© 2023 Software Diagnostics Services

Goto (an unconditional jump) is implemented via the JMP instruction.


Function calls are implemented via CALL instruction. For conditional
branches, please look at the official Intel documentation. We don’t use
these instructions in our exercises.
189

Function Parameters

Function Parameters
 foo(…);

 Right to left PUSH (x86)

 Left to right RCX, RDX, R8, R9, stack (x64)

© 2023 Software Diagnostics Services

On the x86 32-bit platform, C and C++ function parameters are pushed from
right to left to stack. On the x64 platform, the first 4 parameters from left to
right are moved to CPU registers, and the rest are passed via stack locations.
190

Struct Function Parameters

Struct Function Parameters


 ECX (x86) / RCX (x64)

Implicit struct object memory address (&myStruct)

 Right to left PUSH (x86) / RDX, R8, R9, stack (x64)

Struct function parameters (MyStruct::foo(...);)

© 2023 Software Diagnostics Services

When an object struct nonstatic member function is called, the first


parameter is implicit. It is an object address to help methods differentiate
between objects of the same structure type and reference correct fields’
memory. The rest of the parameters are passed as usual.
191

this

this
struct MyStruct
{
int a;
int foo(int i);
MyStruct* myAddress() { return this; }
} myStruct;

// myStruct.myAddress() == &myStruct

int MyStruct::foo(/* myStruct* this, */ int i)


{
return a + i + this->a + (*this).a;
}

© 2023 Software Diagnostics Services

The address of the current object is contained in this pointer inside C++
source code. It can be used to refer to the current object fields and methods
and can also be dereferenced.

The code example:

struct MyStruct
{
int a;
int foo(int i);
MyStruct* myAddress() { return this; }
} myStruct;

// myStruct.myAddress() == &myStruct

int MyStruct::foo(/* myStruct* this, */ int i)


{
return a + i + this->a + (*this).a;
}
192

Function Objects vs. Lambdas

Function Objects vs. Lambdas


struct lambda_1
{
auto operator()(int x) { return -x; }
} negate;

// int negate(int)
auto negate = [](int x) { return -x; };
negate(10);

[](int x) { return -x; }(10);

// mov edx,0Ah
// lea rcx,[rsp+130h] ; &negate
// call <lambda_1>::operator()

© 2023 Software Diagnostics Services

Lambdas are internally implemented as function objects.

The code example:

struct lambda_1
{
auto operator()(int x) { return -x; }
} negate;

// int negate(int)
auto negate = [](int x) { return -x; };
negate(10);

[](int x) { return -x; }(10);

// mov edx,0Ah
// lea rcx,[rsp+130h] ; &negate
// call <lambda_1>::operator()
193

Captures and Closures

Captures and Closures


{
int b{0};

auto negate1 = [](int x) { return b - x; };


auto negate2 = [b](int x) { return b - x; };
auto negate4 = [&b](int x) { return b - x; };
auto negate5 = [=](int x) { return b - x; };
auto negate6 = [&](int x) { return b - x; };
}

// [=] // [&]
struct lambda_5 { struct lambda_6 {
int b; int& rb;
lambda_5(int& _rb) : b(_rb) {} lambda_6(int& _rb) : rb(_rb) {}
auto operator()(int x) { return b-x; } auto operator()(int x) { return rb-x; }
} negate5; } negate6;

lambda_5::lambda_5: lambda_6::lambda_6:
mov qword ptr [rsp+10h],rdx mov qword ptr [rsp+10h],rdx
mov qword ptr [rsp+8],rcx mov qword ptr [rsp+8],rcx
mov rax,qword ptr [rsp+8] mov rax,qword ptr [rsp+8]
mov rcx,qword ptr [rsp+10h] mov rcx,qword ptr [rsp+10h]
mov ecx,dword ptr [rcx] mov qword ptr [rax],rcx
mov dword ptr [rax],ecx mov rax,qword ptr [rsp+8]
mov rax,qword ptr [rsp+8] ret
ret
© 2023 Software Diagnostics Services

Inside lambda code, it is possible to use local objects from the outer scope
either by copy or by reference. This mechanism is internally implemented
by lambda function objects.

The code example:

{
int b{0};
auto negate1 = [](int x) { return b - x; };
auto negate2 = [b](int x) { return b - x; };
auto negate4 = [&b](int x) { return b - x; };
auto negate5 = [=](int x) { return b - x; };
auto negate6 = [&](int x) { return b - x; };
}
194

The pseudo-code examples and the corresponding assembly language:

// [=] // [&]
struct lambda_5 { struct lambda_6 {
int b; int& rb;
lambda_5(int& _rb) : b(_rb) {} lambda_6(int& _rb) : rb(_rb) {}
auto operator()(int x) { return b-x; } auto operator()(int x) { return rb-x; }
} negate5; } negate6;

lambda_5::lambda_5: lambda_6::lambda_6:
mov qword ptr [rsp+10h],rdx mov qword ptr [rsp+10h],rdx
mov qword ptr [rsp+8],rcx mov qword ptr [rsp+8],rcx
mov rax,qword ptr [rsp+8] mov rax,qword ptr [rsp+8]
mov rcx,qword ptr [rsp+10h] mov rcx,qword ptr [rsp+10h]
mov ecx,dword ptr [rcx] mov qword ptr [rax],rcx
mov dword ptr [rax],ecx mov rax,qword ptr [rsp+8]
mov rax,qword ptr [rsp+8] ret
ret
195

Lambdas as Parameters

Lambdas as Parameters
auto negate = [](int x) { return -x; };

int apply(int arg, int (*pf)(int)) {


return pf(arg);
}

int apply2(int arg, decltype(negate) f) {


return f(arg);
}

void foo() {
// apply(100, negate::<lambda>::lambda_invoker_cdecl)
apply(100, negate); apply2(102, negate);
}

apply: apply2:
mov qword ptr [rsp+10h],rdx mov byte ptr [rsp+10h],dl // sizeof(negate) == 1
mov dword ptr [rsp+8],ecx mov dword ptr [rsp+8],ecx
sub rsp,38h sub rsp,28h
mov rax,qword ptr [rsp+48h] mov edx,dword ptr [rsp+30h]
mov qword ptr [rsp+20h],rax lea rcx,[rsp+38h]
mov ecx,dword ptr [rsp+40h] call negate::<lambda>::operator()
call qword ptr [rsp+20h] add rsp,28h
add rsp,38h ret
ret
© 2023 Software Diagnostics Services

Lambdas can be passed as function parameters. We can use decltype to


specify their type. If a normal function pointer is expected, then a special
invoker function is internally called that takes care of the call. If the lambda
type parameter is expected, the lambda function object operator() is
called.

auto negate = [](int x) { return -x; };

int apply(int arg, int (*pf)(int)) {


return pf(arg);
}

int apply2(int arg, decltype(negate) f) {


return f(arg);
}

void foo() {
// apply(100, negate::<lambda>::lambda_invoker_cdecl)
apply(100, negate); apply2(102, negate);
}
196

The corresponding assembly language fragments:

apply: apply2:
mov qword ptr [rsp+10h],rdx // sizeof(negate) == 1
mov dword ptr [rsp+8],ecx mov byte ptr [rsp+10h],dl
sub rsp,38h mov dword ptr [rsp+8],ecx
mov rax,qword ptr [rsp+48h] sub rsp,28h
mov qword ptr [rsp+20h],rax mov edx,dword ptr [rsp+30h]
mov ecx,dword ptr [rsp+40h] lea rcx,[rsp+38h]
call qword ptr [rsp+20h] call negate::<lambda>::operator()
add rsp,38h add rsp,28h
ret ret
197

Lambda Parameter Optimization

Lambda Parameter Optimization


auto negate = [](int x) { return -x; };

int apply3(int arg, const decltype(negate)& crf)


{
return crf(arg);
}

void bar()
{
apply3(103, negate);
}

apply3:
mov qword ptr [rsp+10h],rdx
mov dword ptr [rsp+8],ecx
sub rsp,28h
mov edx,dword ptr [rsp+30h]
mov rcx,qword ptr [rsp+38h]
call negate::<lambda>::operator()
add rsp,28h
ret

© 2023 Software Diagnostics Services

If you pass lambdas by reference, there is no function object copy.

The code example and the corresponding assembly language:

auto negate = [](int x) { return -x; };

int apply3(int arg, const decltype(negate)& crf)


{
return crf(arg);
}

void bar()
{
apply3(103, negate);
}
198

In the disassembly, we see the function object address passed via RDX that
later becomes an implicit RCX parameter for the operator():

apply3:
mov qword ptr [rsp+10h],rdx
mov dword ptr [rsp+8],ecx
sub rsp,28h
mov edx,dword ptr [rsp+30h]
mov rcx,qword ptr [rsp+38h]
call negate::<lambda>::operator()
add rsp,28h
ret
199

Lambdas as Unnamed Functions

Lambdas as Unnamed Functions


int apply(int arg, int (*pf)(int))
{
return pf(arg);
}

void foo()
{
int b{0};
apply(100, [](int x) -> int { return -x-4; });
apply(100, [=](int x) { return -x-b; });
}
lea rdx,[`2'::<lambda_1>::<lambda_invoker_cdecl>]
mov ecx,64h
call apply
...

lambda_invoker_cdecl:
mov dword ptr [rsp+8],ecx
sub rsp,38h
mov edx,dword ptr [rsp+40h]
lea rcx,[rsp+20h]
call `2'::<lambda_1>::operator()
add rsp,38h
ret
© 2023 Software Diagnostics Services

However, the most common usage of lambdas is unnamed functions in a


local context. In such a case, a temporary function object is created.
However, no context capture is allowed if lambdas are passed where
function pointers are expected.

The code example and the corresponding assembly language:

int apply(int arg, int (*pf)(int))


{
return pf(arg);
}

void foo()
{
int b{0};
apply(100, [](int x) -> int { return -x-4; });
apply(100, [=](int x) { return -x-b; });
}
200

lea rdx,[`2'::<lambda_1>::<lambda_invoker_cdecl>]
mov ecx,64h
call apply
...
lambda_invoker_cdecl:
mov dword ptr [rsp+8],ecx
sub rsp,38h
mov edx,dword ptr [rsp+40h]
lea rcx,[rsp+20h]
call `2'::<lambda_1>::operator()
add rsp,38h
ret
201

std::function Lambda Parameters

std::function Lambda Parameters


int apply4(int arg, std::function<int(int)> f) {
return f(arg);
}

void foo() {
int b{0};

apply4(100, [=](int x) -> int { return -x-b; });


}

mov dword ptr [rsp+19Ch],0


lea rax,[rsp+148h]
mov qword ptr [rsp+0B0h],rax
lea rdx,[rsp+19Ch]
lea rcx,[rsp+70h]
call `2'::<lambda_1>::<lambda_1> (00007ff6`39b41de0)
mov rdx,rax
mov rcx,qword ptr [rsp+0B0h]
call std::function<int __cdecl(int)>::function<int __cdecl(int)><`2'::<lambda_1>,0>
mov qword ptr [rsp+0B8h],rax
mov rdx,qword ptr [rsp+0B8h]
mov ecx,64h
call apply4

© 2023 Software Diagnostics Services

To allow passing lambdas to capture context, we can use std::function.

The code example and the corresponding assembly language:

int apply4(int arg, std::function<int(int)> f)


{
return f(arg);
}

void foo()
{
int b{0};

apply4(100, [=](int x) -> int { return -x-b; });


}
202

In disassembly, we see the function object constructor that captures the


context:

mov dword ptr [rsp+19Ch],0


lea rax,[rsp+148h]
mov qword ptr [rsp+0B0h],rax
lea rdx,[rsp+19Ch]
lea rcx,[rsp+70h]
call `2'::<lambda_1>::<lambda_1> (00007ff6`39b41de0)
mov rdx,rax
mov rcx,qword ptr [rsp+0B0h]
call std::function<int __cdecl(int)>::function<int
__cdecl(int)><`2'::<lambda_1>,0>
mov qword ptr [rsp+0B8h],rax
mov rdx,qword ptr [rsp+0B8h]
mov ecx,64h
call apply4
203

auto Lambda Parameters

auto Lambda Parameters


int apply5(int arg, const auto& f) {
return f(arg);
}

void foo() {
int b{0};

apply5(100, [=](int x) -> int { return -x-b; });


}
mov dword ptr [rsp+154h],0
lea rdx,[rsp+154h]
lea rcx,[rsp+88h]
call `2'::<lambda_1>::<lambda_1>
mov rdx,rax
mov ecx,64h
call apply5<`2'::<lambda_1> >
...
apply5<`2'::<lambda_1> >:
mov dword ptr [rsp+10h],rdx
mov dword ptr [rsp+8],ecx
sub rsp,28h
mov edx,dword ptr [rsp+30h]
mov rcx,qword ptr [rsp+38h]
call `2'::<lambda_1>::operator()
add rsp,28h
ret
© 2023 Software Diagnostics Services

To capture the context and allow more flexibility and efficiency, the modern
way is to use auto.

The code example and the corresponding assembly language:

int apply5(int arg, const auto& f)


{
return f(arg);
}

void foo()
{
int b{0};

apply5(100, [=](int x) -> int { return -x-b; });


}
204

In disassembly, we see all necessary functions are generated automatically:

mov dword ptr [rsp+154h],0


lea rdx,[rsp+154h]
lea rcx,[rsp+88h]
call `2'::<lambda_1>::<lambda_1>
mov rdx,rax
mov ecx,64h
call apply5<`2'::<lambda_1> >
...
apply5<`2'::<lambda_1> >:
mov dword ptr [rsp+10h],rdx
mov dword ptr [rsp+8],ecx
sub rsp,28h
mov edx,dword ptr [rsp+30h]
mov rcx,qword ptr [rsp+38h]
call `2'::<lambda_1>::operator()
add rsp,28h
ret
205

Lambdas as Return Values

Lambdas as Return Values


auto getFunc(int par) {
return [par](int x) { return -x - par; };
}

void foo() {
getFunc(200)(16);
}

mov edx,0C8h
lea rcx,[rsp+70h]
call getFunc
mov qword ptr [rsp+0B8h],rax
mov edx,10h
mov rcx,qword ptr [rsp+0B8h]
call `getFunc'::`2'::<lambda_1>::operator()
...
getFunc:
mov dword ptr [rsp+10h],edx
mov qword ptr [rsp+8],rcx
sub rsp,28h
lea rdx,[rsp+38h]
mov rcx,qword ptr [rsp+30h]
call `getFunc'::`2'::<lambda_1>::<lambda_1>
mov rax,qword ptr [rsp+30h]
add rsp,28h
ret
© 2023 Software Diagnostics Services

It is possible to return lambdas and thus mimic the so-called currying feature
of functional programming.

The code example and the corresponding assembly language:

auto getFunc(int par)


{
return [par](int x) { return -x - par; };
}

void foo()
{
getFunc(200)(16);
}
206

The disassembly shows that we return the underlying function object:

mov edx,0C8h
lea rcx,[rsp+70h]
call getFunc
mov qword ptr [rsp+0B8h],rax
mov edx,10h
mov rcx,qword ptr [rsp+0B8h]
call `getFunc'::`2'::<lambda_1>::operator()
...
getFunc:
mov dword ptr [rsp+10h],edx
mov qword ptr [rsp+8],rcx
sub rsp,28h
lea rdx,[rsp+38h]
mov rcx,qword ptr [rsp+30h]
call `getFunc'::`2'::<lambda_1>::<lambda_1>
mov rax,qword ptr [rsp+30h]
add rsp,28h
ret
207

Virtual Function Call

Virtual Function Call

© 2023 Software Diagnostics Services

This section provides an overview of virtual function calls in C++.


208

VTBL Memory Layout

VTBL Memory Layout


struct Base Base::`vftable': &Base::vmethod1
{
virtual void vmethod1 () {}
&Base::vmethod2
virtual void vmethod2 () {}
} myBase;

struct Derived : Base Derived::`vftable': &Base::vmethod1


{
int field;
&Derived::vmethod2
void vmethod2 () override {}
} myDerived;

© 2023 Software Diagnostics Services

These virtual function calls are implemented uniformly by having a specific


virtual function table (VTBL) for each structure where the addresses of the
base structure methods are replaced with those of the derived structure
methods, if any.

The code examples corresponding to the memory diagram:

struct Base
{
virtual void vmethod1 () {}
virtual void vmethod2 () {}
} myBase;

struct Derived : Base


{
int field;
void vmethod2 () override {}
} myDerived;
209

VPTR and Struct Memory Layout

VPTR and Struct Memory Layout


Base *pMyBase = &myBase; Base::`vftable': &Base::vmethod1
pMyBase->vmethod2();

&Base::vmethod2
myBase: vptr (Base::`vftable')

pMyBase = &myDerived; Derived::`vftable': &Base::vmethod1


pMyBase->vmethod2();

&Derived::vmethod2
myDerived: vptr (Derived::`vftable')

© 2023 Software Diagnostics Services

Every object whose structure has virtual methods has an implicit virtual
function table pointer (VPTR) as its first member containing an address of
the corresponding structure virtual functions table. Therefore, each virtual
function call from a base structure pointer is a type-independent call where
the target function address is easily calculated based on the address of the
virtual function table and virtual function offset.

The code examples corresponding to the memory diagram:

Base *pMyBase = &myBase;


pMyBase->vmethod2();

pMyBase = &myDerived;
pMyBase->vmethod2();
210

Templates: A Planck-length Introduction

Templates
A Planck-length Introduction

© 2023 Software Diagnostics Services

C++ templates and template metaprogramming are a vast universe. We only


cover a Planck-length distance in this introduction.
211

Why Templates?

Why Templates?

 Less code to write, reusability

 Better abstractions, type safety

 Performance, flexibility

 Metafunctions, metaprogramming

© 2023 Software Diagnostics Services

In the following slides, we briefly cover various “why” aspects.


212

Reusability

Reusability
tmpl<int> {…}

template<typename T> tmpl {…} compiler tmpl<struct S> {…}

template<typename T> struct S


{
T add (const T& op1, const T& op2) S(int _val) : val(_val) {}
S operator+(const S& s) const {
{ return S(val + s.val);
}
return op1 + op2; int val;
};
} //S operator+(const S& s1, const S& s2) {
// return S(s1.val + s2.val);
//}
void foo()
{
add<int>(1, 2);
add<struct S>(struct S(1), struct S(2));
}
© 2023 Software Diagnostics Services

Templates allow us to write less code with higher abstractions, delegating


implementation details to a compiler. The compiler checks that template
arguments are compatible, for example, that they implement the required
operations and methods.

The code example:

template<typename T>
T add (const T& op1, const T& op2)
{
return op1 + op2;
}

void foo()
{
add<int>(1, 2);
add<struct S>(struct S(1), struct S(2));
}
213

struct S
{
S(int _val) : val(_val) {}
S operator+(const S& s) const
{
return S(val + s.val);
}
int val;
};

// S operator+(const S& s1, const S& s2)


// {
// return S(s1.val + s2.val);
// }
214

Types of Templates

Types of Templates
 Struct templates

template <typename T> struct TStruct { T data; };


struct TStruct<int> ts;

 Function templates

struct FStruct {
template <typename T> T zero() { return T(0); }
} fs;
fs.zero<int>();

 Variable templates

template <typename T> T zero{0};


auto z = zero<int>;

© 2023 Software Diagnostics Services

The following code provides examples for struct, function, and (recent)
variable template categories:

template <typename T> struct TStruct { T data; };


struct TStruct<int> ts;

struct FStruct
{
template <typename T> T zero() { return T(0); }
} fs;
fs.zero<int>();

template <typename T> T zero{0};


auto z = zero<int>;
215

Types of Template Parameters

Types of Template Parameters


 Type parameters

template <typename T> ...

 Non-type parameters

template <int c> decltype(c) constant() { return c; }


constant<1>();

call constant<1>
...
constant<1>:
257 00007ff6`daa73fa0 mov eax,1
257 00007ff6`daa73fa5 ret

© 2023 Software Diagnostics Services

There are two categories of template parameters: type and non-type.

The code example and the corresponding assembly language:

template <int c> decltype(c) constant() { return c; }


constant<1>();

Non-type template parameters allow generation of the very compact code


with a distinct purpose:

call constant<1>
...
constant<1>:
257 00007ff6`daa73fa0 mov eax,1
257 00007ff6`daa73fa5 ret
216

Type Safety

Type Safety
template <typename T> struct S
{
T add (const T& op1, const T& op2) S(int _val) : val(_val) {}
S operator+(const S& s) const {
{ return S(val + s.val);
}
return op1 + op2; int val;
};
}
struct M
{
M(int _val) : val(_val) {}
void foo() int val;
};
{
add(struct M(1), struct M(2));
}

© 2023 Software Diagnostics Services

Compared to pointer casting, the general template code enforces type


safety by checking the required operations and compatible types.

The code example:

struct S
{
S(int _val) : val(_val) {}
S operator+(const S& s) const {
return S(val + s.val);
}
int val;
};

struct M
{
M(int _val) : val(_val) {}
int val;
};
217

template <typename T>


T add (const T& op1, const T& op2)
{
return op1 + op2;
}
void foo()
{
add(struct M(1), struct M(2));
}
218

Flexibility

Flexibility
template<>
struct M add<struct M> (const struct M& op1,
const struct M& op2)
{
return op1.val + op2.val;
}

void foo()
{
add<struct M>(struct M(1), struct M(2));
}

template<> decltype(13) constant<13>() { return 14; }

© 2023 Software Diagnostics Services

It is also possible to specialize template definitions for specific types for


performance and other reasons.

The code example:

template<>
struct M add<struct M> (const struct M& op1,
const struct M& op2)
{
return op1.val + op2.val;
}
void foo()
{
add<struct M>(struct M(1), struct M(2));
}

template<> decltype(13) constant<13>() { return 14; }


219

Metafunctions

Metafunctions
// f: Value -> Value
// mf: Type -> Type

template <typename T>


struct Pointer
{
// typedef T* type;
using type = T*;
};

Pointer<int>::type pInt; // int *pInt;


Pointer<int *>::type ppInt; // int **ppInt;
Pointer<struct M>::type pM; // struct M *pM;

© 2023 Software Diagnostics Services

Metafunctions transform type: they take types as parameters and return


types as output.

The code example:

// f: Value -> Value


// mf: Type -> Type
template <typename T>
struct Pointer
{
// typedef T* type;
using type = T*;
};

Pointer<int>::type pInt; // int *pInt;


Pointer<int *>::type ppInt; // int **ppInt;
Pointer<struct M>::type pM; // struct M *pM;
220

Iterators as Pointers

Iterators as Pointers

© 2023 Software Diagnostics Services

Now, we take a bird’s eye view of standard library containers, iterators, and
algorithms from a pointer perspective.
221

Containers

Containers
 std::array
 std::vector
 std::deque
 std::(forward_)list
 std::(unordered_)(multi)set
 std::(unordered_)(multi)map
 std::stack
 std::(priority_)queue

© 2023 Software Diagnostics Services

There are many container types in the standard C++ library (formerly called
STL, Standard Template Library). In this course edition, we don’t delve into
their specifics. We hope the names intuitively suggest their semantics.
222

Iterators

Iterators
std::vector<int> v{1, 2, 3, 4, 5};
std::vector<int>::iterator it = v.begin();

while (it != v.end())


{
std::cout << *it;
++it;
}

int a[5]{ 1, 2, 3, 4, 5 };
int *pa = &a[0];

while (pa != &a[5])


{
std::cout << *pa;
++pa;
}

© 2023 Software Diagnostics Services

An iterator is a pointer abstraction. You can move it (depending on container


semantics) like a pointer increment/decrement, and you can dereference it
like a pointer to get a value:

std::vector<int> v{1, 2, 3, 4, 5};


std::vector<int>::iterator it = v.begin();

while (it != v.end())


{
std::cout << *it;
++it;
}

int a[5]{ 1, 2, 3, 4, 5 };
int *pa = &a[0];

while (pa != &a[5])


{
std::cout << *pa;
++pa;
}
223

Constant Iterators

Constant Iterators
std::vector<int> v{1, 2, 3, 4, 5};
std::vector<int>::const_iterator cit = v.cbegin();

while (cit != v.cend())


{
std::cout << *cit;
++cit;
}

int a[5]{ 1, 2, 3, 4, 5 };
const int *cpa = &a[0];

while (cpa != &a[5])


{
std::cout << *cpa;
++cpa;
}

© 2023 Software Diagnostics Services

Like a pointer to constant values, there are constant iterators:

std::vector<int> v{1, 2, 3, 4, 5};


std::vector<int>::const_iterator cit = v.cbegin();

while (cit != v.cend())


{
std::cout << *cit;
++cit;
}

int a[5]{ 1, 2, 3, 4, 5 };
const int *cpa = &a[0];

while (cpa != &a[5])


{
std::cout << *cpa;
++cpa;
}
224

Pointers as Iterators

Pointers as Iterators
int a[5]{ 1, 2, 3, 4, 5 };
int *itarr = std::begin(arr);

// auto itarr = std::begin(arr);

while (itarr != std::end(arr))


{
std::cout << *itarr;
++itarr;
}

const int *citarr = std::cbegin(arr);

while (citarr != std::cend(arr))


{
std::cout << *citarr;
++citarr;
}

© 2023 Software Diagnostics Services

Pointers can also be considered as iterators:

int a[5]{ 1, 2, 3, 4, 5 };
int *itarr = std::begin(arr);

// auto itarr = std::begin(arr);

while (itarr != std::end(arr))


{
std::cout << *itarr;
++itarr;
}

const int *citarr = std::cbegin(arr);

while (citarr != std::cend(arr))


{
std::cout << *citarr;
++citarr;
}
225

Algorithms

Algorithms

std::vector<int> v{2, 1, 3, 5, 4};

std::sort(v.begin(), v.end());

int arr[5]{ 2, 1, 3, 5, 4 };

std::sort(std::begin(arr), std::end(arr));

© 2023 Software Diagnostics Services

Both iterators and containers and pointers as iterators and arrays can be
used with the C++ standard library algorithms.

The code examples:

std::vector<int> v{2, 1, 3, 5, 4};


std::sort(v.begin(), v.end());

int arr[5]{ 2, 1, 3, 5, 4 };
std::sort(std::begin(arr), std::end(arr));
226

Memory Ownership

Memory Ownership

© 2023 Software Diagnostics Services

We now look at common memory ownership problems and see how they
are resolved in modern C++.
227

Pointers as Owners

Pointers as Owners
 Ownership of dynamic memory after allocation

 Contain the address of allocated memory

 Can try Read/Write/Execute/Release

© 2023 Software Diagnostics Services

Pointers can be considered owners of dynamically allocated memory since


they contain the address, and that memory can be accessed through them.
228

Problems with Pointer Owners

Problems with Pointer Owners


 Shared ownership after copying a pointer content

 Leak: overwriting the previous address without release

 Multiple release

 Dangling pointers pointing to already released memory

 Leak: no release before going out of scope

© 2023 Software Diagnostics Services

However, manual pointer usage is prone to multiple errors due to possible


shared ownership, crashes due to possible multiple releases, and memory
leaks due to dangling pointers and going out of scope.
229

Smart Pointers

Smart Pointers

© 2023 Software Diagnostics Services

To solve memory ownership problems, modern C++ included several kinds


of smart pointers in its standard library.
230

Basic Design

Basic Design
 A structure with operators mimicking pointer behavior
such as dereferencing

 Encapsulates raw pointers

 Restricts undesirable behavior

 Contains reference count that tracks copies

 Provides a destructor to release memory if the


reference count becomes 0

© 2023 Software Diagnostics Services

A smart pointer should include functionality similar to raw pointers for


seamless use during the refactoring of legacy code and simultaneously
eliminate most, if not all, problems with raw pointer usage.
231

Unique Pointers

Unique Pointers
std::unique_ptr<int> fooU(std::unique_ptr<int> pIntPar)
{
std::unique_ptr<int> pInt(pIntPar.release());
assert(pIntPar == nullptr);
int n = *pInt; // *pInt.get();
return pInt;
}

void barU()
{
std::unique_ptr<int> pIntPar(new int(0));
std::unique_ptr<int> pIntRes;
assert(pIntRes == nullptr);
pIntRes = fooU(std::move(pIntPar));
assert(pIntPar == nullptr && pIntRes != nullptr);
}

© 2023 Software Diagnostics Services

For smart pointers without sharing functionality, the unique_ptr should be


used. Copying must transfer ownership, making the source nullptr.

The code example:

std:: <int> fooU(std::unique_ptr<int> pIntPar)


{
std::unique_ptr<int> pInt(pIntPar.release());
assert(pIntPar == nullptr);
int n = *pInt; // *pInt.get();
return pInt;
}

void barU()
{
std::unique_ptr<int> pIntPar(new int(0));
std::unique_ptr<int> pIntRes;
assert(pIntRes == nullptr);
pIntRes = fooU(std::move(pIntPar));
assert(pIntPar == nullptr && pIntRes != nullptr);
}
232

Handles as Unique Pointers

Handles as Unique Pointers

// HANDLE hXXX = CreateXXX(...);


// CloseHandle(hXXX);

auto hDeleter =
[](auto ph)
{
if (ph && (*ph != INVALID_HANDLE_VALUE))
::CloseHandle(*ph);
};

using Handle = std::unique_ptr<HANDLE, decltype(hDeleter)>;

© 2023 Software Diagnostics Services

Resource handles are good candidates for unique pointers, but they should
be supplied with a custom deletion mechanism.

The code example:

// HANDLE hXXX = CreateXXX(...);


// CloseHandle(hXXX);

auto hDeleter =
[](auto ph)
{
if (ph && (*ph != INVALID_HANDLE_VALUE))
::CloseHandle(*ph);
};

using Handle = std::unique_ptr<HANDLE, decltype(hDeleter)>;


233

Shared Pointers

Shared Pointers
std::shared_ptr<int> fooS(std::shared_ptr<int> pIntPar)
{
std::shared_ptr<int> pInt(pIntPar);
assert(pIntPar != nullptr &&
pInt.use_count() == 3 && pIntPar.use_count() == 3);
int n = *pInt;
return pInt;
}

void barS()
{
std::shared_ptr<int> pIntPar(new int(0));
std::shared_ptr<int> pIntRes;
assert(pIntRes == nullptr &&
pIntRes.use_count() == 0 && pIntPar.use_count() == 1);
pIntRes = fooS(pIntPar);
assert(pIntPar != nullptr && pIntRes != nullptr &&
pIntRes.use_count() == 2 && pIntPar.use_count() == 2);
}

© 2023 Software Diagnostics Services

If we want to freely copy pointers around with all new copies pointing to
the same memory, then the shared_ptr is our choice:

std::shared_ptr<int> fooS(std::shared_ptr<int> pIntPar)


{
std::shared_ptr<int> pInt(pIntPar);
assert(pIntPar != nullptr &&
pInt.use_count() == 3 && pIntPar.use_count() == 3);
int n = *pInt;
return pInt;
}

void barS()
{
std::shared_ptr<int> pIntPar(new int(0));
std::shared_ptr<int> pIntRes;
assert(pIntRes == nullptr &&
pIntRes.use_count() == 0 && pIntPar.use_count() == 1);
pIntRes = fooS(pIntPar);
assert(pIntPar != nullptr && pIntRes != nullptr &&
pIntRes.use_count() == 2 && pIntPar.use_count() == 2);
}
234

RAII

RAII

© 2023 Software Diagnostics Services

Finally, the RAII idiom is specifically about managing resources, including


memory,
235

RAII Definition

RAII Definition
struct Resource
{
 Resource Acquisition Is Initialization
Resource()
{
// acquire resource, e.g., new
// initialize resource, e.g., set memory values to 0
}

 Includes resource release

~Resource()
{
// release resource, e.g., delete
}
};

© 2023 Software Diagnostics Services

An RAII structure encapsulates simultaneous “atomic” resource acquisition


and initialization in its constructors and includes resource release logic in its
destructor.

The code example:

struct Resource
{
Resource()
{
// acquire resource, e.g., new
// initialize resource, e.g., set memory values to 0
}

~Resource()
{
// release resource, e.g., delete
}
};
236

RAII Advantages

RAII Advantages
 Resource safety

void foo { Resource r; }

 Resource life-cycle predictability

 Exception safety

try
{
Resource r;
throw -1;
}
catch (...)
{
}
© 2023 Software Diagnostics Services

There are several advantages to using the RAII idiom. When going out of
scope, it automatically releases a resource due to a called destructor:

void foo { Resource r; }

In the presence of exceptions, the destructor releases the acquired resource


automatically. All these contribute to the predictable resource life cycle.

try
{
Resource r;
throw -1;
}
catch (...)
{
}
237

Handle RAII

Handle RAII
struct RAII_HANDLE : Handle
{
RAII_HANDLE(HANDLE _h) : Handle(&h, hDeleter), h(_h) {};
void operator= (HANDLE _h) { Handle::reset(); h = _h;
Handle::reset(&h); }
operator HANDLE() const { return h; }
private:
HANDLE h;
};

int foo ()
{
RAII_HANDLE hFile = ::CreateFile(...);

if (hFile == INVALID_HANDLE_VALUE)
return -1;
// ...
return 0;
}

© 2023 Software Diagnostics Services

The code example of encapsulating Windows handles using the RAII idiom.
We reuse the Handle type from the previous Handles as Unique Pointers
slide:

struct RAII_HANDLE : Handle


{
RAII_HANDLE(HANDLE _h) : Handle(&h, hDeleter), h(_h) {};
void operator= (HANDLE _h) { Handle::reset(); h = _h;
Handle::reset(&h); }
operator HANDLE() const { return h; }
private:
HANDLE h;
};

int foo ()
{
RAII_HANDLE hFile = ::CreateFile(...);
if (hFile == INVALID_HANDLE_VALUE)
return -1;
// ...
return 0;
}
238

Threads and Synchronization

Threads and Synchronization

© 2023 Software Diagnostics Services

Our final section in this edition is about threads and synchronization in C++.
239

Threads in C/C++

Threads in C/C++
// typedef DWORD (WINAPI *PTHREAD_START_ROUTINE)(
// LPVOID lpThreadParameter
// );
// typedef PTHREAD_START_ROUTINE LPTHREAD_START_ROUTINE;

DWORD WINAPI threadProc (void *param)


{
Sleep((int)param * 1000);
return 0;
}

void foo()
{
CreateThread(NULL, 0, threadProc, (void *)5, 0, NULL);
}

© 2023 Software Diagnostics Services

Traditional C/C++ threading using raw Windows API is limited. Passing any
parameter type and casting it is error-prone.

The code example:

// typedef DWORD (WINAPI *PTHREAD_START_ROUTINE)(


// LPVOID lpThreadParameter
// );
// typedef PTHREAD_START_ROUTINE LPTHREAD_START_ROUTINE;

DWORD WINAPI threadProc (void *param)


{
Sleep((int)param * 1000);
return 0;
}

void foo()
{
CreateThread(NULL, 0, threadProc, (void *)5, 0, NULL);
}
240

Threads in C++ Proper

Threads in C++ Proper


// functions, function objects, lambdas
void threadProcCpp(int param, std::wstring msg)
{
::Sleep(param * 1000);
::MessageBox(NULL,
(L"New Thread Created! " + msg).c_str(),
L"Thread Check", MB_OK | MB_SETFOREGROUND);
}

void foo()
{
std::thread threadCpp(threadProcCpp, 6, L"Hello");
//...
threadCpp.join();
// std::jthread threadCpp(threadProcCpp, 6, L"Hello");
}

© 2023 Software Diagnostics Services

The proper C++ standard library thread allows the utilization of the power
of modern C++ abstractions. Please also note the existence of jthread that
combines both thread creation and join:

// functions, function objects, lambdas


void threadProcCpp(int param, std::wstring msg)
{
::Sleep(param * 1000);
::MessageBox(NULL,
(L"New Thread Created! " + msg).c_str(),
L"Thread Check", MB_OK | MB_SETFOREGROUND);
}

void foo()
{
std::thread threadCpp(threadProcCpp, 6, L"Hello");
//...
threadCpp.join();
// std::jthread threadCpp(threadProcCpp, 6, L"Hello");
}
241

Synchronization Problems

Synchronization Problems
long long counter{0};

void threadProcCpp(std::wstring msg)


{
while (true)
{
std::wcout << msg << L": " << ++counter << std::endl;
}
}

void foo()
{
std::jthread thread1(threadProcCpp, L"Hello1");
std::jthread thread2(threadProcCpp, L"Hello2");
}

// Hello2: 1694
// : 1695

© 2023 Software Diagnostics Services

In the code example, the output from << operators from different threads
is mixed and looks like garbage:

long long counter{0};

void threadProcCpp(std::wstring msg)


{
while (true)
{
std::wcout << msg << L": " << ++counter << std::endl;
}
}

void foo()
{
std::jthread thread1(threadProcCpp, L"Hello1");
std::jthread thread2(threadProcCpp, L"Hello2");
}

// Hello2: 1694
// : 1695
242

Synchronization Solution

Synchronization Solution
std::atomic<long long> counter{0};

std::mutex m;

void threadProcCpp(std::wstring msg)


{
while (true)
{
std::scoped_lock lock {m};
std::wcout << msg << L": " << ++counter << std::endl;
}
}

void foo()
{
std::jthread thread1(threadProcCpp, L"Hello1");
std::jthread thread2(threadProcCpp, L"Hello2");
}

© 2023 Software Diagnostics Services

The following code example solves the data race problem by guarding access
via scoped_lock that is implemented using mutex:

std::atomic<long long> counter{0};

std::mutex m;

void threadProcCpp(std::wstring msg)


{
while (true)
{
std::scoped_lock lock {m};
std::wcout << msg << L": " << ++counter << std::endl;
}
}

void foo()
{
std::jthread thread1(threadProcCpp, L"Hello1");
std::jthread thread2(threadProcCpp, L"Hello2");
}
243

Resources

Resources

© 2023 Software Diagnostics Services

Now, I have a few slides about references and resources for further reading.
244

C and C++

C and C++
 My Road to Modern C++

 A Tour of C++, Third Edition

 Embracing Modern C++ Safely

 cppreference

© 2023 Software Diagnostics Services

My reading list up to C++17:

My Road to Modern C++


https://www.linkedin.com/pulse/my-road-modern-c-dmitry-vostokov/

cppreference
https://en.cppreference.com/w/

Two recent books are also recommended:

A Tour of C++, Third Edition


Embracing Modern C++ Safely
245

Reading Windows-based Code

Reading Windows-based Code

 Legacy Windows code and C language

 Part 1
 Part 2
 Part 3
 Part 4
 Part 5
 Part 6

© 2023 Software Diagnostics Services

In 2004, I developed training sessions for reading Windows-based code. It


combines Windows-specific types with required C language knowledge to
follow basic SDK samples.

Part 1
https://patterndiagnostics.com/Training/Reading-Windows-based-Code-Part1.pdf

Part 2
https://patterndiagnostics.com/Training/Reading-Windows-based-Code-Part2.pdf

Part 3
https://patterndiagnostics.com/Training/Reading-Windows-based-Code-Part3.pdf

Part 4
https://patterndiagnostics.com/Training/Reading-Windows-based-Code-Part4.pdf

Part 5
https://patterndiagnostics.com/Training/Reading-Windows-based-Code-Part5.pdf
246

Part 6
https://patterndiagnostics.com/Training/Reading-Windows-based-Code-Part6.pdf
247

Windows (C and C++)

Windows (C and C++)


 Programming Windows, 5th Edition
 ATL Internals
 Windows Graphics Programming: Win32 GDI and DirectDraw
 Introduction to 3D Game Programming with DirectX 12
 Windows via C/C++
 Introducing Windows 7 for Developers
 Mastering Windows 8 C++ App Development
 Windows System Programming, 4th Edition
 Windows 10 System Programming
 Windows Kernel Programming, 2nd Edition
 Concurrent Programming on Windows
 The Old New Thing
 Fundamentals of Audio & Video Programming for Games
 Software Application Development: A Visual C++, MFC, and STL Tutorial
 Dump2Picture

© 2023 Software Diagnostics Services

The Old New Thing


https://devblogs.microsoft.com/oldnewthing/

The following project illustrates idioms like RAII in the context of Windows
API:

Dump2Picture
https://bitbucket.org/softwarediagnostics/dump2picture

The following Windows C and C++ books are recommended:

Programming Windows, 5th Edition


ATL Internals
Windows Graphics Programming: Win32 GDI and DirectDraw
Introduction to 3D Game Programming with DirectX 12
Windows via C/C++
Introducing Windows 7 for Developers
248

Mastering Windows 8 C++ App Development


Windows System Programming, 4th Edition
Windows 10 System Programming
Windows Kernel Programming, 2nd Edition
Concurrent Programming on Windows
Fundamentals of Audio & Video Programming for Games
Software Application Development: A Visual C++, MFC, and STL Tutorial
249

Training (Windows C and C++)

Training (Windows C and C++)


 Practical Foundations of Windows Debugging, Disassembling, Reversing,
Second Edition

 Accelerated Windows Memory Dump Analysis, Sixth Edition

 Accelerated Windows Debugging4, Third Edition

 Accelerated Disassembly, Reconstruction and Reversing, Third Edition

 Accelerated Windows API for Software Diagnostics

© 2023 Software Diagnostics Services

Additional training courses that use Windows C and C++:

Practical Foundations of Windows Debugging, Disassembling, Reversing,


Second Edition
https://www.patterndiagnostics.com/practical-foundations-windows-
debugging-disassembling-reversing

Accelerated Windows Memory Dump Analysis, Sixth Edition


https://www.patterndiagnostics.com/accelerated-windows-memory-
dump-analysis-book

Accelerated Windows Debugging4, Third Edition


https://www.patterndiagnostics.com/accelerated-windows-debugging-
book
250

Accelerated Disassembly, Reconstruction and Reversing, Third Edition


https://www.patterndiagnostics.com/accelerated-disassembly-
reconstruction-reversing-book

Accelerated Windows API for Software Diagnostics


https://www.patterndiagnostics.com/accelerated-windows-api-book

You might also like