Files
printer-driver/docs/02-pointers-visual.html
2026-10-07 03:45:29 +02:00

1017 lines
56 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!doctype html>
<html lang="es">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
<title>Papel y casa</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Atkinson+Hyperlegible:wght@400;700&family=Bricolage+Grotesque:opsz,wght@12..96,600;12..96,700&family=JetBrains+Mono:wght@400;600&display=swap">
<style>
/* Layout: pestañas por concepto; en cada pantalla, el dibujo de la memoria a la izquierda y las preguntas a la derecha. */
:root {
--bg: #f4f6f9;
--surface: #ffffff;
--fg: #1a2130;
--muted: #5a6476;
--line: #d3d9e3;
--stack: #2a64a8;
--stack-tint: #e7eff9;
--heap: #a85a0c;
--heap-tint: #fbf0e1;
--ptr: #b8306f;
--paper: #fffbe8;
--ok: #1c7f46;
--bad: #b83526;
--font-display: "Bricolage Grotesque", "Segoe UI", system-ui, sans-serif;
--font-body: "Atkinson Hyperlegible", "Segoe UI", system-ui, sans-serif;
--font-mono: "JetBrains Mono", ui-monospace, "Cascadia Mono", Consolas, monospace;
color-scheme: light;
}
@media (prefers-color-scheme: dark) {
:root:not([data-theme="light"]) {
--bg: #11151b; --surface: #181d25; --fg: #e5e9f0; --muted: #9aa4b4; --line: #2b3240;
--stack: #82b2ec; --stack-tint: #172435; --heap: #e3a458; --heap-tint: #2a2015;
--ptr: #f07ab3; --paper: #29261a; --ok: #62cf8f; --bad: #f2806f;
color-scheme: dark;
}
}
:root[data-theme="dark"] {
--bg: #11151b; --surface: #181d25; --fg: #e5e9f0; --muted: #9aa4b4; --line: #2b3240;
--stack: #82b2ec; --stack-tint: #172435; --heap: #e3a458; --heap-tint: #2a2015;
--ptr: #f07ab3; --paper: #29261a; --ok: #62cf8f; --bad: #f2806f;
color-scheme: dark;
}
* { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; }
body {
margin: 0;
background: var(--bg);
color: var(--fg);
font: 16px/1.5 var(--font-body);
padding-inline: 16px;
padding-block: 1.5rem 3rem;
}
.wrap { max-width: 1180px; margin: 0 auto; display: grid; gap: 1.25rem; }
code, .mono { font-family: var(--font-mono); font-size: .9em; }
h1, h2 { font-family: var(--font-display); text-wrap: balance; margin: 0; line-height: 1.15; }
h1 { font-size: clamp(1.8rem, 4vw, 2.6rem); }
h2 { font-size: 1.4rem; }
p { margin: 0; }
.lead { color: var(--muted); max-width: 65ch; }
header { display: grid; gap: .4rem; }
header .sub { color: var(--muted); max-width: 70ch; }
.legend { display: flex; flex-wrap: wrap; gap: .5rem 1.25rem; font-size: .9rem; color: var(--muted); }
.legend span { display: inline-flex; align-items: center; gap: .4rem; }
.sw { width: .9rem; height: .9rem; border-radius: 3px; display: inline-block; border: 1px solid; }
.sw.paper { background: var(--paper); border-color: var(--ptr); border-style: dashed; }
.sw.stack { background: var(--stack-tint); border-color: var(--stack); }
.sw.heap { background: var(--heap-tint); border-color: var(--heap); }
nav.tabs { display: flex; flex-wrap: wrap; gap: .4rem; border-bottom: 1px solid var(--line); padding-bottom: .6rem; }
nav.tabs button {
font: 600 .95rem var(--font-body); color: var(--muted); background: transparent;
border: 1px solid transparent; border-radius: 999px; padding: .4rem .9rem; cursor: pointer;
}
nav.tabs button .n { font-family: var(--font-mono); font-size: .8rem; margin-right: .35rem; opacity: .8; }
nav.tabs button:hover { color: var(--fg); border-color: var(--line); }
nav.tabs button[aria-selected="true"] { color: var(--fg); background: var(--surface); border-color: var(--line); }
button:focus-visible, [tabindex]:focus-visible, input:focus-visible, select:focus-visible { outline: 2px solid var(--ptr); outline-offset: 2px; }
.screen { display: grid; gap: 1rem; }
.screen-head { display: grid; gap: .35rem; }
.screen-grid { display: grid; grid-template-columns: minmax(0, 1.65fr) minmax(0, 1fr); gap: 1.25rem; align-items: start; }
@media (max-width: 900px) { .screen-grid { grid-template-columns: minmax(0, 1fr); } }
.drawing {
position: relative; background: var(--surface); border: 1px solid var(--line);
border-radius: 12px; padding: 1rem; display: grid; gap: .75rem; min-width: 0;
}
svg.arrows { position: absolute; inset: 0; pointer-events: none; overflow: visible; color: var(--ptr); z-index: 2; }
.info { font-size: .9rem; color: var(--muted); min-height: 1.5em; }
.info b { color: var(--fg); }
.zone { border-radius: 10px; padding: .7rem; display: grid; gap: .6rem; border: 1px solid; }
.zone.stack { background: var(--stack-tint); border-color: color-mix(in srgb, var(--stack) 45%, transparent); }
.zone.heap { background: var(--heap-tint); border-color: color-mix(in srgb, var(--heap) 45%, transparent); }
.zone-label { font: 600 .78rem var(--font-mono); letter-spacing: .04em; text-transform: uppercase; display: flex; justify-content: space-between; gap: .5rem; flex-wrap: wrap; }
.zone.stack .zone-label { color: var(--stack); }
.zone.heap .zone-label { color: var(--heap); }
.gap-band {
text-align: center; font-size: .82rem; color: var(--muted); padding: .35rem;
background: repeating-linear-gradient(135deg, transparent 0 8px, color-mix(in srgb, var(--line) 60%, transparent) 8px 9px);
border-radius: 6px;
}
.row { display: flex; flex-wrap: wrap; gap: 1rem 2.5rem; align-items: center; }
.paper {
position: relative; background: var(--paper); border: 1.5px dashed var(--ptr); border-radius: 4px;
padding: .45rem .7rem; display: grid; gap: .1rem; min-width: 10.5rem; transform: rotate(-.6deg);
}
.paper-name { font: 600 .95rem var(--font-mono); }
.paper-val { font: 600 1rem var(--font-mono); color: var(--ptr); }
.paper-addr { font: .75rem var(--font-mono); color: var(--muted); }
.house { position: relative; background: var(--surface); border: 1.5px solid var(--heap); border-radius: 8px; padding: .5rem; display: grid; gap: .4rem; min-width: 15rem; }
.obj-head { display: flex; justify-content: space-between; gap: .6rem; flex-wrap: wrap; font: 600 .82rem var(--font-mono); }
.obj-head .addr { color: var(--muted); font-weight: 400; }
.fields { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: .3rem; }
.field { position: relative; border: 1px solid var(--line); border-radius: 5px; padding: .3rem .4rem; display: grid; gap: .05rem; background: var(--bg); }
.field.is-ptr { background: var(--paper); border: 1.5px dashed var(--ptr); }
.fname { font: .72rem var(--font-mono); color: var(--muted); }
.fval { font: 600 .85rem var(--font-mono); }
.field.is-ptr .fval { color: var(--ptr); }
.block { position: relative; background: var(--surface); border: 1.5px solid var(--heap); border-radius: 8px; padding: .5rem; display: grid; gap: .4rem; flex: 1 1 13rem; min-width: 0; }
.block.on-stack { border-color: var(--stack); }
.cells { display: flex; flex-wrap: wrap; gap: 3px; }
.cell {
position: relative; width: 2.1rem; padding: .2rem 0; text-align: center; border: 1px solid var(--line); border-radius: 4px;
font: 600 .78rem var(--font-mono); background: var(--bg); display: grid;
}
.cell small { font-weight: 400; font-size: .62rem; color: var(--muted); }
.cell.empty { color: var(--muted); font-weight: 400; }
.cell.more { border-style: dashed; color: var(--muted); }
.block-note { font-size: .78rem; color: var(--muted); }
.dist { font: .8rem var(--font-mono); color: var(--stack); display: grid; justify-items: center; }
.dist::before { content: ""; width: 4.5rem; height: 0; border-top: 1.5px solid currentColor; margin-bottom: .2rem; }
/* Velo: lo que todavía no has predicho */
.veiled { position: relative; border-style: dashed !important; background: transparent !important; transform: none; }
.veiled > * { visibility: hidden; }
.veiled::after { content: "?"; position: absolute; inset: 0; display: grid; place-items: center; font: 700 1.3rem var(--font-display); color: var(--muted); }
.dist.veiled::before { visibility: hidden; }
/* Resaltados de la pantalla 2 */
.is-hit { outline: 3px solid var(--ptr) !important; outline-offset: 2px; z-index: 3; }
.is-target { outline: 2px dashed var(--ptr) !important; outline-offset: 4px; z-index: 3; }
[data-badge]::before {
content: attr(data-badge); position: absolute; top: -1.35rem; left: 0; white-space: nowrap;
font: 600 .68rem var(--font-body); background: var(--ptr); color: var(--surface); padding: .05rem .4rem; border-radius: 4px; z-index: 4;
}
/* Panel de preguntas */
.panel { background: var(--surface); border: 1px solid var(--line); border-radius: 12px; padding: 1rem; display: grid; gap: 1rem; min-width: 0; }
.panel h3 { margin: 0; font: 700 .78rem var(--font-body); letter-spacing: .08em; text-transform: uppercase; color: var(--muted); }
.quiz { display: grid; gap: .55rem; }
.quiz + .quiz { border-top: 1px solid var(--line); padding-top: 1rem; }
.quiz .q { font-weight: 700; }
.opts { display: grid; gap: .35rem; }
.opt {
text-align: left; font: 1rem var(--font-body); color: var(--fg); background: var(--bg);
border: 1px solid var(--line); border-radius: 8px; padding: .5rem .7rem; cursor: pointer;
}
.opt:hover:not(:disabled) { border-color: var(--ptr); }
.opt:disabled { cursor: default; }
.opt.chosen { border-width: 2px; }
.opt.right { border-color: var(--ok); color: var(--ok); }
.opt.wrong { border-color: var(--bad); color: var(--bad); }
.opt.right::before { content: "✓ "; }
.opt.wrong::before { content: "✗ "; }
.feedback { font-size: .95rem; padding: .6rem .7rem; border-radius: 8px; background: var(--bg); }
.feedback .tag { font-weight: 700; }
.feedback .tag.ok { color: var(--ok); }
.feedback .tag.ko { color: var(--bad); }
.done-note { font-size: .92rem; color: var(--muted); }
.go {
border: 1px solid var(--line); border-radius: 10px; padding: .7rem .85rem; background: var(--surface);
display: grid; gap: .3rem; font-size: .95rem; max-width: 80ch;
}
.go b.label { font: 700 .72rem var(--font-body); letter-spacing: .08em; text-transform: uppercase; color: var(--stack); }
/* Pantalla 2 */
.expr-list { display: flex; flex-wrap: wrap; gap: .4rem; }
.expr {
font: 600 .88rem var(--font-mono); color: var(--fg); background: var(--bg); border: 1px solid var(--line);
border-radius: 6px; padding: .35rem .6rem; cursor: pointer;
}
.expr:hover { border-color: var(--ptr); }
.expr[aria-pressed="true"] { border-color: var(--ptr); background: var(--paper); }
.expr.seen::after { content: " ✓"; color: var(--ok); }
.choice-group { display: grid; gap: .3rem; }
.choice-group > span { font-size: .9rem; color: var(--muted); }
.choices { display: flex; flex-wrap: wrap; gap: .35rem; }
.choice { font: .95rem var(--font-body); color: var(--fg); background: var(--bg); border: 1px solid var(--line); border-radius: 999px; padding: .3rem .8rem; cursor: pointer; }
.choice[aria-pressed="true"] { border-color: var(--ptr); background: var(--paper); font-weight: 700; }
.btn {
font: 700 .95rem var(--font-body); color: var(--surface); background: var(--fg); border: 0; border-radius: 8px;
padding: .55rem 1rem; cursor: pointer; justify-self: start;
}
.btn:disabled { opacity: .35; cursor: not-allowed; }
.btn.ghost { background: transparent; color: var(--fg); border: 1px solid var(--line); }
.result { display: grid; gap: .5rem; }
.kv { display: grid; grid-template-columns: auto minmax(0, 1fr); gap: .2rem .8rem; font-size: .95rem; }
.kv dt { color: var(--muted); }
.kv dd { margin: 0; font-family: var(--font-mono); font-size: .9rem; overflow-wrap: anywhere; }
.hops { display: flex; flex-wrap: wrap; gap: .3rem; }
.hop { font-size: .85rem; border: 1px solid var(--line); border-radius: 6px; padding: .15rem .45rem; background: var(--bg); }
.hop code { color: var(--ptr); font-weight: 600; }
/* Pantalla 3 */
.mem16 { display: grid; grid-template-columns: repeat(16, minmax(0, 1fr)); gap: 3px; }
@media (max-width: 640px) { .mem16 { grid-template-columns: repeat(8, minmax(0, 1fr)); } }
.mem16 .cell { width: auto; }
.mem16 .cell.alt { background: color-mix(in srgb, var(--heap) 10%, var(--bg)); }
.mem16 .cell.sel { outline: 2.5px solid var(--ptr); outline-offset: -1px; background: var(--paper); }
.mem16 .cell.first::before { content: "p + i"; position: absolute; top: -1.3rem; left: 0; font: 600 .66rem var(--font-mono); color: var(--ptr); white-space: nowrap; }
.controls { display: grid; gap: .8rem; }
.types { display: flex; flex-wrap: wrap; gap: .35rem; }
.slider-row { display: grid; grid-template-columns: auto minmax(0, 1fr) auto; gap: .6rem; align-items: center; }
input[type="range"] { width: 100%; accent-color: var(--ptr); }
.locked { opacity: .4; pointer-events: none; }
.readout { display: grid; gap: .25rem; font: .92rem var(--font-mono); }
.readout .big { font-size: 1.05rem; font-weight: 600; }
.readout .ptr { color: var(--ptr); }
.esc-legend { font-size: .82rem; color: var(--muted); }
/* Pantalla 4 */
.frame { position: relative; border: 1.5px solid var(--stack); border-radius: 8px; padding: .55rem; display: grid; gap: .55rem; background: var(--surface); }
.frame-title { font: 600 .78rem var(--font-mono); color: var(--stack); display: flex; justify-content: space-between; flex-wrap: wrap; gap: .4rem; }
.frame.gone { border-style: dashed; opacity: .5; }
.frame.gone .paper { visibility: hidden; }
.args { display: flex; flex-wrap: wrap; gap: .5rem; align-items: center; font-size: .88rem; }
.chip { font: 600 .8rem var(--font-mono); border: 1.5px dashed var(--ptr); background: var(--paper); border-radius: 4px; padding: .2rem .5rem; }
.steps-nav { display: flex; gap: .5rem; align-items: center; flex-wrap: wrap; }
.step-count { font: .85rem var(--font-mono); color: var(--muted); }
.step-text { min-height: 4.5em; }
.sizeof { display: flex; flex-wrap: wrap; gap: .5rem; }
.sizeof div { border: 1px solid var(--line); border-radius: 8px; padding: .35rem .6rem; font: .9rem var(--font-mono); background: var(--bg); }
.sizeof div b { color: var(--ptr); }
.compare { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: .75rem; }
@media (max-width: 640px) { .compare { grid-template-columns: minmax(0, 1fr); } }
.compare > div { border: 1px solid var(--line); border-radius: 10px; padding: .7rem; display: grid; gap: .4rem; background: var(--surface); align-content: start; }
.compare h4 { margin: 0; font: 700 .8rem var(--font-body); letter-spacing: .06em; text-transform: uppercase; color: var(--muted); }
.travel { display: flex; flex-wrap: wrap; gap: .3rem; }
/* Pantalla 5 */
.hex-table { border-collapse: collapse; font: .92rem var(--font-mono); font-variant-numeric: tabular-nums; }
.hex-table th, .hex-table td { border-bottom: 1px solid var(--line); padding: .3rem .9rem .3rem 0; text-align: left; }
.hex-table th { font: 700 .75rem var(--font-body); letter-spacing: .06em; text-transform: uppercase; color: var(--muted); }
.tool { display: grid; gap: .6rem; }
.tool label { display: grid; gap: .2rem; font-size: .88rem; color: var(--muted); }
.tool input, .tool select {
font: 1rem var(--font-mono); color: var(--fg); background: var(--bg); border: 1px solid var(--line);
border-radius: 8px; padding: .45rem .6rem; width: 100%; min-width: 0;
}
.tool-row { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: .6rem; }
@media (max-width: 640px) { .tool-row { grid-template-columns: minmax(0, 1fr); } }
.tool-out { font: .95rem var(--font-mono); background: var(--bg); border-radius: 8px; padding: .6rem .7rem; overflow-wrap: anywhere; }
.tool-out .err { color: var(--bad); }
footer { color: var(--muted); font-size: .85rem; border-top: 1px solid var(--line); padding-top: .8rem; }
@media (prefers-reduced-motion: no-preference) {
.opt, .expr, .choice, .cell, .paper, .house, .field { transition: outline-color .15s, border-color .15s, background-color .15s; }
}
</style>
</head>
<body>
<div class="wrap">
<header>
<h1>Papel y casa</h1>
<p class="sub">Punteros en C dibujados con las direcciones reales que sacaste con gdb de <code>build/hello</code>. En cada pantalla, primero predices y luego miras.</p>
<div class="legend">
<span><i class="sw paper"></i>papel: un puntero, 8 bytes con una dirección</span>
<span><i class="sw stack"></i>pila: variables locales</span>
<span><i class="sw heap"></i>montón: lo que sale de <code>malloc</code>/<code>realloc</code></span>
</div>
</header>
<nav class="tabs" role="tablist" aria-label="Pantallas">
<button role="tab" data-tab="1" aria-selected="true"><span class="n">1</span>Dónde vive cada cosa</button>
<button role="tab" data-tab="2" aria-selected="false"><span class="n">2</span>Expresiones</button>
<button role="tab" data-tab="3" aria-selected="false"><span class="n">3</span>Aritmética</button>
<button role="tab" data-tab="4" aria-selected="false"><span class="n">4</span>Llamar a una función</button>
<button role="tab" data-tab="5" aria-selected="false"><span class="n">5</span>Hexadecimal</button>
</nav>
<!-- ============ 1 · Mapa ============ -->
<section class="screen" id="s1" data-screen="1">
<div class="screen-head">
<h2>Dónde vive cada cosa</h2>
<p class="lead">El momento: el punto de parada en la línea 42 de <code>hello.c</code>. <code>buffer</code> tiene 100 bytes <code>'A'</code> y <code>buffer_a</code> tiene un <code>0x1B</code>. Contesta las preguntas y el mapa se irá rellenando.</p>
</div>
<div class="screen-grid">
<div class="drawing" id="d1">
<svg class="arrows" aria-hidden="true"></svg>
<div class="zone stack">
<div class="zone-label"><span>Pila · [stack]</span><span>0x7fff…</span></div>
<div class="row">
<div class="paper veiled" id="s1-pb" data-reveal="1" tabindex="0"
data-info="<b>buffer</b> · variable local de main en la pila · está en 0x7fffffffd520 · ocupa 8 bytes · guarda 0x4052b0">
<div class="paper-name">buffer</div><div class="paper-val">0x4052b0</div><div class="paper-addr">en 0x7fffffffd520 · 8 B</div>
</div>
<div class="dist veiled" id="s1-dist" data-reveal="4">0x20 = 32 bytes</div>
<div class="paper veiled" id="s1-pa" data-reveal="1" tabindex="0"
data-info="<b>buffer_a</b> · variable local de main en la pila · está en 0x7fffffffd540 · ocupa 8 bytes · guarda 0x405360">
<div class="paper-name">buffer_a</div><div class="paper-val">0x405360</div><div class="paper-addr">en 0x7fffffffd540 · 8 B</div>
</div>
</div>
</div>
<div class="gap-band">entre las dos zonas hay unos 128 TB de direcciones sin usar</div>
<div class="zone heap">
<div class="zone-label"><span>Montón · [heap]</span><span>0x40…</span></div>
<div class="row">
<div class="house veiled" id="s1-hb" data-reveal="2" tabindex="0"
data-info="<b>*buffer</b> · struct escpos_buffer en el montón · empieza en 0x4052b0 · 24 bytes · lo creó malloc en escpos_buffer_new">
<div class="obj-head"><span>*buffer</span><span class="addr">0x4052b0 · 24 B</span></div>
<div class="fields">
<div class="field is-ptr" id="s1-fb"><span class="fname">data · +0</span><span class="fval">0x4052d0</span></div>
<div class="field"><span class="fname">len · +8</span><span class="fval">100</span></div>
<div class="field"><span class="fname">cap · +16</span><span class="fval">128</span></div>
</div>
</div>
<div class="block veiled" id="s1-bb" data-reveal="3" tabindex="0"
data-info="<b>bloque de datos de buffer</b> · en el montón · empieza en 0x4052d0 · 128 bytes · lo creó realloc en buffer_reserve">
<div class="obj-head"><span>bloque de buffer</span><span class="addr">0x4052d0 · 128 B</span></div>
<div class="cells" id="s1-cells-b"></div>
<div class="block-note">100 usados · 28 libres</div>
</div>
</div>
<div class="row">
<div class="house veiled" id="s1-ha" data-reveal="2" tabindex="0"
data-info="<b>*buffer_a</b> · struct escpos_buffer en el montón · empieza en 0x405360 · 24 bytes">
<div class="obj-head"><span>*buffer_a</span><span class="addr">0x405360 · 24 B</span></div>
<div class="fields">
<div class="field is-ptr" id="s1-fa"><span class="fname">data · +0</span><span class="fval">0x405380</span></div>
<div class="field"><span class="fname">len · +8</span><span class="fval">1</span></div>
<div class="field"><span class="fname">cap · +16</span><span class="fval">64</span></div>
</div>
</div>
<div class="block veiled" id="s1-ba" data-reveal="3" tabindex="0"
data-info="<b>bloque de datos de buffer_a</b> · en el montón · empieza en 0x405380 · 64 bytes">
<div class="obj-head"><span>bloque de buffer_a</span><span class="addr">0x405380 · 64 B</span></div>
<div class="cells" id="s1-cells-a"></div>
<div class="block-note">1 usado · 63 libres</div>
</div>
</div>
</div>
<p class="info" id="info1" aria-live="polite">Las cajas con <b>?</b> aparecen cuando contestes.</p>
</div>
<aside class="panel">
<h3>Predice</h3>
<div id="q1"></div>
</aside>
</div>
<div class="go"><b class="label">En Go</b><p><code>b := &amp;Buffer{}</code> también puede dejar el papel en la pila y la casa en el montón. La diferencia es que lo decide el compilador (<em>escape analysis</em>) y tú no lo ves. En C lo decides tú: si lo pides con <code>malloc</code>, va al montón.</p></div>
</section>
<!-- ============ 2 · Expresiones ============ -->
<section class="screen" id="s2" data-screen="2" hidden>
<div class="screen-head">
<h2>Expresiones: ¿papel o lo que hay dentro?</h2>
<p class="lead">Elige una expresión, predice qué te da y cuánto ocupa, y comprueba. El dibujo marca el resultado. Regla: <code>*</code>, <code>-&gt;</code> y <code>[]</code> van a la casa (un salto cada uno), y <code>&amp;</code> deshace un salto.</p>
</div>
<div class="screen-grid">
<div class="drawing" id="d2">
<svg class="arrows" aria-hidden="true"></svg>
<div class="zone stack">
<div class="zone-label"><span>Pila</span><span>marco de main</span></div>
<div class="row">
<div class="paper" id="s2-p">
<div class="paper-name">buffer</div><div class="paper-val" id="s2-pval">0x4052b0</div><div class="paper-addr">en 0x7fffffffd520 · 8 B</div>
</div>
</div>
</div>
<div class="zone heap">
<div class="zone-label"><span>Montón</span></div>
<div class="row">
<div class="house" id="s2-h">
<div class="obj-head"><span>struct en 0x4052b0</span><span class="addr">24 B</span></div>
<div class="fields">
<div class="field is-ptr" id="s2-fdata"><span class="fname">data · 0x4052b0</span><span class="fval">0x4052d0</span></div>
<div class="field" id="s2-flen"><span class="fname">len · 0x4052b8</span><span class="fval">100</span></div>
<div class="field" id="s2-fcap"><span class="fname">cap · 0x4052c0</span><span class="fval">128</span></div>
</div>
</div>
<div class="block" id="s2-b">
<div class="obj-head"><span>bloque en 0x4052d0</span><span class="addr">128 B</span></div>
<div class="cells" id="s2-cells"></div>
</div>
</div>
</div>
</div>
<aside class="panel">
<h3>Expresión</h3>
<div class="expr-list" id="s2-exprs"></div>
<div id="s2-ask" class="quiz" hidden>
<p class="q">¿Qué te da <code id="s2-cur"></code>?</p>
<div class="choice-group"><span>Es…</span><div class="choices" id="s2-kind"></div></div>
<div class="choice-group"><span>Ocupa…</span><div class="choices" id="s2-size"></div></div>
<button class="btn" id="s2-check" disabled>Comprobar</button>
</div>
<div id="s2-result" class="result" hidden></div>
<p class="done-note" id="s2-hint">Empieza por <code>buffer</code> y baja en orden.</p>
</aside>
</div>
<div class="go"><b class="label">En Go</b><p>Go esconde casi todos los saltos: <code>b.len</code> desreferencia solo aunque <code>b</code> sea un <code>*Buffer</code>, y <code>s[1]</code> no deja ver que hay un puntero dentro del slice. En C cada salto se escribe: <code>-&gt;</code>, <code>*</code> o <code>[]</code>.</p></div>
</section>
<!-- ============ 3 · Aritmética ============ -->
<section class="screen" id="s3" data-screen="3" hidden>
<div class="screen-head">
<h2>Aritmética de punteros</h2>
<p class="lead"><code>p + i</code> no va a ningún sitio: calcula otra dirección, <code>i</code> <strong>elementos</strong> más allá. <code>p[i]</code> calcula esa dirección y va a ella. Cuánto es un elemento depende del tipo del puntero.</p>
</div>
<div class="screen-grid">
<div class="drawing" id="d3">
<div class="obj-head"><span>16 bytes desde 0x4052d0</span><span class="addr">ejemplo: un tique</span></div>
<div class="mem16" id="mem16" style="margin-top:1.2rem"></div>
<p class="esc-legend"><code>1B 40</code> ESC @ · <code>48 6F 6C 61</code> "Hola" · <code>0A</code> LF · <code>1B 64 05</code> ESC d 5 · <code>1D 56 00</code> GS V 0 · <code>00</code> libres</p>
<div class="controls" id="s3-controls">
<div class="choice-group"><span>Tipo del puntero <code>p</code></span><div class="types" id="s3-types"></div></div>
<div class="slider-row">
<code>i =</code>
<input type="range" id="s3-i" min="0" max="15" value="0" aria-label="índice i">
<code id="s3-ival">0</code>
</div>
<div class="readout" id="s3-out"></div>
</div>
</div>
<aside class="panel">
<h3>Predice</h3>
<div id="q3"></div>
</aside>
</div>
<div class="go"><b class="label">En Go</b><p>No hay aritmética de punteros (salvo con <code>unsafe</code>). <code>s[i]</code> comprueba el límite y hace <code>panic</code> si te pasas. En C, <code>p[i]</code> fuera del bloque compila sin avisar y es comportamiento indefinido: lees o escribes lo que haya al lado.</p></div>
</section>
<!-- ============ 4 · Llamada ============ -->
<section class="screen" id="s4" data-screen="4" hidden>
<div class="screen-head">
<h2>Qué viaja al llamar a una función</h2>
<p class="lead">Paso a paso: <code>escpos_buffer_append(buffer_b, data_b)</code>, con <code>uint8_t data_b[300]</code> declarado en <code>main</code>. Direcciones de ejemplo.</p>
</div>
<div class="screen-grid">
<div class="drawing" id="d4">
<svg class="arrows" aria-hidden="true"></svg>
<div class="zone stack">
<div class="zone-label"><span>Pila</span><span>crece hacia abajo ↓</span></div>
<div class="frame" id="s4-main">
<div class="frame-title"><span>marco de main</span><span>direcciones altas</span></div>
<div class="row">
<div class="paper" id="s4-bb">
<div class="paper-name">buffer_b</div><div class="paper-val">0x4053e0</div><div class="paper-addr">en 0x7fffffffd530 · 8 B</div>
</div>
<div class="block on-stack" id="s4-data">
<div class="obj-head"><span>data_b[300]</span><span class="addr">0x7fffffffd3d0 · 300 B</span></div>
<div class="cells" id="s4-cells"></div>
</div>
</div>
</div>
<div class="args" id="s4-args" hidden>
<span>argumentos →</span>
<span class="chip">buffer_b → 0x4053e0</span>
<span class="chip">data_b decae a &amp;data_b[0] → 0x7fffffffd3d0</span>
</div>
<div class="frame" id="s4-fn" hidden>
<div class="frame-title"><span id="s4-fn-title">marco de escpos_buffer_append</span><span>direcciones más bajas</span></div>
<div class="row">
<div class="paper" id="s4-pbuf">
<div class="paper-name">buffer</div><div class="paper-val">0x4053e0</div><div class="paper-addr">copia · 8 B</div>
</div>
<div class="paper" id="s4-pbyte">
<div class="paper-name">byte</div><div class="paper-val">0x7fffffffd3d0</div><div class="paper-addr">copia · 8 B</div>
</div>
</div>
</div>
</div>
<div class="zone heap">
<div class="zone-label"><span>Montón</span></div>
<div class="row">
<div class="house" id="s4-house">
<div class="obj-head"><span>*buffer_b</span><span class="addr">0x4053e0 · 24 B</span></div>
<div class="fields">
<div class="field is-ptr"><span class="fname">data</span><span class="fval">NULL</span></div>
<div class="field"><span class="fname">len</span><span class="fval">0</span></div>
<div class="field"><span class="fname">cap</span><span class="fval">0</span></div>
</div>
</div>
</div>
</div>
</div>
<aside class="panel">
<h3>Paso <span id="s4-n">0</span> de 4</h3>
<p class="step-text" id="s4-text"></p>
<div id="s4-sizeof" class="sizeof" hidden>
<div><code>sizeof byte</code> = <b>8</b></div>
<div><code>sizeof *byte</code> = <b>1</b></div>
<div>¿300? En ningún sitio</div>
</div>
<div id="q4"></div>
<div class="steps-nav">
<button class="btn ghost" id="s4-prev">Anterior</button>
<button class="btn" id="s4-next">Siguiente</button>
</div>
</aside>
</div>
<div class="compare" id="s4-compare" hidden>
<div>
<h4>C · <code>append(escpos_buffer *buffer, uint8_t byte[])</code></h4>
<div class="travel"><span class="chip">buffer · 8 B</span><span class="chip">byte · 8 B</span></div>
<p>Viajan 16 bytes. La longitud no viaja: si la función la necesita, tiene que llegarle como otro argumento.</p>
</div>
<div>
<h4>Go · <code>Append(b *Buffer, data []byte)</code></h4>
<div class="travel"><span class="chip">b · 8 B</span><span class="chip">data.ptr · 8 B</span><span class="chip">data.len = 300</span><span class="chip">data.cap = 300</span></div>
<p>Viajan 32 bytes. El slice es una cabecera de tres campos que se copia entera, y por eso <code>len(data)</code> da 300 dentro de la función.</p>
</div>
</div>
</section>
<!-- ============ 5 · Hex ============ -->
<section class="screen" id="s5" data-screen="5" hidden>
<div class="screen-head">
<h2>Leer y restar hexadecimal</h2>
<p class="lead">Base 16: los dígitos van de <code>0</code> a <code>f</code> (<code>a</code>=10 … <code>f</code>=15), y cada posición vale 16 veces la de su derecha.</p>
</div>
<div class="screen-grid">
<div class="drawing">
<div id="s5-table-wrap" class="veiled" data-reveal="1" style="overflow-x:auto">
<table class="hex-table">
<thead><tr><th>Hex</th><th>Decimal</th><th>Dónde lo has visto</th></tr></thead>
<tbody>
<tr><td>0x08</td><td>8</td><td>sizeof de un puntero o de size_t</td></tr>
<tr><td>0x10</td><td>16</td><td>de data a cap dentro del struct</td></tr>
<tr><td>0x18</td><td>24</td><td>sizeof *buffer</td></tr>
<tr><td>0x20</td><td>32</td><td>de &amp;buffer a &amp;buffer_a; del struct a su data</td></tr>
<tr><td>0x40</td><td>64</td><td>cap inicial del buffer</td></tr>
<tr><td>0x80</td><td>128</td><td>cap tras el primer crecimiento</td></tr>
<tr><td>0x100</td><td>256</td><td>16 × 16</td></tr>
</tbody>
</table>
</div>
<div class="tool">
<label for="s5-dec">Descomponer un número hex
<input id="s5-dec" value="0x12c" spellcheck="false" autocomplete="off">
</label>
<div class="tool-out" id="s5-dec-out"></div>
</div>
<div class="tool">
<div class="tool-row">
<label for="s5-a">Dirección mayor
<input id="s5-a" value="0x7fffffffd540" spellcheck="false" autocomplete="off">
</label>
<label for="s5-b">Dirección menor
<input id="s5-b" value="0x7fffffffd520" spellcheck="false" autocomplete="off">
</label>
<label for="s5-size">Tamaño de un elemento
<select id="s5-size">
<option value="1">1 · uint8_t</option>
<option value="4">4 · uint32_t</option>
<option value="8" selected>8 · puntero o size_t</option>
<option value="24">24 · struct escpos_buffer</option>
</select>
</label>
</div>
<div class="tool-out" id="s5-calc-out"></div>
</div>
</div>
<aside class="panel">
<h3>Predice</h3>
<div id="q5"></div>
<p class="done-note">En gdb: <code>p 0xd540 - 0xd520</code> da 32 y <code>p/x 32</code> da <code>0x20</code>. En bash: <code>echo $((0xd540 - 0xd520))</code>.</p>
</aside>
</div>
<div class="go"><b class="label">En Go</b><p>Los literales son iguales: <code>0x20 == 32</code>, y <code>fmt.Printf("%x", 32)</code> imprime <code>20</code>. En C, <code>printf("%zx", n)</code> para un <code>size_t</code> y <code>%p</code> para un puntero.</p></div>
</section>
<footer>Direcciones de gdb en Fedora (2026-10-06). En tu ejecución pueden cambiar, pero las distancias entre ellas se mantienen. Teoría en <code>02-pointers-and-strings.md</code> y <code>07-dynamic-memory.md</code>.</footer>
</div>
<script>
(function () {
"use strict";
const $ = (s, r) => (r || document).querySelector(s);
const $$ = (s, r) => Array.from((r || document).querySelectorAll(s));
/* ---------- flechas ---------- */
const arrowSets = {};
function rel(el, cr) {
const r = el.getBoundingClientRect();
return { x: r.left - cr.left, y: r.top - cr.top, w: r.width, h: r.height };
}
function hiddenEl(el) {
return !el || el.offsetParent === null || el.closest(".veiled, [hidden], .gone");
}
function clamp(v, lo, hi) { return Math.max(lo, Math.min(hi, v)); }
function pathFor(a, b) {
const ar = a.x + a.w, br = b.x + b.w;
if (b.x >= ar - 2) {
const x1 = ar, y1 = a.y + a.h / 2, x2 = b.x, y2 = b.y + Math.min(b.h / 2, 24);
const d = Math.max(20, (x2 - x1) / 2);
return `M${x1},${y1} C${x1 + d},${y1} ${x2 - d},${y2} ${x2},${y2}`;
}
if (b.y >= a.y + a.h - 2) {
const x1 = a.x + a.w / 2, y1 = a.y + a.h;
const x2 = clamp(x1, b.x + 18, br - 18), y2 = b.y;
const d = Math.max(20, (y2 - y1) / 2);
return `M${x1},${y1} C${x1},${y1 + d} ${x2},${y2 - d} ${x2},${y2}`;
}
if (b.y + b.h <= a.y + 2) {
const x1 = a.x + a.w / 2, y1 = a.y;
const x2 = clamp(x1, b.x + 10, br - 10), y2 = b.y + b.h;
const d = Math.max(20, (y1 - y2) / 2);
return `M${x1},${y1} C${x1},${y1 - d} ${x2},${y2 + d} ${x2},${y2}`;
}
const x1 = a.x, y1 = a.y + a.h / 2, x2 = br, y2 = b.y + b.h / 2;
const d = Math.max(30, (x1 - x2) / 2);
return `M${x1},${y1} C${x1 - d},${y1} ${x2 + d},${y2} ${x2},${y2}`;
}
function drawArrows(drawingId) {
const box = document.getElementById(drawingId);
const set = arrowSets[drawingId];
if (!box || !set || box.offsetParent === null) return;
const svg = $("svg.arrows", box);
const cr = box.getBoundingClientRect();
svg.setAttribute("width", cr.width);
svg.setAttribute("height", cr.height);
const mid = "ah-" + drawingId;
let out = `<defs><marker id="${mid}" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path d="M0,0 L10,5 L0,10 z" fill="currentColor"/></marker></defs>`;
for (const [fromId, toId] of set) {
const a = document.getElementById(fromId), b = document.getElementById(toId);
if (hiddenEl(a) || hiddenEl(b)) continue;
out += `<path d="${pathFor(rel(a, cr), rel(b, cr))}" fill="none" stroke="currentColor" stroke-width="2" marker-end="url(#${mid})"/>`;
}
svg.innerHTML = out;
}
function redraw() {
requestAnimationFrame(() => Object.keys(arrowSets).forEach(drawArrows));
}
window.addEventListener("resize", redraw);
if (document.fonts && document.fonts.ready) document.fonts.ready.then(redraw);
/* ---------- preguntas ---------- */
function quiz(host, cfg) {
const box = document.createElement("div");
box.className = "quiz";
box.innerHTML = `<p class="q">${cfg.q}</p><div class="opts"></div><div class="feedback" hidden></div>`;
const opts = $(".opts", box), fb = $(".feedback", box);
const buttons = cfg.opts.map((label, i) => {
const b = document.createElement("button");
b.type = "button"; b.className = "opt"; b.innerHTML = label;
b.addEventListener("click", () => answer(i));
opts.appendChild(b);
return b;
});
function answer(i) {
buttons.forEach((b, j) => {
b.disabled = true;
if (j === cfg.ok) b.classList.add("right");
if (j === i) { b.classList.add("chosen"); if (i !== cfg.ok) b.classList.add("wrong"); }
});
const good = i === cfg.ok;
fb.innerHTML = `<span class="tag ${good ? "ok" : "ko"}">${good ? "Bien." : "No."}</span> ${cfg.explain}`;
fb.hidden = false;
if (cfg.onDone) cfg.onDone(good);
}
host.appendChild(box);
return box;
}
function sequence(host, items, onAll) {
let k = 0;
function next() {
if (k >= items.length) { if (onAll) onAll(); return; }
const item = items[k++];
quiz(host, Object.assign({}, item, { onDone: (good) => { if (item.onDone) item.onDone(good); next(); } }));
}
next();
}
function reveal(scope, n) {
$$(`[data-reveal="${n}"]`, scope).forEach((el) => el.classList.remove("veiled"));
redraw();
}
/* ---------- pestañas ---------- */
const tabs = $$("nav.tabs button");
function show(n) {
tabs.forEach((t) => t.setAttribute("aria-selected", String(t.dataset.tab === String(n))));
$$("section.screen").forEach((s) => { s.hidden = s.dataset.screen !== String(n); });
try { localStorage.setItem("papel-casa-tab", String(n)); } catch (e) { /* sin almacenamiento */ }
redraw();
}
tabs.forEach((t) => t.addEventListener("click", () => show(t.dataset.tab)));
/* ---------- 1 · mapa ---------- */
function cellsHTML(values, prefix) {
return values.map((v, i) => {
if (v === "…") return `<div class="cell more">…</div>`;
if (v === "") return `<div class="cell empty"${prefix ? ` id="${prefix}${i}"` : ""}>·</div>`;
return `<div class="cell"${prefix ? ` id="${prefix}${i}"` : ""}>${v}</div>`;
}).join("");
}
$("#s1-cells-b").innerHTML = cellsHTML(["41","41","41","41","41","41","41","41","41","41","…"]);
$("#s1-cells-a").innerHTML = cellsHTML(["1B","","","","","","…"]);
arrowSets.d1 = [["s1-pb", "s1-hb"], ["s1-pa", "s1-ha"], ["s1-fb", "s1-bb"], ["s1-fa", "s1-ba"]];
const d1 = $("#d1"), info1 = $("#info1");
function showInfo(e) {
const el = e.target.closest("[data-info]");
if (el && !el.classList.contains("veiled")) info1.innerHTML = el.dataset.info;
}
d1.addEventListener("mouseover", showInfo);
d1.addEventListener("focusin", showInfo);
sequence($("#q1"), [
{
q: "La variable <code>buffer</code> de <code>main</code> es el papel. ¿Dónde vive?",
opts: ["En la pila", "En el montón"], ok: 0,
explain: "Es una variable local de <code>main</code>, y las variables locales van en la pila, dentro del marco de la función. Ocupa 8 bytes: solo guarda una dirección.",
onDone: () => reveal(d1, 1)
},
{
q: "<code>escpos_buffer_new</code> hizo <code>malloc(sizeof *buffer)</code>. ¿Dónde está la casa, el struct de 24 bytes?",
opts: ["En la pila, junto al papel", "En el montón"], ok: 1,
explain: "Todo lo que sale de <code>malloc</code> va al montón. Por eso sobrevive cuando <code>escpos_buffer_new</code> termina: el marco de esa función desaparece, pero el bloque no.",
onDone: () => reveal(d1, 2)
},
{
q: "¿Dónde están los 128 bytes de datos de <code>buffer</code>?",
opts: ["Dentro de la casa, después de <code>cap</code>", "En otro bloque del montón, aparte", "En la pila"], ok: 1,
explain: "Los creó <code>realloc</code> en <code>buffer_reserve</code>: es otro bloque. La casa solo guarda su dirección en el campo <code>data</code>, que es otro papel. Por eso <code>escpos_buffer_free</code> hace dos <code>free</code>.",
onDone: () => reveal(d1, 3)
},
{
q: "<code>&amp;buffer</code> está en <code>0x7fffffffd520</code> y <code>&amp;buffer_a</code> en <code>0x7fffffffd540</code>. La diferencia es <code>0x20</code>. ¿Cómo de cerca están?",
opts: ["Muy cerca: 32 bytes", "Lejos: 20.000 bytes", "Pegadas: 8 bytes, una detrás de otra"], ok: 0,
explain: "<code>0x20</code> = 2 × 16 = 32 bytes. Las dos están en el marco de <code>main</code>. No van seguidas porque el compilador decide dónde coloca cada variable local. Entre una casa y su bloque de datos también hay <code>0x20</code>: 24 bytes del struct y 8 de la cabecera que <code>malloc</code> pone delante de cada bloque.",
onDone: () => reveal(d1, 4)
}
], () => {
const p = document.createElement("p");
p.className = "done-note";
p.textContent = "Mapa completo. Pasa el ratón por cualquier caja, o tócala, para ver su dirección y su tamaño.";
$("#q1").appendChild(p);
info1.textContent = "Pasa el ratón por una caja.";
});
/* ---------- 2 · expresiones ---------- */
const s2cells = [];
for (let i = 0; i < 8; i++) s2cells.push(`<div class="cell" id="s2-c${i}">41<small>…${(0xd0 + i).toString(16)}</small></div>`);
s2cells.push(`<div class="cell more">…</div>`);
$("#s2-cells").innerHTML = s2cells.join("");
arrowSets.d2 = [["s2-p", "s2-h"], ["s2-fdata", "s2-b"]];
const EXPRS = [
{ e: "buffer", kind: "dir", size: 8, type: "escpos_buffer *", value: "0x4052b0",
is: "s2-pval", isBadge: "es esto", points: "s2-h",
hops: [],
why: "Es el papel tal cual: ningún salto. Su valor es la dirección de la casa." },
{ e: "&buffer", kind: "dir", size: 8, type: "escpos_buffer **", value: "0x7fffffffd520",
points: "s2-p",
hops: [["&", "dirección de la variable: dónde está el propio papel"]],
why: "<code>&amp;</code> da dónde está el papel en la pila, no a dónde apunta. Es un papel que apunta a otro papel." },
{ e: "*buffer", kind: "val", size: 24, type: "struct escpos_buffer", value: "{data = 0x4052d0, len = 100, cap = 128}",
is: "s2-h", isBadge: "es esto",
hops: [["*", "va a la casa"]],
why: "Un salto: la casa entera, el struct de 24 bytes." },
{ e: "buffer->data", kind: "dir", size: 8, type: "uint8_t *", value: "0x4052d0",
is: "s2-fdata", isBadge: "es esto", points: "s2-b",
hops: [["->", "va a la casa y coge data"]],
why: "Un salto. Pero lo que coges es otro papel: <code>data</code> guarda la dirección del bloque de bytes." },
{ e: "buffer->len", kind: "val", size: 8, type: "size_t", value: "100",
is: "s2-flen", isBadge: "es esto",
hops: [["->", "va a la casa y coge len"]],
why: "Un salto. <code>len</code> es un número, no un papel. Ocupa 8 bytes porque <code>size_t</code> mide 8 en 64 bits." },
{ e: "buffer->data[1]", kind: "val", size: 1, type: "uint8_t", value: "0x41 ('A')",
is: "s2-c1", isBadge: "es esto",
hops: [["->", "va a la casa y coge data"], ["[1]", "va a la casilla 1 del bloque"]],
why: "Dos saltos: a la casa y luego al bloque. El resultado es un byte." },
{ e: "*(buffer->data + 1)", kind: "val", size: 1, type: "uint8_t", value: "0x41 ('A')",
is: "s2-c1", isBadge: "es esto",
hops: [["->", "va a la casa y coge data"], ["+ 1", "calcula la dirección de la casilla 1 (no va)"], ["*", "va a ella"]],
why: "Es <code>data[1]</code> escrito a mano: <code>+ 1</code> calcula la dirección y <code>*</code> va a ella. Los corchetes son una abreviatura de esto." },
{ e: "buffer->data + 1", kind: "dir", size: 8, type: "uint8_t *", value: "0x4052d1",
points: "s2-c1",
hops: [["->", "va a la casa y coge data"], ["+ 1", "calcula la dirección de la casilla 1 (no va)"]],
why: "Un solo salto. <code>+ 1</code> no va a ningún sitio: calcula una dirección. El resultado sigue siendo un papel." },
{ e: "&buffer->data[1]", kind: "dir", size: 8, type: "uint8_t *", value: "0x4052d1",
points: "s2-c1",
hops: [["->", "va a la casa y coge data"], ["[1]", "iría a la casilla 1"], ["&", "deshace ese salto y se queda con su dirección"]],
why: "<code>[1]</code> iría a la casilla, y <code>&amp;</code> lo deshace. Da lo mismo que <code>buffer->data + 1</code>." }
];
const exprBox = $("#s2-exprs"), ask = $("#s2-ask"), result = $("#s2-result"), checkBtn = $("#s2-check");
let cur = null, pick = { kind: null, size: null };
function makeChoices(host, items, key) {
host.innerHTML = "";
items.forEach(([val, label]) => {
const b = document.createElement("button");
b.type = "button"; b.className = "choice"; b.textContent = label; b.setAttribute("aria-pressed", "false");
b.addEventListener("click", () => {
$$(".choice", host).forEach((x) => x.setAttribute("aria-pressed", "false"));
b.setAttribute("aria-pressed", "true");
pick[key] = val;
checkBtn.disabled = !(pick.kind && pick.size);
});
host.appendChild(b);
});
}
function clearMarks() {
$$("#d2 .is-hit, #d2 .is-target").forEach((el) => {
el.classList.remove("is-hit", "is-target");
el.removeAttribute("data-badge");
});
}
EXPRS.forEach((x, idx) => {
const b = document.createElement("button");
b.type = "button"; b.className = "expr"; b.textContent = x.e; b.setAttribute("aria-pressed", "false");
b.addEventListener("click", () => {
$$(".expr", exprBox).forEach((y) => y.setAttribute("aria-pressed", "false"));
b.setAttribute("aria-pressed", "true");
cur = idx; pick = { kind: null, size: null };
$("#s2-cur").textContent = x.e;
makeChoices($("#s2-kind"), [["dir", "una dirección (un papel)"], ["val", "un valor (lo que hay dentro)"]], "kind");
makeChoices($("#s2-size"), [["1", "1 byte"], ["8", "8 bytes"], ["24", "24 bytes"]], "size");
checkBtn.disabled = true;
ask.hidden = false; result.hidden = true; $("#s2-hint").hidden = true;
clearMarks();
});
exprBox.appendChild(b);
});
checkBtn.addEventListener("click", () => {
const x = EXPRS[cur];
const okKind = pick.kind === x.kind, okSize = Number(pick.size) === x.size;
clearMarks();
if (x.is) { const el = document.getElementById(x.is); el.classList.add("is-hit"); el.dataset.badge = x.isBadge; }
if (x.points) { const el = document.getElementById(x.points); el.classList.add("is-target"); el.dataset.badge = "apunta aquí"; }
const hops = x.hops.length
? x.hops.map(([op, t]) => `<span class="hop"><code>${op.replace(/&/g, "&amp;").replace(/>/g, "&gt;")}</code> ${t}</span>`).join("")
: `<span class="hop">ningún salto</span>`;
const tag = (ok) => `<span class="tag ${ok ? "ok" : "ko"}">${ok ? "✓" : "✗"}</span>`;
result.innerHTML = `
<div class="feedback">${tag(okKind)} Es ${x.kind === "dir" ? "una dirección (un papel)" : "un valor"} · ${tag(okSize)} ocupa ${x.size} ${x.size === 1 ? "byte" : "bytes"}</div>
<dl class="kv"><dt>Tipo</dt><dd>${x.type}</dd><dt>Valor</dt><dd>${x.value}</dd></dl>
<div class="hops">${hops}</div>
<p>${x.why}</p>`;
result.hidden = false;
$$(".expr", exprBox)[cur].classList.add("seen");
});
/* ---------- 3 · aritmética ---------- */
const BYTES = ["1B","40","48","6F","6C","61","0A","1B","64","05","1D","56","00","00","00","00"];
const BASE = 0x4052d0;
const TYPES = [[1, "uint8_t", "uint8_t *p"], [2, "uint16_t", "uint16_t *p"], [4, "uint32_t", "uint32_t *p"], [8, "puntero (8)", "uint8_t **p"]];
let elemSize = 1;
const mem = $("#mem16");
mem.innerHTML = BYTES.map((v, i) => `<div class="cell" data-i="${i}">${v}<small>+${i}</small></div>`).join("");
const slider = $("#s3-i");
const typesBox = $("#s3-types");
TYPES.forEach(([s, name]) => {
const b = document.createElement("button");
b.type = "button"; b.className = "choice"; b.textContent = name; b.setAttribute("aria-pressed", String(s === 1));
b.addEventListener("click", () => {
$$(".choice", typesBox).forEach((x) => x.setAttribute("aria-pressed", "false"));
b.setAttribute("aria-pressed", "true");
elemSize = s;
slider.max = String(16 / s - 1);
if (Number(slider.value) > Number(slider.max)) slider.value = slider.max;
paint3();
});
typesBox.appendChild(b);
});
const hex = (n) => "0x" + n.toString(16);
function paint3() {
const i = Number(slider.value), s = elemSize;
$("#s3-ival").textContent = String(i);
$$(".cell", mem).forEach((c) => {
const k = Number(c.dataset.i);
c.classList.toggle("alt", Math.floor(k / s) % 2 === 1);
c.classList.toggle("sel", k >= i * s && k < i * s + s);
c.classList.toggle("first", k === i * s);
});
const decl = TYPES.find((t) => t[0] === s)[2];
const bytes = BYTES.slice(i * s, i * s + s).join(" ");
$("#s3-out").innerHTML = `
<div><span class="ptr">${decl}</span> = ${hex(BASE)} · cada elemento ocupa ${s} ${s === 1 ? "byte" : "bytes"}</div>
<div class="big"><span class="ptr">p + ${i}</span> = ${hex(BASE)} + ${i} × ${s} = ${hex(BASE + i * s)}</div>
<div class="big"><span class="ptr">p[${i}]</span> lee ${s} ${s === 1 ? "byte" : "bytes"} desde ahí: ${bytes}</div>
${s > 1 ? `<div class="esc-legend">Cómo se juntan esos bytes en un solo número (el orden de los bytes) lo veremos más adelante.</div>` : ""}`;
}
slider.addEventListener("input", paint3);
paint3();
const controls3 = $("#s3-controls");
controls3.classList.add("locked");
sequence($("#q3"), [
{
q: "<code>uint32_t *p</code> apunta a <code>0x4052d0</code>. Un <code>uint32_t</code> ocupa 4 bytes. ¿Qué dirección da <code>p + 2</code>?",
opts: ["0x4052d2", "0x4052d8", "0x4052e0"], ok: 1,
explain: "<code>+ 2</code> avanza 2 <strong>elementos</strong>, no 2 bytes: 2 × 4 = 8 bytes, y <code>0x4052d0</code> + 8 = <code>0x4052d8</code>. Ya puedes mover el deslizador y cambiar el tipo.",
onDone: () => controls3.classList.remove("locked")
},
{
q: "<code>&amp;buffer</code> = <code>0x7fffffffd520</code> y <code>&amp;buffer_a</code> = <code>0x7fffffffd540</code>. Las dos son de tipo <code>escpos_buffer **</code>: apuntan a papeles de 8 bytes. ¿Qué da <code>&amp;buffer_a - &amp;buffer</code>?",
opts: ["32", "4", "0x20"], ok: 1,
explain: "Restar dos punteros cuenta <strong>elementos</strong>, igual que sumar. Hay 32 bytes de distancia y cada elemento ocupa 8: 32 / 8 = 4. Es la operación inversa de la suma: <code>&amp;buffer + 4</code> llega a <code>&amp;buffer_a</code>."
}
]);
/* ---------- 4 · llamada ---------- */
const s4c = ["00","00","00","00","00","00","00","00"].map((v, i) => `<div class="cell" id="s4-c${i}">${v}</div>`).join("") + `<div class="cell more">…</div>`;
$("#s4-cells").innerHTML = s4c;
arrowSets.d4 = [["s4-bb", "s4-house"], ["s4-pbuf", "s4-house"], ["s4-pbyte", "s4-c0"]];
const STEPS = [
"Antes de la llamada. En el marco de <code>main</code> están el papel <code>buffer_b</code>, que apunta a su casa en el montón, y el array <code>data_b</code>. Sus 300 bytes están en la pila, dentro del marco de <code>main</code>.",
"Se evalúan los argumentos. <code>buffer_b</code> vale <code>0x4053e0</code>. <code>data_b</code> no se copia: <strong>decae</strong> a <code>&amp;data_b[0]</code>, la dirección de su primera casilla.",
"Se crea el marco de <code>escpos_buffer_append</code>, más abajo en la pila. Sus parámetros <code>buffer</code> y <code>byte</code> reciben copias de esos dos papeles. Ahora hay dos papeles hacia la misma casa.",
"Dentro de la función. <code>byte</code> apunta a la casilla 0 de <code>data_b</code>, pero no sabe cuántas hay detrás.",
"La función vuelve y su marco desaparece con sus dos papeles. La casa sigue en el montón. Lo que la función haya cambiado en ella lo ve <code>main</code>, porque los dos papeles apuntaban al mismo sitio."
];
let step = 0, gate0 = false, gate3 = false;
const q4 = $("#q4"), prev = $("#s4-prev"), next = $("#s4-next");
function paint4() {
$("#s4-n").textContent = String(step);
$("#s4-text").innerHTML = STEPS[step];
$("#s4-args").hidden = step !== 1;
const fn = $("#s4-fn");
fn.hidden = step < 2;
fn.classList.toggle("gone", step === 4);
$("#s4-fn-title").textContent = step === 4 ? "marco de escpos_buffer_append (ya no existe)" : "marco de escpos_buffer_append";
$("#s4-sizeof").hidden = !(step === 3 && gate3);
$("#s4-compare").hidden = step !== 4;
prev.disabled = step === 0;
next.disabled = step === 4 || (step === 0 && !gate0) || (step === 3 && !gate3);
q4.innerHTML = "";
if (step === 0 && !gate0) {
quiz(q4, {
q: "Vas a llamar a <code>escpos_buffer_append(buffer_b, data_b)</code>. ¿Qué se copia al marco de la función?",
opts: ["La casa (24 bytes) y los 300 bytes", "Un papel (8) y los 300 bytes", "Dos papeles de 8 bytes"], ok: 2,
explain: "En C se copia el <strong>valor</strong> de cada argumento. <code>buffer_b</code> vale una dirección, y <code>data_b</code>, al pasarlo, decae a la dirección de su primera casilla. Viajan 16 bytes.",
onDone: () => { gate0 = true; next.disabled = false; }
});
}
if (step === 3 && !gate3) {
quiz(q4, {
q: "Dentro de la función, <code>byte</code> es un <code>uint8_t *</code>. ¿Cuánto vale <code>sizeof *byte</code>?",
opts: ["300", "8", "1"], ok: 2,
explain: "<code>*byte</code> es una casilla, un <code>uint8_t</code>: 1 byte. <code>sizeof byte</code> es 8, lo que ocupa el papel. Nada dentro de la función sabe que detrás hay 300: ese número se quedó en <code>main</code>.",
onDone: () => { gate3 = true; $("#s4-sizeof").hidden = false; next.disabled = false; }
});
}
redraw();
}
prev.addEventListener("click", () => { if (step > 0) { step--; paint4(); } });
next.addEventListener("click", () => { if (step < 4) { step++; paint4(); } });
paint4();
/* ---------- 5 · hex ---------- */
const scope5 = $("#s5");
sequence($("#q5"), [
{
q: "¿Cuánto es <code>0x20</code> en decimal?",
opts: ["20", "32", "16"], ok: 1,
explain: "Cada posición vale 16 veces la de su derecha: <code>0x20</code> = 2 × 16 + 0 × 1 = 32.",
onDone: () => reveal(scope5, 1)
},
{
q: "¿Y <code>0x80</code>?",
opts: ["80", "128", "256"], ok: 1,
explain: "8 × 16 = 128. Es la <code>cap</code> de tu buffer cuando crece desde 64 (<code>0x40</code>)."
}
]);
function parseHex(s) {
const t = s.trim().toLowerCase().replace(/^0x/, "");
if (!/^[0-9a-f]{1,16}$/.test(t)) return null;
return BigInt("0x" + t);
}
function decompose() {
const raw = $("#s5-dec").value.trim().toLowerCase().replace(/^0x/, "");
const out = $("#s5-dec-out");
if (!/^[0-9a-f]{1,8}$/.test(raw)) { out.innerHTML = `<span class="err">Escribe de 1 a 8 dígitos hex, por ejemplo 0x12c.</span>`; return; }
const n = raw.length;
const parts = raw.split("").map((d, i) => {
const v = parseInt(d, 16), w = 16 ** (n - 1 - i);
return `${/[a-f]/.test(d) ? `${d}(${v})` : d} × ${w}`;
});
out.textContent = `0x${raw} = ${parts.join(" + ")} = ${parseInt(raw, 16)}`;
}
function calc() {
const a = parseHex($("#s5-a").value), b = parseHex($("#s5-b").value);
const size = BigInt($("#s5-size").value);
const out = $("#s5-calc-out");
if (a === null || b === null) { out.innerHTML = `<span class="err">Las dos direcciones tienen que ser hex, por ejemplo 0x7fffffffd540.</span>`; return; }
const diff = a - b, neg = diff < 0n, abs = neg ? -diff : diff;
const sign = neg ? "−" : "";
const elems = abs % size === 0n
? `${sign}${abs / size} elementos de ${size} bytes`
: `no es múltiplo de ${size}: no son elementos enteros`;
out.textContent = `Diferencia: ${sign}${abs} bytes (${sign}0x${abs.toString(16)}) · como resta de punteros: ${elems}`;
}
["#s5-a", "#s5-b", "#s5-size"].forEach((s) => $(s).addEventListener("input", calc));
$("#s5-dec").addEventListener("input", decompose);
decompose(); calc();
/* ---------- arranque ---------- */
let start = "1";
try { start = localStorage.getItem("papel-casa-tab") || "1"; } catch (e) { /* sin almacenamiento */ }
show(/^[1-5]$/.test(start) ? start : "1");
})();
</script>
</body>
</html>