Vostokov Dmitry Memory Thinking For C and C++ Windows Diagnostics
Vostokov Dmitry Memory Thinking For C and C++ Windows Diagnostics
Windows Diagnostics
Slides with Descriptions Only
Dmitry Vostokov
Software Diagnostics Services
OpenTask
2
Memory Thinking for C & C++ Windows Diagnostics: Slides with Descriptions
Only
A CIP catalog record for this book is available from the British Library.
Table of Contents
Table of Contents 3
Preface 15
Introduction 17
Prerequisites 18
Training Goals 19
Training Principles 20
Schedule 21
Training Idea 22
Thought Process 33
4
Philosophy of Pointers 34
Pointer 35
Pointer Dereference 36
Many to One 37
Invalid Pointer 39
Pointer to Pointer 42
Pointers as Entities 46
Mental Exercise 48
Names as Addresses 51
Pointers to Structures 54
Arrays 55
Basic Types 58
Integers 62
Long Integers 63
Little-Endian System 64
Short Integers 65
Booleans 69
Bytes 70
Size 71
6
Alignment 72
LLP64 73
Entity Conversion 76
Numeric Promotion/Conversion 78
Numeric Conversion 79
Incompatible Types 80
Forcing 81
Structures 84
Access Level 85
Pointer to Structure 88
Macros 115
Overview 119
References 144
Values 146
Functions 150
Lambdas 181
this 191
Reusability 212
Flexibility 218
Metafunctions 219
Containers 221
Iterators 222
13
Algorithms 225
RAII 234
Resources 243
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:
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
https://www.linkedin.com/company/software-diagnostics-institute/
https://www.linkedin.com/groups/8473045/
16
Introduction
Dmitry Vostokov
Software Diagnostics Services
18
Prerequisites
Prerequisites
Development experience
and (optional)
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
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
Lots of pictures
Lots of examples
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);
Training Idea
Training Idea
Reversing training
• 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
• 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
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
C
C++ as a better C
Windows specifics
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
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
Scala/FP Functions
Python Data
Philosophy of Pointers
Philosophy of Pointers
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
Pointer Dereference
Pointer Dereference
Pointer
Many to One
Many to One
Pointer Pointer
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
Pointer Pointer
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
Pointer
When you dereference an invalid pointer, you get a problem; for example,
you fail to get an imaginary apple I point to.
41
Pointer
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
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 Pointer
A B C
1 2 3
fadb6810 86556810 a656ffbd
Pointer Pointer
86556810 a656ffbd
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
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
Mental Exercise
Mental Exercise
How many pointers can you count?
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
2ab1000: 2ab1008
2ab1004: ffffffff
2ab1000: 2ab1008 ffffffff
2ab1008: 2ab1010
2ab1008: 2ab1010 2ab100c
2ab100c: 2ab100c
2ab1010: 00000000 00002000
2ab1010: 00000000
2ab1014: 00002000
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
Names as Addresses
Names as Addresses
2ab1000: 2ab1008
2ab1004: ffffffff
2ab1008: 2ab1010
2ab100c: 2ab100c
2ab1010: 00000000
2ab1014: 00002000
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
2ab1000: 2ab1008
2ab1004: ffffffff
2ab1008: 2ab1010
2ab100c
2ab1010: 00000000
2ab1014: 00002000
Pointers to Structures
Pointers to Structures
2ab1000: 2ab1008
2ab1004: ffffffff
2ab1008: 2ab1010
2ab1216: 2ab1004
2ab100c
2ab1010: 00000000
2ab1014: 00002000
Arrays
Arrays
2ab1000: 2ab1008
ffffffff
2ab1008: 2ab1010
2ab1216: 2ab1000
2ab100c
2ab1010: 00000000
00002000
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
2ab1000: ‘H’
2ab1001: ‘e’
2ab1002: ‘l’
2ab1216: 2ab1000
2ab1003: ‘l’
2ab1004: ‘o’
2ab1005: 00
Basic Types
Basic Types
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
char c;
char *pstr; // pstr == 2ab1000
&pstr; // 2ab1216
c = *pstr; // c == ‘H’
++pstr; // 2ab1001
c = *pstr; // c == ‘e’
c = *(pstr+1); // c == ‘l’
60
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
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.
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
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
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
2ab1004: ffffffff
int i;
int *pi; // 2ab1000
2ab1008: 2ab1010
π // 2ab1216
2ab100c: 2ab100c
i = *pi; // i == 0x2ab1008
++pi; // 2ab1004
2ab1010: 00000000 i = *pi; // i == 0xffffffff
// i == -1
2ab1014: 00002000 i = *(pi+1); // i == 0x2ab1010
int i;
int *pi; // 2ab1000
π // 2ab1216
i = *pi; // i == 0x2ab1008
++pi; // 2ab1004
i = *pi; // i == 0xffffffff
// i == -1
i = *(pi+1); // i == 0x2ab1010
63
Long Integers
Long Integers
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
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
2ab1003: 4
MOVBE ; x86_64
2ab1004: 5 ; Move Data After Swapping Bytes
2ab1005: 6
char ba[4]; // { 1, 2, 3, 4 }
int i; // 0x4030201
65
Short Integers
Short Integers
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
short s;
short *ps; // 2ab1000
&ps; // 2ab1216
s = *ps; // s == 0x1008
++ps; // 2ab1002
s = *ps; // s == 0x2ab
s = *(ps+1); // s == 0xffff
66
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
uint8_t b;
uint32_t dw;
uint64_t qw;
uintptr_t p;
uint8_t b;
uint32_t dw;
uint64_t qw;
uintptr_t p;
69
Booleans
Booleans
bool b;
b = true;
b = false;
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.
bool b;
b = true;
b = false;
70
Bytes
Bytes
std::byte b;
Not a character
Not an integer
The latest C++ standards also include a distinct type for bytes.
std::byte b;
71
Size
Size
sizeof operator
The sizeof operator can evaluate the size of types, variables, and target
result types of expressions (without expression evaluation).
int i;
size = sizeof i;
size = sizeof(1 + 1);
72
Alignment
Alignment
2ab0ff8: 0
2ab0ffc: 0
alignof operator
2ab1000: 1
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.
LLP64
LLP64
sizeof(int) == 4
sizeof(int *) == 8
sizeof(long) == 4
sizeof(long long) == 8
Windows uses the so-called LLP64 data model where only long long
integers and pointers are 64-bit.
74
void foo(void);
void *p;
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.
void foo(void);
void *p;
75
auto a = "Hello";
C++11 added automatic type specification, so the type is deduced from the
initializing expression.
auto a = "Hello";
Entity Conversion
Entity Conversion
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
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 π // != 2ab1216
i = *pi; // i == 0x78563412
2ab1005: cd // Intel LSB
// endian
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.
Numeric Promotion/Conversion
Numeric Promotion/Conversion
short s = c;
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)
The following code examples solve the problem with the infinite loop:
Incompatible Types
Incompatible Types
(type)(expr) // C-Style
reinterpret_cast<type>(expr)
p = reinterpret_cast<int *>(1);
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.
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);
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
Structures
Access Level
Access Level
struct MyStruct
{
// public:
int field1;
private:
int field2;
} myStruct;
myStruct.field1 = 1;
myStruct.field2 = 2;
Fields with the private access specified cannot be referenced from the
outside.
struct MyStruct
{
// public:
int field1;
private:
int field2;
} myStruct;
myStruct.field1 = 1;
myStruct.field2 = 2;
86
*
struct tagStruct class tagClass
{ {
// public: public: // (private:)
int field; == int field;
// ... // ...
}; };
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.
Pointer to Structure
Pointer to Structure
Pointer
Pointer
Pointer Pointer
Here, we assign the value of one pointer to another, and both now point to
the same structure.
Pointer Pointer
If we dereference both, we get the same value with the same address.
assert(&*pMyStruct == &*pMyStruct2);
92
MyStruct *pMyStruct;
Pointer
MyStruct *pMyStruct;
93
Pointer
MyStruct *pMyStruct;
MyStruct myStruct = *pMyStruct;
94
Pointer
Pointer Pointer
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.
Pointer Pointer
struct OuterStruct
{
int field1;
struct InnerStruct1
{
int field1;
int field2;
} field2;
struct InnerStruct2
{
int field;
} field3;
} myStruct;
99
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
This example shows field addresses and access when we have a structure
value.
&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
2ab1004: ffffffff
2ab1010: 00000000
2ab1014: 00002000
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
This example shows field addresses and access when we have a pointer to a
structure value.
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
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;
};
myDerived.field;
myDerived.Base::field;
Structure Slicing
Structure Slicing
struct Base
{
int field;
};
struct Base
{
int field;
};
104
myDerived.field;
myDerived.Base::field;
It is possible to inherit privately. In such a case, the base structure fields are
inaccessible from the outside, even with the explicit qualification.
struct Base
{
int field;
};
myDerived.field;
myDerived.Base::field;
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.
0:000> dt wmain::__l2::Struct Dereference!wmain::__l2::Struct +0x000 field1 : Bool +0x002 field2 : Int2B +0x008 field4 : Int8B
Fields may be aligned according to their default type alignment, which may
introduce gaps, increasing the overall structure size.
struct Struct
{
bool field1;
short field2;
long long field3;
} myStruct;
#pragma pack(1)
struct StructPacked
{
bool field1;
short field2;
long long field4;
} myStructPacked;
108
0:000> dt wmain::__l2::Struct Dereference!wmain::__l2::Struct +0x000 field1 : Bool +0x002 field2 : Int2B +0x008 field4 : Int8B
myStruct1.field = 0;
myStruct1.shared_field = 123;
myStruct2.field = 1;
0:000> dt MyStruct
+0x000 field : Int4B
=00007ff7`ba535048 shared_field : Uint4B
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.
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
OuterStruct *pMyStruct;
OuterStruct *pMyStruct(NULL);
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};
When we use the new way of initialization in modern C++, we can use empty
{} to signal default initialization even for stack memory.
OuterStruct *pMyStruct{};
OuterStruct *pMyStruct{NULL};
OuterStruct *pMyStruct{nullptr};
OuterStruct *pMyStruct{&myStruct};
112
struct MyStruct
{
int field;
inline static unsigned shared_field{123};
} myStruct1, myStruct2;
myStruct1.field = 0;
myStruct1.shared_field = 123;
myStruct2.field = 1;
The latest C++ standards allow static field initialization inside the structure
definition instead of the classic C++ ways of outside initialization (shown in
comments).
struct MyStruct
{
int field;
inline static unsigned shared_field{123};
} myStruct1, myStruct2;
myStruct1.field = 0;
myStruct1.shared_field = 123;
myStruct2.field = 1;
114
Type names may be long or inconvenient. There are some ways to construct
easier type names.
115
Macros
Macros
#define TRUE 1
#define TRUE 1
#define BYTE unsigned char
#define PBYTE unsigned char *
#define PMyStruct struct MyStruct *
116
Old Way
Old Way
New Way
New Way
Memory Storage
Memory Storage
Overview
Overview
Global (link)
TU static (file)
Function static
Local (stack)
Dynamic (heap)
Local-dynamic (stack → heap)
In-place (allocator)
Polymorphic (allocator)
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
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.
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
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
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.
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)
{
...
...
}
void foo(int i)
{
int a = 0;
{
int b = 0;
}
if (i)
goto end;
int c = 0x78563412;
end:
assert(c == 0x78563412);
}
125
Microsoft-specific
_malloca / _freea
_alloca (deprecated)
deprecated _alloca
https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/alloca
126
(m|c|re)alloc
free
Can be replaced
Global operators
Structure-specific operators
Can be replaced
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
Memory Expressions
Memory Expressions
Use memory operators
new
delete / delete[]
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
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;
pi: 2ab1008
char buf[sizeof(int)];
int *pi = new(buf) int;
*pi = 1;
TU TU TU
Compiler
Linker
EXE/DLL/SYS
client.h server.h
Preprocessor
TU TU TU
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;
Compiler
OBJ OBJ
Linker
Name Conflict
Variables in different translation units having the same name may conflict
during the linkage phase.
// TU A
int g_nCounter;
// TU B
int g_nCounter;
137
// TU A // TU B
int g_nCounter;
Compiler
OBJ OBJ
Linker
EXE/DLL/SYS
To avoid name conflicts during linkage, C and classic C++ suggest using the
static specifier.
// TU A
// TU B
int g_nCounter;
138
Namespace TU Isolation
Namespace TU Isolation
// TU A // TU B
Compiler
OBJ OBJ
Linker
EXE/DLL/SYS
// TU A
namespace
{
int g_nCounter;
}
// TU B
int g_nCounter;
139
Multiple declarations
TU Definition Conflicts
TU Definition Conflicts
Example:
Multiple declarations of the same entity are allowed, but only one definition
is allowed, the essence of ODR, One Definition Rule.
Named namespaces
Example:
api.h impl.h
<<includes>>
Preprocessor
TU TU TU
Incomplete Types
Incomplete Types
struct MyStruct; // declaration
Such separation is achieved via incomplete types and the so-called PImpl
(Pointer to Implementation) idiom:
References
References
Now, a slide for C++ references. We plan to extend this section in the second
edition.
145
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
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
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
Constant Expressions
Constant Expressions
#define myConst 1
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
Functions
Functions
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
Functions are code bytes and, therefore, occupy some memory locations
with their start addresses.
pf(10);
(*pf)(10);
rf(10)
153
PF func{foo}; func(10);
Function pointer type declarations can be done using the classic typedef
syntax or via the more modern using type alias.
PF func{foo}; func(10);
154
Reading Declarations
Reading Declarations
Examples:
struct
{
int field;
PF pFunc;
} myStruct {0, foo};
myStruct.pFunc(0);
Structures may contain fields that are pointers to functions: the obvious way
to implement OOP in C.
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};
myStruct.pFunc(0);
myStruct.method(0);
C++ introduced structure or class methods. They can be defined either inside
the structure definition (next slide) or outside with the structure name
qualification.
struct MyStruct
{
int field;
PF pFunc;
int method (int i);
} myStruct {0, foo};
myStruct.pFunc(0);
myStruct.method(0);
157
struct MyStruct
{
int field;
PF pFunc;
int method (int i) { return i; }
} myStruct {0, foo};
myStruct.pFunc(0);
myStruct.method(0);
struct MyStruct
{
int field;
PF pFunc;
int method (int i) { return i; }
} myStruct {0, foo};
myStruct.pFunc(0);
myStruct.method(0);
158
myDerived.method(0);
myDerived.Base::method(0);
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; }
};
myDerived.method(0);
myDerived.Base::method(0);
159
struct Base
{
int method (int i) { return i; }
virtual int vmethod (int i) { return i; }
};
pMyBase->vmethod(0);
Base::vmethod(0);
162
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.
struct Base
{
int method (int i) { return i; }
virtual int vmethod (int i) = 0;
};
Structure as Interface
Structure as Interface
struct Interface
{
virtual int vmethod1 (int i) = 0;
virtual int vmethod2 (int i) = 0;
};
} myObject;
Pure virtual functions allow specifying abstract interfaces the derived types
have to implement.
struct Interface
{
virtual int vmethod1 (int i) = 0;
virtual int vmethod2 (int i) = 0;
};
Function Structure
Function Structure
struct MyFunction
{
int field{1};
int operator()() { return field; }
} myFunction;
myFunction();
Functions may also encapsulate state. The best way to do it is via structures
that implement function call operators.
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();
struct MyFunction
{
MyFunction(): field{1} { }
MyFunction(int _field): field{_field} { }
int field;
int operator()() { return field; }
} myFunction, myFunction2(2);
myFunction();
myFunction2();
166
MyFunction myFunction2(myFunction);
MyFunction myFunction3 = myFunction;
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.
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
MyFunction myFunction2;
myFunction2 = myFunction;
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; }
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
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 IFunction
{
virtual ~IFunction() { }
virtual int operator()() = 0;
};
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();
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
func(myStruct); func(1);
func(myStruct); func(1);
175
Parameters by Pointer/Reference
Parameters by Pointer/Reference
func(&myStruct); func(int(0));
func(&myStruct); func(int(0));
177
Possible Mistake
Possible Mistake
func(myStruct); func(myStruct);
If you come from languages that use implicit references, you may omit & by
mistake:
func(myStruct); func(myStruct);
178
Function Overloading
Function Overloading
Function overloading allows reusing the same function names for functions
with different numbers and types of parameters.
cvdump
https://github.com/microsoft/microsoft-pdb
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);
myCStruct.get();
myCStruct.set(2);
If you have const objects, you are only allowed to call methods that have
the const specifier in their definition.
struct MyStruct
{
MyStruct(int _field) : field{_field} { }
int get() const { return field; }
void set(int newval) { field = newval; }
private:
int field;
} myStruct(1);
myCStruct.get();
myCStruct.set(2);
180
MyStruct::get_count();
assert(myStruct1.get_count() == myStruct2.get_count());
Static structure functions are only allowed to access static structure fields
shared among objects:
MyStruct::get_count();
assert(myStruct1.get_count() == myStruct2.get_count());
181
Lambdas
Lambdas
A function parameter
A return value
Counter: RCX
Stack: RSP
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
Examples:
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
RSP+0x8 →
RSP+0x8 →
RSP+0x8 →
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
Opcode DST
Examples:
Opcode DST|SRC
Examples:
Flow Instructions
Flow Instructions
Opcode DST
Examples:
Function Parameters
Function Parameters
foo(…);
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
this
this
struct MyStruct
{
int a;
int foo(int i);
MyStruct* myAddress() { return this; }
} myStruct;
// myStruct.myAddress() == &myStruct
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.
struct MyStruct
{
int a;
int foo(int i);
MyStruct* myAddress() { return this; }
} myStruct;
// myStruct.myAddress() == &myStruct
// int negate(int)
auto negate = [](int x) { return -x; };
negate(10);
// mov edx,0Ah
// lea rcx,[rsp+130h] ; &negate
// call <lambda_1>::operator()
struct lambda_1
{
auto operator()(int x) { return -x; }
} negate;
// int negate(int)
auto negate = [](int x) { return -x; };
negate(10);
// mov edx,0Ah
// lea rcx,[rsp+130h] ; &negate
// call <lambda_1>::operator()
193
// [=] // [&]
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.
{
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
// [=] // [&]
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; };
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
void foo() {
// apply(100, negate::<lambda>::lambda_invoker_cdecl)
apply(100, negate); apply2(102, negate);
}
196
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
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
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
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
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
void foo() {
int b{0};
void foo()
{
int b{0};
void foo() {
int b{0};
To capture the context and allow more flexibility and efficiency, the modern
way is to use auto.
void foo()
{
int b{0};
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.
void foo()
{
getFunc(200)(16);
}
206
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
struct Base
{
virtual void vmethod1 () {}
virtual void vmethod2 () {}
} myBase;
&Base::vmethod2
myBase: vptr (Base::`vftable')
&Derived::vmethod2
myDerived: vptr (Derived::`vftable')
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.
pMyBase = &myDerived;
pMyBase->vmethod2();
210
Templates
A Planck-length Introduction
Why Templates?
Why Templates?
Performance, flexibility
Metafunctions, metaprogramming
Reusability
Reusability
tmpl<int> {…}
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;
};
Types of Templates
Types of Templates
Struct templates
Function templates
struct FStruct {
template <typename T> T zero() { return T(0); }
} fs;
fs.zero<int>();
Variable templates
The following code provides examples for struct, function, and (recent)
variable template categories:
struct FStruct
{
template <typename T> T zero() { return T(0); }
} fs;
fs.zero<int>();
Non-type parameters
call constant<1>
...
constant<1>:
257 00007ff6`daa73fa0 mov eax,1
257 00007ff6`daa73fa5 ret
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));
}
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
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<>
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));
}
Metafunctions
Metafunctions
// f: Value -> Value
// mf: Type -> Type
Iterators as Pointers
Iterators as Pointers
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
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();
int a[5]{ 1, 2, 3, 4, 5 };
int *pa = &a[0];
int a[5]{ 1, 2, 3, 4, 5 };
int *pa = &a[0];
Constant Iterators
Constant Iterators
std::vector<int> v{1, 2, 3, 4, 5};
std::vector<int>::const_iterator cit = v.cbegin();
int a[5]{ 1, 2, 3, 4, 5 };
const int *cpa = &a[0];
int a[5]{ 1, 2, 3, 4, 5 };
const int *cpa = &a[0];
Pointers as Iterators
Pointers as Iterators
int a[5]{ 1, 2, 3, 4, 5 };
int *itarr = std::begin(arr);
int a[5]{ 1, 2, 3, 4, 5 };
int *itarr = std::begin(arr);
Algorithms
Algorithms
std::sort(v.begin(), v.end());
int arr[5]{ 2, 1, 3, 5, 4 };
std::sort(std::begin(arr), std::end(arr));
Both iterators and containers and pointers as iterators and arrays can be
used with the C++ standard library algorithms.
int arr[5]{ 2, 1, 3, 5, 4 };
std::sort(std::begin(arr), std::end(arr));
226
Memory Ownership
Memory Ownership
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
Multiple release
Smart Pointers
Smart Pointers
Basic Design
Basic Design
A structure with operators mimicking pointer behavior
such as dereferencing
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);
}
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
auto hDeleter =
[](auto ph)
{
if (ph && (*ph != INVALID_HANDLE_VALUE))
::CloseHandle(*ph);
};
Resource handles are good candidates for unique pointers, but they should
be supplied with a custom deletion mechanism.
auto hDeleter =
[](auto ph)
{
if (ph && (*ph != INVALID_HANDLE_VALUE))
::CloseHandle(*ph);
};
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);
}
If we want to freely copy pointers around with all new copies pointing to
the same memory, then the shared_ptr is our choice:
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
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
}
~Resource()
{
// release resource, e.g., delete
}
};
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
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:
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;
}
The code example of encapsulating Windows handles using the RAII idiom.
We reuse the Handle type from the previous Handles as Unique Pointers
slide:
int foo ()
{
RAII_HANDLE hFile = ::CreateFile(...);
if (hFile == INVALID_HANDLE_VALUE)
return -1;
// ...
return 0;
}
238
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;
void foo()
{
CreateThread(NULL, 0, threadProc, (void *)5, 0, NULL);
}
Traditional C/C++ threading using raw Windows API is limited. Passing any
parameter type and casting it is error-prone.
void foo()
{
CreateThread(NULL, 0, threadProc, (void *)5, 0, NULL);
}
240
void foo()
{
std::thread threadCpp(threadProcCpp, 6, L"Hello");
//...
threadCpp.join();
// std::jthread threadCpp(threadProcCpp, 6, L"Hello");
}
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:
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 foo()
{
std::jthread thread1(threadProcCpp, L"Hello1");
std::jthread thread2(threadProcCpp, L"Hello2");
}
// Hello2: 1694
// : 1695
In the code example, the output from << operators from different threads
is mixed and looks like garbage:
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 foo()
{
std::jthread thread1(threadProcCpp, L"Hello1");
std::jthread thread2(threadProcCpp, L"Hello2");
}
The following code example solves the data race problem by guarding access
via scoped_lock that is implemented using mutex:
std::mutex m;
void foo()
{
std::jthread thread1(threadProcCpp, L"Hello1");
std::jthread thread2(threadProcCpp, L"Hello2");
}
243
Resources
Resources
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++
cppreference
cppreference
https://en.cppreference.com/w/
Part 1
Part 2
Part 3
Part 4
Part 5
Part 6
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
The following project illustrates idioms like RAII in the context of Windows
API:
Dump2Picture
https://bitbucket.org/softwarediagnostics/dump2picture