1017 lines
56 KiB
HTML
1017 lines
56 KiB
HTML
<!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 := &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>-></code> y <code>[]</code> van a la casa (un salto cada uno), y <code>&</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>-></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 &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 &buffer a &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>&buffer</code> está en <code>0x7fffffffd520</code> y <code>&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>&</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>&</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, "&").replace(/>/g, ">")}</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>&buffer</code> = <code>0x7fffffffd520</code> y <code>&buffer_a</code> = <code>0x7fffffffd540</code>. Las dos son de tipo <code>escpos_buffer **</code>: apuntan a papeles de 8 bytes. ¿Qué da <code>&buffer_a - &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>&buffer + 4</code> llega a <code>&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>&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>
|