Fundamentals 22 min read

Unlocking C++ Object Memory Layout: From Basics to Inheritance and ASLR

This article walks through C++ object memory layout using concrete examples, covering basic data members, methods, private and static members, inheritance (with and without virtual functions), compiler optimizations, and address‑space layout randomization, all demonstrated with GDB inspections and code snippets.

Tencent Architect
Tencent Architect
Tencent Architect
Unlocking C++ Object Memory Layout: From Basics to Inheritance and ASLR

1. Simple object memory layout

We start with a minimal

Basic

class containing an

int

and a

double

. After compiling and running, GDB shows the object's start address (

0x7fffffffe3b0

) and the addresses of

a

and

b

(

0x7fffffffe3b0

and

0x7fffffffe3b8

), illustrating that

a

occupies the first four bytes and

b

starts eight bytes later due to alignment padding.

<code>#include &lt;iostream&gt;
using namespace std;

class Basic {
public:
    int a;
    double b;
};

int main() {
    Basic temp;
    temp.a = 10;
    return 0;
}</code>

Alignment ensures that each member starts at an address that matches the platform’s alignment requirements, improving memory‑access efficiency.

2. Object with methods

Adding a member function

setB

does not change the object size; the method lives in the program’s text segment. GDB confirms the method address (

0x5555555551d2

) resides in

.text

, while the object layout remains 16 bytes.

<code>#include &lt;iostream&gt;

class Basic {
public:
    int a;
    double b;
    void setB(double value) { b = value; }
};

int main() {
    Basic temp;
    temp.a = 10;
    temp.setB(3.14);
    return 0;
}</code>

The

this

pointer (passed as the first argument) lets the method access members via a fixed offset (e.g.,

0x8(%rax)

for

b

).

3. Private members and static members

Private data members are laid out exactly like public ones; they occupy consecutive slots in the object. Private methods also reside in

.text

and can be invoked via a function‑pointer trick, though this bypasses compile‑time access control and is unsafe.

<code>#include &lt;iostream&gt;

class Basic {
public:
    int a;
    double b;
    void setB(double value) { b = value; secret(b); }
private:
    int c;
    double d;
    void secret(int temp) { d = temp + c; }
    static void (Basic::*getSecretPtr())(int) { return &amp;Basic::secret; }
};

int main() {
    Basic temp;
    temp.a = 10;
    temp.setB(3.14);
    // invoke private method via pointer
    void (Basic::*funcPtr)(int) = Basic::getSecretPtr();
    (temp.*funcPtr)(10);
    return 0;
}</code>

Static data members are stored once in the program’s data segment (

.data

for initialized values,

.bss

for zero‑initialized). Their addresses differ greatly from instance addresses.

4. Inheritance without virtual functions

When a class

Derived

inherits from

Basic

, the object layout places the base‑class subobject first, followed by the derived members. The total size is the sum of both parts, and the layout is contiguous.

<code>#include &lt;iostream&gt;

class Basic { public: int a; double b; void setB(double v){b=v;} };
class Derived : public Basic { public: int c; void setC(int v){c=v;} };

int main(){
    Derived obj;
    obj.a = 10; obj.setB(3.14); obj.c = 1; obj.setC(2);
    return 0;
}</code>

5. Inheritance with virtual functions

Introducing virtual functions adds an 8‑byte vptr at the beginning of each polymorphic object. The vptr points to a vtable that holds addresses of the virtual functions. GDB shows the vptr (

0x555555557d80

) and the two entries for

printInfo

and

printB

.

<code>#include &lt;iostream&gt;

class Basic {
public:
    int a; double b;
    virtual void printInfo(){ std::cout << "Basic" << std::endl; }
    virtual void printB(){ std::cout << "Basic in B" << std::endl; }
    void setB(double v){ b=v; }
};

class Derived : public Basic {
public:
    int c;
    void printInfo() override { std::cout << "Derived" << std::endl; }
    void setC(int v){ c=v; }
};

int main(){
    Derived d; d.a=10; d.setB(3.14); d.c=1; d.setC(2);
    Basic* p=&amp;d; p->printInfo(); p->printB();
    return 0;
}</code>

Polymorphic calls work only through pointers or references because the vptr is part of the actual object; copying a derived object into a base‑type object slices away the vptr.

6. Address‑Space Layout Randomization (ASLR)

Linux randomizes the virtual address space of each process (ASLR) to mitigate memory‑corruption attacks. GDB disables ASLR by default; enabling it with

set disable‑randomization off

makes object addresses vary between runs.

<code>(gdb) set disable-randomization off</code>

7. Summary

Object memory is contiguous; members follow declaration order with compiler‑determined padding.

Member functions live in the text segment and do not affect object size.

Private members have no runtime protection; they can be accessed via raw offsets.

Static members reside in the data or BSS segment, shared across instances.

Inheritance typically places base members before derived members.

Polymorphic classes contain a vptr; virtual calls resolve through the vtable.

ASLR randomizes process addresses; GDB can toggle this behavior.

C++memory alignmentGDBvirtual functionsASLRinheritanceObject Layout
Tencent Architect
Written by

Tencent Architect

We share technical insights on storage, computing, and access, and explore industry-leading product technologies together.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.