π§ What is a Pointer to a Pointer?
A pointer to a pointer is exactly what it sounds like:
It is a variable that stores the address of another pointer, which in turn stores the address of a variable.
Itβs like two levels of indirection.
π Basic Idea:
Letβs take a regular variable:
int x = 10; // x holds value 10
int *p = &x; // p holds address of x
int **pp = &p; // pp holds address of pSo:
xβ actual integer valuepβ pointer toxppβ pointer to pointer ofx
<div class="infographic-cptrptr">
<style>
.infographic-cptrptr {
--ink: #1a1a1a;
--ink-soft: #5c5c5c;
--paper: #ffffff;
--paper-alt: #f0ece4;
--accent: #2563eb;
--accent-soft:#e3ebfd;
--line: #ddd6c9;
--warn: #b45309;
--warn-soft: #fdf0dc;
}
.infographic-cptrptr[data-theme="dark"] {
--ink: #e8e6e1;
--ink-soft: #9a968c;
--paper: #121212;
--paper-alt: #1e1e26;
--accent: #5b8def;
--accent-soft:#202942;
--line: #2c313d;
--warn: #e0a458;
--warn-soft: #3a2c14;
}
.infographic-cptrptr {
box-sizing: border-box;
max-width: 780px;
margin: 0 auto;
background: var(--paper);
color: var(--ink);
font-family: 'Inter', -apple-system, sans-serif;
padding: 0 clamp(1rem, 4vw, 2.25rem) 1.25rem;
}
.infographic-cptrptr *, .infographic-cptrptr *::before, .infographic-cptrptr *::after {
box-sizing: border-box;
}
.infographic-cptrptr h1 {
font-family: 'Fraunces', Georgia, serif;
font-weight: 600;
font-size: clamp(1.4rem, 3.5vw, 1.9rem);
margin: 1.25rem 0 0.2rem;
text-align: center;
}
.infographic-cptrptr .subhead { color: var(--ink-soft); font-size: 0.85rem; margin-bottom: 1.4rem; text-align: center; }
.infographic-cptrptr .scene {
position: relative;
display: flex;
align-items: center;
justify-content: center;
gap: 3.4rem;
padding: 2.2rem 0.5rem 1.6rem;
flex-wrap: wrap;
}
.infographic-cptrptr .mem-box { position: relative; text-align: center; }
.infographic-cptrptr .mem-addr {
font-family: 'IBM Plex Mono', monospace;
font-size: 0.64rem;
color: var(--ink-soft);
margin-bottom: 0.3rem;
}
.infographic-cptrptr .mem-cell {
width: 6.2rem;
height: 3.2rem;
border: 2px solid var(--line);
border-radius: 10px;
background: var(--paper-alt);
display: flex;
align-items: center;
justify-content: center;
transition: border-color 0.3s, background 0.3s;
}
.infographic-cptrptr .mem-cell.is-linked { border-color: var(--accent); background: var(--accent-soft); }
.infographic-cptrptr .mem-cell.is-null { border-color: var(--warn); background: var(--warn-soft); }
.infographic-cptrptr .mem-cell input {
all: unset;
width: 4.2rem;
text-align: center;
font-family: 'IBM Plex Mono', monospace;
font-size: 1.05rem;
font-weight: 700;
color: var(--ink);
}
.infographic-cptrptr .ptr-content {
font-family: 'IBM Plex Mono', monospace;
font-size: 0.72rem;
font-weight: 700;
color: var(--accent);
}
.infographic-cptrptr .mem-cell.is-null .ptr-content { color: var(--warn); font-size: 0.8rem; }
.infographic-cptrptr .mem-name {
font-family: 'IBM Plex Mono', monospace;
font-size: 0.85rem;
font-weight: 700;
margin-top: 0.4rem;
color: var(--ink-soft);
}
.infographic-cptrptr .arrow-svg { position: absolute; top: 0; left: 0; width: 100%; height: 100%; pointer-events: none; overflow: visible; }
.infographic-cptrptr .arrow-path {
fill: none;
stroke: var(--accent);
stroke-width: 2.2;
stroke-dasharray: 220;
stroke-dashoffset: 220;
transition: stroke-dashoffset 0.5s ease, opacity 0.3s ease;
opacity: 0;
}
.infographic-cptrptr .arrow-path.is-visible { stroke-dashoffset: 0; opacity: 1; }
.infographic-cptrptr .arrow-head { fill: var(--accent); opacity: 0; transition: opacity 0.3s ease 0.3s; }
.infographic-cptrptr .arrow-head.is-visible { opacity: 1; }
.infographic-cptrptr .ctrl-row { display: flex; align-items: center; justify-content: center; gap: 0.6rem; flex-wrap: wrap; margin-bottom: 1.3rem; }
.infographic-cptrptr .ctrl-btn {
all: unset; cursor: pointer;
font-family: 'IBM Plex Mono', monospace;
font-size: 0.78rem; font-weight: 600;
padding: 0.4rem 0.85rem;
border-radius: 999px;
border: 1px solid var(--line);
background: var(--paper-alt);
color: var(--ink);
transition: border-color 0.2s;
}
.infographic-cptrptr .ctrl-btn:hover { border-color: var(--accent); }
.infographic-cptrptr .ctrl-btn.primary { background: var(--accent); color: #fff; border-color: var(--accent); }
.infographic-cptrptr .deref-grid {
display: flex;
justify-content: center;
gap: 2.2rem;
flex-wrap: wrap;
font-family: 'IBM Plex Mono', monospace;
font-size: 0.92rem;
}
.infographic-cptrptr .deref-item { display: flex; align-items: center; gap: 0.5rem; }
.infographic-cptrptr .deref-item input {
font-family: 'IBM Plex Mono', monospace;
font-size: 0.92rem;
padding: 0.3rem 0.5rem;
width: 4.2rem;
text-align: center;
border-radius: 6px;
border: 1px solid var(--line);
background: var(--paper);
color: var(--ink);
}
.infographic-cptrptr .deref-item input.is-locked { opacity: 0.4; background: var(--paper-alt); cursor: not-allowed; }
.infographic-cptrptr .crash-msg {
text-align: center;
font-size: 0.8rem;
color: var(--warn);
font-weight: 600;
min-height: 1.2rem;
margin-top: 0.7rem;
}
.infographic-cptrptr .takeaway {
font-size: 0.82rem; color: var(--ink-soft);
margin-top: 1rem; border-top: 1px solid var(--line); padding-top: 0.9rem;
}
.infographic-cptrptr .takeaway strong { color: var(--ink); }
@media (prefers-reduced-motion: reduce) {
.infographic-cptrptr * { transition: none !important; }
}
</style>
<h1>ππ Pointer to a Pointer</h1>
<p class="subhead">int **pp β a box that stores the address of a box that stores an address.</p>
<div class="scene" id="scene">
<svg class="arrow-svg" id="arrow-svg">
<path class="arrow-path" id="arrow-pp-p" d=""></path>
<polygon class="arrow-head" id="head-pp-p" points=""></polygon>
<path class="arrow-path" id="arrow-p-x" d=""></path>
<polygon class="arrow-head" id="head-p-x" points=""></polygon>
</svg>
<div class="mem-box">
<div class="mem-addr">0x7ffe1ff0</div>
<div class="mem-cell is-null" id="pp-cell"><span class="ptr-content" id="pp-content">NULL</span></div>
<div class="mem-name">pp</div>
</div>
<div class="mem-box">
<div class="mem-addr">0x7ffe2000</div>
<div class="mem-cell is-null" id="p-cell"><span class="ptr-content" id="p-content">NULL</span></div>
<div class="mem-name">p</div>
</div>
<div class="mem-box">
<div class="mem-addr">0x7ffe2010</div>
<div class="mem-cell" id="x-cell"><input type="number" id="x-input" value="42"></div>
<div class="mem-name">x</div>
</div>
</div>
<div class="ctrl-row">
<button class="ctrl-btn primary" id="p-assign">p = &x</button>
<button class="ctrl-btn" id="p-null">p = NULL</button>
</div>
<div class="ctrl-row">
<button class="ctrl-btn primary" id="pp-assign">pp = &p</button>
<button class="ctrl-btn" id="pp-null">pp = NULL</button>
</div>
<div class="deref-grid">
<div class="deref-item"><span>*p =</span><input type="number" id="deref1-input" readonly class="is-locked"></div>
<div class="deref-item"><span>**pp =</span><input type="number" id="deref2-input" readonly class="is-locked"></div>
</div>
<div class="crash-msg" id="crash-msg"></div>
<p class="takeaway"><strong>The trap:</strong> pp can be perfectly valid while **pp still crashes β if pp points to p, but p itself is NULL, that second dereference has nowhere to land.</p>
</div>
<script>
(function () {
const wrapper = document.currentScript.closest('.infographic-cptrptr')
|| document.querySelector('.infographic-cptrptr');
const bodyAttr = document.body.getAttribute('data-darkmode');
let dark;
if (bodyAttr === 'true') { dark = true; }
else if (bodyAttr === 'false') { dark = false; }
else {
const match = document.cookie.match(/(?:^|;\s*)darkMode=([^;]+)/);
dark = match ? match[1] === 'true' : false;
}
if (dark) wrapper.setAttribute('data-theme', 'dark');
function $(sel) { return wrapper.querySelector(sel); }
const ppCell = $('#pp-cell'), ppContent = $('#pp-content');
const pCell = $('#p-cell'), pContent = $('#p-content');
const xCell = $('#x-cell'), xInput = $('#x-input');
const pAssign = $('#p-assign'), pNull = $('#p-null');
const ppAssign = $('#pp-assign'), ppNull = $('#pp-null');
const deref1 = $('#deref1-input'); // *p
const deref2 = $('#deref2-input'); // **pp
const crashMsg = $('#crash-msg');
const arrowPP = $('#arrow-pp-p'), headPP = $('#head-pp-p');
const arrowP = $('#arrow-p-x'), headP = $('#head-p-x');
const svg = $('#arrow-svg');
let pLinked = false;
let ppLinked = false;
function drawArrowBetween(fromEl, toEl, pathEl, headEl) {
const svgRect = svg.getBoundingClientRect();
const fromRect = fromEl.getBoundingClientRect();
const toRect = toEl.getBoundingClientRect();
const startX = fromRect.right - svgRect.left;
const startY = fromRect.top + fromRect.height / 2 - svgRect.top;
const endX = toRect.left - svgRect.left - 10;
const endY = toRect.top + toRect.height / 2 - svgRect.top;
const midY = Math.min(startY, endY) - 36;
pathEl.setAttribute('d', 'M' + startX + ',' + startY + ' C' + (startX + 30) + ',' + midY + ' ' + (endX - 30) + ',' + midY + ' ' + endX + ',' + endY);
headEl.setAttribute('points', endX + ',' + endY + ' ' + (endX - 9) + ',' + (endY - 6) + ' ' + (endX - 9) + ',' + (endY + 6));
}
function updateDisplay() {
// p box
if (pLinked) {
pCell.classList.remove('is-null'); pCell.classList.add('is-linked');
pContent.textContent = '0x7ffe2010';
drawArrowBetween(pCell, xCell, arrowP, headP);
arrowP.classList.add('is-visible'); headP.classList.add('is-visible');
} else {
pCell.classList.remove('is-linked'); pCell.classList.add('is-null');
pContent.textContent = 'NULL';
arrowP.classList.remove('is-visible'); headP.classList.remove('is-visible');
}
// pp box
if (ppLinked) {
ppCell.classList.remove('is-null'); ppCell.classList.add('is-linked');
ppContent.textContent = '0x7ffe2000';
drawArrowBetween(ppCell, pCell, arrowPP, headPP);
arrowPP.classList.add('is-visible'); headPP.classList.add('is-visible');
} else {
ppCell.classList.remove('is-linked'); ppCell.classList.add('is-null');
ppContent.textContent = 'NULL';
arrowPP.classList.remove('is-visible'); headPP.classList.remove('is-visible');
}
// *p
if (pLinked) {
deref1.readOnly = false; deref1.classList.remove('is-locked');
deref1.value = xInput.value;
} else {
deref1.readOnly = true; deref1.classList.add('is-locked');
deref1.value = '';
}
// **pp β needs pp AND p both valid
if (ppLinked && pLinked) {
deref2.readOnly = false; deref2.classList.remove('is-locked');
deref2.value = xInput.value;
} else {
deref2.readOnly = true; deref2.classList.add('is-locked');
deref2.value = '';
}
}
pAssign.addEventListener('click', () => { pLinked = true; updateDisplay(); crashMsg.textContent = ''; });
pNull.addEventListener('click', () => { pLinked = false; updateDisplay(); crashMsg.textContent = ''; });
ppAssign.addEventListener('click', () => { ppLinked = true; updateDisplay(); crashMsg.textContent = ''; });
ppNull.addEventListener('click', () => { ppLinked = false; updateDisplay(); crashMsg.textContent = ''; });
xInput.addEventListener('input', () => {
if (pLinked) deref1.value = xInput.value;
if (pLinked && ppLinked) deref2.value = xInput.value;
});
deref1.addEventListener('input', () => {
xInput.value = deref1.value;
if (ppLinked && pLinked) deref2.value = deref1.value;
});
deref2.addEventListener('input', () => {
xInput.value = deref2.value;
deref1.value = deref2.value;
});
deref1.addEventListener('click', () => {
if (deref1.readOnly) crashMsg.textContent = 'π₯ Segmentation fault β p is NULL, *p has nowhere to read from';
});
deref2.addEventListener('click', () => {
if (deref2.readOnly) {
crashMsg.textContent = !ppLinked
? 'π₯ Segmentation fault β pp itself is NULL'
: 'π₯ Segmentation fault β pp is fine, but p is NULL';
}
});
window.addEventListener('resize', updateDisplay);
updateDisplay();
})();
</script>β Example Code
#include <stdio.h>
int main() {
int x = 42;
int *p = &x;
int **pp = &p;
printf("x = %d\\n", x); // 42
printf("*p = %d\\n", *p); // 42
printf("**pp = %d\\n", **pp); // 42
return 0;
}x = 42
*p = 42
**pp = 42π Accessing Values
Expression | Meaning |
|---|---|
| Actual value |
| Address of |
| Value of |
| Address of |
| Value stored in |
| Final value of |
π‘ When is Pointer to Pointer Used?
β Dynamic 2D Arrays
int **matrix;Used to create a matrix (array of arrays) dynamically using malloc.
β Modifying a Pointer from Function
If you want to change the address stored in a pointer inside a function, pass a pointer to pointer.
void allocate(int **p) {
*p = malloc(sizeof(int));
**p = 100;
}β Command Line Arguments
int main(int argc, char **argv)Here, argv is a pointer to an array of character pointers, i.e., char **.
π Pointer to Pointer with Functions (Full Example)
#include <stdio.h>
void update(int **ptr) {
static int val = 99;
*ptr = &val; // update original pointer to point to new value
}
int main() {
int x = 10;
int *p = &x;
int **pp = &p;
printf("Before: *p = %d\\n", *p); // 10
update(pp);
printf("After : *p = %d\\n", *p); // 99
return 0;
}π§ Tip to Understand:
Think of pointers as addresses and * as the key to open the address and see what's inside.
So:
**pp
β first *pp = p
β second *p = x