<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Ilean Monterrubio Jr</title>
    <description>The latest articles on DEV Community by Ilean Monterrubio Jr (@elcapitan).</description>
    <link>https://dev.to/elcapitan</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2648828%2F4d9e8133-29de-4164-b93c-8376ddd05528.png</url>
      <title>DEV Community: Ilean Monterrubio Jr</title>
      <link>https://dev.to/elcapitan</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/elcapitan"/>
    <language>en</language>
    <item>
      <title>Common Embedded Patterns: Finite State Machine</title>
      <dc:creator>Ilean Monterrubio Jr</dc:creator>
      <pubDate>Fri, 09 Oct 2026 00:00:00 +0000</pubDate>
      <link>https://dev.to/elcapitan/common-embedded-patterns-finite-state-machine-2kgo</link>
      <guid>https://dev.to/elcapitan/common-embedded-patterns-finite-state-machine-2kgo</guid>
      <description>&lt;p&gt;This is the third part of the series re-exploring common embedded patterns. This one focuses on the finite state machine. The complete, tested source lives in the companion repo: ileanmjr88/tetzontli. Each pattern in this series is its own module with a full GoogleTest suite, so you can clone it and run the tests yourself. The suite also wires up the full motor controller used throughout this post, so it doubles as the usage example. Basic Concept A finite state machine is one of the most common ways we design behavior into a system, and in embedded it is especially important. Behavior is what turns hardware into a product: the system has to know what it is doing right now, and what to do when something happens. Think of it like following a recipe for bread. You gather the ingredients, mix the dry ones, mix the wet ones, then combine the two. Each step is a state. You are in exactly one at a time, and you only move on when that step is done. The recipe also has notes like "if the dough is too watery, add flour; if it is too firm, add water." Those notes are the interesting part. Something happens (you check the dough), a condition decides which note applies (too watery or too firm), and you take an action (add flour or water) before carrying on. That is all a state machine is: states, the events that arrive while you are in them, the conditions (guards) that decide which rule applies, and the actions you take. Modeling behavior this way is what lets a system adapt. We would all like perfect, ideal conditions, but the real world is chaotic, and the system has to handle it anyway. Almost every embedded system is a state machine: a button debouncer, a modem driver, a battery charger, a bootloader. Most start life as switch(state) with a nested switch(event) in each case. That works for three states and four events. By ten states it is a wall of cases where the behavior is scattered across hundreds of lines, entry and exit logic is copy-pasted, and nobody can answer "what happens if OVERTEMP arrives while we are CHARGING?" without reading all of it. The Idea The recipe showed that we all already use state machines. Before looking at an example state machine, let's pin down the vocabulary, since every representation that follows uses the same five ideas.  State: what the system is doing right now. In the recipe, each step is a state. The system is always in exactly one. Event: something that happens and needs a response, like an input, a timer firing, or a sensor reading. In the recipe, checking the dough. Guard: a condition that decides whether a rule applies. In the recipe: is the dough too watery? Action: what the system does in response. In the recipe: add flour. Transition: moving from one state to another because of an event (and its guard). In the recipe: the dough is ready, so you move on to the next step.  Here's the motor controller from the GoogleTest suite, written the switch way. It has four states (IDLE, ARMED, RUNNING, FAULT) and events like EV_ARM, EV_START, and EV_ESTOP. if (event == EV_ESTOP) { /* any state &lt;em&gt;/ motor_off(); state = FAULT; return; } switch (state) { case IDLE: if (event == EV_ARM) { state = ARMED; } break; case ARMED: switch (event) { case EV_DISARM: state = IDLE; break; case EV_START: if (battery_ok()) { state = RUNNING; } else { warn_battery_low(); /&lt;/em&gt; stay ARMED &lt;em&gt;/ } break; default: break; } break; case RUNNING: switch (event) { case EV_STOP: state = ARMED; break; case EV_SET_SPEED: set_speed(); /&lt;/em&gt; stay RUNNING &lt;em&gt;/ break; case EV_OVERCURRENT: motor_off(); state = FAULT; break; default: break; } break; case FAULT: if (event == EV_RESET &amp;amp;&amp;amp; fault_cleared()) { state = IDLE; } break; }  It works, but look at what's already happening with just four states. The emergency stop had to live outside the switch, because a rule that applies to every state has no natural home. To answer "what does EV_START do when the battery is low?" you have to trace nested branches. And every case needs its break: my first draft of this example was missing several, and it silently fell through from IDLE into ARMED. Add ten states and twenty events, and this becomes hundreds of lines where nobody can see the whole behavior at once. Table-Driven State Machine Here's the same behavior as a table: static const sm_transition_t kMotorTable[] = { // from event guard action to {SM_ANY_STATE, EV_ESTOP, NULL, motor_off, FAULT}, {IDLE, EV_ARM, NULL, NULL, ARMED}, {ARMED, EV_DISARM, NULL, NULL, IDLE}, {ARMED, EV_START, battery_ok, NULL, RUNNING}, {ARMED, EV_START, NULL, warn_battery_low, ARMED}, {RUNNING, EV_STOP, NULL, NULL, ARMED}, {RUNNING, EV_SET_SPEED, NULL, set_speed, RUNNING}, {RUNNING, EV_OVERCURRENT, NULL, motor_off, FAULT}, {FAULT, EV_RESET, fault_cleared, NULL, IDLE}, };  Each row defines a single rule. The from state and the event together act as the key: when an event arrives, the state machine looks for a row that matches its current state and that event. The guard is an optional condition that has to be true for the rule to apply. If it is, the machine runs the action (if there is one) and moves to the to state. Rules are checked from top to bottom, and the first matching rule wins. That one convention does a lot of work:  The SM_ANY_STATE row for EV_ESTOP sits at the top, so an emergency stop wins from any state. It finally has a natural home. The two ARMED, EV_START rows depend on order: the rule guarded by battery_ok is checked first, and the unguarded rule underneath is the fallback that warns about the battery. Swap them, and the warning always fires. If no rule fires, like EV_RESET while the fault hasn't cleared (the row matches, but its guard says no), nothing happens and the machine stays where it is.  The whole behavior now fits on one screen, and it reads like a specification. Answering "what does EV_START do when the battery is low?" means reading two rows instead of tracing nested branches. Implementation Having compared a switch-based state machine with a table-driven one, the natural question is: what actually triggers the guards and actions? The short answer is sm_dispatch, but first we need the pieces it works with. The behavior lives in two caller-owned tables: the transition table (sm_transition_t), which defines how the machine responds to events, and an optional hooks table (sm_state_hooks_t), which defines what each state does when it's entered, while it's active, and when it's left. Table Structs The transition table. Each row reads as one sentence: in from, on event, if guard passes, run action and go to to. // modules/state_machine/state_machine.h typedef struct { sm_state_t from; /&lt;/em&gt;&lt;em&gt;&amp;lt; State, or SM_ANY_STATE. */ sm_event_t event; /&lt;/em&gt;&lt;em&gt;&amp;lt; Triggering event. */ sm_guard_fn guard; /&lt;/em&gt;&lt;em&gt;&amp;lt; NULL = always passes. */ sm_action_fn action; /&lt;/em&gt;&lt;em&gt;&amp;lt; NULL = no action. */ sm_state_t to; /&lt;/em&gt;&lt;em&gt;&amp;lt; Next state. */ } sm_transition_t;  The hooks table (optional). One entry per state, each with on_entry, on_exit, and on_run. Entry and exit logic live in one place per state, instead of being repeated on every transition that touches it. // modules/state_machine/state_machine.h typedef struct { sm_action_fn on_entry; /&lt;/em&gt;&lt;em&gt;&amp;lt; NULL = none. */ sm_action_fn on_exit; /&lt;/em&gt;&lt;em&gt;&amp;lt; NULL = none. */ sm_run_fn on_run; /&lt;/em&gt;&lt;em&gt;&amp;lt; NULL = none. */ } sm_state_hooks_t;  For the motor, the hooks table looks like this: static const sm_state_hooks_t kMotorHooks[STATE_COUNT] = { // on_entry on_exit on_run {idle_entry, NULL, NULL}, // IDLE {NULL, NULL, NULL}, // ARMED {running_entry, running_exit, running_run}, // RUNNING {fault_entry, NULL, NULL}, // FAULT };  One entry per state, in enum order. IDLE and FAULT engage the brake when entered, ARMED needs nothing, and RUNNING turns the PWM output on in running_entry, off in running_exit, and polls the motor current in running_run. State Machine Struct The caller owns everything: the tables, the struct, the context. The library never allocates, which matters on systems where malloc isn't welcome (see the memory pool post). // modules/state_machine/state_machine.h typedef struct { const sm_transition_t *table; size_t count; const sm_state_hooks_t *hooks; sm_state_t state_count; sm_state_t current; bool started; void *ctx; } sm_t;  Types Looking over the structs we used to define the tables, you may be wondering about some of the data types that aren't familiar. They're custom types defined by the library. // modules/state_machine/state_machine.h typedef uint8_t sm_state_t; /&lt;/em&gt;&lt;em&gt;&amp;lt; 0 .. state_count - 1; 0xFF reserved. */ typedef uint8_t sm_event_t; /&lt;/em&gt;&lt;em&gt;&amp;lt; Caller-defined; 0xFF reserved. */ #define SM_ANY_STATE ((sm_state_t)0xFFu) /&lt;/em&gt;&lt;em&gt;&amp;lt; &lt;code&gt;from&lt;/code&gt; wildcard: any state. */ #define SM_NO_EVENT ((sm_event_t)0xFFu) /&lt;/em&gt;&lt;em&gt;&amp;lt; on_run: nothing to dispatch. */ typedef bool (*sm_guard_fn)(void *ctx); /&lt;/em&gt;&lt;em&gt;&amp;lt; true = allow the row. */ typedef void (*sm_action_fn)(void *ctx); /&lt;/em&gt;&lt;em&gt;&amp;lt; Action, entry or exit. */ typedef sm_event_t (*sm_run_fn)(void *ctx); /&lt;/em&gt;&lt;em&gt;&amp;lt; Event or SM_NO_EVENT. */   sm_state_t and sm_event_t are uint8_t. The library can't know your states and events, so you define your own enum and the library just stores the numbers. Eight bits keep each table row small, at the cost of a limit: 255 states and events, with 0xFF reserved as a sentinel (SM_ANY_STATE for states, SM_NO_EVENT for events). For a firmware state machine, that's plenty. sm_guard_fn returns bool: should this row apply? It answers a question and changes nothing. sm_action_fn does the work. The same type serves transition actions and the on_entry and on_exit hooks, since they all have the same shape: do something, return nothing. sm_run_fn is the interesting one: it returns an event. A state's on_run can watch the world (poll a sensor, check a timeout) and report what happened, or return SM_NO_EVENT if nothing did. That's how a state generates its own events without the caller having to. Every callback takes void *ctx. The machine passes your context pointer through, so callbacks never need globals. That means you can run several independent machines, and in tests you can hand in a fake context and check exactly what happened.  The functions that drive the machine return an sm_result_t, so the caller always knows what happened to an event: // modules/state_machine/state_machine.h typedef enum { SM_HANDLED, /&lt;/em&gt;&lt;em&gt;&amp;lt; A row fired, or sm_run() had nothing to do. */ SM_UNHANDLED, /&lt;/em&gt;&lt;em&gt;&amp;lt; No row matches. */ SM_GUARD_REJECTED, /&lt;/em&gt;&lt;em&gt;&amp;lt; Rows matched; every guard rejected. */ SM_ERROR /&lt;/em&gt;*&amp;lt; NULL, not started, or state out of range. */ } sm_result_t;   SM_HANDLED: a rule matched and fired, or sm_run() had nothing to do. SM_UNHANDLED: no rule exists for this state and event. The event was ignored. SM_GUARD_REJECTED: rules existed for this state and event, but every guard said no. The event was understood but blocked. SM_ERROR: misuse, not behavior. It covers a NULL argument, a machine that hasn't been started, or a state out of range.  Init Function Like the ring buffer and memory pool, the state machine needs an init function. The caller owns the sm_t and the tables; sm_init validates them and wires them together. // modules/state_machine/state_machine.c bool sm_init(sm_t *sm, const sm_transition_t *table, size_t count, const sm_state_hooks_t *hooks, sm_state_t state_count, sm_state_t initial, void *ctx) { if (sm == NULL) { return false; } *sm = (sm_t){0}; if (table == NULL || count == 0u || state_count == 0u || initial &amp;gt;= state_count) { return false; } for (size_t i = 0u; i &amp;lt; count; i++) { if (table[i].from != SM_ANY_STATE &amp;amp;&amp;amp; table[i].from &amp;gt;= state_count) { return false; } if (table[i].to &amp;gt;= state_count || table[i].event == SM_NO_EVENT) { return false; } } sm-&amp;gt;table = table; sm-&amp;gt;count = count; sm-&amp;gt;hooks = hooks; sm-&amp;gt;state_count = state_count; sm-&amp;gt;ctx = ctx; sm-&amp;gt;current = initial; return true; }   NULL check on sm first. Without somewhere to write, there's nothing else to do. *sm = (sm_t){0}; happens before the rest of the validation, on purpose. If any later check fails, the caller is left with a zeroed machine: started is false and table is NULL. sm_start refuses it, and sm_dispatch and sm_run return SM_ERROR instead of acting on garbage. A failed init leaves the machine in a known safe state. Basic argument checks: the table must exist, count and state_count can't be zero, and initial must be a valid state. Every row is validated once, up front:  from must be a real state or SM_ANY_STATE. to must be a real state. Since SM_ANY_STATE is 0xFF, which is always &amp;gt;= state_count, "any state" is automatically rejected as a destination. You can't transition to any state, and the check falls out for free. event can't be SM_NO_EVENT, since that value is reserved to mean "nothing happened."   Then the fields are stored. Note what's not set: started stays false.  Helper Functions Before we start the machine, let's look at two small helpers that keep the hooks optional at every level: // modules/state_machine/state_machine.c static void run_entry(const sm_t *sm, sm_state_t state) { if (sm-&amp;gt;hooks != NULL &amp;amp;&amp;amp; sm-&amp;gt;hooks[state].on_entry != NULL) { sm-&amp;gt;hooks[state].on_entry(sm-&amp;gt;ctx); } } static void run_exit(const sm_t *sm, sm_state_t state) { if (sm-&amp;gt;hooks != NULL &amp;amp;&amp;amp; sm-&amp;gt;hooks[state].on_exit != NULL) { sm-&amp;gt;hooks[state].on_exit(sm-&amp;gt;ctx); } }   static: they're private to state_machine.c. Callers never invoke hooks directly; the machine decides when they run. Two NULL checks, two levels of "optional": the whole hooks table can be NULL (a machine with no hooks at all), and any single hook in it can be NULL (a state that only needs on_entry, say). Either way, nothing runs. Indexed by state, with no bounds check here. The helpers trust state, because every state that reaches them has already been validated: the initial state in sm_init, and each to state when its row was checked. Validate once at the edges, and the inner code stays simple. The caller's ctx is passed through, so hooks work on the caller's data, never globals.  Start Function sm_init wires the machine together, but it doesn't run anything. Starting is a separate step: sm_start enters the initial state, running its on_entry hook, and marks the machine as live. // modules/state_machine/state_machine.c bool sm_start(sm_t *sm) { if (sm == NULL || sm-&amp;gt;table == NULL || sm-&amp;gt;started) { return false; } run_entry(sm, sm-&amp;gt;current); sm-&amp;gt;started = true; return true; }   sm-&amp;gt;table == NULL catches a failed init. Because sm_init zeroes the struct before validating, a machine that failed to initialize has a NULL table, so sm_start refuses it. That's the payoff of zeroing first. sm-&amp;gt;started guards against starting twice. A second call would run the current state's on_entry again: re-enabling hardware or resetting a timer that's already running. Returning false makes the mistake visible instead of silently repeating side effects. run_entry(sm, sm-&amp;gt;current) runs the initial state's on_entry hook, if there's a hooks table and that state has one, passing along the caller's ctx. Then started is set and the function returns true.  Dispatch Function sm_dispatch does the heavy lifting: given an event, it finds the first rule that applies to the current state and carries it out. // modules/state_machine/state_machine.c sm_result_t sm_dispatch(sm_t *sm, sm_event_t event) { if (sm == NULL || !sm-&amp;gt;started || sm-&amp;gt;current &amp;gt;= sm-&amp;gt;state_count) { return SM_ERROR; } bool matched = false; for (size_t i = 0u; i &amp;lt; sm-&amp;gt;count; i++) { const sm_transition_t *row = &amp;amp;sm-&amp;gt;table[i]; if ((row-&amp;gt;from != sm-&amp;gt;current &amp;amp;&amp;amp; row-&amp;gt;from != SM_ANY_STATE) || row-&amp;gt;event != event) { continue; } matched = true; if (row-&amp;gt;guard != NULL &amp;amp;&amp;amp; !row-&amp;gt;guard(sm-&amp;gt;ctx)) { continue; } if (row-&amp;gt;to != sm-&amp;gt;current) { run_exit(sm, sm-&amp;gt;current); } if (row-&amp;gt;action != NULL) { row-&amp;gt;action(sm-&amp;gt;ctx); } if (row-&amp;gt;to != sm-&amp;gt;current) { sm-&amp;gt;current = row-&amp;gt;to; run_entry(sm, sm-&amp;gt;current); } return SM_HANDLED; } return matched ? SM_GUARD_REJECTED : SM_UNHANDLED; }   Defensive checks: a NULL machine, a machine that was never started (including one whose init failed), or a current state outside the valid range all return SM_ERROR. sm_init already validated the table, but this check is cheap insurance against a corrupted struct. A linear scan, top to bottom. A row matches when its from is the current state (or SM_ANY_STATE) and its event matches. Everything else is skipped. matched = true is set before the guard is checked. That one flag is what separates SM_UNHANDLED (no rule exists) from SM_GUARD_REJECTED (rules existed, but every guard said no). A failed guard means continue, not return. The scan keeps looking, which is exactly how the fallback works: in ARMED, the battery_ok row fails, so the next EV_START row (the battery warning) gets its chance. The order of a transition is exit, then action, then entry. The old state cleans up, the transition does its work, and then the new state sets itself up. First match wins: the function returns SM_HANDLED immediately, so later rows never run.  Self-transitions don't re-run the hooks. When to equals the current state (like RUNNING on EV_SET_SPEED), only the action runs. Exit and entry are skipped. Changing speed shouldn't stop and restart the motor, which is exactly what running running_exit and then running_entry would do to the PWM output. Some frameworks re-run the hooks on self-transitions; I chose not to, because in embedded code entry and exit usually touch hardware. Guards, actions, and hooks must not call sm_dispatch or sm_run. During the action, the old state has already exited, but current hasn't been updated yet. A nested dispatch would see a state that's halfway through a transition. A nested call from a guard is no better: the outer scan is still partway down the table, and it would carry on comparing rows against a current that may have changed underneath it. If an action needs to trigger another event, queue it, and dispatch after the first one returns. (The ring buffer from part one is a natural event queue.) Run Function sm_run is what gets called from the main loop. Where sm_dispatch reacts to events someone else delivers, sm_run gives the current state a chance to look at the world and report its own. // modules/state_machine/state_machine.c sm_result_t sm_run(sm_t *sm) { if (sm == NULL || !sm-&amp;gt;started || sm-&amp;gt;current &amp;gt;= sm-&amp;gt;state_count) { return SM_ERROR; } if (sm-&amp;gt;hooks == NULL || sm-&amp;gt;hooks[sm-&amp;gt;current].on_run == NULL) { return SM_HANDLED; } sm_event_t ev = sm-&amp;gt;hooks[sm-&amp;gt;current].on_run(sm-&amp;gt;ctx); if (ev == SM_NO_EVENT) { return SM_HANDLED; } return sm_dispatch(sm, ev); }   Same defensive checks as sm_dispatch. Misuse returns SM_ERROR before any user code runs. No on_run, nothing to do. If there's no hooks table, or the current state has no on_run, it returns SM_HANDLED. That makes it safe to call sm_run on every pass of the loop, even when the current state only reacts to events from outside. The current state's on_run gets the caller's ctx and returns an event. This is where polling lives: reading a sensor, comparing a timer against a timeout, or checking a flag an ISR set. SM_NO_EVENT means nothing happened, which is the common case, so it returns SM_HANDLED. This is also why sm_init rejects any row with SM_NO_EVENT as its event: that value can never be dispatched. Anything else goes straight to sm_dispatch, and its result is returned as is. An event reported by on_run goes through the same table as one from the caller, so every transition takes the same path.  This is why on_run returns an event instead of calling sm_dispatch itself. Returning lets the hook finish before the transition starts, so the machine never dispatches from inside one of its own hooks. It's the same rule as for actions: hooks report what happened, and the machine decides what to do about it. In the simplest case, where every event comes from a state's on_run, the main loop is a single call: for (;;) { sm_run(&amp;amp;motor_sm); }  Most machines aren't that simple. In the motor example, EV_ARM, EV_START, and EV_ESTOP come from outside, so they still go through sm_dispatch. Current State Function The last function is the simplest: sm_state returns the state the machine is currently in. // modules/state_machine/state_machine.c sm_state_t sm_state(const sm_t *sm) { return (sm == NULL) ? SM_ANY_STATE : sm-&amp;gt;current; }   Read-only. It takes a const sm_t *, so asking can't change anything. The fields of sm_t are readable, but going through sm_state keeps callers from reaching into the struct. NULL returns SM_ANY_STATE. Since 0xFF can never be a real state, it doubles as "no answer." There's no need for a bool return and an out parameter the way ring_buffer_get does it. It doesn't check started. Before sm_start, it returns the initial state from sm_init. After a failed init it returns 0, from the zeroed struct, which looks like a real state. Check the return value of sm_init instead of relying on sm_state to tell you something went wrong. It's mostly for tests and logging. A typical test dispatches an event and then checks sm_state to confirm where the machine landed.  Wrap-Up That's a complete state machine. The behavior lives in a table instead of a wall of switch cases, so the whole machine fits on one screen and reads like a specification. It does no dynamic allocation, validates its transition table once at init, and sends every event down the same path, whether it came from the caller or from a state's own on_run. The trades, all deliberate:  Not ISR-safe or thread-safe. sm_dispatch reads and writes current with no protection. Events from an ISR go into a queue and get dispatched from one context. No re-entrant calls. Guards, actions, and hooks must not call sm_dispatch or sm_run on the same machine. on_run returns its event instead, and anything else gets queued. Guards must be side-effect free. A guard can run for a row that never fires, so it answers a question and changes nothing. Row order is behavior. The first match wins, so SM_ANY_STATE safety rows go first and guarded rows go above their fallbacks. Reordering the table changes what the machine does. Self-transitions skip entry and exit. Only the action runs, so changing speed doesn't stop and restart the motor. Linear scan. Each dispatch walks the table top to bottom, which is fine for dozens of rows. Flat machine. There are no hierarchical states, so behavior shared across states goes in SM_ANY_STATE rows. 255 states and 255 events. uint8_t keeps each row small, and 0xFF is reserved for SM_ANY_STATE and SM_NO_EVENT. The hooks table size isn't checked. sm_init only gets a pointer, so it can't tell whether hooks has state_count entries, and a short table means jumping through a garbage function pointer. Declare it as hooks[STATE_COUNT], with STATE_COUNT as the last value of your state enum. The compiler flags extra entries (build with -Werror to make that a hard error), and missing ones are zero-filled to NULL, which means no hook. sm_t fields are read-only. They're readable for debugging, but writing them skips the validation sm_init did. Ask sm_state where the machine is.  Three patterns in, and this is the one where the data starts to mean something. Most embedded applications break down into a state machine once you list what the system can be doing and what can happen to it, and writing that list as a table forces you to answer every "what if" up front. The ring buffer and the pool gave us storage that didn't care what it held. The state machine decides what happens next, and since sm_event_t is a single byte, the ring buffer from part one can carry its events as they are. Next in the series is the command dispatcher. It reads its input straight out of a ring buffer and maps each command to the function that handles it, the same table-driven idea applied to text instead of events. See you there.&lt;/p&gt;

</description>
      <category>embeddedsystems</category>
      <category>statemachines</category>
      <category>c</category>
      <category>microcontrollers</category>
    </item>
    <item>
      <title>Patrones embebidos comunes: máquina de estados finitos</title>
      <dc:creator>Ilean Monterrubio Jr</dc:creator>
      <pubDate>Fri, 09 Oct 2026 00:00:00 +0000</pubDate>
      <link>https://dev.to/elcapitan/patrones-embebidos-comunes-maquina-de-estados-finitos-1968</link>
      <guid>https://dev.to/elcapitan/patrones-embebidos-comunes-maquina-de-estados-finitos-1968</guid>
      <description>&lt;p&gt;Esta es la tercera parte de la serie donde vuelvo a explorar los patrones embebidos más comunes. Esta vez le toca a la máquina de estados finitos (finite state machine). El código fuente completo y probado vive en el repositorio complementario: ileanmjr88/tetzontli. Cada patrón de esta serie es su propio módulo con una suite completa de GoogleTest, así que se puede clonar el repositorio y ejecutar las pruebas uno mismo. La suite también arma el controlador de motor completo que se usa a lo largo de este post, así que sirve además como ejemplo de uso. Concepto básico Una máquina de estados finitos es una de las formas más comunes de diseñar el comportamiento de un sistema, y en sistemas embebidos es especialmente importante. El comportamiento es lo que convierte el hardware en un producto: el sistema tiene que saber qué está haciendo en este momento, y qué hacer cuando algo pasa. Es como seguir una receta de pan. Se juntan los ingredientes, se mezclan los secos, se mezclan los húmedos, y luego se combinan ambos. Cada paso es un estado. Se está en un solo paso a la vez, y solo pasa al siguiente cuando ese paso termina. La receta también tiene notas como «si la masa está muy aguada, agregar harina; si está muy firme, agregar agua». Esas notas son la parte interesante. Algo pasa (se revisa la masa), una condición decide qué nota aplica (muy aguada o muy firme), y se toma una acción (agregar harina o agua) antes de continuar. Eso es todo lo que es una máquina de estados: estados, los eventos que llegan mientras se está en ellos, las condiciones (guardas) que deciden qué regla aplica y las acciones que se toman. Modelar el comportamiento así es lo que permite que un sistema se adapte. A todos nos gustarían condiciones perfectas e ideales, pero el mundo real es caótico, y el sistema tiene que manejarlo de todos modos. Casi todo sistema embebido es una máquina de estados: un antirrebote de botón, un driver de módem, un cargador de batería, un bootloader. La mayoría empiezan como un switch(state) con un switch(event) anidado en cada caso. Eso funciona para tres estados y cuatro eventos. Con diez estados es un muro de casos donde el comportamiento está disperso en cientos de líneas, la lógica de entrada y salida está copiada y pegada, y nadie puede responder «¿qué pasa si llega OVERTEMP mientras estamos en CHARGING?» sin leerlo todo. La idea La receta mostró que todos ya usamos máquinas de estados. Antes de ver una máquina de estados de ejemplo, fijemos el vocabulario, ya que cada representación que sigue usa las mismas cinco ideas.  Estado: lo que el sistema está haciendo en este momento. En la receta, cada paso es un estado. El sistema siempre está exactamente en uno. Evento: algo que pasa y necesita una respuesta, como una entrada, un temporizador que se dispara o una lectura de un sensor. En la receta, revisar la masa. Guarda (guard): una condición que decide si una regla aplica. En la receta: ¿la masa está muy aguada? Acción: lo que el sistema hace en respuesta. En la receta: agregar harina. Transición: pasar de un estado a otro por un evento (y su guarda). En la receta: la masa está lista, así que se pasa al siguiente paso.  Aquí está el controlador de motor de la suite de GoogleTest, escrito a la manera del switch. Tiene cuatro estados (IDLE, ARMED, RUNNING, FAULT) y eventos como EV_ARM, EV_START y EV_ESTOP. if (event == EV_ESTOP) { /* any state &lt;em&gt;/ motor_off(); state = FAULT; return; } switch (state) { case IDLE: if (event == EV_ARM) { state = ARMED; } break; case ARMED: switch (event) { case EV_DISARM: state = IDLE; break; case EV_START: if (battery_ok()) { state = RUNNING; } else { warn_battery_low(); /&lt;/em&gt; stay ARMED &lt;em&gt;/ } break; default: break; } break; case RUNNING: switch (event) { case EV_STOP: state = ARMED; break; case EV_SET_SPEED: set_speed(); /&lt;/em&gt; stay RUNNING &lt;em&gt;/ break; case EV_OVERCURRENT: motor_off(); state = FAULT; break; default: break; } break; case FAULT: if (event == EV_RESET &amp;amp;&amp;amp; fault_cleared()) { state = IDLE; } break; }  Funciona, pero veamos lo que ya está pasando con solo cuatro estados. El paro de emergencia tuvo que vivir fuera del switch, porque una regla que aplica a todos los estados no tiene un lugar natural. Para responder «¿qué hace EV_START cuando la batería está baja?» hay que rastrear ramas anidadas. Y cada caso necesita su break: a mi primer borrador de este ejemplo le faltaban varios, y en silencio se pasaba de IDLE a ARMED. Con diez estados y veinte eventos, esto se vuelve cientos de líneas donde nadie puede ver todo el comportamiento a la vez. Máquina de estados basada en tablas Aquí está el mismo comportamiento como tabla: static const sm_transition_t kMotorTable[] = { // from event guard action to {SM_ANY_STATE, EV_ESTOP, NULL, motor_off, FAULT}, {IDLE, EV_ARM, NULL, NULL, ARMED}, {ARMED, EV_DISARM, NULL, NULL, IDLE}, {ARMED, EV_START, battery_ok, NULL, RUNNING}, {ARMED, EV_START, NULL, warn_battery_low, ARMED}, {RUNNING, EV_STOP, NULL, NULL, ARMED}, {RUNNING, EV_SET_SPEED, NULL, set_speed, RUNNING}, {RUNNING, EV_OVERCURRENT, NULL, motor_off, FAULT}, {FAULT, EV_RESET, fault_cleared, NULL, IDLE}, };  Cada fila define una sola regla. El estado from y el event juntos actúan como la clave: cuando llega un evento, la máquina de estados busca una fila que coincida con su estado actual y con ese evento. La guard es una condición opcional que tiene que ser verdadera para que la regla aplique. Si lo es, la máquina ejecuta la action (si hay una) y pasa al estado to. Las reglas se revisan de arriba hacia abajo, y gana la primera regla que coincide. Esa sola convención hace mucho trabajo:  La fila SM_ANY_STATE para EV_ESTOP está hasta arriba, así que un paro de emergencia gana desde cualquier estado. Por fin tiene un lugar natural. Las dos filas ARMED, EV_START dependen del orden: la regla con la guarda battery_ok se revisa primero, y la regla sin guarda que está debajo es el respaldo que avisa sobre la batería. Si se intercambian, la advertencia se dispara siempre. Si ninguna regla se dispara, como EV_RESET mientras la falla siga activa (la fila coincide, pero su guarda dice que no), no pasa nada y la máquina se queda donde está.  Todo el comportamiento ahora cabe en una pantalla, y se lee como una especificación. Responder «¿qué hace EV_START cuando la batería está baja?» significa leer dos filas en lugar de rastrear ramas anidadas. Implementación Después de comparar una máquina de estados basada en switch con una basada en tablas, la pregunta natural es: ¿qué es lo que realmente dispara las guardas y las acciones? La respuesta corta es sm_dispatch, pero primero necesitamos las piezas con las que trabaja. El comportamiento vive en dos tablas que son propiedad de quien llama: la tabla de transiciones (sm_transition_t), que define cómo responde la máquina a los eventos, y una tabla de hooks opcional (sm_state_hooks_t), que define qué hace cada estado al entrar, mientras está activo y al salir. Structs de las tablas La tabla de transiciones. Cada fila se lee como una oración: en from, ante event, si guard pasa, ejecutar action e ir a to. // modules/state_machine/state_machine.h typedef struct { sm_state_t from; /&lt;/em&gt;&lt;em&gt;&amp;lt; State, or SM_ANY_STATE. */ sm_event_t event; /&lt;/em&gt;&lt;em&gt;&amp;lt; Triggering event. */ sm_guard_fn guard; /&lt;/em&gt;&lt;em&gt;&amp;lt; NULL = always passes. */ sm_action_fn action; /&lt;/em&gt;&lt;em&gt;&amp;lt; NULL = no action. */ sm_state_t to; /&lt;/em&gt;&lt;em&gt;&amp;lt; Next state. */ } sm_transition_t;  La tabla de hooks (opcional). Una entrada por estado, cada una con on_entry, on_exit y on_run. La lógica de entrada y salida vive en un solo lugar por estado, en lugar de repetirse en cada transición que lo toca. // modules/state_machine/state_machine.h typedef struct { sm_action_fn on_entry; /&lt;/em&gt;&lt;em&gt;&amp;lt; NULL = none. */ sm_action_fn on_exit; /&lt;/em&gt;&lt;em&gt;&amp;lt; NULL = none. */ sm_run_fn on_run; /&lt;/em&gt;&lt;em&gt;&amp;lt; NULL = none. */ } sm_state_hooks_t;  Para el motor, la tabla de hooks se ve así: static const sm_state_hooks_t kMotorHooks[STATE_COUNT] = { // on_entry on_exit on_run {idle_entry, NULL, NULL}, // IDLE {NULL, NULL, NULL}, // ARMED {running_entry, running_exit, running_run}, // RUNNING {fault_entry, NULL, NULL}, // FAULT };  Una entrada por estado, en el orden del enum. IDLE y FAULT activan el freno al entrar, ARMED no necesita nada, y RUNNING enciende la salida PWM en running_entry, la apaga en running_exit y sondea la corriente del motor en running_run. Struct de la máquina de estados Quien llama es dueño de todo: las tablas, el struct, el contexto. La biblioteca nunca reserva memoria, lo cual importa en sistemas donde malloc no es bienvenido (ver el post del pool de memoria). // modules/state_machine/state_machine.h typedef struct { const sm_transition_t *table; size_t count; const sm_state_hooks_t *hooks; sm_state_t state_count; sm_state_t current; bool started; void *ctx; } sm_t;  Tipos Al revisar los structs que usamos para definir las tablas, puede que surjan dudas sobre algunos tipos de datos que no resultan familiares. Son tipos personalizados definidos por la biblioteca. // modules/state_machine/state_machine.h typedef uint8_t sm_state_t; /&lt;/em&gt;&lt;em&gt;&amp;lt; 0 .. state_count - 1; 0xFF reserved. */ typedef uint8_t sm_event_t; /&lt;/em&gt;&lt;em&gt;&amp;lt; Caller-defined; 0xFF reserved. */ #define SM_ANY_STATE ((sm_state_t)0xFFu) /&lt;/em&gt;&lt;em&gt;&amp;lt; &lt;code&gt;from&lt;/code&gt; wildcard: any state. */ #define SM_NO_EVENT ((sm_event_t)0xFFu) /&lt;/em&gt;&lt;em&gt;&amp;lt; on_run: nothing to dispatch. */ typedef bool (*sm_guard_fn)(void *ctx); /&lt;/em&gt;&lt;em&gt;&amp;lt; true = allow the row. */ typedef void (*sm_action_fn)(void *ctx); /&lt;/em&gt;&lt;em&gt;&amp;lt; Action, entry or exit. */ typedef sm_event_t (*sm_run_fn)(void *ctx); /&lt;/em&gt;&lt;em&gt;&amp;lt; Event or SM_NO_EVENT. */   sm_state_t y sm_event_t son uint8_t. La biblioteca no puede conocer los estados y eventos de cada quien, así que uno define su propio enum y la biblioteca solo guarda los números. Ocho bits mantienen chica cada fila de la tabla, a costa de un límite: 255 estados y eventos, con 0xFF reservado como centinela (SM_ANY_STATE para estados, SM_NO_EVENT para eventos). Para una máquina de estados de firmware, eso sobra. sm_guard_fn devuelve bool: ¿debería aplicar esta fila? Responde una pregunta y no cambia nada. sm_action_fn hace el trabajo. El mismo tipo sirve para las acciones de transición y para los hooks on_entry y on_exit, ya que todos tienen la misma forma: hacer algo, no devolver nada. sm_run_fn es el interesante: devuelve un evento. El on_run de un estado puede observar el mundo (sondear un sensor, revisar un timeout) y reportar lo que pasó, o devolver SM_NO_EVENT si no pasó nada. Así es como un estado genera sus propios eventos sin que quien llama tenga que hacerlo. Todo callback recibe void *ctx. La máquina pasa tal cual el puntero de contexto de quien llama, así que los callbacks nunca necesitan variables globales. Eso significa que se pueden ejecutar varias máquinas independientes, y en las pruebas se puede pasar un contexto falso y verificar exactamente qué pasó.  Las funciones que manejan la máquina devuelven un sm_result_t, así que quien llama siempre sabe qué le pasó a un evento: // modules/state_machine/state_machine.h typedef enum { SM_HANDLED, /&lt;/em&gt;&lt;em&gt;&amp;lt; A row fired, or sm_run() had nothing to do. */ SM_UNHANDLED, /&lt;/em&gt;&lt;em&gt;&amp;lt; No row matches. */ SM_GUARD_REJECTED, /&lt;/em&gt;&lt;em&gt;&amp;lt; Rows matched; every guard rejected. */ SM_ERROR /&lt;/em&gt;*&amp;lt; NULL, not started, or state out of range. */ } sm_result_t;   SM_HANDLED: una regla coincidió y se disparó, o sm_run() no tenía nada que hacer. SM_UNHANDLED: no existe ninguna regla para este estado y evento. El evento se ignoró. SM_GUARD_REJECTED: existían reglas para este estado y evento, pero todas las guardas dijeron que no. El evento se entendió, pero se bloqueó. SM_ERROR: mal uso, no comportamiento. Cubre un argumento NULL, una máquina que no se ha arrancado o un estado fuera de rango.  Función de inicialización Igual que el búfer circular y el pool de memoria, la máquina de estados necesita una función de inicialización. Quien llama es dueño del sm_t y de las tablas; sm_init las valida y las conecta. // modules/state_machine/state_machine.c bool sm_init(sm_t *sm, const sm_transition_t *table, size_t count, const sm_state_hooks_t *hooks, sm_state_t state_count, sm_state_t initial, void *ctx) { if (sm == NULL) { return false; } *sm = (sm_t){0}; if (table == NULL || count == 0u || state_count == 0u || initial &amp;gt;= state_count) { return false; } for (size_t i = 0u; i &amp;lt; count; i++) { if (table[i].from != SM_ANY_STATE &amp;amp;&amp;amp; table[i].from &amp;gt;= state_count) { return false; } if (table[i].to &amp;gt;= state_count || table[i].event == SM_NO_EVENT) { return false; } } sm-&amp;gt;table = table; sm-&amp;gt;count = count; sm-&amp;gt;hooks = hooks; sm-&amp;gt;state_count = state_count; sm-&amp;gt;ctx = ctx; sm-&amp;gt;current = initial; return true; }   Primero, la verificación de NULL sobre sm. Sin un lugar donde escribir, no hay nada más que hacer. *sm = (sm_t){0}; ocurre antes del resto de la validación, a propósito. Si cualquier verificación posterior falla, quien llama se queda con una máquina en cero: started es falso y table es NULL. sm_start la rechaza, y sm_dispatch y sm_run devuelven SM_ERROR en lugar de actuar sobre basura. Un init fallido deja la máquina en un estado seguro y conocido. Verificaciones básicas de argumentos: la tabla tiene que existir, count y state_count no pueden ser cero, e initial tiene que ser un estado válido. Cada fila se valida una sola vez, por adelantado:  from tiene que ser un estado real o SM_ANY_STATE. to tiene que ser un estado real. Como SM_ANY_STATE es 0xFF, que siempre es &amp;gt;= state_count, «cualquier estado» queda rechazado automáticamente como destino. No se puede pasar a cualquier estado, y la verificación sale gratis. event no puede ser SM_NO_EVENT, ya que ese valor está reservado para significar «no pasó nada».   Después se guardan los campos. Nótese lo que no se asigna: started sigue en falso.  Funciones auxiliares Antes de arrancar la máquina, veamos dos pequeñas funciones auxiliares que mantienen los hooks opcionales en todos los niveles: // modules/state_machine/state_machine.c static void run_entry(const sm_t *sm, sm_state_t state) { if (sm-&amp;gt;hooks != NULL &amp;amp;&amp;amp; sm-&amp;gt;hooks[state].on_entry != NULL) { sm-&amp;gt;hooks[state].on_entry(sm-&amp;gt;ctx); } } static void run_exit(const sm_t *sm, sm_state_t state) { if (sm-&amp;gt;hooks != NULL &amp;amp;&amp;amp; sm-&amp;gt;hooks[state].on_exit != NULL) { sm-&amp;gt;hooks[state].on_exit(sm-&amp;gt;ctx); } }   static: son privadas de state_machine.c. Quien llama nunca invoca los hooks directamente; la máquina decide cuándo se ejecutan. Dos verificaciones de NULL, dos niveles de «opcional»: toda la tabla de hooks puede ser NULL (una máquina sin ningún hook), y cualquier hook individual dentro de ella puede ser NULL (un estado que solo necesita on_entry, por ejemplo). En ambos casos, no se ejecuta nada. Indexadas por estado, sin verificación de límites aquí. Las funciones confían en state, porque todo estado que llega a ellas ya se validó: el estado inicial en sm_init, y cada estado to cuando se revisó su fila. Se valida una vez en los bordes, y el código interno se mantiene simple. Se pasa el ctx de quien llama, así que los hooks trabajan sobre los datos de quien llama, nunca sobre variables globales.  Función de arranque sm_init conecta la máquina, pero no ejecuta nada. Arrancar es un paso aparte: sm_start entra al estado inicial, ejecutando su hook on_entry, y marca la máquina como activa. // modules/state_machine/state_machine.c bool sm_start(sm_t *sm) { if (sm == NULL || sm-&amp;gt;table == NULL || sm-&amp;gt;started) { return false; } run_entry(sm, sm-&amp;gt;current); sm-&amp;gt;started = true; return true; }   sm-&amp;gt;table == NULL atrapa un init fallido. Como sm_init pone el struct en cero antes de validar, una máquina que no se pudo inicializar tiene una tabla NULL, así que sm_start la rechaza. Esa es la recompensa de poner en cero primero. sm-&amp;gt;started protege contra arrancar dos veces. Una segunda llamada ejecutaría otra vez el on_entry del estado actual: volver a habilitar hardware o reiniciar un temporizador que ya está en marcha. Devolver false hace visible el error en lugar de repetir efectos secundarios en silencio. run_entry(sm, sm-&amp;gt;current) ejecuta el hook on_entry del estado inicial, si hay tabla de hooks y ese estado tiene uno, pasando el ctx de quien llama. Luego se asigna started y la función devuelve true.  Función de despacho sm_dispatch hace el trabajo pesado: dado un evento, encuentra la primera regla que aplica al estado actual y la lleva a cabo. // modules/state_machine/state_machine.c sm_result_t sm_dispatch(sm_t *sm, sm_event_t event) { if (sm == NULL || !sm-&amp;gt;started || sm-&amp;gt;current &amp;gt;= sm-&amp;gt;state_count) { return SM_ERROR; } bool matched = false; for (size_t i = 0u; i &amp;lt; sm-&amp;gt;count; i++) { const sm_transition_t *row = &amp;amp;sm-&amp;gt;table[i]; if ((row-&amp;gt;from != sm-&amp;gt;current &amp;amp;&amp;amp; row-&amp;gt;from != SM_ANY_STATE) || row-&amp;gt;event != event) { continue; } matched = true; if (row-&amp;gt;guard != NULL &amp;amp;&amp;amp; !row-&amp;gt;guard(sm-&amp;gt;ctx)) { continue; } if (row-&amp;gt;to != sm-&amp;gt;current) { run_exit(sm, sm-&amp;gt;current); } if (row-&amp;gt;action != NULL) { row-&amp;gt;action(sm-&amp;gt;ctx); } if (row-&amp;gt;to != sm-&amp;gt;current) { sm-&amp;gt;current = row-&amp;gt;to; run_entry(sm, sm-&amp;gt;current); } return SM_HANDLED; } return matched ? SM_GUARD_REJECTED : SM_UNHANDLED; }   Verificaciones defensivas: una máquina NULL, una máquina que nunca se arrancó (incluida una cuyo init falló) o un estado actual fuera del rango válido devuelven SM_ERROR. sm_init ya validó la tabla, pero esta verificación es un seguro barato contra un struct corrupto. Un recorrido lineal, de arriba hacia abajo. Una fila coincide cuando su from es el estado actual (o SM_ANY_STATE) y su event coincide. Todo lo demás se salta. matched = true se asigna antes de revisar la guarda. Esa sola bandera es lo que separa SM_UNHANDLED (no existe ninguna regla) de SM_GUARD_REJECTED (existían reglas, pero todas las guardas dijeron que no). Una guarda fallida significa continue, no return. El recorrido sigue buscando, que es exactamente como funciona el respaldo: en ARMED, la fila de battery_ok falla, así que la siguiente fila de EV_START (la advertencia de batería) tiene su oportunidad. El orden de una transición es salida, luego acción, luego entrada. El estado viejo limpia, la transición hace su trabajo, y luego el estado nuevo se prepara. Gana la primera coincidencia: la función devuelve SM_HANDLED de inmediato, así que las filas posteriores nunca se ejecutan.  Las autotransiciones no vuelven a ejecutar los hooks. Cuando to es igual al estado actual (como RUNNING ante EV_SET_SPEED), solo se ejecuta la acción. La salida y la entrada se omiten. Cambiar la velocidad no debería detener y volver a arrancar el motor, que es justo lo que running_exit y luego running_entry le harían a la salida PWM. Algunos frameworks vuelven a ejecutar los hooks en las autotransiciones; elegí no hacerlo, porque en código embebido la entrada y la salida suelen tocar hardware. Las guardas, las acciones y los hooks no deben llamar a sm_dispatch ni a sm_run. Durante la acción, el estado viejo ya salió, pero current todavía no se ha actualizado. Un despacho anidado vería un estado a medio camino de una transición. Una llamada anidada desde una guarda no es mejor: el recorrido externo todavía va a la mitad de la tabla, y seguiría comparando filas contra un current que pudo haber cambiado sin que se diera cuenta. Si una acción necesita disparar otro evento, hay que encolarlo y despacharlo después de que regrese el primero. (El búfer circular de la primera parte es una cola de eventos natural.) Función de ejecución sm_run es lo que se llama desde el bucle principal. Mientras que sm_dispatch reacciona a eventos que entrega alguien más, sm_run le da al estado actual la oportunidad de mirar el mundo y reportar los suyos. // modules/state_machine/state_machine.c sm_result_t sm_run(sm_t *sm) { if (sm == NULL || !sm-&amp;gt;started || sm-&amp;gt;current &amp;gt;= sm-&amp;gt;state_count) { return SM_ERROR; } if (sm-&amp;gt;hooks == NULL || sm-&amp;gt;hooks[sm-&amp;gt;current].on_run == NULL) { return SM_HANDLED; } sm_event_t ev = sm-&amp;gt;hooks[sm-&amp;gt;current].on_run(sm-&amp;gt;ctx); if (ev == SM_NO_EVENT) { return SM_HANDLED; } return sm_dispatch(sm, ev); }   Las mismas verificaciones defensivas que sm_dispatch. El mal uso devuelve SM_ERROR antes de que se ejecute cualquier código del usuario. Sin on_run, no hay nada que hacer. Si no hay tabla de hooks, o el estado actual no tiene on_run, devuelve SM_HANDLED. Eso hace que sea seguro llamar a sm_run en cada pasada del bucle, aun cuando el estado actual solo reacciona a eventos externos. El on_run del estado actual recibe el ctx de quien llama y devuelve un evento. Aquí es donde vive el sondeo (polling): leer un sensor, comparar un temporizador contra un timeout o revisar una bandera que activó una ISR. SM_NO_EVENT significa que no pasó nada, que es el caso común, así que devuelve SM_HANDLED. Por eso también sm_init rechaza cualquier fila con SM_NO_EVENT como evento: ese valor nunca se puede despachar. Cualquier otra cosa va directo a sm_dispatch, y su resultado se devuelve tal cual. Un evento reportado por on_run pasa por la misma tabla que uno de quien llama, así que todas las transiciones siguen el mismo camino.  Por eso on_run devuelve un evento en lugar de llamar a sm_dispatch por su cuenta. Devolverlo permite que el hook termine antes de que empiece la transición, así que la máquina nunca despacha desde dentro de uno de sus propios hooks. Es la misma regla que para las acciones: los hooks reportan lo que pasó, y la máquina decide qué hacer al respecto. En el caso más simple, donde todos los eventos vienen del on_run de un estado, el bucle principal es una sola llamada: for (;;) { sm_run(&amp;amp;motor_sm); }  La mayoría de las máquinas no son tan simples. En el ejemplo del motor, EV_ARM, EV_START y EV_ESTOP vienen de fuera, así que siguen pasando por sm_dispatch. Función de estado actual La última función es la más simple: sm_state devuelve el estado en el que está la máquina. // modules/state_machine/state_machine.c sm_state_t sm_state(const sm_t *sm) { return (sm == NULL) ? SM_ANY_STATE : sm-&amp;gt;current; }   Solo lectura. Recibe un const sm_t *, así que preguntar no puede cambiar nada. Los campos de sm_t se pueden leer, pero pasar por sm_state evita que quien llama meta mano dentro del struct. NULL devuelve SM_ANY_STATE. Como 0xFF nunca puede ser un estado real, sirve también como «sin respuesta». No hace falta devolver un bool y usar un parámetro de salida como lo hace ring_buffer_get. No verifica started. Antes de sm_start, devuelve el estado inicial de sm_init. Después de un init fallido devuelve 0, del struct en cero, que parece un estado real. Hay que revisar el valor de retorno de sm_init en lugar de confiar en que sm_state avise que algo salió mal. Sirve sobre todo para pruebas y logging. Una prueba típica despacha un evento y luego revisa sm_state para confirmar dónde terminó la máquina.  Cierre Eso es una máquina de estados completa. El comportamiento vive en una tabla en lugar de un muro de casos de switch, así que toda la máquina cabe en una pantalla y se lee como una especificación. No hace reserva dinámica, valida su tabla de transiciones una sola vez en el init y manda cada evento por el mismo camino, ya venga de quien llama o del propio on_run de un estado. Las concesiones, todas deliberadas:  No es ISR-safe ni thread-safe. sm_dispatch lee y escribe current sin ninguna protección. Los eventos de una ISR van a una cola y se despachan desde un solo contexto. Sin llamadas reentrantes. Las guardas, las acciones y los hooks no deben llamar a sm_dispatch ni a sm_run sobre la misma máquina. on_run devuelve su evento en su lugar, y todo lo demás se encola. Las guardas no deben tener efectos secundarios. Una guarda se puede ejecutar para una fila que nunca se dispara, así que responde una pregunta y no cambia nada. El orden de las filas es comportamiento. Gana la primera coincidencia, así que las filas de seguridad con SM_ANY_STATE van primero y las filas con guarda van arriba de sus respaldos. Reordenar la tabla cambia lo que hace la máquina. Las autotransiciones omiten la entrada y la salida. Solo se ejecuta la acción, así que cambiar la velocidad no detiene ni vuelve a arrancar el motor. Recorrido lineal. Cada despacho recorre la tabla de arriba hacia abajo, lo cual está bien para decenas de filas. Máquina plana. No hay estados jerárquicos, así que el comportamiento compartido entre estados va en filas SM_ANY_STATE. 255 estados y 255 eventos. uint8_t mantiene chica cada fila, y 0xFF está reservado para SM_ANY_STATE y SM_NO_EVENT. No se verifica el tamaño de la tabla de hooks. sm_init solo recibe un puntero, así que no puede saber si hooks tiene state_count entradas, y una tabla corta significa saltar a través de un puntero a función basura. Hay que declararla como hooks[STATE_COUNT], con STATE_COUNT como el último valor del enum de estados. El compilador marca las entradas de más (compilar con -Werror para que sea un error duro), y las que faltan se rellenan con ceros, es decir NULL, que significa sin hook. Los campos de sm_t son de solo lectura. Se pueden leer para depurar, pero escribirlos se salta la validación que hizo sm_init. Para saber dónde está la máquina, hay que preguntarle a sm_state.  Llevamos tres patrones, y este es en el que los datos empiezan a significar algo. La mayoría de las aplicaciones embebidas se descomponen en una máquina de estados en cuanto se enumera lo que el sistema puede estar haciendo y lo que le puede pasar, y escribir esa lista como una tabla obliga a responder cada «¿y si...?» por adelantado. El búfer circular y el pool nos dieron almacenamiento al que no le importaba qué guardaba. La máquina de estados decide qué pasa después, y como sm_event_t es un solo byte, el búfer circular de la primera parte puede transportar sus eventos tal cual. Lo que sigue en la serie es el despachador de comandos. Lee su entrada directamente de un búfer circular y asigna cada comando a la función que lo maneja: la misma idea basada en tablas, aplicada a texto en lugar de eventos. Nos vemos ahí.&lt;/p&gt;

</description>
      <category>embeddedsystems</category>
      <category>statemachines</category>
      <category>c</category>
      <category>microcontrollers</category>
    </item>
    <item>
      <title>Common Embedded Patterns: Memory Pool</title>
      <dc:creator>Ilean Monterrubio Jr</dc:creator>
      <pubDate>Thu, 10 Sep 2026 00:00:00 +0000</pubDate>
      <link>https://dev.to/elcapitan/common-embedded-patterns-memory-pool-1db2</link>
      <guid>https://dev.to/elcapitan/common-embedded-patterns-memory-pool-1db2</guid>
      <description>&lt;p&gt;This is the second part of the series re-exploring common embedded patterns. This one focuses on the memory pool. This pattern is often used as an alternative to &lt;code&gt;malloc()&lt;/code&gt; and &lt;code&gt;free()&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The complete, tested source lives in the companion repo: &lt;a href="https://github.com/ileanmjr88/tetzontli" rel="noopener noreferrer"&gt;ileanmjr88/tetzontli&lt;/a&gt;. Each pattern in this series is its own module with a full GoogleTest suite, so you can clone it and run the tests yourself.&lt;/p&gt;

&lt;h2&gt;
  
  
  Basic Concept
&lt;/h2&gt;

&lt;p&gt;Let's start with a quick comparison to the ring buffer pattern. Both patterns are used to store data, but they differ in their point of view of going about it. We refer to the ring buffer as a container. Each item inserted into the data structure is homogeneous and uniform, and it owns the order of that data: you push in, you pop out, following FIFO. The buffer is aware of what's inside it, at least in terms of size and sequence.&lt;/p&gt;

&lt;p&gt;The memory pool takes the opposite point of view. It isn't a container, it's custom memory management. It doesn't know the structure of the data being stored, it doesn't care what's in the data, and it doesn't track the sequence. You ask for a block, it checks if one is available, and if it is, it hands you a pointer to that block. From the memory pool's point of view it only sees blocks as occupied or free, with no reference to their structure or the order in which they were handed out.&lt;/p&gt;

&lt;p&gt;At this point you're probably wondering what a memory pool is in the simplest terms. It's a buffer of equal-sized memory blocks, typically implemented as an array, so we have a chunk or pool of memory we can grab from. Our &lt;code&gt;init&lt;/code&gt; determines the size of our blocks, and initializes the manager structure that keeps track of where the next available block is located.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Quick Look at &lt;code&gt;malloc()&lt;/code&gt; and &lt;code&gt;free()&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Before diving into the memory pool itself, it's worth stepping back and recalling what &lt;code&gt;malloc()&lt;/code&gt; and &lt;code&gt;free()&lt;/code&gt; actually do. This is important since we'll be implementing similar functionality. The difference is that we pre-allocate a fixed buffer up front, usually a static array, while these functions dynamically allocate and deallocate memory on the heap.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;malloc()&lt;/code&gt; asks the heap allocator for a chunk of memory, sized for the item you're trying to store. If the allocation is successful, meaning the heap allocator found space, it returns a pointer. If it's unable to find any space it returns &lt;code&gt;NULL&lt;/code&gt;, indicating failure. The heap allocator manages this by keeping track of free and used chunks internally. Each time we use &lt;code&gt;malloc()&lt;/code&gt; it keeps track of how much we used, and the same goes for &lt;code&gt;free()&lt;/code&gt;: each time we free memory it marks it as available. &lt;code&gt;free()&lt;/code&gt; does the reverse of &lt;code&gt;malloc()&lt;/code&gt;. We call it with the pointer we want to give back to the heap allocator, it marks that chunk as free, and moves on.&lt;/p&gt;

&lt;p&gt;These two functions for dynamically allocating and deallocating memory work great on modern computers and in server farms where they have large amounts of memory and a large address space. If a &lt;code&gt;malloc()&lt;/code&gt; takes a microsecond longer to find a chunk of memory it's not a big deal. Embedded systems don't get to make those same assumptions, and that's where the cracks start to show:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Fragmentation&lt;/strong&gt; : as we allocate and deallocate chunks of mixed sizes, the free memory left behind stops being contiguous. Eventually a request fails even though there are more than enough free bytes in total, just not enough of them next to each other. The failure is late, intermittent, and not reproducible on the bench.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Non-deterministic timing&lt;/strong&gt; : not every embedded system needs deterministic timing, but for the ones that do, &lt;code&gt;malloc()&lt;/code&gt; is a problem. The allocator searches for a chunk that fits, so how long it takes depends on the state of the heap. That's hard to accept in a hard-deadline loop, and impossible to budget for.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Late failure&lt;/strong&gt; : you learn you're out of memory at the exact moment you need memory, which is usually the moment you can least do anything about it. There's no boot-time or link-time answer to "will this fit."&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Keeping track of the available blocks
&lt;/h2&gt;

&lt;p&gt;While we aren't using &lt;code&gt;malloc()&lt;/code&gt; or &lt;code&gt;free()&lt;/code&gt;, it also means we aren't using the built-in heap manager that kept its own record of which chunks were free and which were handed out. Our implementation needs to do that bookkeeping itself. There are really only two things it has to know: whether any block is available at all, and if so, where one is.&lt;/p&gt;

&lt;p&gt;Before we get into the implementation used in the tetzontli repo, let's briefly explore two common approaches to this bookkeeping. The first is the bitmap, where each bit of an integer is used to indicate whether a block is in use or free. The number of blocks determines the integer type used. The alternative is the free list method, which threads the available blocks into a linked list, keeping a tracker pointer aimed at the next block that's available. When we deallocate, the block goes back onto the tracker pointer. There is one clear advantage to the free list method: it never has to search. Taking a block means grabbing whatever the tracker pointer is aimed at, and giving one back means putting it at the front. Both are a single pointer update, no matter how full the pool is. The bitmap has to scan for a spare bit, and how long that takes depends on how many blocks are already in use. The bitmap does have one advantage we are giving up though: because it records the state of every block, it can tell a double free from a legitimate one. The free list cannot, and we will come back to what that costs us.&lt;/p&gt;

&lt;p&gt;The tetzontli implementation uses the intrusive free list approach. It's a common variation on the free list where each free block itself knows where the next available chunk of memory is located. The reason for this approach is that there's no need to keep a bitmap to keep track of the available blocks, since our &lt;code&gt;next&lt;/code&gt; pointer already knows the next available block. Also a reminder, since we aren't allocating memory dynamically, we're using an array as the container to create the pool of memory. That makes traversal easier after &lt;code&gt;init&lt;/code&gt;. Once we start to allocate and deallocate, we want the most recently deallocated blocks to be the ones we hand out first, keeping it like a stack, which is friendlier to cache.&lt;/p&gt;

&lt;h2&gt;
  
  
  Implementation
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Memory pool structure
&lt;/h3&gt;

&lt;p&gt;Similar to the ring buffer, we will create a struct. This is a simple way to keep all the parameters together and accessible from a single variable pointer.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="c1"&gt;// modules/memory_pool/memory_pool.h&lt;/span&gt;
&lt;span class="k"&gt;typedef&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kt"&gt;uint8_t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kt"&gt;size_t&lt;/span&gt; &lt;span class="n"&gt;block_size&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kt"&gt;size_t&lt;/span&gt; &lt;span class="n"&gt;capacity&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kt"&gt;size_t&lt;/span&gt; &lt;span class="n"&gt;used&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kt"&gt;size_t&lt;/span&gt; &lt;span class="n"&gt;high_water&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;free_head&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="n"&gt;memory_pool_t&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;uint8_t *base&lt;/code&gt; : the address of block &lt;code&gt;0&lt;/code&gt; , and the anchor the entire pool is measured from. Every block lives at &lt;code&gt;base + i * block_size&lt;/code&gt; . It is tempting to read this as the first byte of the buffer you hand to the pool, and most of the time that's exactly what it is, but the two can come apart, for a reason we'll get into in &lt;code&gt;memory_pool_init()&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;size_t block_size&lt;/code&gt;: this is what we will often call the stride. It defines the size of each memory block and how far apart they are from block to block.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;size_t capacity&lt;/code&gt;: this is the number of blocks in the pool. Fixed at &lt;code&gt;memory_pool_init()&lt;/code&gt; and never changes. How many are free at any moment is &lt;code&gt;capacity - used&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;size_t used&lt;/code&gt;: the number of blocks currently handed out. Incremented by &lt;code&gt;memory_pool_alloc()&lt;/code&gt;, decremented by &lt;code&gt;memory_pool_free()&lt;/code&gt;. Note handed out, not in use. The pool has no idea what you're doing with a block, only whether it has given it away and not gotten it back.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;size_t high_water&lt;/code&gt; : the peak value &lt;code&gt;used&lt;/code&gt; has ever reached since &lt;code&gt;memory_pool_init()&lt;/code&gt;. This is the number you size the pool from: run the system under worst-case load, read it back, and you know how many blocks you actually needed. It deliberately survives &lt;code&gt;memory_pool_reset()&lt;/code&gt;. Handing every block back doesn't change the fact that you once needed that many at once, and clearing the peak would throw away the measurement. Only a fresh &lt;code&gt;memory_pool_init()&lt;/code&gt; starts it over.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;void *free_head&lt;/code&gt;: the head of the free list. The first available block to hand out, or &lt;code&gt;NULL&lt;/code&gt; when every block is outstanding. The list is intrusive, meaning its links live inside the free blocks themselves rather than in separate node structs. Each free block stores a pointer to the next one in its own first bytes. That's what makes the free list cost zero extra memory, and it's why &lt;code&gt;memory_pool_init()&lt;/code&gt; refuses a &lt;code&gt;block_size&lt;/code&gt; smaller than a pointer.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Round-up function
&lt;/h3&gt;

&lt;p&gt;We will leverage the &lt;code&gt;is_power_of_two()&lt;/code&gt; in this pattern as well but we will also add a new method to round up to the next multiple of &lt;code&gt;a&lt;/code&gt;. We covered &lt;code&gt;is_power_of_two()&lt;/code&gt; in detail in the ring buffer post, so we won't repeat it here. It rejects zero as well as non-powers of two, which is what keeps &lt;code&gt;block_align = 0&lt;/code&gt; from ever reaching &lt;code&gt;round_up()&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="c1"&gt;// modules/memory_pool/memory_pool.c&lt;/span&gt;
&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;size_t&lt;/span&gt; &lt;span class="nf"&gt;round_up&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;size_t&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;size_t&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1u&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;~&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1u&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;(x + (a - 1u))&lt;/code&gt; : This takes the variable &lt;code&gt;x&lt;/code&gt;, the number we want to round up, and the variable &lt;code&gt;a&lt;/code&gt;, the alignment we want to round it up to. Adding &lt;code&gt;a - 1&lt;/code&gt; first is what pushes any value that isn't already on a boundary past the next one.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;~(a - 1u)&lt;/code&gt; : this takes the number we want to align by, subtracts one, and does a bitwise not, which gives us a mask that clears the low bits and leaves everything above them alone.&lt;/li&gt;
&lt;li&gt;Taking both and doing the bitwise &lt;code&gt;&amp;amp;&lt;/code&gt; drops the value back down onto a boundary. On its own that would round down. Adding &lt;code&gt;a - 1&lt;/code&gt; first is what turns it into a round up. This can be confusing at first glance.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;caller: round_up(x = 12, a = 16);

- (x + (a - 1u)) = (12 + (16 - 1u)) = (12 + 15) = 27 = 0b00011011
- (a - 1u) = (16 - 1u) = 15

Taking the evaluation further on the right hand side of the &amp;amp;
- ~(15) = ~(0b00001111) = 0b11110000

Now putting both sides together 
- 0b00011011 &amp;amp; 0b11110000 = 0b00010000 = 16

Meaning we need to round up 12 to 16

And when the value is already on a boundary, nothing moves:
- round_up(16, 16) = (16 + 15) &amp;amp; ~15
- 0b00011111 &amp;amp; 0b11110000 = 0b00010000 = 16

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the ring buffer, the masking trick only worked because the capacity was a power of two. The same rule applies here, except it lands on &lt;code&gt;a&lt;/code&gt;, the alignment we are rounding up to, and not on the capacity. &lt;code&gt;a&lt;/code&gt; needs to be a power of two, which is why &lt;code&gt;memory_pool_init()&lt;/code&gt; runs &lt;code&gt;is_power_of_two()&lt;/code&gt; on it before doing any rounding at all.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Example when a is not a power of two.

- round_up(12, 12) = (12 + 11) &amp;amp; ~11 = 23 &amp;amp; ~11
- 0b00010111 &amp;amp; ~0b00001011 = 0b00010111 &amp;amp; 0b11110100 = 0b00010100 = 20

It should yield 12, since 12 is already a multiple of 12 and a correct
round up would leave it alone. The reason it breaks is that 11 is
0b00001011, not a clean run of ones, so the mask clears the wrong bits.
This is why the power of two is used here to be able to optimize the round_up.

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Read next and write next functions
&lt;/h3&gt;

&lt;p&gt;This pair of functions are the ones that will read which is the next available block and update the next available block of memory. Let's start with &lt;code&gt;read_next()&lt;/code&gt;, for each allocation we will need to read where the next block to allocate is.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="c1"&gt;// modules/memory_pool/memory_pool.c&lt;/span&gt;
&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nf"&gt;read_next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;slot&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;next&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="n"&gt;memcpy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;next&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;slot&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;sizeof&lt;/span&gt; &lt;span class="n"&gt;next&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;next&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This function takes the pointer &lt;code&gt;block&lt;/code&gt;, which arrives as a &lt;code&gt;const void *&lt;/code&gt;, and casts it to &lt;code&gt;void *const *&lt;/code&gt;. That cast reads right to left: &lt;code&gt;slot&lt;/code&gt; is a pointer to a constant pointer to &lt;code&gt;void&lt;/code&gt;. The &lt;code&gt;const&lt;/code&gt; applies to the pointer stored inside the block, not to &lt;code&gt;slot&lt;/code&gt; itself, and it's there so the cast doesn't quietly throw away the qualifier the parameter already carried. We create a variable &lt;code&gt;void *next&lt;/code&gt; as a container to copy into. We do a memory copy of &lt;code&gt;sizeof next&lt;/code&gt; bytes out of the slot and into &lt;code&gt;next&lt;/code&gt;. This is how we return a memory pointer of the next block of data that is available.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;memcpy&lt;/code&gt; is worth pausing on, because the obvious version is a single line, &lt;code&gt;return *(void **)block;&lt;/code&gt;. The problem is that those bytes live inside a &lt;code&gt;uint8_t&lt;/code&gt; array, so reading them through a &lt;code&gt;void**&lt;/code&gt; means accessing an object through a pointer of a different type, which is what the aliasing rules forbid. The compiler is allowed to assume we didn't do that. &lt;code&gt;memcpy&lt;/code&gt; is the sanctioned way to say copy these bytes as they are without making a claim about types, and it costs nothing at any optimization level above &lt;code&gt;-O0&lt;/code&gt;: every compiler recognizes a pointer-sized &lt;code&gt;memcpy&lt;/code&gt; and emits a single load or store. In a debug build it stays a real call, which is worth knowing but not worth changing the code over.&lt;/p&gt;

&lt;p&gt;Next, is &lt;code&gt;write_next()&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="c1"&gt;// modules/memory_pool/memory_pool.c&lt;/span&gt;
&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;write_next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;next&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;slot&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="n"&gt;memcpy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;slot&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;next&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;sizeof&lt;/span&gt; &lt;span class="n"&gt;next&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This function mirrors &lt;code&gt;read_next()&lt;/code&gt;. The difference is that we take in &lt;code&gt;void *block&lt;/code&gt; and declare &lt;code&gt;slot&lt;/code&gt; as a &lt;code&gt;void **&lt;/code&gt;, assigning it the cast of &lt;code&gt;block&lt;/code&gt;, which tells the compiler to treat those first bytes as a place where a pointer lives. Then we memory copy &lt;code&gt;sizeof next&lt;/code&gt; bytes from &lt;code&gt;&amp;amp;next&lt;/code&gt;, the address of our local, into the slot. What lands in the block is the pointer value itself. Neither of these two functions ever looks at &lt;code&gt;block_size&lt;/code&gt;, they only touch the first &lt;code&gt;sizeof(void *)&lt;/code&gt; bytes, which is why the free list costs the same no matter how large the blocks are.&lt;/p&gt;

&lt;h3&gt;
  
  
  Reset function
&lt;/h3&gt;

&lt;p&gt;Before we get to the &lt;code&gt;init&lt;/code&gt; function, it's worth talking about how we reset the memory pool. This does the job of relinking the blocks of memory in sequential order, which makes it handy to reuse inside &lt;code&gt;init&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="c1"&gt;// modules/memory_pool/memory_pool.c&lt;/span&gt;
&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;memory_pool_reset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;memory_pool_t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;mp&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mp&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="n"&gt;mp&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;free_head&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;size_t&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mp&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;capacity&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;uint8_t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;blk&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mp&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;base&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;mp&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;block_size&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;write_next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;blk&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mp&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;free_head&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;mp&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;free_head&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;blk&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="n"&gt;mp&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;used&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0u&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We start with a quick check that we didn't pass a &lt;code&gt;NULL&lt;/code&gt; pointer to the function. Without it, the very next line, &lt;code&gt;mp-&amp;gt;free_head = NULL&lt;/code&gt;, would dereference that null handle. On a desktop that's a segmentation fault. On most microcontrollers there's no MMU to catch it, so address zero is real memory, often the vector table, and the write quietly succeeds and corrupts something instead. If &lt;code&gt;mp&lt;/code&gt; is an actual memory address, we set &lt;code&gt;mp-&amp;gt;free_head = NULL&lt;/code&gt; so we know the state we're starting from before we assign the next pointer in each block.&lt;/p&gt;

&lt;p&gt;Then we iterate with &lt;code&gt;for (size_t i = mp-&amp;gt;capacity; i-- &amp;gt; 0;)&lt;/code&gt;. The decrement lives in the comparison so the check runs first and the decrement second, which means the body sees &lt;code&gt;capacity - 1&lt;/code&gt; down to &lt;code&gt;0&lt;/code&gt;, and the loop exits after &lt;code&gt;0&lt;/code&gt; has run. The obvious version, &lt;code&gt;for (size_t i = capacity - 1; i &amp;gt;= 0; i--)&lt;/code&gt;, never terminates. &lt;code&gt;size_t&lt;/code&gt; is unsigned, so &lt;code&gt;i &amp;gt;= 0&lt;/code&gt; is always true, and decrementing &lt;code&gt;0&lt;/code&gt; wraps around to &lt;code&gt;SIZE_MAX&lt;/code&gt; rather than going negative. This is the same unsigned wraparound we leaned on in the ring buffer's &lt;code&gt;count&lt;/code&gt;, working against us here instead of for us.&lt;/p&gt;

&lt;p&gt;The direction matters for a second reason. Each pass writes the current head into a block and then makes that block the new head, so walking the array backwards produces a list that comes out forwards. After a reset the first &lt;code&gt;memory_pool_alloc()&lt;/code&gt; returns block &lt;code&gt;0&lt;/code&gt;, the next returns block &lt;code&gt;1&lt;/code&gt;, and so on in address order. Build the list the other way and the pool works identically, it just hands out descending addresses, which is harder to read when you are staring at pointer values in a debugger.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;memory_pool_reset()&lt;/code&gt; rebuilds the whole list without asking who is holding what, so it invalidates every outstanding pointer. &lt;code&gt;used&lt;/code&gt; goes back to zero, while &lt;code&gt;high_water&lt;/code&gt; deliberately does not.&lt;/p&gt;

&lt;h3&gt;
  
  
  Initialization function
&lt;/h3&gt;

&lt;p&gt;The initialization function is our entry point of actually starting to use this pattern. It will accept as parameters: &lt;code&gt;memory_pool_t *mp&lt;/code&gt;, &lt;code&gt;void *storage&lt;/code&gt;, &lt;code&gt;size_t storage_size&lt;/code&gt;, &lt;code&gt;size_t block_size&lt;/code&gt;, &lt;code&gt;size_t block_align&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="c1"&gt;// modules/memory_pool/memory_pool.c&lt;/span&gt;
&lt;span class="n"&gt;bool&lt;/span&gt; &lt;span class="nf"&gt;memory_pool_init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;memory_pool_t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;mp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;storage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;size_t&lt;/span&gt; &lt;span class="n"&gt;storage_size&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;size_t&lt;/span&gt; &lt;span class="n"&gt;block_size&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;size_t&lt;/span&gt; &lt;span class="n"&gt;block_align&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mp&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;storage&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;is_power_of_two&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;block_align&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;block_size&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="k"&gt;sizeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;block_align&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="k"&gt;_Alignof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;block_align&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;_Alignof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kt"&gt;size_t&lt;/span&gt; &lt;span class="n"&gt;stride&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;round_up&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;block_size&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;block_align&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stride&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;block_size&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kt"&gt;uintptr_t&lt;/span&gt; &lt;span class="n"&gt;addr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;uintptr_t&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;storage&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kt"&gt;uintptr_t&lt;/span&gt; &lt;span class="n"&gt;aligned&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;uintptr_t&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;round_up&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="kt"&gt;size_t&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;addr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;block_align&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kt"&gt;uint8_t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;base&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;uint8_t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;aligned&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kt"&gt;size_t&lt;/span&gt; &lt;span class="n"&gt;offset&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;size_t&lt;/span&gt;&lt;span class="p"&gt;)(&lt;/span&gt;&lt;span class="n"&gt;base&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;uint8_t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;storage&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;offset&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;storage_size&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kt"&gt;size_t&lt;/span&gt; &lt;span class="n"&gt;usable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;storage_size&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;offset&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kt"&gt;size_t&lt;/span&gt; &lt;span class="n"&gt;capacity&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;usable&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;stride&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;capacity&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0u&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="n"&gt;mp&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;base&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="n"&gt;mp&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;block_size&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;stride&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="n"&gt;mp&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;capacity&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;capacity&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="n"&gt;mp&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;used&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0u&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="n"&gt;mp&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;high_water&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0u&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="n"&gt;memory_pool_reset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mp&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first thing when passing or dealing with pointers is to make sure they are not &lt;code&gt;NULL&lt;/code&gt;. A null pointer means we would be writing to memory address zero. As stated before, on a computer that's a segmentation fault, but here we could write at address zero and corrupt an unintended address. The check also verifies explicitly that &lt;code&gt;block_align&lt;/code&gt; is a power of two, since &lt;code&gt;round_up&lt;/code&gt; is called twice further down and only works under that guarantee. The final check is &lt;code&gt;block_size &amp;lt; sizeof(void *)&lt;/code&gt;. Any block we hand out has to be big enough to hold a free list link while it's free, so a block smaller than a pointer would have its link spill into the block next to it.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Alignment clamp: &lt;code&gt;block_align&lt;/code&gt; gets raised to &lt;code&gt;_Alignof(void *)&lt;/code&gt; if you ask for less. You're allowed to request 1-byte alignment, but the free list stores a pointer inside every free block, so the pool quietly gives you pointer alignment instead. Same reason as the &lt;code&gt;block_size&lt;/code&gt; guard, from the other direction. It is worth spelling out what falls out of this: because &lt;code&gt;base&lt;/code&gt; is aligned and the stride is a multiple of &lt;code&gt;block_align&lt;/code&gt;, every block in the pool is aligned, not just the first one. That is the guarantee you are leaning on when you put a struct in a block, so pass &lt;code&gt;_Alignof(T)&lt;/code&gt; for a pool of &lt;code&gt;T&lt;/code&gt;. The clamp only promises pointer alignment on its own.&lt;/li&gt;
&lt;li&gt;Overflow guard: &lt;code&gt;if (stride &amp;lt; block_size)&lt;/code&gt; looks unreachable, since rounding up can't make a number smaller. It catches wraparound: &lt;code&gt;round_up&lt;/code&gt; adds &lt;code&gt;a - 1&lt;/code&gt; before masking, and if &lt;code&gt;block_size&lt;/code&gt; is near &lt;code&gt;SIZE_MAX&lt;/code&gt; that addition wraps. That is unsigned wraparound biting us again, this time caught on purpose.&lt;/li&gt;
&lt;li&gt;Base bump: the caller's buffer may not start on an aligned address, so &lt;code&gt;base&lt;/code&gt; gets rounded up and the lead-in bytes are lost. That's &lt;code&gt;offset&lt;/code&gt;, and it's why &lt;code&gt;base&lt;/code&gt; and &lt;code&gt;storage&lt;/code&gt; can differ.&lt;/li&gt;
&lt;li&gt;Capacity math: &lt;code&gt;usable / stride&lt;/code&gt;, with the remainder discarded. Between the bump and the truncation, capacity is usually less than &lt;code&gt;storage_size / block_size&lt;/code&gt;, which is why &lt;code&gt;memory_pool_capacity()&lt;/code&gt; exists rather than the caller computing it. Align your storage yourself, with &lt;code&gt;alignas&lt;/code&gt; on the array set at least as large as the &lt;code&gt;block_align&lt;/code&gt; you pass, and the bump costs you nothing. Leave it to chance and you can quietly lose a block.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Allocate and free functions
&lt;/h3&gt;

&lt;p&gt;Now that we have initialized the memory pool we can start using it. To &lt;code&gt;memory_pool_alloc()&lt;/code&gt; we will need to pass the parameter of &lt;code&gt;memory_pool_t *mp&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="c1"&gt;// modules/memory_pool/memory_pool.c&lt;/span&gt;
&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nf"&gt;memory_pool_alloc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;memory_pool_t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;mp&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mp&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;mp&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;free_head&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mp&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;free_head&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="n"&gt;mp&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;free_head&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;read_next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="n"&gt;mp&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;used&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mp&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;used&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;mp&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;high_water&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;mp&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;high_water&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mp&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;used&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We do our standard check that &lt;code&gt;mp&lt;/code&gt; is not &lt;code&gt;NULL&lt;/code&gt;, and alongside it we check &lt;code&gt;mp-&amp;gt;free_head&lt;/code&gt;. That second one is how an exhausted pool announces itself: the free list being empty is the head being &lt;code&gt;NULL&lt;/code&gt;, so there is no need for a separate &lt;code&gt;is_empty&lt;/code&gt; helper the way the ring buffer needed one. Returning &lt;code&gt;NULL&lt;/code&gt; here also matches &lt;code&gt;malloc()&lt;/code&gt;, so a caller that already handles a failed allocation doesn't have to learn a new convention. If both checks pass, we create a new pointer &lt;code&gt;p&lt;/code&gt; and point it at &lt;code&gt;mp-&amp;gt;free_head&lt;/code&gt;. That is the block we are about to hand back to the caller, and before we let go of it we call &lt;code&gt;read_next(p)&lt;/code&gt; to pull the link out of its first bytes, which is what becomes the new head. The last thing we do is bookkeeping: &lt;code&gt;mp-&amp;gt;used++&lt;/code&gt; records that one more block is outstanding, and if that pushes &lt;code&gt;used&lt;/code&gt; past &lt;code&gt;mp-&amp;gt;high_water&lt;/code&gt; we move the peak up with it. That comparison is the only place &lt;code&gt;high_water&lt;/code&gt; ever changes.&lt;/p&gt;

&lt;p&gt;Next, &lt;code&gt;memory_pool_free()&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="c1"&gt;// modules/memory_pool/memory_pool.c&lt;/span&gt;
&lt;span class="n"&gt;bool&lt;/span&gt; &lt;span class="nf"&gt;memory_pool_free&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;memory_pool_t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;mp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;memory_pool_owns&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mp&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;used&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0u&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="n"&gt;write_next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mp&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;free_head&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="n"&gt;mp&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;free_head&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="n"&gt;mp&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;used&lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Giving a block back is the mirror image. The first check hands the pointer to &lt;code&gt;memory_pool_owns()&lt;/code&gt;, the subject of the next section, which answers whether this is actually the start of a block in this pool. The second, &lt;code&gt;mp-&amp;gt;used == 0u&lt;/code&gt;, rejects a free into a pool with nothing outstanding. Both run before we write anything, so a rejected free leaves the pool completely untouched, free list included. The order of those two is not interchangeable either, since &lt;code&gt;mp-&amp;gt;used&lt;/code&gt; dereferences &lt;code&gt;mp&lt;/code&gt; and is only safe because &lt;code&gt;memory_pool_owns()&lt;/code&gt; already turned away a null pool. Swap them to fail on the cheaper check first and you have written a null dereference.&lt;/p&gt;

&lt;p&gt;Then we push. &lt;code&gt;write_next(block, mp-&amp;gt;free_head)&lt;/code&gt; writes the current head into the returned block, &lt;code&gt;mp-&amp;gt;free_head = block&lt;/code&gt; makes that block the new head, and &lt;code&gt;mp-&amp;gt;used--&lt;/code&gt; brings the count down. This is the stack behavior from earlier: the block you just gave back is the one the next &lt;code&gt;memory_pool_alloc()&lt;/code&gt; hands out.&lt;/p&gt;

&lt;p&gt;There is one case these checks cannot catch, freeing the same block twice. We will come back to that after the implementation.&lt;/p&gt;

&lt;h3&gt;
  
  
  Memory pool owns memory address
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="c1"&gt;// modules/memory_pool/memory_pool.c&lt;/span&gt;
&lt;span class="n"&gt;bool&lt;/span&gt; &lt;span class="nf"&gt;memory_pool_owns&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;memory_pool_t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;mp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mp&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kt"&gt;uintptr_t&lt;/span&gt; &lt;span class="n"&gt;addr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;uintptr_t&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kt"&gt;uintptr_t&lt;/span&gt; &lt;span class="n"&gt;base&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;uintptr_t&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;mp&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kt"&gt;uintptr_t&lt;/span&gt; &lt;span class="n"&gt;end&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;base&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="kt"&gt;uintptr_t&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;mp&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;capacity&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;uintptr_t&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;mp&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;block_size&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;addr&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;base&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;addr&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;end&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;addr&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;uintptr_t&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;mp&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;block_size&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0u&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the validation &lt;code&gt;memory_pool_free()&lt;/code&gt; leans on, and it asks two questions. The first is whether the pointer is inside the arena at all, comparing it against &lt;code&gt;base&lt;/code&gt; and &lt;code&gt;end&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The second is the interesting one: &lt;code&gt;(addr - base) % mp-&amp;gt;block_size == 0u&lt;/code&gt; checks the pointer is exactly on a block boundary. A pointer into the middle of a block passes the range test but is not a block, and freeing it would splice a bogus node into the free list at an offset, so the next allocation would hand out a block overlapping its neighbor. This modulo is the one division in the module, and it is the same cost we avoided in the ring buffer. Making the stride a power of two turns it back into a mask.&lt;/p&gt;

&lt;h3&gt;
  
  
  Capacity function
&lt;/h3&gt;

&lt;p&gt;Knowing the capacity is important because from that we can identify how much of the memory pool we have to hand out to be used.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="c1"&gt;// modules/memory_pool/memory_pool.c&lt;/span&gt;
&lt;span class="kt"&gt;size_t&lt;/span&gt; &lt;span class="nf"&gt;memory_pool_capacity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;memory_pool_t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;mp&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mp&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="mi"&gt;0u&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;mp&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;capacity&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We do our &lt;code&gt;NULL&lt;/code&gt; check on &lt;code&gt;mp&lt;/code&gt;. If it is null we return &lt;code&gt;0u&lt;/code&gt;, otherwise we return the capacity derived back in &lt;code&gt;memory_pool_init()&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Helper functions
&lt;/h3&gt;

&lt;p&gt;These are the functions to access the data members of the &lt;code&gt;memory_pool_t&lt;/code&gt; without modifying them. We will get started with &lt;code&gt;memory_pool_used()&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="c1"&gt;// modules/memory_pool/memory_pool.c&lt;/span&gt;
&lt;span class="kt"&gt;size_t&lt;/span&gt; &lt;span class="nf"&gt;memory_pool_used&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;memory_pool_t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;mp&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mp&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="mi"&gt;0u&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;mp&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;used&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here we just check that the &lt;code&gt;mp&lt;/code&gt; passed as an argument is not &lt;code&gt;NULL&lt;/code&gt;, otherwise we return the field we are asking for, in this case &lt;code&gt;mp-&amp;gt;used&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="c1"&gt;// modules/memory_pool/memory_pool.c&lt;/span&gt;
&lt;span class="kt"&gt;size_t&lt;/span&gt; &lt;span class="nf"&gt;memory_pool_available&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;memory_pool_t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;mp&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mp&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="mi"&gt;0u&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;mp&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;capacity&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;mp&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;used&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kt"&gt;size_t&lt;/span&gt; &lt;span class="nf"&gt;memory_pool_high_water&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;memory_pool_t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;mp&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mp&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="mi"&gt;0u&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;mp&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;high_water&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here we have the remainder of the helper functions, following the same &lt;code&gt;NULL&lt;/code&gt; check and returning the value we asked for. Note that &lt;code&gt;memory_pool_available()&lt;/code&gt; works out &lt;code&gt;capacity - used&lt;/code&gt; rather than walking the free list to count what's left, which is the same answer without the traversal.&lt;/p&gt;

&lt;h2&gt;
  
  
  Example usage
&lt;/h2&gt;

&lt;p&gt;Now that we have explained the full implementation, let's see how we will use the pattern.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="k"&gt;typedef&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kt"&gt;uint32_t&lt;/span&gt; &lt;span class="n"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kt"&gt;int16_t&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kt"&gt;uint8_t&lt;/span&gt; &lt;span class="n"&gt;sensor_id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="n"&gt;sample_t&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;alignas&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;uint8_t&lt;/span&gt; &lt;span class="n"&gt;sample_storage&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;sizeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sample_t&lt;/span&gt;&lt;span class="p"&gt;)];&lt;/span&gt;
&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;memory_pool_t&lt;/span&gt; &lt;span class="n"&gt;sample_pool&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;sample_t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="n"&gt;bool&lt;/span&gt; &lt;span class="nf"&gt;sensor_init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;memory_pool_init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;sample_pool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sample_storage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;sizeof&lt;/span&gt; &lt;span class="n"&gt;sample_storage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                          &lt;span class="k"&gt;sizeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sample_t&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="k"&gt;_Alignof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sample_t&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;bool&lt;/span&gt; &lt;span class="nf"&gt;sensor_take_reading&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;uint8_t&lt;/span&gt; &lt;span class="n"&gt;sensor_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;current&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;memory_pool_alloc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;sample_pool&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;current&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;timestamp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;read_adc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sensor_id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;sensor_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sensor_id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;sensor_flush&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;current&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="n"&gt;transmit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="n"&gt;memory_pool_free&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;sample_pool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="n"&gt;current&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This example shows we will be using the memory pool to store &lt;code&gt;sample_t&lt;/code&gt;, which is made up of &lt;code&gt;timestamp&lt;/code&gt;, &lt;code&gt;value&lt;/code&gt; and &lt;code&gt;sensor_id&lt;/code&gt;. Notice the block is allocated in one function and freed in another. That is the point of the pool, the memory outlives the call that asked for it, which is what lets a reading sit around until something else is ready to deal with it. One slot keeps the example short; a real caller holds several at once.&lt;/p&gt;

&lt;p&gt;Now &lt;code&gt;alignas(void *)&lt;/code&gt; on &lt;code&gt;sample_storage&lt;/code&gt;. We pass &lt;code&gt;_Alignof(sample_t)&lt;/code&gt; to &lt;code&gt;init&lt;/code&gt;, but &lt;code&gt;init&lt;/code&gt; clamps that up to &lt;code&gt;_Alignof(void *)&lt;/code&gt;, because every free block has to hold a pointer. The storage has to satisfy whichever of the two ends up larger, and here that's &lt;code&gt;void *&lt;/code&gt;, on a 64-bit host it's 8 against &lt;code&gt;sample_t&lt;/code&gt;'s 4, and on a 32-bit target the two are equal, so aligning to the pointer covers both. Get it wrong and &lt;code&gt;base&lt;/code&gt; gets bumped forward and you quietly lose a block. Notice we declare &lt;code&gt;sample_storage&lt;/code&gt; as &lt;code&gt;uint8_t&lt;/code&gt; so the array is raw bytes and we can size it by multiplying the block count by &lt;code&gt;sizeof(sample_t)&lt;/code&gt;. Note that &lt;code&gt;alignas&lt;/code&gt; needs &lt;code&gt;&amp;lt;stdalign.h&amp;gt;&lt;/code&gt; in C11, and the snippet leaves out the includes.&lt;/p&gt;

&lt;p&gt;Also notice the pool has no idea which sensor a block belongs to. &lt;code&gt;sensor_id&lt;/code&gt; is a field we put in the struct ourselves, not something the pool tracks. It only ever knew the block size and alignment we handed it at &lt;code&gt;init&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Notice that &lt;code&gt;alloc&lt;/code&gt;, &lt;code&gt;free&lt;/code&gt;, and the way we use the memory in between are the same shape as &lt;code&gt;malloc()&lt;/code&gt; and &lt;code&gt;free()&lt;/code&gt;. Ask for memory, check for &lt;code&gt;NULL&lt;/code&gt;, use it, hand it back. That is deliberate, code written against the heap ports over by changing two call sites. Two differences worth knowing: &lt;code&gt;memory_pool_free()&lt;/code&gt; returns a &lt;code&gt;bool&lt;/code&gt; instead of nothing, and freeing the same block twice does not behave the way your heap allocator would. We will get to that next.&lt;/p&gt;

&lt;h2&gt;
  
  
  Double-free edge case
&lt;/h2&gt;

&lt;p&gt;Back in &lt;code&gt;memory_pool_free()&lt;/code&gt; we said there was one case the checks cannot catch. Freeing into an empty pool is rejected by &lt;code&gt;mp-&amp;gt;used == 0u&lt;/code&gt;, and a pointer that isn't a block start is rejected by &lt;code&gt;memory_pool_owns()&lt;/code&gt;. But freeing a block that is &lt;em&gt;already&lt;/em&gt; on the free list passes both. It is a legitimate block start, so &lt;code&gt;owns&lt;/code&gt; says yes, and other blocks are still outstanding, so &lt;code&gt;used&lt;/code&gt; is nonzero.&lt;/p&gt;

&lt;p&gt;So the push runs. We write &lt;code&gt;free_head&lt;/code&gt; into the block and then make the block the new head. If that block was already the head, it now points at itself.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Before:
  free_head -&amp;gt; [B] -&amp;gt; [C] -&amp;gt; NULL

memory_pool_free(mp, B) a second time:
  write_next(B, free_head) B's next now points at B
  free_head = B

After:
  free_head -&amp;gt; [B] -&amp;gt; [B] -&amp;gt; [B] -&amp;gt; ...

  alloc() returns B
  alloc() returns B again, to a different caller

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nothing reports this. &lt;code&gt;memory_pool_free()&lt;/code&gt; returns &lt;code&gt;true&lt;/code&gt;, &lt;code&gt;used&lt;/code&gt; looks plausible, and every query says the pool is healthy. You find it later, in whatever code was unlucky enough to own the block second.&lt;/p&gt;

&lt;p&gt;There are ways to catch it and both cost something we chose this pattern for. We could walk the free list on every free and reject a block already on it, which is correct and turns a constant-time free into a scan. We could keep a side bitmap of allocated blocks, which costs memory and gives up the zero-overhead property that motivated the intrusive list in the first place. For a small firmware codebase where the allocation sites are few and reviewable, documenting it is a defensible trade, and the limitation is written into the header right next to the function that cannot catch it. For a larger codebase it would be worth paying for a debug-build check.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrap-up
&lt;/h2&gt;

&lt;p&gt;That's a complete memory pool. It does no dynamic allocation, hands blocks out and takes them back in constant time with no search, carries zero bytes of per-block overhead, cannot fragment by construction, and tells you afterwards how much of it you actually needed.&lt;/p&gt;

&lt;p&gt;The trades, all deliberate:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;One size per pool.&lt;/strong&gt; Four object types means four pools. This is what buys away fragmentation, and it makes the pool a poor fit for genuinely variable-sized data.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Not thread-safe or ISR-safe.&lt;/strong&gt; Unlike the ring buffer, there is no lock-free path here. Every mutating call touches &lt;code&gt;free_head&lt;/code&gt;, and there is no way to split that between two contexts the way &lt;code&gt;head&lt;/code&gt; and &lt;code&gt;tail&lt;/code&gt; split. A shared pool needs a critical section around every mutating call, which is also why there is no &lt;code&gt;volatile&lt;/code&gt; anywhere in &lt;code&gt;memory_pool_t&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Blocks aren't zeroed.&lt;/strong&gt; These are &lt;code&gt;malloc&lt;/code&gt; semantics, not &lt;code&gt;calloc&lt;/code&gt;. A fresh block holds a stale free list pointer in its first bytes and whatever the last owner left in the rest.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Double free is only partly detectable.&lt;/strong&gt; Covered above, and the most important limitation in the module.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Capacity is derived, not given.&lt;/strong&gt; Stride rounding, the alignment bump, and the tail remainder all eat into it. Read it back with &lt;code&gt;memory_pool_capacity()&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Two patterns in and they have started to lean on each other. The ring buffer gave us fixed storage for bytes, the pool gives us fixed storage for objects, and the event queue later in this series will want both: a ring of pointers to blocks handed out by a pool. That is how you get a queue of structs without a single call to &lt;code&gt;malloc&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Next in the series is the state machine. The ring buffer and the pool both dealt in storage, bytes in one and blocks in the other, and neither cared what any of it meant. The state machine is where it starts to mean something.&lt;/p&gt;

</description>
      <category>embeddedsystems</category>
      <category>memorymanagement</category>
      <category>c</category>
      <category>microcontrollers</category>
    </item>
    <item>
      <title>Patrones embebidos comunes: pool de memoria</title>
      <dc:creator>Ilean Monterrubio Jr</dc:creator>
      <pubDate>Thu, 10 Sep 2026 00:00:00 +0000</pubDate>
      <link>https://dev.to/elcapitan/patrones-embebidos-comunes-pool-de-memoria-1l8</link>
      <guid>https://dev.to/elcapitan/patrones-embebidos-comunes-pool-de-memoria-1l8</guid>
      <description>&lt;p&gt;Esta es la segunda parte de la serie donde vuelvo a explorar los patrones embebidos más comunes. Esta vez le toca al pool de memoria (memory pool). Este patrón se usa a menudo como alternativa a &lt;code&gt;malloc()&lt;/code&gt; y &lt;code&gt;free()&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;El código fuente completo y probado vive en el repositorio complementario: &lt;a href="https://github.com/ileanmjr88/tetzontli" rel="noopener noreferrer"&gt;ileanmjr88/tetzontli&lt;/a&gt;. Cada patrón de esta serie es su propio módulo con una suite completa de pruebas en GoogleTest, así que puedes clonarlo y ejecutar las pruebas tú mismo.&lt;/p&gt;

&lt;h2&gt;
  
  
  Concepto básico
&lt;/h2&gt;

&lt;p&gt;Empecemos con una comparación rápida contra el búfer circular. Ambos patrones sirven para guardar datos, pero difieren en el punto de vista desde el cual lo hacen. Al búfer circular lo llamamos contenedor. Cada elemento que se inserta en la estructura de datos es homogéneo y uniforme, y el búfer es dueño del orden de esos datos: se empuja por un lado y se saca por el otro, siguiendo FIFO. El búfer sabe qué hay dentro, al menos en cuanto a tamaño y secuencia.&lt;/p&gt;

&lt;p&gt;El pool de memoria toma el punto de vista opuesto. No es un contenedor, es gestión de memoria propia. No conoce la estructura de los datos que se guardan, no le importa qué contienen, y no lleva registro de la secuencia. Uno pide un bloque, el pool revisa si hay alguno disponible, y si lo hay, devuelve un puntero a ese bloque. Desde el punto de vista del pool solo existen bloques ocupados o libres, sin referencia a su estructura ni al orden en que se entregaron.&lt;/p&gt;

&lt;p&gt;Llegados a este punto, la pregunta obvia es qué es un pool de memoria en los términos más simples. Es un búfer de bloques de memoria del mismo tamaño, normalmente implementado como un arreglo, así que tenemos un trozo o pool de memoria del cual tomar. Nuestro &lt;code&gt;init&lt;/code&gt; determina el tamaño de los bloques e inicializa la estructura administradora que lleva registro de dónde está el siguiente bloque disponible.&lt;/p&gt;

&lt;h2&gt;
  
  
  Un repaso rápido a &lt;code&gt;malloc()&lt;/code&gt; y &lt;code&gt;free()&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Antes de entrar al pool de memoria en sí, vale la pena dar un paso atrás y recordar qué hacen realmente &lt;code&gt;malloc()&lt;/code&gt; y &lt;code&gt;free()&lt;/code&gt;. Es importante porque vamos a implementar una funcionalidad parecida. La diferencia es que nosotros reservamos por adelantado un búfer de tamaño fijo, normalmente un arreglo estático, mientras que estas funciones reservan y liberan memoria del heap de forma dinámica.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;malloc()&lt;/code&gt; le pide al asignador del heap un trozo de memoria del tamaño del elemento que se quiere guardar. Si la reserva tiene éxito, es decir, si el asignador encontró espacio, devuelve un puntero. Si no logra encontrar espacio devuelve &lt;code&gt;NULL&lt;/code&gt;, indicando el fallo. El asignador del heap gestiona esto llevando su propio registro interno de los trozos libres y usados. Cada vez que usamos &lt;code&gt;malloc()&lt;/code&gt; lleva registro de cuánto usamos, y lo mismo con &lt;code&gt;free()&lt;/code&gt;: cada vez que liberamos memoria la marca como disponible. &lt;code&gt;free()&lt;/code&gt; hace lo contrario de &lt;code&gt;malloc()&lt;/code&gt;: se llama con el puntero que queremos devolverle al asignador del heap, este marca ese trozo como libre, y sigue adelante.&lt;/p&gt;

&lt;p&gt;Estas dos funciones para reservar y liberar memoria dinámicamente funcionan muy bien en computadoras modernas y en granjas de servidores, donde hay grandes cantidades de memoria y un espacio de direcciones amplio. Si un &lt;code&gt;malloc()&lt;/code&gt; tarda un microsegundo más en encontrar un trozo de memoria no pasa nada. Los sistemas embebidos no pueden hacer esas mismas suposiciones, y ahí es donde empiezan a aparecer las grietas:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Fragmentación&lt;/strong&gt; : a medida que reservamos y liberamos trozos de tamaños mezclados, la memoria libre que va quedando deja de ser contigua. Con el tiempo una petición falla aunque en total haya bytes libres de sobra, solo que no hay suficientes juntos. El fallo llega tarde, es intermitente y no se reproduce en el banco de pruebas.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tiempos no deterministas&lt;/strong&gt; : no todo sistema embebido necesita tiempos deterministas, pero para los que sí, &lt;code&gt;malloc()&lt;/code&gt; es un problema. El asignador busca un trozo que quepa, así que cuánto tarda depende del estado del heap. Eso es difícil de aceptar en un bucle con plazos estrictos, e imposible de presupuestar.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fallo tardío&lt;/strong&gt; : uno se entera de que se quedó sin memoria justo en el momento en que necesita memoria, que suele ser el momento en que menos puede hacer al respecto. No hay respuesta en tiempo de arranque ni en tiempo de enlazado a la pregunta «¿esto va a caber?».&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Llevar el registro de los bloques disponibles
&lt;/h2&gt;

&lt;p&gt;Que no usemos &lt;code&gt;malloc()&lt;/code&gt; ni &lt;code&gt;free()&lt;/code&gt; también significa que no usamos el administrador del heap, que llevaba su propio registro de qué trozos estaban libres y cuáles entregados. Nuestra implementación tiene que hacer esa contabilidad por su cuenta. En realidad solo hay dos cosas que necesita saber: si hay algún bloque disponible, y si lo hay, dónde está.&lt;/p&gt;

&lt;p&gt;Antes de entrar en la implementación que se usa en el repositorio tetzontli, exploremos brevemente dos enfoques comunes para esta contabilidad. El primero es el mapa de bits (bitmap), donde cada bit de un entero indica si un bloque está en uso o libre. La cantidad de bloques determina el tipo de entero que se usa. La alternativa es el método de la lista de libres (free list), que enhebra los bloques disponibles en una lista enlazada y mantiene un puntero apuntando al siguiente bloque disponible. Al liberar, el bloque vuelve al frente de ese puntero. Hay una ventaja clara en el método de la lista de libres: nunca tiene que buscar. Tomar un bloque significa quedarse con aquello a lo que apunta el puntero de seguimiento, y devolver uno significa ponerlo al frente. Ambas son una sola actualización de puntero, sin importar qué tan lleno esté el pool. El mapa de bits tiene que recorrer buscando un bit libre, y cuánto tarda depende de cuántos bloques ya estén en uso. Eso sí, el mapa de bits tiene una ventaja a la que estamos renunciando: como registra el estado de cada bloque, puede distinguir una doble liberación de una legítima. La lista de libres no puede, y volveremos a lo que eso nos cuesta.&lt;/p&gt;

&lt;p&gt;La implementación de tetzontli usa el enfoque de lista de libres intrusiva. Es una variación común donde cada bloque libre sabe por sí mismo dónde está el siguiente trozo de memoria disponible. La razón de este enfoque es que no hace falta un mapa de bits para llevar registro de los bloques disponibles, ya que nuestro puntero &lt;code&gt;next&lt;/code&gt; ya sabe cuál es el siguiente bloque libre. También un recordatorio: como no reservamos memoria dinámicamente, usamos un arreglo como contenedor para crear el pool de memoria. Eso facilita el recorrido después del &lt;code&gt;init&lt;/code&gt;. Una vez que empezamos a reservar y liberar, queremos que los bloques liberados más recientemente sean los primeros en entregarse, manteniéndolo como una pila, lo cual es más amigable con caché.&lt;/p&gt;

&lt;h2&gt;
  
  
  Implementación
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Estructura del pool de memoria
&lt;/h3&gt;

&lt;p&gt;Igual que en el búfer circular, vamos a crear un struct. Es una forma simple de mantener todos los parámetros juntos y accesibles desde un solo puntero.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="c1"&gt;// modules/memory_pool/memory_pool.h&lt;/span&gt;
&lt;span class="k"&gt;typedef&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kt"&gt;uint8_t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kt"&gt;size_t&lt;/span&gt; &lt;span class="n"&gt;block_size&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kt"&gt;size_t&lt;/span&gt; &lt;span class="n"&gt;capacity&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kt"&gt;size_t&lt;/span&gt; &lt;span class="n"&gt;used&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kt"&gt;size_t&lt;/span&gt; &lt;span class="n"&gt;high_water&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;free_head&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="n"&gt;memory_pool_t&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;uint8_t *base&lt;/code&gt;: la dirección del bloque &lt;code&gt;0&lt;/code&gt;, y el ancla desde la cual se mide todo el pool. Cada bloque vive en &lt;code&gt;base + i * block_size&lt;/code&gt;. Es tentador leer esto como el primer byte del búfer que se le entrega al pool, y la mayoría de las veces eso es exactamente lo que es, pero los dos pueden separarse, por una razón que veremos en &lt;code&gt;memory_pool_init()&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;size_t block_size&lt;/code&gt;: esto es lo que solemos llamar el stride. Define el tamaño de cada bloque de memoria y qué tan separados están entre sí.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;size_t capacity&lt;/code&gt;: la cantidad de bloques del pool. Se fija en &lt;code&gt;memory_pool_init()&lt;/code&gt; y nunca cambia. Cuántos están libres en un momento dado es &lt;code&gt;capacity - used&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;size_t used&lt;/code&gt;: la cantidad de bloques entregados. &lt;code&gt;memory_pool_alloc()&lt;/code&gt; lo incrementa y &lt;code&gt;memory_pool_free()&lt;/code&gt; lo decrementa. Ojo: entregados, no en uso. El pool no tiene idea de qué se hace con un bloque, solo de si lo entregó y no lo ha recuperado.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;size_t high_water&lt;/code&gt;: el valor máximo que &lt;code&gt;used&lt;/code&gt; ha alcanzado desde &lt;code&gt;memory_pool_init()&lt;/code&gt;. Este es el número con el que se dimensiona el pool: ejecutar el sistema bajo la peor carga, leerlo de vuelta, y así se sabe cuántos bloques hacían falta realmente. Sobrevive deliberadamente a &lt;code&gt;memory_pool_reset()&lt;/code&gt;. Devolver todos los bloques no cambia el hecho de que en algún momento se necesitaron todos esos a la vez, y borrar el pico tiraría a la basura la medición. Solo un &lt;code&gt;memory_pool_init()&lt;/code&gt; nuevo lo reinicia.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;void *free_head&lt;/code&gt;: la cabeza de la lista de libres. El primer bloque disponible para entregar, o &lt;code&gt;NULL&lt;/code&gt; cuando todos los bloques están fuera. La lista es intrusiva, es decir, sus enlaces viven dentro de los propios bloques libres en vez de en structs de nodo aparte. Cada bloque libre guarda un puntero al siguiente en sus primeros bytes. Eso es lo que hace que la lista de libres no cueste memoria extra, y es la razón por la que &lt;code&gt;memory_pool_init()&lt;/code&gt; rechaza un &lt;code&gt;block_size&lt;/code&gt; más chico que un puntero.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Función de redondeo hacia arriba
&lt;/h3&gt;

&lt;p&gt;También vamos a aprovechar &lt;code&gt;is_power_of_two()&lt;/code&gt; en este patrón, y además agregamos un método nuevo para redondear hacia arriba al siguiente múltiplo de &lt;code&gt;a&lt;/code&gt;. Cubrimos &lt;code&gt;is_power_of_two()&lt;/code&gt; en detalle en el post del búfer circular, así que no lo repetimos aquí. Rechaza el cero además de los números que no son potencia de dos, que es lo que impide que &lt;code&gt;block_align = 0&lt;/code&gt; llegue a &lt;code&gt;round_up()&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="c1"&gt;// modules/memory_pool/memory_pool.c&lt;/span&gt;
&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;size_t&lt;/span&gt; &lt;span class="nf"&gt;round_up&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;size_t&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;size_t&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1u&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;~&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1u&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;(x + (a - 1u))&lt;/code&gt;: toma la variable &lt;code&gt;x&lt;/code&gt;, el número que queremos redondear hacia arriba, y la variable &lt;code&gt;a&lt;/code&gt;, la alineación a la que queremos redondearlo. Sumar &lt;code&gt;a - 1&lt;/code&gt; primero es lo que empuja cualquier valor que no esté ya en un límite más allá del siguiente.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;~(a - 1u)&lt;/code&gt;: toma el número por el que queremos alinear, le resta uno, y aplica un not a nivel de bits, lo cual nos da una máscara que limpia los bits bajos y deja intacto todo lo que está por encima.&lt;/li&gt;
&lt;li&gt;Tomar ambos lados y hacer el &lt;code&gt;&amp;amp;&lt;/code&gt; a nivel de bits baja el valor de vuelta a un límite. Por sí solo eso redondearía hacia abajo. Sumar &lt;code&gt;a - 1&lt;/code&gt; primero es lo que lo convierte en un redondeo hacia arriba. Esto puede confundir a primera vista.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;llamada: round_up(x = 12, a = 16);

- (x + (a - 1u)) = (12 + (16 - 1u)) = (12 + 15) = 27 = 0b00011011
- (a - 1u) = (16 - 1u) = 15

Siguiendo la evaluación del lado derecho del &amp;amp;
- ~(15) = ~(0b00001111) = 0b11110000

Ahora juntando ambos lados
- 0b00011011 &amp;amp; 0b11110000 = 0b00010000 = 16

Es decir, hay que redondear 12 hacia arriba hasta 16

Y cuando el valor ya está en un límite, nada se mueve:
- round_up(16, 16) = (16 + 15) &amp;amp; ~15
- 0b00011111 &amp;amp; 0b11110000 = 0b00010000 = 16

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;En el búfer circular, el truco de la máscara solo funcionaba porque la capacidad era potencia de dos. Aquí aplica la misma regla, salvo que recae sobre &lt;code&gt;a&lt;/code&gt;, la alineación a la que redondeamos, y no sobre la capacidad. &lt;code&gt;a&lt;/code&gt; tiene que ser potencia de dos, y por eso &lt;code&gt;memory_pool_init()&lt;/code&gt; le aplica &lt;code&gt;is_power_of_two()&lt;/code&gt; antes de hacer cualquier redondeo.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Ejemplo cuando a no es potencia de dos.

- round_up(12, 12) = (12 + 11) &amp;amp; ~11 = 23 &amp;amp; ~11
- 0b00010111 &amp;amp; ~0b00001011 = 0b00010111 &amp;amp; 0b11110100 = 0b00010100 = 20

Debería dar 12, ya que 12 ya es múltiplo de 12 y un redondeo correcto
lo dejaría igual. Falla porque 11 es 0b00001011, no una serie limpia
de unos, así que la máscara limpia los bits equivocados.
Por eso aquí se usa la potencia de dos, para poder optimizar round_up.

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Funciones de lectura y escritura del siguiente
&lt;/h3&gt;

&lt;p&gt;Este par de funciones son las que leen cuál es el siguiente bloque disponible y actualizan cuál pasa a serlo. Empecemos con &lt;code&gt;read_next()&lt;/code&gt;: para cada reserva vamos a necesitar leer dónde está el siguiente bloque a entregar.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="c1"&gt;// modules/memory_pool/memory_pool.c&lt;/span&gt;
&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nf"&gt;read_next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;slot&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;next&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="n"&gt;memcpy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;next&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;slot&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;sizeof&lt;/span&gt; &lt;span class="n"&gt;next&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;next&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Esta función toma el puntero &lt;code&gt;block&lt;/code&gt;, que llega como &lt;code&gt;const void *&lt;/code&gt;, y lo castea a &lt;code&gt;void *const *&lt;/code&gt;. Ese cast se lee de derecha a izquierda: &lt;code&gt;slot&lt;/code&gt; es un puntero a un puntero constante a &lt;code&gt;void&lt;/code&gt;. El &lt;code&gt;const&lt;/code&gt; aplica al puntero guardado dentro del bloque, no a &lt;code&gt;slot&lt;/code&gt; en sí, y está ahí para que el cast no descarte silenciosamente el calificador que el parámetro ya traía. Creamos la variable &lt;code&gt;void *next&lt;/code&gt; como contenedor al cual copiar. Hacemos una copia de memoria de &lt;code&gt;sizeof next&lt;/code&gt; bytes desde el slot hacia &lt;code&gt;next&lt;/code&gt;. Así es como devolvemos un puntero al siguiente bloque de datos disponible.&lt;/p&gt;

&lt;p&gt;Vale la pena detenerse en el &lt;code&gt;memcpy&lt;/code&gt;, porque la versión obvia es una sola línea, &lt;code&gt;return *(void **)block;&lt;/code&gt;. El problema es que esos bytes viven dentro de un arreglo de &lt;code&gt;uint8_t&lt;/code&gt;, así que leerlos a través de un &lt;code&gt;void**&lt;/code&gt; significa acceder a un objeto mediante un puntero de otro tipo, que es justo lo que prohíben las reglas de aliasing. El compilador tiene permiso de asumir que no hicimos eso. &lt;code&gt;memcpy&lt;/code&gt; es la manera sancionada de decir «copia estos bytes tal cual sin afirmar nada sobre tipos», y no cuesta nada en cualquier nivel de optimización por encima de &lt;code&gt;-O0&lt;/code&gt;: todo compilador reconoce un &lt;code&gt;memcpy&lt;/code&gt; del tamaño de un puntero y emite una sola carga o escritura. En una compilación de depuración se queda como una llamada real, lo cual conviene saber pero no amerita cambiar el código.&lt;/p&gt;

&lt;p&gt;Sigue &lt;code&gt;write_next()&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="c1"&gt;// modules/memory_pool/memory_pool.c&lt;/span&gt;
&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;write_next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;next&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;slot&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="n"&gt;memcpy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;slot&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;next&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;sizeof&lt;/span&gt; &lt;span class="n"&gt;next&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Esta función es el espejo de &lt;code&gt;read_next()&lt;/code&gt;. La diferencia es que recibimos &lt;code&gt;void *block&lt;/code&gt; y declaramos &lt;code&gt;slot&lt;/code&gt; como &lt;code&gt;void **&lt;/code&gt;, asignándole el cast de &lt;code&gt;block&lt;/code&gt;, lo cual le dice al compilador que trate esos primeros bytes como un lugar donde vive un puntero. Después copiamos &lt;code&gt;sizeof next&lt;/code&gt; bytes desde &lt;code&gt;&amp;amp;next&lt;/code&gt;, la dirección de nuestra variable local, hacia el slot. Lo que queda en el bloque es el valor del puntero mismo. Ninguna de estas dos funciones mira nunca &lt;code&gt;block_size&lt;/code&gt;, solo tocan los primeros &lt;code&gt;sizeof(void *)&lt;/code&gt; bytes, y por eso la lista de libres cuesta lo mismo sin importar qué tan grandes sean los bloques.&lt;/p&gt;

&lt;h3&gt;
  
  
  Función de reinicio (reset)
&lt;/h3&gt;

&lt;p&gt;Antes de llegar a la función &lt;code&gt;init&lt;/code&gt;, conviene hablar de cómo reiniciamos el pool de memoria. Esta hace el trabajo de reenlazar los bloques de memoria en orden secuencial, lo cual la vuelve práctica para reutilizarla dentro de &lt;code&gt;init&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="c1"&gt;// modules/memory_pool/memory_pool.c&lt;/span&gt;
&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;memory_pool_reset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;memory_pool_t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;mp&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mp&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="n"&gt;mp&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;free_head&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;size_t&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mp&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;capacity&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;uint8_t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;blk&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mp&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;base&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;mp&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;block_size&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;write_next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;blk&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mp&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;free_head&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;mp&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;free_head&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;blk&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="n"&gt;mp&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;used&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0u&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Empezamos con una verificación rápida de que no le pasamos un puntero &lt;code&gt;NULL&lt;/code&gt; a la función. Sin ella, la siguiente línea, &lt;code&gt;mp-&amp;gt;free_head = NULL&lt;/code&gt;, dereferenciaría ese handle nulo. En una computadora de escritorio eso es un fallo de segmentación. En la mayoría de los microcontroladores no hay MMU que lo atrape, así que la dirección cero es memoria real, a menudo la tabla de vectores, y la escritura tiene éxito en silencio y corrompe algo. Si &lt;code&gt;mp&lt;/code&gt; es una dirección de memoria real, ponemos &lt;code&gt;mp-&amp;gt;free_head = NULL&lt;/code&gt; para saber desde qué estado partimos antes de asignar el puntero siguiente en cada bloque.&lt;/p&gt;

&lt;p&gt;Después iteramos con &lt;code&gt;for (size_t i = mp-&amp;gt;capacity; i-- &amp;gt; 0;)&lt;/code&gt;. El decremento vive dentro de la comparación, así que la verificación va primero y el decremento después, lo que significa que el cuerpo ve desde &lt;code&gt;capacity - 1&lt;/code&gt; hasta &lt;code&gt;0&lt;/code&gt;, y el bucle termina después de ejecutar el &lt;code&gt;0&lt;/code&gt;. La versión obvia, &lt;code&gt;for (size_t i = capacity - 1; i &amp;gt;= 0; i--)&lt;/code&gt;, nunca termina. &lt;code&gt;size_t&lt;/code&gt; es sin signo, así que &lt;code&gt;i &amp;gt;= 0&lt;/code&gt; siempre es verdadero, y decrementar &lt;code&gt;0&lt;/code&gt; da la vuelta hasta &lt;code&gt;SIZE_MAX&lt;/code&gt; en lugar de volverse negativo. Este es el mismo desbordamiento sin signo en el que nos apoyamos con el &lt;code&gt;count&lt;/code&gt; del búfer circular, solo que aquí trabaja en nuestra contra.&lt;/p&gt;

&lt;p&gt;La dirección importa por una segunda razón. Cada pasada escribe la cabeza actual dentro de un bloque y luego hace de ese bloque la nueva cabeza, así que recorrer el arreglo hacia atrás produce una lista que sale hacia adelante. Después de un reinicio, el primer &lt;code&gt;memory_pool_alloc()&lt;/code&gt; devuelve el bloque &lt;code&gt;0&lt;/code&gt;, el siguiente devuelve el &lt;code&gt;1&lt;/code&gt;, y así en orden de direcciones. Si se construye la lista al revés el pool funciona igual, solo que entrega direcciones descendentes, lo cual es más difícil de leer cuando uno está mirando valores de punteros en un depurador.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;memory_pool_reset()&lt;/code&gt; reconstruye la lista completa sin preguntar quién tiene qué, así que invalida todos los punteros pendientes. &lt;code&gt;used&lt;/code&gt; vuelve a cero, mientras que &lt;code&gt;high_water&lt;/code&gt; deliberadamente no.&lt;/p&gt;

&lt;h3&gt;
  
  
  Función de inicialización
&lt;/h3&gt;

&lt;p&gt;La función de inicialización es nuestro punto de entrada para empezar a usar el patrón. Acepta como parámetros: &lt;code&gt;memory_pool_t *mp&lt;/code&gt;, &lt;code&gt;void *storage&lt;/code&gt;, &lt;code&gt;size_t storage_size&lt;/code&gt;, &lt;code&gt;size_t block_size&lt;/code&gt;, &lt;code&gt;size_t block_align&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="c1"&gt;// modules/memory_pool/memory_pool.c&lt;/span&gt;
&lt;span class="n"&gt;bool&lt;/span&gt; &lt;span class="nf"&gt;memory_pool_init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;memory_pool_t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;mp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;storage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;size_t&lt;/span&gt; &lt;span class="n"&gt;storage_size&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;size_t&lt;/span&gt; &lt;span class="n"&gt;block_size&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;size_t&lt;/span&gt; &lt;span class="n"&gt;block_align&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mp&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;storage&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;is_power_of_two&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;block_align&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;block_size&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="k"&gt;sizeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;block_align&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="k"&gt;_Alignof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;block_align&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;_Alignof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kt"&gt;size_t&lt;/span&gt; &lt;span class="n"&gt;stride&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;round_up&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;block_size&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;block_align&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stride&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;block_size&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kt"&gt;uintptr_t&lt;/span&gt; &lt;span class="n"&gt;addr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;uintptr_t&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;storage&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kt"&gt;uintptr_t&lt;/span&gt; &lt;span class="n"&gt;aligned&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;uintptr_t&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;round_up&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="kt"&gt;size_t&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;addr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;block_align&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kt"&gt;uint8_t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;base&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;uint8_t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;aligned&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kt"&gt;size_t&lt;/span&gt; &lt;span class="n"&gt;offset&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;size_t&lt;/span&gt;&lt;span class="p"&gt;)(&lt;/span&gt;&lt;span class="n"&gt;base&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;uint8_t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;storage&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;offset&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;storage_size&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kt"&gt;size_t&lt;/span&gt; &lt;span class="n"&gt;usable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;storage_size&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;offset&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kt"&gt;size_t&lt;/span&gt; &lt;span class="n"&gt;capacity&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;usable&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;stride&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;capacity&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0u&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="n"&gt;mp&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;base&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="n"&gt;mp&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;block_size&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;stride&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="n"&gt;mp&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;capacity&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;capacity&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="n"&gt;mp&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;used&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0u&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="n"&gt;mp&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;high_water&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0u&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="n"&gt;memory_pool_reset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mp&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Lo primero al pasar o manejar punteros es asegurarse de que no sean &lt;code&gt;NULL&lt;/code&gt;. Un puntero nulo significa que estaríamos escribiendo en la dirección de memoria cero. Como ya dijimos, en una computadora eso es un fallo de segmentación, pero aquí podríamos escribir en la dirección cero y corromper una dirección no intencionada. La verificación también comprueba explícitamente que &lt;code&gt;block_align&lt;/code&gt; sea potencia de dos, ya que &lt;code&gt;round_up&lt;/code&gt; se llama dos veces más abajo y solo funciona bajo esa garantía. La última verificación es &lt;code&gt;block_size &amp;lt; sizeof(void *)&lt;/code&gt;. Cualquier bloque que entreguemos tiene que ser lo bastante grande para guardar un enlace de la lista de libres mientras está libre, así que un bloque más chico que un puntero derramaría su enlace sobre el bloque de al lado.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Ajuste de alineación: &lt;code&gt;block_align&lt;/code&gt; se sube a &lt;code&gt;_Alignof(void *)&lt;/code&gt; si se pide menos. Está permitido pedir alineación de 1 byte, pero la lista de libres guarda un puntero dentro de cada bloque libre, así que el pool entrega en silencio alineación de puntero en su lugar. Misma razón que la guarda de &lt;code&gt;block_size&lt;/code&gt;, desde el otro lado. Conviene explicitar lo que se desprende de esto: como &lt;code&gt;base&lt;/code&gt; está alineado y el stride es múltiplo de &lt;code&gt;block_align&lt;/code&gt;, todos los bloques del pool quedan alineados, no solo el primero. Esa es la garantía en la que uno se apoya al meter un struct en un bloque, así que hay que pasar &lt;code&gt;_Alignof(T)&lt;/code&gt; para un pool de &lt;code&gt;T&lt;/code&gt;. El ajuste por sí solo únicamente promete alineación de puntero.&lt;/li&gt;
&lt;li&gt;Guarda de desbordamiento: &lt;code&gt;if (stride &amp;lt; block_size)&lt;/code&gt; parece inalcanzable, ya que redondear hacia arriba no puede hacer un número más chico. Lo que atrapa es la vuelta al inicio: &lt;code&gt;round_up&lt;/code&gt; suma &lt;code&gt;a - 1&lt;/code&gt; antes de enmascarar, y si &lt;code&gt;block_size&lt;/code&gt; está cerca de &lt;code&gt;SIZE_MAX&lt;/code&gt; esa suma da la vuelta. Es el desbordamiento sin signo mordiéndonos otra vez, esta vez atrapado a propósito.&lt;/li&gt;
&lt;li&gt;Corrimiento de la base: el búfer de quien llama puede no empezar en una dirección alineada, así que &lt;code&gt;base&lt;/code&gt; se redondea hacia arriba y los bytes iniciales se pierden. Eso es &lt;code&gt;offset&lt;/code&gt;, y por eso &lt;code&gt;base&lt;/code&gt; y &lt;code&gt;storage&lt;/code&gt; pueden diferir.&lt;/li&gt;
&lt;li&gt;Cálculo de la capacidad: &lt;code&gt;usable / stride&lt;/code&gt;, descartando el resto. Entre el corrimiento y el truncamiento, la capacidad suele ser menor que &lt;code&gt;storage_size / block_size&lt;/code&gt;, y por eso existe &lt;code&gt;memory_pool_capacity()&lt;/code&gt; en vez de que quien llama la calcule. Si uno alinea su propio almacenamiento, con &lt;code&gt;alignas&lt;/code&gt; en el arreglo puesto al menos tan grande como el &lt;code&gt;block_align&lt;/code&gt; que pasa, el corrimiento no cuesta nada. Si se deja al azar, se puede perder un bloque en silencio.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Funciones de reserva y liberación
&lt;/h3&gt;

&lt;p&gt;Ahora que inicializamos el pool de memoria podemos empezar a usarlo. A &lt;code&gt;memory_pool_alloc()&lt;/code&gt; hay que pasarle el parámetro &lt;code&gt;memory_pool_t *mp&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="c1"&gt;// modules/memory_pool/memory_pool.c&lt;/span&gt;
&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nf"&gt;memory_pool_alloc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;memory_pool_t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;mp&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mp&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;mp&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;free_head&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mp&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;free_head&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="n"&gt;mp&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;free_head&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;read_next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="n"&gt;mp&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;used&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mp&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;used&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;mp&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;high_water&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;mp&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;high_water&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mp&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;used&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Hacemos nuestra verificación estándar de que &lt;code&gt;mp&lt;/code&gt; no sea &lt;code&gt;NULL&lt;/code&gt;, y junto a ella verificamos &lt;code&gt;mp-&amp;gt;free_head&lt;/code&gt;. Esa segunda es la forma en que un pool agotado se anuncia: que la lista de libres esté vacía es que la cabeza sea &lt;code&gt;NULL&lt;/code&gt;, así que no hace falta un helper &lt;code&gt;is_empty&lt;/code&gt; aparte como sí lo necesitaba el búfer circular. Devolver &lt;code&gt;NULL&lt;/code&gt; aquí también coincide con &lt;code&gt;malloc()&lt;/code&gt;, así que quien ya maneja una reserva fallida no tiene que aprender una convención nueva. Si ambas verificaciones pasan, creamos un puntero nuevo &lt;code&gt;p&lt;/code&gt; y lo apuntamos a &lt;code&gt;mp-&amp;gt;free_head&lt;/code&gt;. Ese es el bloque que estamos por devolverle a quien llama, y antes de soltarlo llamamos a &lt;code&gt;read_next(p)&lt;/code&gt; para sacar el enlace de sus primeros bytes, que es lo que pasa a ser la nueva cabeza. Lo último que hacemos es contabilidad: &lt;code&gt;mp-&amp;gt;used++&lt;/code&gt; registra que hay un bloque más entregado, y si eso empuja &lt;code&gt;used&lt;/code&gt; por encima de &lt;code&gt;mp-&amp;gt;high_water&lt;/code&gt; subimos el pico con él. Esa comparación es el único lugar donde &lt;code&gt;high_water&lt;/code&gt; cambia.&lt;/p&gt;

&lt;p&gt;Sigue &lt;code&gt;memory_pool_free()&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="c1"&gt;// modules/memory_pool/memory_pool.c&lt;/span&gt;
&lt;span class="n"&gt;bool&lt;/span&gt; &lt;span class="nf"&gt;memory_pool_free&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;memory_pool_t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;mp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;memory_pool_owns&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mp&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;used&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0u&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="n"&gt;write_next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mp&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;free_head&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="n"&gt;mp&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;free_head&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="n"&gt;mp&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;used&lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Devolver un bloque es la imagen espejo. La primera verificación le pasa el puntero a &lt;code&gt;memory_pool_owns()&lt;/code&gt;, tema de la siguiente sección, que responde si esto es realmente el inicio de un bloque de este pool. La segunda, &lt;code&gt;mp-&amp;gt;used == 0u&lt;/code&gt;, rechaza una liberación en un pool que no tiene nada pendiente. Ambas se ejecutan antes de escribir nada, así que una liberación rechazada deja el pool completamente intacto, lista de libres incluida. El orden de esas dos tampoco es intercambiable, ya que &lt;code&gt;mp-&amp;gt;used&lt;/code&gt; dereferencia &lt;code&gt;mp&lt;/code&gt; y solo es seguro porque &lt;code&gt;memory_pool_owns()&lt;/code&gt; ya rechazó un pool nulo. Si se invierten para fallar primero en la verificación más barata, lo que se escribió es una dereferencia nula.&lt;/p&gt;

&lt;p&gt;Después empujamos. &lt;code&gt;write_next(block, mp-&amp;gt;free_head)&lt;/code&gt; escribe la cabeza actual dentro del bloque devuelto, &lt;code&gt;mp-&amp;gt;free_head = block&lt;/code&gt; hace de ese bloque la nueva cabeza, y &lt;code&gt;mp-&amp;gt;used--&lt;/code&gt; baja la cuenta. Este es el comportamiento de pila del que hablábamos antes: el bloque que se acaba de devolver es el que va a entregar el siguiente &lt;code&gt;memory_pool_alloc()&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Hay un caso que estas verificaciones no pueden atrapar: liberar el mismo bloque dos veces. Volveremos a eso después de la implementación.&lt;/p&gt;

&lt;h3&gt;
  
  
  El pool es dueño de la dirección de memoria
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="c1"&gt;// modules/memory_pool/memory_pool.c&lt;/span&gt;
&lt;span class="n"&gt;bool&lt;/span&gt; &lt;span class="nf"&gt;memory_pool_owns&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;memory_pool_t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;mp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mp&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kt"&gt;uintptr_t&lt;/span&gt; &lt;span class="n"&gt;addr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;uintptr_t&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kt"&gt;uintptr_t&lt;/span&gt; &lt;span class="n"&gt;base&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;uintptr_t&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;mp&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kt"&gt;uintptr_t&lt;/span&gt; &lt;span class="n"&gt;end&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;base&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="kt"&gt;uintptr_t&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;mp&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;capacity&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;uintptr_t&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;mp&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;block_size&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;addr&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;base&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;addr&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;end&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;addr&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;uintptr_t&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;mp&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;block_size&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0u&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Esta es la validación en la que se apoya &lt;code&gt;memory_pool_free()&lt;/code&gt;, y hace dos preguntas. La primera es si el puntero está dentro de la zona del pool siquiera, comparándolo contra &lt;code&gt;base&lt;/code&gt; y &lt;code&gt;end&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;La segunda es la interesante: &lt;code&gt;(addr - base) % mp-&amp;gt;block_size == 0u&lt;/code&gt; verifica que el puntero esté exactamente en un límite de bloque. Un puntero al medio de un bloque pasa la prueba de rango pero no es un bloque, y liberarlo insertaría un nodo falso en la lista de libres a un desplazamiento, así que la siguiente reserva entregaría un bloque que se solapa con su vecino. Esta operación módulo es la única división de todo el módulo, y es el mismo costo que evitamos en el búfer circular. Hacer que el stride sea potencia de dos la vuelve a convertir en una máscara.&lt;/p&gt;

&lt;h3&gt;
  
  
  Función de capacidad
&lt;/h3&gt;

&lt;p&gt;Conocer la capacidad es importante porque a partir de ella podemos identificar cuánto del pool de memoria tenemos para entregar.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="c1"&gt;// modules/memory_pool/memory_pool.c&lt;/span&gt;
&lt;span class="kt"&gt;size_t&lt;/span&gt; &lt;span class="nf"&gt;memory_pool_capacity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;memory_pool_t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;mp&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mp&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="mi"&gt;0u&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;mp&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;capacity&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Hacemos nuestra verificación de &lt;code&gt;NULL&lt;/code&gt; sobre &lt;code&gt;mp&lt;/code&gt;. Si es nulo devolvemos &lt;code&gt;0u&lt;/code&gt;, de lo contrario devolvemos la capacidad derivada allá en &lt;code&gt;memory_pool_init()&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Funciones auxiliares
&lt;/h3&gt;

&lt;p&gt;Estas son las funciones para acceder a los miembros de &lt;code&gt;memory_pool_t&lt;/code&gt; sin modificarlos. Empezamos con &lt;code&gt;memory_pool_used()&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="c1"&gt;// modules/memory_pool/memory_pool.c&lt;/span&gt;
&lt;span class="kt"&gt;size_t&lt;/span&gt; &lt;span class="nf"&gt;memory_pool_used&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;memory_pool_t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;mp&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mp&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="mi"&gt;0u&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;mp&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;used&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Aquí solo verificamos que el &lt;code&gt;mp&lt;/code&gt; pasado como argumento no sea &lt;code&gt;NULL&lt;/code&gt;, y si no lo es devolvemos el campo que pedimos, en este caso &lt;code&gt;mp-&amp;gt;used&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="c1"&gt;// modules/memory_pool/memory_pool.c&lt;/span&gt;
&lt;span class="kt"&gt;size_t&lt;/span&gt; &lt;span class="nf"&gt;memory_pool_available&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;memory_pool_t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;mp&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mp&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="mi"&gt;0u&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;mp&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;capacity&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;mp&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;used&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kt"&gt;size_t&lt;/span&gt; &lt;span class="nf"&gt;memory_pool_high_water&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;memory_pool_t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;mp&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mp&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="mi"&gt;0u&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;mp&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;high_water&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Aquí está el resto de las funciones auxiliares, siguiendo la misma verificación de &lt;code&gt;NULL&lt;/code&gt; y devolviendo el valor que pedimos. Nótese que &lt;code&gt;memory_pool_available()&lt;/code&gt; calcula &lt;code&gt;capacity - used&lt;/code&gt; en lugar de recorrer la lista de libres para contar lo que queda, que es la misma respuesta sin el recorrido.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ejemplo de uso
&lt;/h2&gt;

&lt;p&gt;Ahora que explicamos la implementación completa, veamos cómo vamos a usar el patrón.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="k"&gt;typedef&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kt"&gt;uint32_t&lt;/span&gt; &lt;span class="n"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kt"&gt;int16_t&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kt"&gt;uint8_t&lt;/span&gt; &lt;span class="n"&gt;sensor_id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="n"&gt;sample_t&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;alignas&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;uint8_t&lt;/span&gt; &lt;span class="n"&gt;sample_storage&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;sizeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sample_t&lt;/span&gt;&lt;span class="p"&gt;)];&lt;/span&gt;
&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;memory_pool_t&lt;/span&gt; &lt;span class="n"&gt;sample_pool&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;sample_t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="n"&gt;bool&lt;/span&gt; &lt;span class="nf"&gt;sensor_init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;memory_pool_init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;sample_pool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sample_storage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;sizeof&lt;/span&gt; &lt;span class="n"&gt;sample_storage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                          &lt;span class="k"&gt;sizeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sample_t&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="k"&gt;_Alignof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sample_t&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;bool&lt;/span&gt; &lt;span class="nf"&gt;sensor_take_reading&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;uint8_t&lt;/span&gt; &lt;span class="n"&gt;sensor_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;current&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;memory_pool_alloc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;sample_pool&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;current&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;timestamp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;read_adc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sensor_id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;sensor_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sensor_id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;sensor_flush&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;current&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="n"&gt;transmit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="n"&gt;memory_pool_free&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;sample_pool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="n"&gt;current&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Este ejemplo muestra que vamos a usar el pool de memoria para guardar &lt;code&gt;sample_t&lt;/code&gt;, que está compuesto por &lt;code&gt;timestamp&lt;/code&gt;, &lt;code&gt;value&lt;/code&gt; y &lt;code&gt;sensor_id&lt;/code&gt;. Nótese que el bloque se reserva en una función y se libera en otra. Ese es el punto del pool: la memoria sobrevive a la llamada que la pidió, que es lo que permite que una lectura se quede ahí hasta que algo más esté listo para ocuparse de ella. Un solo espacio mantiene corto el ejemplo; un llamador real tiene varios a la vez.&lt;/p&gt;

&lt;p&gt;Ahora el &lt;code&gt;alignas(void *)&lt;/code&gt; sobre &lt;code&gt;sample_storage&lt;/code&gt;. A &lt;code&gt;init&lt;/code&gt; le pasamos &lt;code&gt;_Alignof(sample_t)&lt;/code&gt;, pero &lt;code&gt;init&lt;/code&gt; lo sube a &lt;code&gt;_Alignof(void *)&lt;/code&gt;, porque todo bloque libre tiene que poder guardar un puntero. El almacenamiento tiene que satisfacer al mayor de los dos, y aquí ese es &lt;code&gt;void *&lt;/code&gt;: en un host de 64 bits son 8 contra los 4 de &lt;code&gt;sample_t&lt;/code&gt;, y en un objetivo de 32 bits los dos son iguales, así que alinear al puntero cubre ambos casos. Si uno se equivoca, &lt;code&gt;base&lt;/code&gt; se corre hacia adelante y se pierde un bloque en silencio. Nótese que declaramos &lt;code&gt;sample_storage&lt;/code&gt; como &lt;code&gt;uint8_t&lt;/code&gt;, así que el arreglo son bytes crudos y podemos dimensionarlo multiplicando la cantidad de bloques por &lt;code&gt;sizeof(sample_t)&lt;/code&gt;. Ojo que &lt;code&gt;alignas&lt;/code&gt; necesita &lt;code&gt;&amp;lt;stdalign.h&amp;gt;&lt;/code&gt; en C11, y el fragmento omite los includes.&lt;/p&gt;

&lt;p&gt;Nótese también que el pool no tiene idea de a qué sensor pertenece un bloque. &lt;code&gt;sensor_id&lt;/code&gt; es un campo que pusimos nosotros en el struct, no algo que el pool registre. Lo único que supo siempre fue el tamaño de bloque y la alineación que le entregamos en &lt;code&gt;init&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Nótese que &lt;code&gt;alloc&lt;/code&gt;, &lt;code&gt;free&lt;/code&gt;, y la forma en que usamos la memoria entre medio tienen la misma forma que &lt;code&gt;malloc()&lt;/code&gt; y &lt;code&gt;free()&lt;/code&gt;. Pedir memoria, verificar &lt;code&gt;NULL&lt;/code&gt;, usarla, devolverla. Eso es deliberado: el código escrito contra el heap se porta cambiando dos puntos de llamada. Dos diferencias que conviene conocer: &lt;code&gt;memory_pool_free()&lt;/code&gt; devuelve un &lt;code&gt;bool&lt;/code&gt; en lugar de nada, y liberar el mismo bloque dos veces no se comporta como lo haría el asignador del heap. Vamos a eso ahora.&lt;/p&gt;

&lt;h2&gt;
  
  
  Caso límite: doble liberación
&lt;/h2&gt;

&lt;p&gt;Allá en &lt;code&gt;memory_pool_free()&lt;/code&gt; dijimos que había un caso que las verificaciones no pueden atrapar. Liberar en un pool vacío lo rechaza &lt;code&gt;mp-&amp;gt;used == 0u&lt;/code&gt;, y un puntero que no es el inicio de un bloque lo rechaza &lt;code&gt;memory_pool_owns()&lt;/code&gt;. Pero liberar un bloque que &lt;em&gt;ya&lt;/em&gt; está en la lista de libres pasa ambas. Es un inicio de bloque legítimo, así que &lt;code&gt;owns&lt;/code&gt; dice que sí, y hay otros bloques pendientes, así que &lt;code&gt;used&lt;/code&gt; no es cero.&lt;/p&gt;

&lt;p&gt;Así que el empuje se ejecuta. Escribimos &lt;code&gt;free_head&lt;/code&gt; dentro del bloque y luego hacemos de ese bloque la nueva cabeza. Si ese bloque ya era la cabeza, ahora se apunta a sí mismo.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Antes:
  free_head -&amp;gt; [B] -&amp;gt; [C] -&amp;gt; NULL

memory_pool_free(mp, B) por segunda vez:
  write_next(B, free_head) el next de B ahora apunta a B
  free_head = B

Después:
  free_head -&amp;gt; [B] -&amp;gt; [B] -&amp;gt; [B] -&amp;gt; ...

  alloc() devuelve B
  alloc() devuelve B otra vez, a otro llamador

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nada reporta esto. &lt;code&gt;memory_pool_free()&lt;/code&gt; devuelve &lt;code&gt;true&lt;/code&gt;, &lt;code&gt;used&lt;/code&gt; se ve plausible, y toda consulta dice que el pool está sano. Uno lo encuentra después, en el código que tuvo la mala suerte de ser el segundo dueño del bloque.&lt;/p&gt;

&lt;p&gt;Hay formas de atraparlo, y ambas cuestan algo por lo cual elegimos este patrón. Podríamos recorrer la lista de libres en cada liberación y rechazar un bloque que ya esté en ella, lo cual es correcto y convierte una liberación de tiempo constante en un recorrido. Podríamos mantener un mapa de bits aparte con los bloques reservados, lo cual cuesta memoria y renuncia a la propiedad de cero sobrecarga que motivó la lista intrusiva en primer lugar. Para una base de código de firmware pequeña, donde los puntos de reserva son pocos y revisables, documentarlo es una decisión defendible, y la limitación está escrita en el header justo al lado de la función que no puede atraparla. Para una base de código más grande valdría la pena pagar una verificación en la compilación de depuración.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cierre
&lt;/h2&gt;

&lt;p&gt;Eso es un pool de memoria completo. No hace reserva dinámica, entrega bloques y los recupera en tiempo constante sin búsquedas, no lleva ni un byte de sobrecarga por bloque, no puede fragmentarse por construcción, y después dice cuánto de él hacía falta realmente.&lt;/p&gt;

&lt;p&gt;Los sacrificios, todos deliberados:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Un solo tamaño por pool.&lt;/strong&gt; Cuatro tipos de objeto significan cuatro pools. Esto es lo que compra la ausencia de fragmentación, y hace que el pool sea mala opción para datos de tamaño genuinamente variable.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No es thread-safe ni ISR-safe.&lt;/strong&gt; A diferencia del búfer circular, aquí no hay un camino sin bloqueos. Toda llamada que muta toca &lt;code&gt;free_head&lt;/code&gt;, y no hay forma de repartir eso entre dos contextos como sí se reparten &lt;code&gt;head&lt;/code&gt; y &lt;code&gt;tail&lt;/code&gt;. Un pool compartido necesita una sección crítica alrededor de cada llamada que muta, que es también la razón por la que no hay ningún &lt;code&gt;volatile&lt;/code&gt; en &lt;code&gt;memory_pool_t&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Los bloques no se ponen en cero.&lt;/strong&gt; Estas son semánticas de &lt;code&gt;malloc&lt;/code&gt;, no de &lt;code&gt;calloc&lt;/code&gt;. Un bloque recién entregado tiene un puntero viejo de la lista de libres en sus primeros bytes y lo que haya dejado el dueño anterior en el resto.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;La doble liberación solo es parcialmente detectable.&lt;/strong&gt; Cubierto arriba, y la limitación más importante del módulo.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;La capacidad se deriva, no se da.&lt;/strong&gt; El redondeo del stride, el corrimiento por alineación y el resto de la cola le comen espacio. Hay que leerla de vuelta con &lt;code&gt;memory_pool_capacity()&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Dos patrones y ya empezaron a apoyarse uno en el otro. El búfer circular nos dio almacenamiento fijo para bytes, el pool nos da almacenamiento fijo para objetos, y la cola de eventos, más adelante en la serie, va a querer los dos: un anillo de punteros a bloques entregados por un pool. Así se consigue una cola de structs sin una sola llamada a &lt;code&gt;malloc&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Lo que sigue en la serie es la máquina de estados. El búfer circular y el pool trataron con almacenamiento, bytes en uno y bloques en el otro, y a ninguno le importaba qué significaba nada de eso. La máquina de estados es donde empieza a significar algo.&lt;/p&gt;

</description>
      <category>embeddedsystems</category>
      <category>memorymanagement</category>
      <category>c</category>
      <category>microcontrollers</category>
    </item>
    <item>
      <title>Patrones embebidos comunes: búfer circular</title>
      <dc:creator>Ilean Monterrubio Jr</dc:creator>
      <pubDate>Fri, 31 Jul 2026 00:00:00 +0000</pubDate>
      <link>https://dev.to/elcapitan/patrones-embebidos-comunes-bufer-circular-3280</link>
      <guid>https://dev.to/elcapitan/patrones-embebidos-comunes-bufer-circular-3280</guid>
      <description>&lt;p&gt;Esta es la primera parte de una nueva serie donde vuelvo a explorar los patrones embebidos más comunes, empezando por el búfer circular (ring buffer). Es uno de esos patrones que se usan en la comunicación serial para asegurarnos de no perder ni una trama de datos, y además es un bloque de construcción para otros patrones que veremos más adelante.&lt;/p&gt;

&lt;p&gt;El código fuente completo y probado vive en el repositorio complementario: &lt;a href="https://github.com/ileanmjr88/tetzontli" rel="noopener noreferrer"&gt;ileanmjr88/tetzontli&lt;/a&gt;. Cada patrón de esta serie es su propio módulo con una suite completa de pruebas en GoogleTest, así que puedes clonarlo y ejecutar las pruebas tú mismo.&lt;/p&gt;

&lt;h2&gt;
  
  
  Concepto básico
&lt;/h2&gt;

&lt;p&gt;El búfer circular usa dos índices, &lt;code&gt;head&lt;/code&gt; y &lt;code&gt;tail&lt;/code&gt;. Para agregar datos hacemos &lt;code&gt;put&lt;/code&gt; de un byte en &lt;code&gt;head&lt;/code&gt; e incrementamos &lt;code&gt;head&lt;/code&gt; en uno. Para quitar datos hacemos &lt;code&gt;get&lt;/code&gt; de un byte en &lt;code&gt;tail&lt;/code&gt; e incrementamos &lt;code&gt;tail&lt;/code&gt; en uno. Ambos índices solo avanzan hacia adelante: &lt;code&gt;tail&lt;/code&gt; persigue a &lt;code&gt;head&lt;/code&gt; por todo el búfer.&lt;/p&gt;

&lt;p&gt;Este patrón se puede implementar con una lista enlazada o con un arreglo (array) de longitud fija. La lista enlazada suena tentadora, pero en un sistema embebido no quieres asignación dinámica de memoria. En una computadora moderna con gigabytes de memoria, llamar a &lt;code&gt;malloc&lt;/code&gt; no es problema. En un microcontrolador tienes kilobytes, y tres problemas más grandes: el heap se fragmenta en un dispositivo que funciona durante meses, el tiempo que tarda una asignación no es determinista, y muchas veces necesitas agregar datos desde dentro de una interrupción, donde no te puedes dar el lujo de ninguna de las dos cosas. Un arreglo fijo evita todo eso, porque el espacio queda garantizado en tiempo de compilación.&lt;/p&gt;

&lt;p&gt;Ese espacio garantizado es justo lo que le da tiempo al consumidor: los bytes que llegan más rápido de lo que los puedes procesar se quedan seguros en el búfer hasta que llegues a ellos, en lugar de perderse.&lt;/p&gt;

&lt;h2&gt;
  
  
  Dar la vuelta dentro del arreglo
&lt;/h2&gt;

&lt;p&gt;Un arreglo no da la vuelta. Cuando llegas al final e intentas pasarte, no regresa al inicio. Te sales del espacio asignado, lo que puede provocar un fallo de segmentación, devolverte un puntero NULL o entregarte basura.&lt;/p&gt;

&lt;h3&gt;
  
  
  La intuición
&lt;/h3&gt;

&lt;p&gt;La forma intuitiva de regresar al inicio es el operador módulo &lt;code&gt;%&lt;/code&gt;, que devuelve el residuo de una división.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Array of length/capacity: 10

Formula: index % capacity -&amp;gt; array index

- index 0: 0 % 10 = 0
- index 1: 1 % 10 = 1
- index 4: 4 % 10 = 4
- index 10: 10 % 10 = 0
- index 11: 11 % 10 = 1

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;El módulo nos deja tratar el arreglo fijo como un anillo, lo que lo hace ideal para un búfer circular. Funciona bien en una computadora moderna con varios núcleos, frecuencias altas y una unidad de división por hardware. Pero el módulo tiene una desventaja: requiere división, y muchos microcontroladores no tienen división por hardware. El compilador la emula por software, que es lento, y pagamos ese costo con cada byte que pasa por el búfer.&lt;/p&gt;

&lt;h3&gt;
  
  
  Optimizando con una máscara de bits
&lt;/h3&gt;

&lt;p&gt;Hay una forma más rápida de hacer exactamente lo mismo que el operador módulo, con una restricción: la longitud del arreglo debe ser una potencia de dos. Esa restricción es la llave que nos permite recrear el módulo con una sola operación a nivel de bits. La fórmula es &lt;code&gt;index = head &amp;amp; mask&lt;/code&gt;, donde &lt;code&gt;mask = capacity - 1&lt;/code&gt;. Repasemos las matemáticas.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Array of length/capacity: 8 (power of two)

mask = 8 - 1 = 7 = 0b0111

- index 0: 0b0000 &amp;amp; 0b0111 = 0
- index 1: 0b0001 &amp;amp; 0b0111 = 1
- index 2: 0b0010 &amp;amp; 0b0111 = 2
- index 8: 0b1000 &amp;amp; 0b0111 = 0
- index 10: 0b1010 &amp;amp; 0b0111 = 2
- index 14: 0b1110 &amp;amp; 0b0111 = 6

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Fíjate que son las mismas respuestas que daría el módulo: &lt;code&gt;8 % 8 = 0&lt;/code&gt;, &lt;code&gt;10 % 8 = 2&lt;/code&gt;, &lt;code&gt;14 % 8 = 6&lt;/code&gt;. No es coincidencia. La máscara &lt;code&gt;0b0111&lt;/code&gt; conserva solo los tres bits más bajos y limpia todo lo que está arriba, y los tres bits más bajos de cualquier número son exactamente ese número módulo 8. Quedarte con los bits bajos &lt;em&gt;es&lt;/em&gt; sacar el residuo. Como la capacidad es potencia de dos, &lt;code&gt;capacity - 1&lt;/code&gt; es una secuencia limpia de unos, y eso es lo que hace que el AND coincida perfectamente con el módulo. Intenta esto con un número que no sea potencia de dos y se cae todo: &lt;code&gt;11 &amp;amp; 9 = 9&lt;/code&gt;, pero &lt;code&gt;11 % 10 = 1&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Implementación
&lt;/h2&gt;

&lt;h3&gt;
  
  
  La estructura del búfer circular
&lt;/h3&gt;

&lt;p&gt;Creamos un struct para mantener juntos todos los datos. Esto nos facilita la vida, porque con un solo puntero a la estructura podemos acceder a &lt;code&gt;buffer&lt;/code&gt;, &lt;code&gt;capacity&lt;/code&gt;, &lt;code&gt;mask&lt;/code&gt;, &lt;code&gt;head&lt;/code&gt; y &lt;code&gt;tail&lt;/code&gt;. También hace que los datos sean fáciles de pasar por puntero a cualquier función que necesite agregar o leer de él.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="c1"&gt;// modules/ring_buffer/ring_buffer.h&lt;/span&gt;
&lt;span class="k"&gt;typedef&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kt"&gt;uint8_t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kt"&gt;size_t&lt;/span&gt; &lt;span class="n"&gt;capacity&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kt"&gt;size_t&lt;/span&gt; &lt;span class="n"&gt;mask&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;volatile&lt;/span&gt; &lt;span class="kt"&gt;size_t&lt;/span&gt; &lt;span class="n"&gt;head&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;volatile&lt;/span&gt; &lt;span class="kt"&gt;size_t&lt;/span&gt; &lt;span class="n"&gt;tail&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="n"&gt;ring_buffer_t&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Como puedes ver, el struct usa varios tipos distintos, y cada uno es deliberado.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;uint8_t&lt;/code&gt;: el búfer guarda bytes crudos. Sea cual sea la fuente, datos seriales de UART, I2C o cualquier otra cosa, llegan como octetos, así que un arreglo de bytes es el almacenamiento natural para la cola.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;size_t&lt;/code&gt;: &lt;code&gt;capacity&lt;/code&gt;, &lt;code&gt;mask&lt;/code&gt;, &lt;code&gt;head&lt;/code&gt; y &lt;code&gt;tail&lt;/code&gt; son todos &lt;code&gt;size_t&lt;/code&gt;. Es un tipo sin signo del tamaño de la plataforma (normalmente un alias de &lt;code&gt;unsigned int&lt;/code&gt; o &lt;code&gt;unsigned long&lt;/code&gt;), lo que mantiene la aritmética de índices portable entre compiladores y evita problemas de desbordamiento con signo. Que sea sin signo importa más adelante, cuando dependemos de que &lt;code&gt;head - tail&lt;/code&gt; dé la vuelta limpiamente.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;volatile&lt;/code&gt;: &lt;code&gt;head&lt;/code&gt; y &lt;code&gt;tail&lt;/code&gt; están marcados como &lt;code&gt;volatile&lt;/code&gt; para que el compilador siempre los lea de memoria en lugar de guardarse una copia vieja en un registro. El productor y el consumidor pueden ejecutarse en contextos distintos (por ejemplo, una ISR llenando el búfer mientras el bucle principal lo vacía), así que un valor guardado en un registro podría perderse la actualización del otro lado. Ojo: &lt;code&gt;volatile&lt;/code&gt; solo garantiza que la lectura ocurra, no hace que el acceso sea atómico ni seguro entre hilos. Veremos por qué este diseño es seguro de todos modos en la sección sobre interrupciones.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Las funciones de inicialización y de potencia de dos
&lt;/h3&gt;

&lt;p&gt;Empecemos con &lt;code&gt;init&lt;/code&gt; y su ayudante &lt;code&gt;is_power_of_two&lt;/code&gt;. La función &lt;code&gt;init&lt;/code&gt; prepara la estructura de datos, y la verificación de potencia de dos es el primer filtro: si quien la llama pide una capacidad que no es potencia de dos, la rechazamos, porque todo el esquema de enmascarado depende de eso. Arranquemos con el ayudante.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="c1"&gt;// modules/ring_buffer/ring_buffer.c&lt;/span&gt;
&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;bool&lt;/span&gt; &lt;span class="nf"&gt;is_power_of_two&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;size_t&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="mi"&gt;0u&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1u&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0u&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Esta implementación hace lo siguiente:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;x != 0u&lt;/code&gt;: verifica que &lt;code&gt;x&lt;/code&gt; no sea &lt;code&gt;0&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;(x &amp;amp; (x - 1u)) == 0u&lt;/code&gt;: esta es la parte que verifica que haya un solo bit encendido. Una potencia de dos tiene exactamente un bit encendido. Restarle uno apaga ese bit y enciende todos los que están debajo, así que &lt;code&gt;x&lt;/code&gt; y &lt;code&gt;x - 1&lt;/code&gt; no comparten ningún bit y el AND da cero. Toma &lt;code&gt;x = 8&lt;/code&gt;: &lt;code&gt;8 - 1 = 7&lt;/code&gt;, y &lt;code&gt;0b1000 &amp;amp; 0b0111 = 0&lt;/code&gt;. Ahora toma un número que no sea potencia de dos, &lt;code&gt;x = 12&lt;/code&gt;: &lt;code&gt;12 - 1 = 11&lt;/code&gt;, y &lt;code&gt;0b1100 &amp;amp; 0b1011 = 0b1000&lt;/code&gt;, que no es cero. Ese bit que sobra delata que &lt;code&gt;x&lt;/code&gt; tenía más de un bit encendido.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Si ambas condiciones se cumplen, entonces &lt;code&gt;x&lt;/code&gt; es potencia de dos, así que la función devuelve &lt;code&gt;true&lt;/code&gt; si y solo si &lt;code&gt;x&lt;/code&gt; es potencia de dos. Ahora pasemos a la función &lt;code&gt;ring_buffer_init&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="c1"&gt;// modules/ring_buffer/ring_buffer.c&lt;/span&gt;
&lt;span class="n"&gt;bool&lt;/span&gt; &lt;span class="nf"&gt;ring_buffer_init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ring_buffer_t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;rb&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;uint8_t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;storage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;size_t&lt;/span&gt; &lt;span class="n"&gt;capacity&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rb&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;storage&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;is_power_of_two&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;capacity&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="n"&gt;rb&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;buffer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;storage&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="n"&gt;rb&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;capacity&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;capacity&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="n"&gt;rb&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;mask&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;capacity&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1u&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="n"&gt;rb&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;head&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0u&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="n"&gt;rb&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;tail&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0u&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;init&lt;/code&gt; valida primero sus entradas: un puntero nulo al struct, un &lt;code&gt;storage&lt;/code&gt; nulo, o una capacidad que no sea potencia de dos hacen que se salga y devuelva &lt;code&gt;false&lt;/code&gt;. Una vez que pasan las verificaciones, arma el struct, precalcula &lt;code&gt;mask = capacity - 1&lt;/code&gt; para que &lt;code&gt;put&lt;/code&gt; y &lt;code&gt;get&lt;/code&gt; nunca tengan que recalcularlo, y pone ambos índices en cero.&lt;/p&gt;

&lt;p&gt;Fíjate que &lt;code&gt;init&lt;/code&gt; no asigna nada. Quien la llama nos entrega el arreglo &lt;code&gt;storage&lt;/code&gt;, así que el búfer puede vivir en memoria estática o en la pila. Este es el principio de no usar &lt;code&gt;malloc&lt;/code&gt; que mencionamos antes, hecho concreto: no hay ni un &lt;code&gt;malloc&lt;/code&gt; a la vista, y el espacio quedó garantizado en el momento en que el llamador declaró el arreglo.&lt;/p&gt;

&lt;h3&gt;
  
  
  Las funciones put y get
&lt;/h3&gt;

&lt;p&gt;Estas funciones ponen y sacan un solo byte a la vez. Las limitamos a un byte porque a veces un solo byte es todo lo que necesitamos, y porque las funciones &lt;code&gt;write&lt;/code&gt; y &lt;code&gt;read&lt;/code&gt; de la siguiente sección están construidas encima de ellas. Empecemos con &lt;code&gt;ring_buffer_put&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="c1"&gt;// modules/ring_buffer/ring_buffer.c&lt;/span&gt;
&lt;span class="n"&gt;bool&lt;/span&gt; &lt;span class="nf"&gt;ring_buffer_put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ring_buffer_t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;rb&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;uint8_t&lt;/span&gt; &lt;span class="n"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rb&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;ring_buffer_is_full&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rb&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="n"&gt;rb&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;rb&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;head&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;rb&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;mask&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="n"&gt;rb&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;head&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;La función empieza validando que &lt;code&gt;rb&lt;/code&gt; no sea &lt;code&gt;NULL&lt;/code&gt;, y luego llama al ayudante &lt;code&gt;ring_buffer_is_full&lt;/code&gt;. Si el búfer está lleno salimos temprano y devolvemos &lt;code&gt;false&lt;/code&gt;, avisándole al llamador que no se guardó ningún byte. Veremos cómo funciona &lt;code&gt;is_full&lt;/code&gt; en la siguiente sección. Si hay espacio, hacemos la escritura, y aquí es donde rinde frutos la sección anterior: &lt;code&gt;rb-&amp;gt;head &amp;amp; rb-&amp;gt;mask&lt;/code&gt; calcula el índice del arreglo con el AND a nivel de bits, envolviendo el &lt;code&gt;head&lt;/code&gt; de avance libre de regreso a una ranura válida. Escribimos el byte ahí, y luego incrementamos &lt;code&gt;head&lt;/code&gt; en uno para que el siguiente &lt;code&gt;put&lt;/code&gt; caiga en la ranura que sigue. Fíjate en el orden: primero escribimos la ranura, después avanzamos &lt;code&gt;head&lt;/code&gt;. Ese orden importa para la seguridad con interrupciones, y llegaremos a eso más adelante. Ahora veamos &lt;code&gt;ring_buffer_get&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="c1"&gt;// modules/ring_buffer/ring_buffer.c&lt;/span&gt;
&lt;span class="n"&gt;bool&lt;/span&gt; &lt;span class="nf"&gt;ring_buffer_get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ring_buffer_t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;rb&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;uint8_t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rb&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;out&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;ring_buffer_is_empty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rb&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;out&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rb&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;rb&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;tail&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;rb&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;mask&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
  &lt;span class="n"&gt;rb&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;tail&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Esta función también empieza validando que &lt;code&gt;rb&lt;/code&gt; no sea &lt;code&gt;NULL&lt;/code&gt;, y luego verifica que &lt;code&gt;out&lt;/code&gt; tampoco lo sea. Ese es el puntero donde se guarda el byte que leemos, que es la forma en que &lt;code&gt;get&lt;/code&gt; le devuelve datos al llamador dejando libre su valor de retorno &lt;code&gt;bool&lt;/code&gt; para reportar éxito o fracaso. Por último llama al ayudante &lt;code&gt;ring_buffer_is_empty&lt;/code&gt;. Si el búfer está vacío salimos temprano y devolvemos &lt;code&gt;false&lt;/code&gt;, avisándole al llamador que no se leyó ningún byte. Veremos cómo funciona &lt;code&gt;is_empty&lt;/code&gt; en la siguiente sección. Si no está vacío, hacemos la lectura, y otra vez la sección anterior rinde frutos: &lt;code&gt;rb-&amp;gt;tail &amp;amp; rb-&amp;gt;mask&lt;/code&gt; calcula el índice del arreglo con el AND a nivel de bits, envolviendo el &lt;code&gt;tail&lt;/code&gt; de avance libre en una ranura válida. Leemos el byte e incrementamos &lt;code&gt;tail&lt;/code&gt; en uno. Fíjate que &lt;code&gt;get&lt;/code&gt; nunca borra nada. Lee el byte y mueve &lt;code&gt;tail&lt;/code&gt; hacia adelante, y esa ranura cuenta como libre en el momento en que &lt;code&gt;tail&lt;/code&gt; pasa por encima de ella. Los índices por sí solos deciden qué son datos y qué es espacio libre.&lt;/p&gt;

&lt;h3&gt;
  
  
  Las funciones is_empty, is_full y count
&lt;/h3&gt;

&lt;p&gt;Estos tres ayudantes, &lt;code&gt;is_empty&lt;/code&gt;, &lt;code&gt;is_full&lt;/code&gt; y &lt;code&gt;count&lt;/code&gt;, son en los que se apoyan &lt;code&gt;put&lt;/code&gt; y &lt;code&gt;get&lt;/code&gt; para decidir si pueden seguir adelante. Aquí es donde los índices de avance libre por fin se ganan su lugar. Empecemos con &lt;code&gt;ring_buffer_is_empty&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="c1"&gt;// modules/ring_buffer/ring_buffer.c&lt;/span&gt;
&lt;span class="n"&gt;bool&lt;/span&gt; &lt;span class="nf"&gt;ring_buffer_is_empty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;ring_buffer_t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;rb&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;rb&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;head&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;rb&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;tail&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;El búfer está vacío cuando &lt;code&gt;head&lt;/code&gt; y &lt;code&gt;tail&lt;/code&gt; tienen el mismo valor. Tiene sentido: &lt;code&gt;tail&lt;/code&gt; persigue a &lt;code&gt;head&lt;/code&gt;, y cuando lo alcanza por completo ya no queda nada por leer. Aquí está la parte sutil. En muchos diseños de búfer circular los índices se envuelven de regreso al rango válido, y entonces &lt;code&gt;head == tail&lt;/code&gt; es ambiguo, porque significa &lt;em&gt;tanto&lt;/em&gt; completamente vacío &lt;em&gt;como&lt;/em&gt; completamente lleno, y no puedes distinguir cuál. Como nuestros índices avanzan libres y nunca se envuelven, &lt;code&gt;head == tail&lt;/code&gt; solo puede significar vacío. En un momento veremos cómo se maneja el caso lleno sin esa ambigüedad.&lt;/p&gt;

&lt;p&gt;Sigue &lt;code&gt;ring_buffer_count&lt;/code&gt;, que es el corazón de todo esto:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="c1"&gt;// modules/ring_buffer/ring_buffer.c&lt;/span&gt;
&lt;span class="kt"&gt;size_t&lt;/span&gt; &lt;span class="nf"&gt;ring_buffer_count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;ring_buffer_t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;rb&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;rb&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;head&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;rb&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;tail&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ambos índices solo aumentan. Cada &lt;code&gt;put&lt;/code&gt; incrementa &lt;code&gt;head&lt;/code&gt;, cada &lt;code&gt;get&lt;/code&gt; incrementa &lt;code&gt;tail&lt;/code&gt;, y ninguno se envuelve. Así que &lt;code&gt;head&lt;/code&gt; siempre va adelante de &lt;code&gt;tail&lt;/code&gt; por exactamente la cantidad de bytes que se han escrito pero todavía no se han leído, que es el nivel de llenado. La diferencia &lt;code&gt;head - tail&lt;/code&gt; &lt;em&gt;es&lt;/em&gt; la cuenta.&lt;/p&gt;

&lt;p&gt;Lo bonito es lo que pasa cuando &lt;code&gt;head&lt;/code&gt; finalmente se desborda. &lt;code&gt;size_t&lt;/code&gt; tiene un máximo, y después de suficientes bytes &lt;code&gt;head&lt;/code&gt; lo rebasa y da la vuelta hacia cero. Podrías esperar que &lt;code&gt;head - tail&lt;/code&gt; se rompa en ese punto, pero no pasa. La resta sin signo da la vuelta igual que la suma sin signo, así que la diferencia sigue siendo correcta incluso a través del desbordamiento. Justo por esto &lt;code&gt;head&lt;/code&gt; y &lt;code&gt;tail&lt;/code&gt; son sin signo, y por esto el búfer puede funcionar indefinidamente sin ningún manejo especial para el desbordamiento.&lt;/p&gt;

&lt;p&gt;Por último, &lt;code&gt;ring_buffer_is_full&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="c1"&gt;// modules/ring_buffer/ring_buffer.c&lt;/span&gt;
&lt;span class="n"&gt;bool&lt;/span&gt; &lt;span class="nf"&gt;ring_buffer_is_full&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;ring_buffer_t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;rb&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;ring_buffer_count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rb&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;rb&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;capacity&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Una vez que tenemos &lt;code&gt;count&lt;/code&gt;, lo de lleno es trivial: el búfer está lleno cuando contiene &lt;code&gt;capacity&lt;/code&gt; bytes. Sin desperdiciar una ranura, sin un contador aparte que mantener sincronizado, sin ambigüedad con el caso vacío. Los índices de avance libre nos dan vacío (&lt;code&gt;head == tail&lt;/code&gt;), lleno (&lt;code&gt;count == capacity&lt;/code&gt;) y todo lo que hay en medio, todo a partir de dos números que solo cuentan hacia arriba.&lt;/p&gt;

&lt;h3&gt;
  
  
  Las funciones write y read
&lt;/h3&gt;

&lt;p&gt;Estas funciones escriben y leen más de un byte a la vez. Empecemos con &lt;code&gt;ring_buffer_write&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="c1"&gt;// modules/ring_buffer/ring_buffer.c&lt;/span&gt;
&lt;span class="kt"&gt;size_t&lt;/span&gt; &lt;span class="nf"&gt;ring_buffer_write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ring_buffer_t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;rb&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;uint8_t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;size_t&lt;/span&gt; &lt;span class="n"&gt;len&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rb&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;0u&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="kt"&gt;size_t&lt;/span&gt; &lt;span class="n"&gt;written&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0u&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;written&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;len&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;ring_buffer_put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rb&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;written&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;written&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;written&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Esta función devuelve la cantidad de bytes que realmente se escribieron, que puede ser menor a la que se pidió. Devuelve &lt;code&gt;0&lt;/code&gt; en la salida temprana, cuando el búfer circular &lt;code&gt;rb&lt;/code&gt; o el puntero de origen &lt;code&gt;data&lt;/code&gt; son &lt;code&gt;NULL&lt;/code&gt;. Si no, inicializamos &lt;code&gt;written&lt;/code&gt; para llevar la cuenta de cuántos bytes hemos copiado de &lt;code&gt;data&lt;/code&gt; al búfer. El ciclo se ejecuta con dos condiciones unidas por &lt;code&gt;&amp;amp;&amp;amp;&lt;/code&gt;: &lt;code&gt;written &amp;lt; len&lt;/code&gt; nos impide leer más allá del final de la entrada, y &lt;code&gt;ring_buffer_put&lt;/code&gt; devuelve &lt;code&gt;false&lt;/code&gt; en cuanto el búfer se llena. Mientras ambas se cumplan, seguimos copiando bytes e incrementando &lt;code&gt;written&lt;/code&gt;. El ciclo se detiene en cuanto una de las dos falla, ya sea porque se nos acabó la entrada o porque se nos acabó el espacio. La cuenta que devolvemos le dice al llamador exactamente cuánto entró: todo, una parte, o cero si el búfer ya estaba lleno.&lt;/p&gt;

&lt;p&gt;Sigue &lt;code&gt;ring_buffer_read&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="c1"&gt;// modules/ring_buffer/ring_buffer.c&lt;/span&gt;
&lt;span class="kt"&gt;size_t&lt;/span&gt; &lt;span class="nf"&gt;ring_buffer_read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ring_buffer_t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;rb&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;uint8_t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;size_t&lt;/span&gt; &lt;span class="n"&gt;len&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rb&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;out&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;0u&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="kt"&gt;size_t&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0u&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;len&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;ring_buffer_get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rb&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Esta función también devuelve la cantidad de bytes que realmente se leyeron, que puede ser menor a la que se pidió. Devuelve &lt;code&gt;0&lt;/code&gt; en la salida temprana, cuando el búfer circular &lt;code&gt;rb&lt;/code&gt; o el puntero de destino &lt;code&gt;out&lt;/code&gt; son &lt;code&gt;NULL&lt;/code&gt;. Si no, inicializamos &lt;code&gt;count&lt;/code&gt; para llevar la cuenta de cuántos bytes hemos leído del búfer hacia &lt;code&gt;out&lt;/code&gt;. El ciclo se ejecuta con dos condiciones unidas por &lt;code&gt;&amp;amp;&amp;amp;&lt;/code&gt;: &lt;code&gt;count &amp;lt; len&lt;/code&gt; nos impide escribir más allá del final de la salida, y &lt;code&gt;ring_buffer_get&lt;/code&gt; devuelve &lt;code&gt;false&lt;/code&gt; en cuanto el búfer se vacía. Mientras ambas se cumplan, seguimos leyendo bytes e incrementando &lt;code&gt;count&lt;/code&gt;. El ciclo se detiene en cuanto una de las dos falla, ya sea porque el búfer del llamador se llenó o porque el búfer circular se quedó seco. La cuenta que devolvemos le dice al llamador exactamente cuántos bytes recibió.&lt;/p&gt;

&lt;h2&gt;
  
  
  Seguridad con la rutina de servicio de interrupción (ISR)
&lt;/h2&gt;

&lt;p&gt;Para quienes van empezando en embebidos, puede que no sea obvio por qué se considera seguro este diseño, ni cuáles son sus aplicaciones típicas. El uso principal de un búfer circular, como mencionamos antes, es la comunicación serial. Toma el ejemplo de dos microcontroladores hablando entre sí, cada uno ejecutando su propio firmware hecho a la medida de su tarea. No sabemos cuándo va a necesitar enviar datos ninguno de los dos, así que para asegurarnos de no perder un solo byte, montamos la ruta de recepción de datos (RX) usando la rutina de servicio de interrupción (Interrupt Service Routine, ISR) del microcontrolador. En cuanto llega el inicio de una transmisión, la ISR toma el control y captura cada byte.&lt;/p&gt;

&lt;p&gt;Este es el lado productor del búfer. La ISR llama a &lt;code&gt;put&lt;/code&gt; por cada byte que recibe. En otro lado, el bucle principal es el consumidor, llamando a &lt;code&gt;get&lt;/code&gt; cuando tiene chance de procesar lo que llegó. Los dos se ejecutan en contextos distintos, y aquí está el detalle: la ISR puede dispararse en cualquier momento, entre cualesquiera dos instrucciones que esté ejecutando el bucle principal. Así que la pregunta justa es: si la interrupción cae a la mitad de un &lt;code&gt;get&lt;/code&gt;, ¿pueden pisarse entre sí y corromper el búfer?&lt;/p&gt;

&lt;p&gt;La respuesta es no, y la razón es que cada índice tiene exactamente un escritor. &lt;code&gt;put&lt;/code&gt; es la única función que escribe &lt;code&gt;head&lt;/code&gt;, y solo se ejecuta en la ISR. &lt;code&gt;get&lt;/code&gt; es la única función que escribe &lt;code&gt;tail&lt;/code&gt;, y solo se ejecuta en el bucle principal. Ninguno de los dos lados escribe el índice del otro. El productor lee &lt;code&gt;tail&lt;/code&gt; para saber si el búfer está lleno, y el consumidor lee &lt;code&gt;head&lt;/code&gt; para saber si está vacío, pero leer es seguro. Como ningún índice tiene dos escritores compitiendo por cambiarlo, no hay nada que corromper, y no necesitamos deshabilitar interrupciones ni recurrir a un candado. A esto se refiere la gente cuando habla de un solo productor y un solo consumidor, o SPSC por sus siglas en inglés.&lt;/p&gt;

&lt;p&gt;Esa regla de un solo escritor también es el límite. En el momento en que tengas dos cosas llamando a &lt;code&gt;put&lt;/code&gt;, o dos cosas llamando a &lt;code&gt;get&lt;/code&gt;, la garantía se acaba y necesitas sincronización de verdad. Este búfer es seguro para exactamente un productor y un consumidor, ni uno más.&lt;/p&gt;

&lt;p&gt;Hay un detalle más que lo hace funcionar, y es el orden que señalamos allá en &lt;code&gt;put&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="n"&gt;rb&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;rb&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;head&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;rb&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;mask&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// 1. write the data&lt;/span&gt;
&lt;span class="n"&gt;rb&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;head&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// 2. then publish it&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Primero escribimos el byte en la ranura, y después incrementamos &lt;code&gt;head&lt;/code&gt;. Ese orden no es accidente. &lt;code&gt;head&lt;/code&gt; es la señal que le dice al consumidor que hay un byte nuevo disponible. Si incrementáramos &lt;code&gt;head&lt;/code&gt; primero y el tiempo de la interrupción se acomodara mal, el consumidor podría ver el nuevo &lt;code&gt;head&lt;/code&gt;, ir a leer esa ranura y sacar un byte que en realidad todavía no habíamos escrito. Al escribir la ranura antes de avanzar &lt;code&gt;head&lt;/code&gt;, garantizamos que en el instante en que el consumidor puede ver el byte, el byte de verdad ya está ahí. La actualización del índice publica los datos, así que va al final. &lt;code&gt;get&lt;/code&gt; hace el espejo: primero leemos el byte y después avanzamos &lt;code&gt;tail&lt;/code&gt;, así nunca marcamos una ranura como libre antes de habernos llevado su contenido.&lt;/p&gt;

&lt;p&gt;Un par de advertencias honestas para quien las quiera. Primero, &lt;code&gt;volatile&lt;/code&gt; nos da visibilidad, no atomicidad. Este diseño asume que leer o escribir &lt;code&gt;head&lt;/code&gt; y &lt;code&gt;tail&lt;/code&gt; sucede en una sola operación de máquina ininterrumpible, lo cual es cierto cuando son del tamaño de palabra en el objetivo. En un MCU de 8 bits, donde &lt;code&gt;size_t&lt;/code&gt; abarca varios bytes, la actualización de un índice podría partirse en varias instrucciones y quedar rota por una interrupción, y tendrías que tomarlo en cuenta. Segundo, en un microcontrolador de un solo núcleo con una ISR, &lt;code&gt;volatile&lt;/code&gt; más el orden de escribir y luego publicar es suficiente. En un sistema multinúcleo con ordenamiento de memoria débil recurrirías a operaciones atómicas con semántica explícita de adquisición y liberación, para garantizar que la escritura de la ranura se vea antes que la del índice. Para el caso clásico de ISR y bucle principal, que es para el que está hecho este búfer, estamos en terreno firme.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cierre
&lt;/h2&gt;

&lt;p&gt;Eso es un búfer circular completo y funcional en un par de cientos de líneas de C. No hace asignación dinámica, da la vuelta con un solo AND a nivel de bits en lugar de una división, distingue lleno de vacío sin desperdiciar una ranura ni llevar un contador aparte, y es seguro llenarlo desde una interrupción mientras lo vaciamos desde el bucle principal. Para una estructura tan pequeña, trae metida una cantidad sorprendente de sutilezas.&lt;/p&gt;

&lt;p&gt;Vale la pena ser honesto sobre lo que este diseño no hace, porque cada una de estas cosas es un intercambio deliberado, no un descuido:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;La capacidad debe ser potencia de dos.&lt;/strong&gt; Este es el precio de cambiar el módulo por una máscara. Si necesitas guardar exactamente 100 bytes, redondeas a 128 y desperdicias un poco de espacio. En un sistema embebido casi siempre es un intercambio que conviene.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Solo un productor y un consumidor.&lt;/strong&gt; Un escritor por índice es lo que lo hace libre de candados. Dos ISR escribiendo, o dos contextos leyendo, y necesitas sincronización de verdad.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No hay modo de sobrescribir lo más viejo.&lt;/strong&gt; Cuando el búfer está lleno, &lt;code&gt;put&lt;/code&gt; rechaza el byte nuevo en lugar de descartar el más viejo. Algunas aplicaciones quieren lo contrario, conservar las muestras más recientes y tirar las viejas. Esa es una variante razonable, solo que no es esta.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Solo bytes.&lt;/strong&gt; Este búfer guarda &lt;code&gt;uint8_t&lt;/code&gt;. Guardar elementos más anchos, o structs arbitrarios, implica generalizar el tamaño del elemento, lo que cambia la aritmética de índices y el almacenamiento.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Ninguna de estas limitaciones es difícil de levantar. Son las preguntas que salen naturalmente, y varias de ellas se convierten en sus propios patrones más adelante en esta serie.&lt;/p&gt;

&lt;p&gt;Y hablando de eso, el búfer circular es un cimiento, no un destino. Aparece por debajo de varios de los patrones que faltan: una cola de eventos es un búfer circular de mensajes en lugar de bytes, un filtro de promedio móvil es un búfer circular de muestras, y el despachador de comandos que construiremos lee su entrada directamente de uno. Lo que sigue en la serie es el pool de memoria. El búfer circular nos dio almacenamiento fijo para bytes; el pool de memoria nos da almacenamiento fijo para objetos, y es lo que permite que una cola de eventos guarde structs en lugar de datos crudos. Nos vemos ahí.&lt;/p&gt;

</description>
      <category>spanish</category>
      <category>embeddedsystems</category>
      <category>datastructures</category>
      <category>c</category>
    </item>
    <item>
      <title>Common Embedded Patterns: Ring Buffer</title>
      <dc:creator>Ilean Monterrubio Jr</dc:creator>
      <pubDate>Fri, 31 Jul 2026 00:00:00 +0000</pubDate>
      <link>https://dev.to/elcapitan/common-embedded-patterns-ring-buffer-36jc</link>
      <guid>https://dev.to/elcapitan/common-embedded-patterns-ring-buffer-36jc</guid>
      <description>&lt;p&gt;This is the first part of a new series where I'll re-explore common embedded patterns, starting with the ring buffer. It's one of those patterns used in serial communication to make sure no frame of data is lost, and it's also a building block for other patterns we'll cover later.&lt;/p&gt;

&lt;p&gt;The complete, tested source lives in the companion repo: &lt;a href="https://github.com/ileanmjr88/tetzontli" rel="noopener noreferrer"&gt;ileanmjr88/tetzontli&lt;/a&gt;. Each pattern in this series is its own module with a full GoogleTest suite, so you can clone it and run the tests yourself.&lt;/p&gt;

&lt;h2&gt;
  
  
  Basic Concept
&lt;/h2&gt;

&lt;p&gt;The ring buffer uses two indices, &lt;code&gt;head&lt;/code&gt; and &lt;code&gt;tail&lt;/code&gt;. To add data we &lt;code&gt;put&lt;/code&gt; a byte at &lt;code&gt;head&lt;/code&gt; and increment &lt;code&gt;head&lt;/code&gt; by one. To remove data we &lt;code&gt;get&lt;/code&gt; a byte from &lt;code&gt;tail&lt;/code&gt; and increment &lt;code&gt;tail&lt;/code&gt; by one. Both indices only move forward, the tail chases the head around the buffer.&lt;/p&gt;

&lt;p&gt;This pattern can be implemented with a linked list or a fixed-length array. A linked list might be tempting, but on an embedded system you don't want dynamic allocation. On a modern computer with gigabytes of memory, calling &lt;code&gt;malloc&lt;/code&gt; is a non-issue. On a microcontroller you have kilobytes, and three bigger problems: the heap fragments over a device that runs for months, allocation timing is non-deterministic, and you often need to add data from inside an interrupt where you can't afford either. A fixed array sidesteps all of it, the space is guaranteed at compile time.&lt;/p&gt;

&lt;p&gt;That guaranteed space is what buys the consumer time: bytes arriving faster than you can process them sit safely in the buffer until you get to them, instead of being lost.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping around the array
&lt;/h2&gt;

&lt;p&gt;An array does not loop. When you reach the end and try to go past it, it doesn't wrap around. You run beyond the allocated space, which can cause a segmentation fault, return a NULL pointer, or hand you garbage data.&lt;/p&gt;

&lt;h3&gt;
  
  
  Intuition
&lt;/h3&gt;

&lt;p&gt;The intuitive way to loop back around is the modulo operator &lt;code&gt;%&lt;/code&gt;, which returns the remainder after division.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Array of length/capacity: 10

Formula: index % capacity -&amp;gt; array index

- index 0: 0 % 10 = 0
- index 1: 1 % 10 = 1
- index 4: 4 % 10 = 4
- index 10: 10 % 10 = 0
- index 11: 11 % 10 = 1

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Modulo lets us treat the fixed array as a ring, which makes it a great fit for a ring buffer. It works fine on a modern computer with multiple cores, high clock speeds, and a hardware divide unit. But modulo has a drawback. It requires division, and many microcontrollers have no hardware divide. The compiler emulates it in software, which is slow, and we pay that cost on every byte we move through the buffer.&lt;/p&gt;

&lt;h3&gt;
  
  
  Optimizing with a bitmask
&lt;/h3&gt;

&lt;p&gt;There's a faster way to do exactly what the modulo operator does, with one restriction: the array length must be a power of two. That restriction is the key that lets us recreate modulo with a single bitwise operation. The formula is &lt;code&gt;index = head &amp;amp; mask&lt;/code&gt;, where &lt;code&gt;mask = capacity - 1&lt;/code&gt;. Let's walk through the math.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Array of length/capacity: 8 (power of two)

mask = 8 - 1 = 7 = 0b0111

- index 0: 0b0000 &amp;amp; 0b0111 = 0
- index 1: 0b0001 &amp;amp; 0b0111 = 1
- index 2: 0b0010 &amp;amp; 0b0111 = 2
- index 8: 0b1000 &amp;amp; 0b0111 = 0
- index 10: 0b1010 &amp;amp; 0b0111 = 2
- index 14: 0b1110 &amp;amp; 0b0111 = 6

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice these are the same answers modulo would give: &lt;code&gt;8 % 8 = 0&lt;/code&gt;, &lt;code&gt;10 % 8 = 2&lt;/code&gt;, &lt;code&gt;14 % 8 = 6&lt;/code&gt;. That's not a coincidence. The mask &lt;code&gt;0b0111&lt;/code&gt; keeps only the lowest three bits and clears everything above, and the lowest three bits of any number are exactly that number modulo 8. Keeping the low bits &lt;em&gt;is&lt;/em&gt; taking the remainder. Because the capacity is a power of two, &lt;code&gt;capacity - 1&lt;/code&gt; is a clean run of ones, which is what makes the AND line up perfectly with modulo. Try this with a non power of two and it falls apart: &lt;code&gt;11 &amp;amp; 9 = 9&lt;/code&gt;, but &lt;code&gt;11 % 10 = 1&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Implementation
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Ring buffer structure
&lt;/h3&gt;

&lt;p&gt;We create a struct to keep all the data types together. This makes life easy, because with a single pointer to the data structure we can access: &lt;code&gt;buffer&lt;/code&gt;, &lt;code&gt;capacity&lt;/code&gt;, &lt;code&gt;mask&lt;/code&gt;, &lt;code&gt;head&lt;/code&gt;, and &lt;code&gt;tail&lt;/code&gt;. It also makes the data easy to pass by pointer for any function that needs to add and read from it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="c1"&gt;// modules/ring_buffer/ring_buffer.h&lt;/span&gt;
&lt;span class="k"&gt;typedef&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kt"&gt;uint8_t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kt"&gt;size_t&lt;/span&gt; &lt;span class="n"&gt;capacity&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kt"&gt;size_t&lt;/span&gt; &lt;span class="n"&gt;mask&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;volatile&lt;/span&gt; &lt;span class="kt"&gt;size_t&lt;/span&gt; &lt;span class="n"&gt;head&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;volatile&lt;/span&gt; &lt;span class="kt"&gt;size_t&lt;/span&gt; &lt;span class="n"&gt;tail&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="n"&gt;ring_buffer_t&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you can see the struct uses a few different types, and each one is deliberate.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;uint8_t&lt;/code&gt;: the buffer stores raw bytes. Whatever the source is, serial data from UART, I2C, or anything else, it arrives as octets, so a byte array is the natural storage for the queue.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;size_t&lt;/code&gt;: &lt;code&gt;capacity&lt;/code&gt;, &lt;code&gt;mask&lt;/code&gt;, &lt;code&gt;head&lt;/code&gt;, and &lt;code&gt;tail&lt;/code&gt; are all &lt;code&gt;size_t&lt;/code&gt;. It's an unsigned type sized to the platform (usually an alias for &lt;code&gt;unsigned int&lt;/code&gt; or &lt;code&gt;unsigned long&lt;/code&gt;), which keeps the index math portable across compilers and avoids signed-overflow issues. The unsignedness matters later, when we rely on &lt;code&gt;head - tail&lt;/code&gt; wrapping cleanly.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;volatile&lt;/code&gt;: &lt;code&gt;head&lt;/code&gt; and &lt;code&gt;tail&lt;/code&gt; are marked &lt;code&gt;volatile&lt;/code&gt; so the compiler always reads them from memory instead of caching a stale copy in a register. The producer and consumer can run in different execution contexts (for example, an ISR filling the buffer while the main loop drains it), so a value cached in a register could miss the other side's update. Note that &lt;code&gt;volatile&lt;/code&gt; only guarantees the read happens, it does not make the access atomic or thread-safe. We'll cover why this design is safe anyway in the section on interrupts.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Initialization and power of two functions
&lt;/h3&gt;

&lt;p&gt;Let's start with &lt;code&gt;init&lt;/code&gt; and its helper &lt;code&gt;is_power_of_two&lt;/code&gt;. The &lt;code&gt;init&lt;/code&gt; function sets up the data structure, and the power-of-two check is the first gate: if the caller asks for a capacity that isn't a power of two, we refuse, because the whole masking scheme depends on it. We'll start with the helper.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="c1"&gt;// modules/ring_buffer/ring_buffer.c&lt;/span&gt;
&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;bool&lt;/span&gt; &lt;span class="nf"&gt;is_power_of_two&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;size_t&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="mi"&gt;0u&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1u&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0u&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This implementation does the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;x != 0u&lt;/code&gt;: checks if &lt;code&gt;x&lt;/code&gt; is not &lt;code&gt;0&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;(x &amp;amp; (x - 1u)) == 0u&lt;/code&gt;: this is the part that checks for a single set bit. A power of two has exactly one bit set. Subtracting one flips that bit off and turns every bit below it on, so &lt;code&gt;x&lt;/code&gt; and &lt;code&gt;x - 1&lt;/code&gt; share no bits and the AND comes out zero. Take &lt;code&gt;x = 8&lt;/code&gt;: &lt;code&gt;8 - 1 = 7&lt;/code&gt;, and &lt;code&gt;0b1000 &amp;amp; 0b0111 = 0&lt;/code&gt;. Now take a number that isn't a power of two, &lt;code&gt;x = 12&lt;/code&gt;: &lt;code&gt;12 - 1 = 11&lt;/code&gt;, and &lt;code&gt;0b1100 &amp;amp; 0b1011 = 0b1000&lt;/code&gt;, which is not zero. The leftover bit is the tell that &lt;code&gt;x&lt;/code&gt; had more than one bit set. If both conditions are true then &lt;code&gt;x&lt;/code&gt; is a power of two, so the function returns &lt;code&gt;true&lt;/code&gt; if and only if &lt;code&gt;x&lt;/code&gt; is a power of two. Now let's turn to the &lt;code&gt;ring_buffer_init&lt;/code&gt; function.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="c1"&gt;// modules/ring_buffer/ring_buffer.c&lt;/span&gt;
&lt;span class="n"&gt;bool&lt;/span&gt; &lt;span class="nf"&gt;ring_buffer_init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ring_buffer_t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;rb&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;uint8_t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;storage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;size_t&lt;/span&gt; &lt;span class="n"&gt;capacity&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rb&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;storage&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;is_power_of_two&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;capacity&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="n"&gt;rb&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;buffer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;storage&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="n"&gt;rb&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;capacity&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;capacity&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="n"&gt;rb&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;mask&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;capacity&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1u&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="n"&gt;rb&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;head&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0u&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="n"&gt;rb&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;tail&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0u&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;init&lt;/code&gt; validates its inputs first: a null struct pointer, null storage, or a capacity that isn't a power of two all cause it to bail out and return &lt;code&gt;false&lt;/code&gt;. Once the checks pass, it wires up the struct, precomputes &lt;code&gt;mask = capacity - 1&lt;/code&gt; so &lt;code&gt;put&lt;/code&gt; and &lt;code&gt;get&lt;/code&gt; never have to recompute it, and zeroes both indices.&lt;/p&gt;

&lt;p&gt;Notice &lt;code&gt;init&lt;/code&gt; doesn't allocate anything. The caller hands us the &lt;code&gt;storage&lt;/code&gt; array, so the buffer can live in static or stack memory. This is the no-malloc principle from earlier, made concrete: there's no &lt;code&gt;malloc&lt;/code&gt; in sight, and the space was guaranteed the moment the caller declared the array.&lt;/p&gt;

&lt;h3&gt;
  
  
  Put and get functions
&lt;/h3&gt;

&lt;p&gt;These functions put and get a single byte at a time. We scope them to one byte because sometimes a single byte is all we need, and because the next section's &lt;code&gt;write&lt;/code&gt; and &lt;code&gt;read&lt;/code&gt; functions are built on top of them. Let's start with &lt;code&gt;ring_buffer_put&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="c1"&gt;// modules/ring_buffer/ring_buffer.c&lt;/span&gt;
&lt;span class="n"&gt;bool&lt;/span&gt; &lt;span class="nf"&gt;ring_buffer_put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ring_buffer_t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;rb&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;uint8_t&lt;/span&gt; &lt;span class="n"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rb&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;ring_buffer_is_full&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rb&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="n"&gt;rb&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;rb&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;head&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;rb&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;mask&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="n"&gt;rb&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;head&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The function starts by validating that &lt;code&gt;rb&lt;/code&gt; isn't &lt;code&gt;NULL&lt;/code&gt;, then calls the helper &lt;code&gt;ring_buffer_is_full&lt;/code&gt;. If the buffer is full we exit early and return &lt;code&gt;false&lt;/code&gt;, telling the caller no byte was stored. We'll see how &lt;code&gt;is_full&lt;/code&gt; works in the next section. If there's room, we do the actual write, and this is where the previous section pays off: &lt;code&gt;rb-&amp;gt;head &amp;amp; rb-&amp;gt;mask&lt;/code&gt; computes the array index using the bitwise AND, wrapping the free-running &lt;code&gt;head&lt;/code&gt; back into a valid slot. We write the byte there, then increment &lt;code&gt;head&lt;/code&gt; by one so the next &lt;code&gt;put&lt;/code&gt; lands in the following slot. Notice the order: we write the slot first, then advance &lt;code&gt;head&lt;/code&gt;. That ordering matters for interrupt safety, which we'll get to later. Now let's take a look at &lt;code&gt;ring_buffer_get&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="c1"&gt;// modules/ring_buffer/ring_buffer.c&lt;/span&gt;
&lt;span class="n"&gt;bool&lt;/span&gt; &lt;span class="nf"&gt;ring_buffer_get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ring_buffer_t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;rb&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;uint8_t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rb&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;out&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;ring_buffer_is_empty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rb&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;out&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rb&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;rb&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;tail&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;rb&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;mask&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
  &lt;span class="n"&gt;rb&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;tail&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The function also starts by validating that &lt;code&gt;rb&lt;/code&gt; isn't &lt;code&gt;NULL&lt;/code&gt;, then checks that &lt;code&gt;out&lt;/code&gt; isn't &lt;code&gt;NULL&lt;/code&gt;. This is the pointer where the byte we read gets stored, which is how &lt;code&gt;get&lt;/code&gt; hands data back to the caller while keeping its &lt;code&gt;bool&lt;/code&gt; return free to report success or failure. Finally it calls the helper &lt;code&gt;ring_buffer_is_empty&lt;/code&gt;. If the buffer is empty we exit early and return &lt;code&gt;false&lt;/code&gt;, telling the caller no byte was read. We'll see how &lt;code&gt;is_empty&lt;/code&gt; works in the next section. If it's not empty, we do the read, and again the previous section pays off: &lt;code&gt;rb-&amp;gt;tail &amp;amp; rb-&amp;gt;mask&lt;/code&gt; computes the array index with the bitwise AND, wrapping the free-running &lt;code&gt;tail&lt;/code&gt; into a valid slot. We read the byte, then increment &lt;code&gt;tail&lt;/code&gt; by one. Notice &lt;code&gt;get&lt;/code&gt; never erases anything. It reads the byte and moves &lt;code&gt;tail&lt;/code&gt; forward, and that slot counts as free again the moment &lt;code&gt;tail&lt;/code&gt; passes it. The indices alone decide what's data and what's free space.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is empty, is full, and count
&lt;/h3&gt;

&lt;p&gt;These three helpers, &lt;code&gt;is_empty&lt;/code&gt;, &lt;code&gt;is_full&lt;/code&gt;, and &lt;code&gt;count&lt;/code&gt;, are what &lt;code&gt;put&lt;/code&gt; and &lt;code&gt;get&lt;/code&gt; lean on to decide whether they can proceed. This is where the free-running indices finally earn their keep. Let's start with &lt;code&gt;ring_buffer_is_empty&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="c1"&gt;// modules/ring_buffer/ring_buffer.c&lt;/span&gt;
&lt;span class="n"&gt;bool&lt;/span&gt; &lt;span class="nf"&gt;ring_buffer_is_empty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;ring_buffer_t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;rb&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;rb&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;head&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;rb&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;tail&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The buffer is empty when &lt;code&gt;head&lt;/code&gt; and &lt;code&gt;tail&lt;/code&gt; hold the same value. That makes sense: &lt;code&gt;tail&lt;/code&gt; chases &lt;code&gt;head&lt;/code&gt;, and when it catches all the way up there's nothing left to read. Here's the subtle part. In a lot of ring buffer designs the indices get wrapped back into range, and then &lt;code&gt;head == tail&lt;/code&gt; is ambiguous, it means &lt;em&gt;both&lt;/em&gt; completely empty and completely full, and you can't tell which. Because our indices run free and never wrap, &lt;code&gt;head == tail&lt;/code&gt; can only ever mean empty. We'll see in a moment how full is handled without that ambiguity.&lt;/p&gt;

&lt;p&gt;Next, &lt;code&gt;ring_buffer_count&lt;/code&gt;, which is the heart of it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="c1"&gt;// modules/ring_buffer/ring_buffer.c&lt;/span&gt;
&lt;span class="kt"&gt;size_t&lt;/span&gt; &lt;span class="nf"&gt;ring_buffer_count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;ring_buffer_t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;rb&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;rb&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;head&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;rb&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;tail&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both indices only ever increase. Every &lt;code&gt;put&lt;/code&gt; bumps &lt;code&gt;head&lt;/code&gt;, every &lt;code&gt;get&lt;/code&gt; bumps &lt;code&gt;tail&lt;/code&gt;, and neither wraps. So &lt;code&gt;head&lt;/code&gt; is always ahead of &lt;code&gt;tail&lt;/code&gt; by exactly the number of bytes that have been written but not yet read, which is the fill level. The difference &lt;code&gt;head - tail&lt;/code&gt; &lt;em&gt;is&lt;/em&gt; the count.&lt;/p&gt;

&lt;p&gt;The nice part is what happens when &lt;code&gt;head&lt;/code&gt; eventually overflows. &lt;code&gt;size_t&lt;/code&gt; has a maximum, and after enough bytes &lt;code&gt;head&lt;/code&gt; wraps past it back toward zero. You might expect &lt;code&gt;head - tail&lt;/code&gt; to break at that point, but it doesn't. Unsigned subtraction wraps the same way unsigned addition does, so the difference stays correct even across the overflow. This is exactly why &lt;code&gt;head&lt;/code&gt; and &lt;code&gt;tail&lt;/code&gt; are unsigned, and it's why the buffer can run indefinitely without any special handling for the wraparound.&lt;/p&gt;

&lt;p&gt;Finally, &lt;code&gt;ring_buffer_is_full&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="c1"&gt;// modules/ring_buffer/ring_buffer.c&lt;/span&gt;
&lt;span class="n"&gt;bool&lt;/span&gt; &lt;span class="nf"&gt;ring_buffer_is_full&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;ring_buffer_t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;rb&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;ring_buffer_count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rb&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;rb&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;capacity&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once we have &lt;code&gt;count&lt;/code&gt;, full is trivial: the buffer is full when it holds &lt;code&gt;capacity&lt;/code&gt; bytes. No wasted slot, no separate counter to keep in sync, no ambiguity with the empty case. The free-running indices give us empty (&lt;code&gt;head == tail&lt;/code&gt;), full (&lt;code&gt;count == capacity&lt;/code&gt;), and everything in between, all from two numbers that only count upward.&lt;/p&gt;

&lt;h3&gt;
  
  
  Write and read functions
&lt;/h3&gt;

&lt;p&gt;These functions write and read more than a single byte of data at a time. Let's start with &lt;code&gt;ring_buffer_write&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="c1"&gt;// modules/ring_buffer/ring_buffer.c&lt;/span&gt;
&lt;span class="kt"&gt;size_t&lt;/span&gt; &lt;span class="nf"&gt;ring_buffer_write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ring_buffer_t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;rb&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;uint8_t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;size_t&lt;/span&gt; &lt;span class="n"&gt;len&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rb&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;0u&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="kt"&gt;size_t&lt;/span&gt; &lt;span class="n"&gt;written&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0u&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;written&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;len&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;ring_buffer_put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rb&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;written&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;written&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;written&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This function returns the number of bytes actually written, which may be fewer than requested. It returns &lt;code&gt;0&lt;/code&gt; on the early exit when either the ring buffer &lt;code&gt;rb&lt;/code&gt; or the source pointer &lt;code&gt;data&lt;/code&gt; is &lt;code&gt;NULL&lt;/code&gt;. Otherwise we initialize &lt;code&gt;written&lt;/code&gt; to track how many bytes we've copied from &lt;code&gt;data&lt;/code&gt; into the buffer. The loop runs on two conditions joined by &lt;code&gt;&amp;amp;&amp;amp;&lt;/code&gt;: &lt;code&gt;written &amp;lt; len&lt;/code&gt; keeps us from reading past the end of the input, and &lt;code&gt;ring_buffer_put&lt;/code&gt; returns &lt;code&gt;false&lt;/code&gt; the moment the buffer is full. As long as both hold, we keep copying bytes and incrementing &lt;code&gt;written&lt;/code&gt;. The loop stops as soon as either one fails, whether we ran out of input or ran out of room. The count we return tells the caller exactly how much made it in: all of it, part of it, or zero if the buffer was already full.&lt;/p&gt;

&lt;p&gt;Next, &lt;code&gt;ring_buffer_read&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="c1"&gt;// modules/ring_buffer/ring_buffer.c&lt;/span&gt;
&lt;span class="kt"&gt;size_t&lt;/span&gt; &lt;span class="nf"&gt;ring_buffer_read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ring_buffer_t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;rb&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;uint8_t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;size_t&lt;/span&gt; &lt;span class="n"&gt;len&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rb&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;out&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;0u&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="kt"&gt;size_t&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0u&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;len&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;ring_buffer_get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rb&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This function also returns the number of bytes actually read, which may be fewer than requested. It returns &lt;code&gt;0&lt;/code&gt; on the early exit when either the ring buffer &lt;code&gt;rb&lt;/code&gt; or the destination pointer &lt;code&gt;out&lt;/code&gt; is &lt;code&gt;NULL&lt;/code&gt;. Otherwise we initialize &lt;code&gt;count&lt;/code&gt; to track how many bytes we've read from the buffer into &lt;code&gt;out&lt;/code&gt;. The loop runs on two conditions joined by &lt;code&gt;&amp;amp;&amp;amp;&lt;/code&gt;: &lt;code&gt;count &amp;lt; len&lt;/code&gt; keeps us from writing past the end of the output, and &lt;code&gt;ring_buffer_get&lt;/code&gt; returns &lt;code&gt;false&lt;/code&gt; the moment the buffer is empty. As long as both hold, we keep reading bytes and incrementing &lt;code&gt;count&lt;/code&gt;. The loop stops as soon as either one fails, whether the caller's buffer is full or the ring buffer ran dry. The count we return tells the caller exactly how many bytes they got.&lt;/p&gt;

&lt;h2&gt;
  
  
  Safety with the Interrupt Service Routine (ISR)
&lt;/h2&gt;

&lt;p&gt;For those new to embedded, it might not be obvious why this design is considered safe, or what its typical applications are. The primary use for a ring buffer, as we mentioned earlier, is serial communication. Take the example of two microcontrollers talking to each other, each running its own firmware tailored to its own task. We don't know when either one will need to send data, so to make sure we don't miss a single byte, we set up the data receive (RX) path using the microcontroller's Interrupt Service Routine (ISR). As soon as the start of a transmission arrives, the ISR takes over and captures each byte.&lt;/p&gt;

&lt;p&gt;This is the producer side of the buffer. The ISR calls &lt;code&gt;put&lt;/code&gt; for every byte it receives. Somewhere else, the main loop is the consumer, calling &lt;code&gt;get&lt;/code&gt; when it gets around to processing what came in. The two run in different execution contexts, and here's the catch: the ISR can fire at any moment, between any two instructions the main loop is running. So the fair question is, if the interrupt lands in the middle of a &lt;code&gt;get&lt;/code&gt;, can the two step on each other and corrupt the buffer?&lt;/p&gt;

&lt;p&gt;The answer is no, and the reason is that each index has exactly one writer. &lt;code&gt;put&lt;/code&gt; is the only function that writes &lt;code&gt;head&lt;/code&gt;, and it only ever runs in the ISR. &lt;code&gt;get&lt;/code&gt; is the only function that writes &lt;code&gt;tail&lt;/code&gt;, and it only ever runs in the main loop. Neither side writes the other's index. The producer reads &lt;code&gt;tail&lt;/code&gt; to check whether the buffer is full, and the consumer reads &lt;code&gt;head&lt;/code&gt; to check whether it's empty, but reading is safe. Since no index has two writers racing to change it, there's nothing to corrupt, and we don't need to disable interrupts or reach for a lock. This is what people mean by single-producer, single-consumer, or SPSC.&lt;/p&gt;

&lt;p&gt;That single-writer rule is also the boundary. The moment you have two things calling &lt;code&gt;put&lt;/code&gt;, or two things calling &lt;code&gt;get&lt;/code&gt;, the guarantee is gone and you need real synchronization. This buffer is safe for exactly one producer and one consumer, no more.&lt;/p&gt;

&lt;p&gt;One more detail makes it work, and it's the ordering we flagged back in &lt;code&gt;put&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="n"&gt;rb&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;rb&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;head&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;rb&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;mask&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// 1. write the data&lt;/span&gt;
&lt;span class="n"&gt;rb&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;head&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// 2. then publish it&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We write the byte into the slot first, then increment &lt;code&gt;head&lt;/code&gt;. That order is not an accident. &lt;code&gt;head&lt;/code&gt; is the signal that tells the consumer a new byte is available. If we bumped &lt;code&gt;head&lt;/code&gt; first and the interrupt timing worked out badly, the consumer could see the new &lt;code&gt;head&lt;/code&gt;, go read that slot, and pull out a byte we hadn't actually written yet. By writing the slot before advancing &lt;code&gt;head&lt;/code&gt;, we guarantee that the instant the consumer can see the byte, the byte is really there. The index update publishes the data, so it comes last. &lt;code&gt;get&lt;/code&gt; mirrors this: we read the byte out first, then advance &lt;code&gt;tail&lt;/code&gt;, so we never mark a slot free before we've taken its contents.&lt;/p&gt;

&lt;p&gt;A couple of honest caveats for the readers who want them. First, &lt;code&gt;volatile&lt;/code&gt; gets us visibility, not atomicity. This design assumes reading or writing &lt;code&gt;head&lt;/code&gt; and &lt;code&gt;tail&lt;/code&gt; happens in a single, uninterruptible machine operation, which is true when they're word-sized on the target. On an 8-bit MCU where &lt;code&gt;size_t&lt;/code&gt; spans multiple bytes, an index update could be split across instructions and torn by an interrupt, and you'd need to account for that. Second, on a single-core microcontroller with an ISR, &lt;code&gt;volatile&lt;/code&gt; plus the write-then-publish ordering is enough. On a multi-core system with weak memory ordering you'd reach for atomics with explicit acquire and release semantics to guarantee the slot write is seen before the index write. For the classic ISR-and-main-loop case this buffer is built for, we're on solid ground.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrap-up
&lt;/h2&gt;

&lt;p&gt;That's a complete, working ring buffer in a couple hundred lines of C. It does no dynamic allocation, wraps with a single bitwise AND instead of a division, tells full from empty without wasting a slot or keeping a separate counter, and it's safe to fill from an interrupt while draining it from the main loop. For a structure this small, there's a surprising amount of nuance packed in.&lt;/p&gt;

&lt;p&gt;It's worth being honest about what this design does not do, because every one of these is a deliberate trade, not an oversight:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Capacity must be a power of two.&lt;/strong&gt; This is the price of replacing modulo with a mask. If you need to hold exactly 100 bytes you round up to 128 and waste a little space. On an embedded system that's almost always a trade worth making.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Single producer, single consumer only.&lt;/strong&gt; One writer per index is what makes it lock-free. Two ISRs writing, or two contexts reading, and you need real synchronization.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No overwrite-oldest mode.&lt;/strong&gt; When the buffer is full, &lt;code&gt;put&lt;/code&gt; refuses the new byte rather than discarding the oldest one. Some applications want the opposite (keep the newest samples, drop the stale ones). That's a reasonable variant, just not this one.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bytes only.&lt;/strong&gt; This buffer stores &lt;code&gt;uint8_t&lt;/code&gt;. Storing wider elements, or arbitrary structs, means generalizing the element size, which changes the index math and the storage. None of these are hard to lift. They're the natural next questions, and a few of them turn into their own patterns later in this series.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Speaking of which, the ring buffer is a foundation, not a destination. It shows up underneath several of the patterns still to come: an event queue is a ring buffer of messages instead of bytes, a moving-average filter is a ring buffer of samples, and the command dispatcher we'll build reads its input straight out of one. Next in the series we'll tackle the memory pool. The ring buffer gave us fixed storage for bytes; the memory pool gives us fixed storage for objects, and it's what lets an event queue hold structs instead of raw data. See you there.&lt;/p&gt;

</description>
      <category>embeddedsystems</category>
      <category>datastructures</category>
      <category>c</category>
      <category>microcontrollers</category>
    </item>
    <item>
      <title>Compendium: Reproducible Developer Environments in One Config</title>
      <dc:creator>Ilean Monterrubio Jr</dc:creator>
      <pubDate>Mon, 18 May 2026 00:00:00 +0000</pubDate>
      <link>https://dev.to/elcapitan/compendium-reproducible-developer-environments-in-one-config-3aon</link>
      <guid>https://dev.to/elcapitan/compendium-reproducible-developer-environments-in-one-config-3aon</guid>
      <description>&lt;h2&gt;
  
  
  Why I Built It
&lt;/h2&gt;

&lt;p&gt;Earlier this year I kept coming back to a project that had been on the back burner for a while. The original idea was an IDE with a built-in way to configure development environments, but the more I researched, the clearer it got that I was looking at a multi-year build. Not wanting to wait that long to ship something useful, I narrowed in on the feature I cared about most: the ability to configure and set up a development environment easily, without the IDE wrapped around it.&lt;/p&gt;

&lt;p&gt;Throughout my career, setting up toolchains and development environments has always been a painful experience: embedded cross-compilers that only build cleanly on the maintainer's exact OS version, READMEs that promise five minutes and take three days. I've also spent weeks building containers to wall off the development environment, only to watch the image balloon every time another tool got added. The documentation goes stale almost as fast as it gets written, because the tools moved on and nobody updated it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why C and C++ First
&lt;/h2&gt;

&lt;p&gt;For Compendium's first iteration I wanted to focus on the language where the setup pain runs deepest: C and C++. Every scripting language already has a version manager that works well enough. nvm handles Node, pyenv handles Python, asdf and mise cover most of the rest. None of them seriously handle GCC, clang, or arm-none-eabi-gcc. Embedded toolchains in particular are where I lose the most time, between USB drivers, OpenOCD configs, and compiler versions that have to match the linker script the project was tested against. Starting there meant solving the hardest case first. Adding Go, Python, and Node on top of that architecture turned out to be the easy part.&lt;/p&gt;

&lt;h2&gt;
  
  
  v0.1.1 Ships Today
&lt;/h2&gt;

&lt;p&gt;v0.1.1 ships today. The CLI is a single Go binary, the config is a single TOML file, and everything installs under &lt;code&gt;~/.local/compendium/&lt;/code&gt; without sudo. Rather than recap the docs here, I'll point you at them: &lt;a href="https://compendium.ilean.me/" rel="noopener noreferrer"&gt;compendium.ilean.me&lt;/a&gt; has the quick start, the full tool list, the config reference, and per-language guides for C/C++, Go, Python, Node, and embedded.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tech Stack
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;CLI&lt;/strong&gt; : Go (single static binary, no runtime dependencies)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Configuration&lt;/strong&gt; : TOML&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Registry&lt;/strong&gt; : JSON manifests, hosted on GitHub&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Platforms&lt;/strong&gt; : macOS (Apple Silicon, Intel), Linux (x86_64, arm64)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;License&lt;/strong&gt; : GPL v3 (CLI), CC0 (registry)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Roadmap
&lt;/h2&gt;

&lt;p&gt;v0.2 broadens C/C++ build system support to Autotools, adds Java and Rust, completes the embedded cross-compiler set with Xtensa for ESP32, and wires up the custom git hook runner. v0.3 introduces a Compendium-native C/C++ package manager. The full roadmap lives in the docs. If you want to follow along, contribute, or request a tool for the registry, GitHub issues and discussions are open.&lt;/p&gt;

&lt;h2&gt;
  
  
  Project Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://compendium.ilean.me/" rel="noopener noreferrer"&gt;Compendium Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/ileanmjr88/compendium" rel="noopener noreferrer"&gt;Compendium Repository&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/ileanmjr88/compendium-registry" rel="noopener noreferrer"&gt;Compendium Registry&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>compendium</category>
      <category>devtools</category>
      <category>go</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Compendium: entornos de desarrollo reproducibles en una sola configuración</title>
      <dc:creator>Ilean Monterrubio Jr</dc:creator>
      <pubDate>Mon, 18 May 2026 00:00:00 +0000</pubDate>
      <link>https://dev.to/elcapitan/compendium-entornos-de-desarrollo-reproducibles-en-una-sola-configuracion-47i2</link>
      <guid>https://dev.to/elcapitan/compendium-entornos-de-desarrollo-reproducibles-en-una-sola-configuracion-47i2</guid>
      <description>&lt;h2&gt;
  
  
  Por qué lo construí
&lt;/h2&gt;

&lt;p&gt;A principios de este año seguía volviendo a un proyecto que llevaba un buen rato en pausa. La idea original era un IDE con una forma integrada de configurar entornos de desarrollo, pero entre más investigaba, más claro me quedaba que estaba frente a un desarrollo de varios años. Como no quería esperar tanto para lanzar algo útil, me enfoqué en la funcionalidad que más me importaba: poder configurar y preparar un entorno de desarrollo con facilidad, sin el IDE alrededor.&lt;/p&gt;

&lt;p&gt;A lo largo de mi carrera, configurar toolchains y entornos de desarrollo siempre ha sido una experiencia dolorosa: compiladores cruzados de embebidos que solo compilan limpiamente en la versión exacta del sistema operativo del mantenedor, READMEs que prometen cinco minutos y toman tres días. También he pasado semanas construyendo contenedores para aislar el entorno de desarrollo, solo para ver cómo la imagen se inflaba cada vez que se agregaba otra herramienta. La documentación queda obsoleta casi tan rápido como se escribe, porque las herramientas avanzaron y nadie la actualizó.&lt;/p&gt;

&lt;h2&gt;
  
  
  Por qué primero C y C++
&lt;/h2&gt;

&lt;p&gt;Para la primera iteración de Compendium quise enfocarme en el lenguaje donde el dolor de la configuración es más profundo: C y C++. Todos los lenguajes de scripting ya tienen un gestor de versiones que funciona lo suficientemente bien. nvm maneja Node, pyenv maneja Python, asdf y mise cubren casi todo lo demás. Ninguno de ellos maneja en serio GCC, clang o arm-none-eabi-gcc. Los toolchains de embebidos en particular son donde más tiempo pierdo, entre drivers USB, configuraciones de OpenOCD y versiones de compilador que tienen que coincidir con el linker script contra el que se probó el proyecto. Empezar ahí significaba resolver primero el caso más difícil. Agregar Go, Python y Node sobre esa arquitectura resultó ser la parte fácil.&lt;/p&gt;

&lt;h2&gt;
  
  
  La v0.1.1 se lanza hoy
&lt;/h2&gt;

&lt;p&gt;La v0.1.1 se lanza hoy. La CLI es un único binario de Go, la configuración es un único archivo TOML y todo se instala en &lt;code&gt;~/.local/compendium/&lt;/code&gt; sin sudo. En lugar de repetir la documentación aquí, te dirijo a ella: &lt;a href="https://compendium.ilean.me/" rel="noopener noreferrer"&gt;compendium.ilean.me&lt;/a&gt; tiene la guía de inicio rápido, la lista completa de herramientas, la referencia de configuración y guías por lenguaje para C/C++, Go, Python, Node y embebidos.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stack tecnológico
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;CLI&lt;/strong&gt; : Go (un único binario estático, sin dependencias en tiempo de ejecución)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Configuración&lt;/strong&gt; : TOML&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Registro&lt;/strong&gt; : manifiestos JSON, alojados en GitHub&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Plataformas&lt;/strong&gt; : macOS (Apple Silicon, Intel), Linux (x86_64, arm64)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Licencia&lt;/strong&gt; : GPL v3 (CLI), CC0 (registro)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Hoja de ruta
&lt;/h2&gt;

&lt;p&gt;La v0.2 amplía el soporte de sistemas de build de C/C++ a Autotools, agrega Java y Rust, completa el conjunto de compiladores cruzados para embebidos con Xtensa para el ESP32 y conecta el ejecutor de git hooks personalizados. La v0.3 introduce un gestor de paquetes de C/C++ nativo de Compendium. La hoja de ruta completa vive en la documentación. Si quieres seguir el avance, contribuir o solicitar una herramienta para el registro, los issues y las discusiones de GitHub están abiertos.&lt;/p&gt;

&lt;h2&gt;
  
  
  Enlaces del proyecto
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://compendium.ilean.me/" rel="noopener noreferrer"&gt;Documentación de Compendium&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/ileanmjr88/compendium" rel="noopener noreferrer"&gt;Repositorio de Compendium&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/ileanmjr88/compendium-registry" rel="noopener noreferrer"&gt;Registro de Compendium&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>spanish</category>
      <category>devtools</category>
      <category>go</category>
      <category>opensource</category>
    </item>
    <item>
      <title>How I Built a Low-Friction Blog Pipeline</title>
      <dc:creator>Ilean Monterrubio Jr</dc:creator>
      <pubDate>Mon, 27 Apr 2026 00:00:00 +0000</pubDate>
      <link>https://dev.to/elcapitan/how-i-built-a-low-friction-blog-pipeline-1lof</link>
      <guid>https://dev.to/elcapitan/how-i-built-a-low-friction-blog-pipeline-1lof</guid>
      <description>&lt;h2&gt;
  
  
  Why Build a Pipeline?
&lt;/h2&gt;

&lt;p&gt;I wanted to grow my online presence, a dedicated corner of the internet to showcase my projects and skills, point people to my GitHub, LinkedIn, and socials, and share my thoughts on things I was building or learning. Simple enough goal. The execution, though, turned out to be a learning process.&lt;/p&gt;

&lt;p&gt;My first attempt at blogging was manual and messy. Write a post, publish it somewhere, copy it somewhere else, and hope people find it. I had set up the Dev.to RSS feed fetching early on, but that was about it. There was no real visibility into whether anyone was reading, and no reason to keep going. Unsurprisingly, I took a year-long gap between posts, not because I stopped having things to say, but because the friction was too high and life got in the way. When I started a new job, I focused on learning my new responsibilities and let the blog quietly go dormant.&lt;/p&gt;

&lt;p&gt;When I came back to it, I wanted to do things differently. The goal was simple: write once, publish everywhere, no copy-pasting across platforms, no manual distribution, and some way to know if the work was actually reaching anyone. The RSS feed was already there but the imports were messy. The frontmatter in my blog posts didn't match the RSS schema, so every import came in inconsistent and making changes or adding new frontmatter fields meant manually fixing every post. What I did this time was get my Astro frontmatter to match the RSS schema properly, so imports were clean and uniform from the start. Now it was about connecting the remaining pieces into a proper pipeline.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Stack
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Astro&lt;/strong&gt; Before launching my personal site, I had tried to build it with Next.js a year earlier. I didn't fully understand it at the time and kept running into hydration issues. At a local Code and Coffee meetup, I asked one of the attendees for advice and he pointed me to Astro. Within days of going through a tutorial, I had my site live for the first time. I paired it with Tailwind CSS to make theming easy to maintain and update down the road.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pexels&lt;/strong&gt; Every post needs a cover image. Pexels is my go-to for finding images, royalty-free stock photos and videos shared by creators. High quality, free to use, and no licensing headaches.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Dev.to&lt;/strong&gt; This came much later in the process. After some digging I discovered that Astro can generate an &lt;code&gt;rss.xml&lt;/code&gt; feed if set up correctly, and Dev.to can periodically pull from that feed to automatically import new posts. All I need to do is keep my frontmatter in sync with what Dev.to expects from the feed, no manual copying.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Umami&lt;/strong&gt; I wanted visibility into who was actually reading. After some research I landed on Umami: privacy-friendly, no cookie banners, and a free hobby tier that covers up to 3 websites.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Mastodon&lt;/strong&gt; After Twitter was acquired I decided to leave and delete my profile. Mastodon turned out to be a great fit, especially for the tech community, and the fediverse has a different energy that I appreciate.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;LinkedIn&lt;/strong&gt; Where I manually share each post to take advantage of how LinkedIn's algorithm circulates content to your network and extended network for a few days after publishing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting Up Astro
&lt;/h2&gt;

&lt;p&gt;Each blog post is written in Markdown format, which Astro handles exceptionally well out of the box. One of the keys to making everything work smoothly is Astro's content collections configuration, this is how you tell Astro the shape of your content so it can validate and query it consistently. For this project I have Markdown files for blog entries and JSON files to define my projects. The &lt;a href="https://docs.astro.build/en/guides/content-collections/" rel="noopener noreferrer"&gt;Astro content collections documentation&lt;/a&gt; is worth reading carefully to understand how to define your schema.&lt;/p&gt;

&lt;p&gt;Before looking at the frontmatter, it helps to see the schema that enforces it. This lives in &lt;code&gt;src/content.config.ts&lt;/code&gt; and uses Astro's content collections with Zod validation. If a frontmatter field is missing or the wrong type, Astro will throw an error at build time rather than silently generating a broken RSS feed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;defineCollection&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;astro:content&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;glob&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;astro/loaders&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;blog&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;defineCollection&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;loader&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;glob&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;pattern&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;**/*.md&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;base&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./src/content/blog&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="na"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="na"&gt;pubDate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;date&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="na"&gt;author&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="na"&gt;categories&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;()),&lt;/span&gt;
  &lt;span class="p"&gt;}),&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;collections&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;blog&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each blog post starts with a frontmatter block that drives everything downstream, from the RSS feed to Dev.to imports:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Building&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;a&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Terminal&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Text&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Editor:&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;The&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;View&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;(Part&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;3)"&lt;/span&gt;
&lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;In&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Part&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;3&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;of&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;building&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;wordNebula,&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;I&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;cover&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;the&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;View&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;layer,&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;why&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;I&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;chose&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;FTXUI&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;over&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;ncurses..."&lt;/span&gt;
&lt;span class="na"&gt;pubDate&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;2026-04-05&lt;/span&gt;
&lt;span class="na"&gt;author&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Ilean&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Monterrubio&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Jr"&lt;/span&gt;
&lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;/src/content/images/pexels-markus-winkler-1430818-4065400.jpg'&lt;/span&gt;
&lt;span class="na"&gt;categories&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;cpp'&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;terminal'&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;architecture'&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;programming'&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Keeping this frontmatter consistent and aligned with the RSS schema is what makes the rest of the pipeline work without any manual cleanup. This also makes the &lt;code&gt;rss.xml.ts&lt;/code&gt; configuration straightforward to set up. All it needs to do is read the frontmatter content and it will generate the &lt;code&gt;rss.xml&lt;/code&gt; file at build time.&lt;/p&gt;

&lt;p&gt;Here is what a basic &lt;code&gt;rss.xml.ts&lt;/code&gt; file looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;rss&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@astrojs/rss&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;getCollection&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;astro:content&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;GET&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;posts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getCollection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;blog&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;rss&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Your Blog Name&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Your blog description&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;site&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;site&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;pubDate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pubDate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;description&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;author&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;author&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;categories&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;categories&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;link&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`/blog/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;})),&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The fields in the &lt;code&gt;items&lt;/code&gt; map directly to the frontmatter fields you define. As long as the frontmatter is consistent, the RSS output will be clean and Dev.to will be able to import it without any issues. For additional information visit the &lt;a href="https://docs.astro.build/en/recipes/rss/" rel="noopener noreferrer"&gt;Astro RSS documentation&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Auto-Importing to Dev.to via RSS
&lt;/h2&gt;

&lt;p&gt;For this part you will need a Dev.to account. Once your website is live and the RSS feed URL is active, navigate to your Dev.to dashboard. On the left-hand sidebar you will find &lt;strong&gt;RSS Import Feeds&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fca2t9mhnrayjf5rd9muk.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fca2t9mhnrayjf5rd9muk.webp" alt="Dev.to RSS feed import setup screen" width="800" height="643"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;From there, click &lt;strong&gt;+ Add a Feed Source&lt;/strong&gt; to expand the form. Enter your &lt;code&gt;rss.xml&lt;/code&gt; URL in the RSS Feed URL field. A couple of settings worth paying attention to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Mark the RSS source as canonical URL by default:&lt;/strong&gt; keep this checked. This is one of the most important settings in the whole pipeline. It tells Dev.to that your personal site is the original source of the content, which means search engines will credit your site and not the Dev.to version. Get this wrong and Dev.to can end up outranking your own site for your own writing. Always leave this on.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Replace self-referential links with DEV Community-specific links:&lt;/strong&gt; leave this unchecked unless you are migrating your entire blog to Dev.to permanently.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once you click &lt;strong&gt;Add Feed Source&lt;/strong&gt; , Dev.to will periodically check your feed and pull in any new posts. Imported posts land as &lt;strong&gt;drafts&lt;/strong&gt; , so before publishing you will want to review each one and add a &lt;strong&gt;series name&lt;/strong&gt; if it is part of a multi-part post, pick &lt;strong&gt;tags&lt;/strong&gt; that match popular Dev.to tags for discoverability (for example: &lt;code&gt;cpp&lt;/code&gt;, &lt;code&gt;terminal&lt;/code&gt;, &lt;code&gt;architecture&lt;/code&gt;, &lt;code&gt;programming&lt;/code&gt;), and add a &lt;strong&gt;cover image&lt;/strong&gt; sourced from Pexels. When you are ready to go live, open the post editor and flip &lt;code&gt;published&lt;/code&gt; to &lt;code&gt;true&lt;/code&gt; in the Dev.to frontmatter at the top of the post.&lt;/p&gt;

&lt;h2&gt;
  
  
  Mastodon Verification and Author Attribution
&lt;/h2&gt;

&lt;p&gt;I wanted to establish a presence on Mastodon, and it turns out Astro makes it easy to connect your blog to the fediverse with just two small additions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Verification with rel="me"&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Mastodon uses the &lt;code&gt;rel="me"&lt;/code&gt; link standard to verify that you own a website. Adding it to your Astro layout head tag gives you the green checkmark on your Mastodon profile:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;link&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"me"&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"https://mastodon.social/@yourusername"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Author Attribution with fediverse:creator&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;fediverse:creator&lt;/code&gt; meta tag is a newer addition that connects your blog posts to your Mastodon identity. When someone shares one of your posts on Mastodon, it automatically credits your account:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"fediverse:creator"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"@yourusername@mastodon.social"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add this to your blog post layout so it only appears on post pages rather than every page on the site. Together these two tags make your blog a proper fediverse citizen, verified, attributable, and discoverable by the tech community that has made Mastodon their home.&lt;/p&gt;

&lt;h2&gt;
  
  
  Adding Privacy-Friendly Analytics with Umami
&lt;/h2&gt;

&lt;p&gt;When I started getting posts out I wanted to know if anyone was actually reading them. Google Analytics was the obvious choice but it felt like overkill, heavy, cookie-dependent, and requiring a consent banner just to get started. After some digging I found Umami, a lightweight privacy-friendly alternative that ticked every box. The free hobby tier covers up to 3 websites, 100K events per month, and 6 months of data retention. No cookies, no consent banners, no tracking across sites.&lt;/p&gt;

&lt;p&gt;Setup is straightforward. Once you create your Umami account and add your site, you get a script tag to drop into your Astro layout file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;script
  &lt;/span&gt;&lt;span class="na"&gt;defer&lt;/span&gt;
  &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://cloud.umami.is/script.js"&lt;/span&gt;
  &lt;span class="na"&gt;data-website-id=&lt;/span&gt;&lt;span class="s"&gt;"your-website-id"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add this to your main &lt;code&gt;Layout.astro&lt;/code&gt; file and it will be included on every page automatically. I also added a small notice in the footer to let visitors know the site uses privacy-friendly analytics with no cookies or personal data collected, a small but honest touch that sets the right expectations.&lt;/p&gt;

&lt;p&gt;The Umami dashboard covers all the basics: visitors, page views, referrers, locations, devices, and bounce rate. It is everything you need to understand how your content is performing without compromising your readers' privacy.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Publishing Workflow
&lt;/h2&gt;

&lt;p&gt;I typically write about something I worked on professionally, a personal project, or a topic I consider myself an expert in. Here is the end-to-end process I follow from idea to published post:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Write the draft in Notion&lt;/strong&gt; I started out using Google Docs but since returning to the blog I migrated to Notion. It handles code blocks and tables properly and exports to Markdown in a way that actually translates correctly, which makes the next step much smoother.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Generate the Astro-ready Markdown file with frontmatter&lt;/strong&gt; Once the draft is ready I convert it into a Markdown file with the correct frontmatter fields — title, description, pubDate, author, image, and categories — making sure everything aligns with the RSS schema.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Deploy to ilean.me on Sunday morning&lt;/strong&gt; Sunday is my publishing day. I push the new post and deploy the site.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Dev.to picks up via RSS&lt;/strong&gt; Dev.to periodically polls the RSS feed URL, so the post will appear as a draft in my Dev.to dashboard within a few hours depending on when it last checked.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Post on Mastodon the same day&lt;/strong&gt; I share the post on Mastodon with hashtags, typically the same ones used in the post frontmatter for consistency.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;6. Schedule LinkedIn for Monday morning&lt;/strong&gt; LinkedIn has a post scheduling feature which has been a game changer. I schedule it to go live Monday morning at 8-9 AM, when recruiters and professionals are most active, letting LinkedIn's algorithm circulate it to my network and extended network over the following days.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;7. Review and publish the Dev.to draft&lt;/strong&gt; Once it lands in Dev.to I review it, add a series name if it is part of a multi-part post, set the tags, add a cover image from Pexels, and flip &lt;code&gt;published&lt;/code&gt; to &lt;code&gt;true&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Results So Far
&lt;/h2&gt;

&lt;p&gt;I revived the blog in February and March of 2026. On Dev.to, in February the most views I received in a single day was 16. Since then that number has climbed to 36 views in a single day. As of April 25th 2026 Dev.to shows 435 total views, though down 24% compared to the previous 7 days, a good reminder that consistency matters.&lt;/p&gt;

&lt;p&gt;On ilean.me, Umami shows 48 unique visitors so far. What surprised me was the geographic spread. A large portion of visitors are coming from Asia, which makes sense given that embedded systems and C++ are a common stack there. The majority are from the US, with California showing up frequently, which tracks given the tech industry concentration there.&lt;/p&gt;

&lt;p&gt;The numbers are small but the trend is encouraging. Search engine traffic compounds over time; every post indexed is another entry point for someone to find your work. The goal was never overnight virality, it was to slowly and consistently build an online presence, and the data shows that is exactly what is happening.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd Do Differently
&lt;/h2&gt;

&lt;p&gt;A few honest lessons from getting this pipeline up and running.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Double check your publication dates.&lt;/strong&gt; One post did not show up on Dev.to on time and after some digging I realized the date in the frontmatter was off by a day. Dev.to pulls from the RSS feed based on the pubDate field, so if that is wrong the post gets skipped or delayed. Always verify the date before deploying.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Be patient with the RSS fetch timing.&lt;/strong&gt; Dev.to does not poll your feed instantly. Depending on when it last checked, it could take several hours for a new post to appear as a draft. Do not panic and assume something is broken; just give it time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Filter out your own visits.&lt;/strong&gt; Early on I got excited seeing a visitor from Houston in Umami before realizing it was just me clicking my own links. The free Umami tier does not have a built-in way to exclude your own IP, so just keep that in mind when reading your early numbers and take them with a grain of salt.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Find your community.&lt;/strong&gt; One thing I want to do going forward is find Houston based tech Discord servers with software engineers and developers to share my content in. Posting in relevant communities can accelerate growth in a way that passive RSS and social posts alone cannot.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Do not check analytics too often.&lt;/strong&gt; It is tempting at the start but the numbers move slowly and checking constantly just creates unnecessary anxiety. Set a cadence, maybe once a week, and focus on writing the next post instead.&lt;/p&gt;

&lt;p&gt;The whole point of building this pipeline was to remove friction, and it worked. There is no year-long gap waiting to happen again; there is just the next post.&lt;/p&gt;

</description>
      <category>astro</category>
      <category>blogging</category>
      <category>devto</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Cómo construí un pipeline de blog sin fricciones</title>
      <dc:creator>Ilean Monterrubio Jr</dc:creator>
      <pubDate>Mon, 27 Apr 2026 00:00:00 +0000</pubDate>
      <link>https://dev.to/elcapitan/como-construi-un-pipeline-de-blog-sin-fricciones-1elk</link>
      <guid>https://dev.to/elcapitan/como-construi-un-pipeline-de-blog-sin-fricciones-1elk</guid>
      <description>&lt;h2&gt;
  
  
  ¿Por qué construir un pipeline?
&lt;/h2&gt;

&lt;p&gt;Quería hacer crecer mi presencia en línea: un rincón propio de internet para mostrar mis proyectos y habilidades, dirigir a la gente a mi GitHub, LinkedIn y redes sociales, y compartir mis ideas sobre lo que estaba construyendo o aprendiendo. Un objetivo bastante sencillo. La ejecución, sin embargo, resultó ser todo un proceso de aprendizaje.&lt;/p&gt;

&lt;p&gt;Mi primer intento de bloguear fue manual y desordenado. Escribir una entrada, publicarla en algún lado, copiarla a otro y esperar que la gente la encontrara. Había configurado la obtención del feed RSS de Dev.to desde el principio, pero hasta ahí llegaba. No había una visibilidad real de si alguien estaba leyendo, ni una razón para seguir. Como era de esperarse, dejé pasar un año entero entre entradas, no porque dejara de tener cosas que decir, sino porque la fricción era demasiado alta y la vida se atravesó. Cuando empecé un nuevo trabajo, me concentré en aprender mis nuevas responsabilidades y dejé que el blog quedara en silencio.&lt;/p&gt;

&lt;p&gt;Cuando volví a retomarlo, quería hacer las cosas de otra manera. El objetivo era simple: escribir una vez, publicar en todas partes, sin copiar y pegar entre plataformas, sin distribución manual, y con alguna forma de saber si el trabajo realmente llegaba a alguien. El feed RSS ya estaba ahí, pero las importaciones eran un desorden. El frontmatter de mis entradas no coincidía con el esquema del RSS, así que cada importación llegaba inconsistente, y hacer cambios o agregar nuevos campos al frontmatter significaba corregir cada entrada a mano. Esta vez lo que hice fue lograr que el frontmatter de Astro coincidiera correctamente con el esquema del RSS, para que las importaciones fueran limpias y uniformes desde el inicio. Ahora se trataba de conectar las piezas restantes en un pipeline en forma.&lt;/p&gt;

&lt;h2&gt;
  
  
  El stack
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Astro&lt;/strong&gt; Antes de lanzar mi sitio personal, había intentado construirlo con Next.js un año antes. En ese momento no lo entendía del todo y me topaba constantemente con problemas de hidratación. En un meetup local de Code and Coffee le pedí consejo a uno de los asistentes y me recomendó Astro. A los pocos días de seguir un tutorial, tenía mi sitio en vivo por primera vez. Lo combiné con Tailwind CSS para que el manejo de temas fuera fácil de mantener y actualizar más adelante.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pexels&lt;/strong&gt; Cada entrada necesita una imagen de portada. Pexels es mi opción de cabecera para encontrar imágenes: fotos y videos de stock libres de regalías compartidos por creadores. De alta calidad, gratis y sin dolores de cabeza por licencias.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Dev.to&lt;/strong&gt; Esto llegó mucho después en el proceso. Tras investigar un poco, descubrí que Astro puede generar un feed &lt;code&gt;rss.xml&lt;/code&gt; si se configura correctamente, y que Dev.to puede consultar ese feed de forma periódica para importar automáticamente las nuevas entradas. Lo único que tengo que hacer es mantener mi frontmatter en sincronía con lo que Dev.to espera del feed, sin copiar nada a mano.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Umami&lt;/strong&gt; Quería tener visibilidad de quién estaba leyendo realmente. Después de investigar, me decidí por Umami: respetuoso con la privacidad, sin banners de cookies y con un plan gratuito para aficionados que cubre hasta 3 sitios web.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Mastodon&lt;/strong&gt; Después de que adquirieran Twitter, decidí irme y eliminar mi perfil. Mastodon resultó encajar muy bien, sobre todo para la comunidad tech, y el fediverso tiene una energía distinta que aprecio.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;LinkedIn&lt;/strong&gt; Donde comparto manualmente cada entrada para aprovechar cómo el algoritmo de LinkedIn hace circular el contenido por tu red y tu red extendida durante unos días después de publicar.&lt;/p&gt;

&lt;h2&gt;
  
  
  Configurando Astro
&lt;/h2&gt;

&lt;p&gt;Cada entrada del blog está escrita en formato Markdown, que Astro maneja excepcionalmente bien desde el inicio. Una de las claves para que todo funcione sin problemas es la configuración de las content collections de Astro; así es como le dices a Astro la forma de tu contenido para que pueda validarlo y consultarlo de manera consistente. Para este proyecto tengo archivos Markdown para las entradas del blog y archivos JSON para definir mis proyectos. Vale la pena leer con atención la &lt;a href="https://docs.astro.build/en/guides/content-collections/" rel="noopener noreferrer"&gt;documentación de content collections de Astro&lt;/a&gt; para entender cómo definir tu esquema.&lt;/p&gt;

&lt;p&gt;Antes de ver el frontmatter, ayuda conocer el esquema que lo hace cumplir. Vive en &lt;code&gt;src/content.config.ts&lt;/code&gt; y usa las content collections de Astro con validación de Zod. Si falta un campo del frontmatter o es del tipo incorrecto, Astro lanzará un error en tiempo de compilación en lugar de generar en silencio un feed RSS roto:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;defineCollection&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;astro:content&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;glob&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;astro/loaders&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;blog&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;defineCollection&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;loader&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;glob&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;pattern&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;**/*.md&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;base&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./src/content/blog&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="na"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="na"&gt;pubDate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;date&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="na"&gt;author&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="na"&gt;categories&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;()),&lt;/span&gt;
  &lt;span class="p"&gt;}),&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;collections&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;blog&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Cada entrada del blog comienza con un bloque de frontmatter que impulsa todo lo que viene después, desde el feed RSS hasta las importaciones a Dev.to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Building&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;a&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Terminal&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Text&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Editor:&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;The&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;View&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;(Part&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;3)"&lt;/span&gt;
&lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;In&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Part&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;3&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;of&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;building&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;wordNebula,&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;I&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;cover&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;the&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;View&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;layer,&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;why&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;I&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;chose&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;FTXUI&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;over&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;ncurses..."&lt;/span&gt;
&lt;span class="na"&gt;pubDate&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;2026-04-05&lt;/span&gt;
&lt;span class="na"&gt;author&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Ilean&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Monterrubio&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Jr"&lt;/span&gt;
&lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;/src/content/images/pexels-markus-winkler-1430818-4065400.jpg'&lt;/span&gt;
&lt;span class="na"&gt;categories&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;cpp'&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;terminal'&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;architecture'&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;programming'&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Mantener este frontmatter consistente y alineado con el esquema del RSS es lo que hace que el resto del pipeline funcione sin ninguna limpieza manual. Esto también hace que la configuración de &lt;code&gt;rss.xml.ts&lt;/code&gt; sea sencilla de armar. Lo único que necesita hacer es leer el contenido del frontmatter y generará el archivo &lt;code&gt;rss.xml&lt;/code&gt; en tiempo de compilación.&lt;/p&gt;

&lt;p&gt;Así se ve un archivo &lt;code&gt;rss.xml.ts&lt;/code&gt; básico:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;rss&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@astrojs/rss&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;getCollection&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;astro:content&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;GET&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;posts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getCollection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;blog&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;rss&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Your Blog Name&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Your blog description&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;site&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;site&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;pubDate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pubDate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;description&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;author&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;author&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;categories&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;categories&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;link&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`/blog/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;})),&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Los campos del &lt;code&gt;items&lt;/code&gt; se corresponden directamente con los campos del frontmatter que definas. Mientras el frontmatter sea consistente, la salida del RSS será limpia y Dev.to podrá importarlo sin ningún problema. Para más información, visita la &lt;a href="https://docs.astro.build/en/recipes/rss/" rel="noopener noreferrer"&gt;documentación de RSS de Astro&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Importación automática a Dev.to vía RSS
&lt;/h2&gt;

&lt;p&gt;Para esta parte necesitarás una cuenta de Dev.to. Una vez que tu sitio web esté en vivo y la URL del feed RSS esté activa, ve a tu panel de Dev.to. En la barra lateral izquierda encontrarás &lt;strong&gt;RSS Import Feeds&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.ilean.me%2Fes%2Fblog%2Fimages%2Fdevto_rss_setup.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.ilean.me%2Fes%2Fblog%2Fimages%2Fdevto_rss_setup.png" alt="Pantalla de configuración de importación del feed RSS de Dev.to"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Desde ahí, haz clic en &lt;strong&gt;+ Add a Feed Source&lt;/strong&gt; para desplegar el formulario. Ingresa la URL de tu &lt;code&gt;rss.xml&lt;/code&gt; en el campo RSS Feed URL. Un par de ajustes a los que vale la pena prestar atención:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Mark the RSS source as canonical URL by default:&lt;/strong&gt; déjalo activado. Este es uno de los ajustes más importantes de todo el pipeline. Le indica a Dev.to que tu sitio personal es la fuente original del contenido, lo que significa que los motores de búsqueda darán crédito a tu sitio y no a la versión de Dev.to. Si te equivocas en esto, Dev.to puede terminar posicionándose por encima de tu propio sitio con tus propios textos. Déjalo siempre activado.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Replace self-referential links with DEV Community-specific links:&lt;/strong&gt; déjalo desactivado, a menos que estés migrando todo tu blog a Dev.to de forma permanente.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Una vez que hagas clic en &lt;strong&gt;Add Feed Source&lt;/strong&gt; , Dev.to revisará tu feed periódicamente e importará cualquier entrada nueva. Las entradas importadas llegan como &lt;strong&gt;borradores&lt;/strong&gt; , así que antes de publicar conviene revisar cada una y agregar un &lt;strong&gt;nombre de serie&lt;/strong&gt; si forma parte de una entrada de varias partes, elegir &lt;strong&gt;etiquetas&lt;/strong&gt; que coincidan con las etiquetas populares de Dev.to para que sea más fácil de descubrir (por ejemplo: &lt;code&gt;cpp&lt;/code&gt;, &lt;code&gt;terminal&lt;/code&gt;, &lt;code&gt;architecture&lt;/code&gt;, &lt;code&gt;programming&lt;/code&gt;) y agregar una &lt;strong&gt;imagen de portada&lt;/strong&gt; tomada de Pexels. Cuando estés listo para publicar, abre el editor de la entrada y cambia &lt;code&gt;published&lt;/code&gt; a &lt;code&gt;true&lt;/code&gt; en el frontmatter de Dev.to al inicio de la entrada.&lt;/p&gt;

&lt;h2&gt;
  
  
  Verificación con Mastodon y atribución de autoría
&lt;/h2&gt;

&lt;p&gt;Quería establecer una presencia en Mastodon, y resulta que Astro facilita conectar tu blog al fediverso con solo dos pequeñas adiciones.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Verificación con rel="me"&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Mastodon usa el estándar de enlace &lt;code&gt;rel="me"&lt;/code&gt; para verificar que eres dueño de un sitio web. Agregarlo a la etiqueta head de tu layout de Astro te da la palomita verde en tu perfil de Mastodon:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;link&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"me"&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"https://mastodon.social/@yourusername"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Atribución de autoría con fediverse:creator&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;La meta etiqueta &lt;code&gt;fediverse:creator&lt;/code&gt; es una adición más reciente que conecta las entradas de tu blog con tu identidad de Mastodon. Cuando alguien comparte una de tus entradas en Mastodon, le da crédito a tu cuenta automáticamente:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"fediverse:creator"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"@yourusername@mastodon.social"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Agrégalo al layout de tus entradas para que solo aparezca en las páginas de las entradas y no en todas las páginas del sitio. Juntas, estas dos etiquetas convierten a tu blog en un ciudadano del fediverso en forma: verificado, atribuible y fácil de descubrir por la comunidad tech que ha hecho de Mastodon su hogar.&lt;/p&gt;

&lt;h2&gt;
  
  
  Agregando analíticas respetuosas con la privacidad con Umami
&lt;/h2&gt;

&lt;p&gt;Cuando empecé a sacar entradas, quería saber si alguien las estaba leyendo de verdad. Google Analytics era la opción obvia, pero se sentía excesivo: pesado, dependiente de cookies y que requería un banner de consentimiento solo para empezar. Tras investigar un poco encontré Umami, una alternativa ligera y respetuosa con la privacidad que cumplía con todo. El plan gratuito para aficionados cubre hasta 3 sitios web, 100 mil eventos al mes y 6 meses de retención de datos. Sin cookies, sin banners de consentimiento, sin rastreo entre sitios.&lt;/p&gt;

&lt;p&gt;La configuración es sencilla. Una vez que creas tu cuenta de Umami y agregas tu sitio, obtienes una etiqueta de script para colocar en el archivo de layout de Astro:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;script
  &lt;/span&gt;&lt;span class="na"&gt;defer&lt;/span&gt;
  &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://cloud.umami.is/script.js"&lt;/span&gt;
  &lt;span class="na"&gt;data-website-id=&lt;/span&gt;&lt;span class="s"&gt;"your-website-id"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Agrégalo a tu archivo principal &lt;code&gt;Layout.astro&lt;/code&gt; y se incluirá automáticamente en cada página. También agregué un pequeño aviso en el pie de página para que los visitantes sepan que el sitio usa analíticas respetuosas con la privacidad, sin cookies ni recolección de datos personales; un detalle pequeño pero honesto que establece las expectativas correctas.&lt;/p&gt;

&lt;p&gt;El panel de Umami cubre todo lo básico: visitantes, vistas de página, referencias, ubicaciones, dispositivos y tasa de rebote. Es todo lo que necesitas para entender cómo está funcionando tu contenido sin comprometer la privacidad de tus lectores.&lt;/p&gt;

&lt;h2&gt;
  
  
  El flujo de publicación
&lt;/h2&gt;

&lt;p&gt;Normalmente escribo sobre algo en lo que trabajé profesionalmente, un proyecto personal o un tema en el que me considero experto. Este es el proceso de principio a fin que sigo desde la idea hasta la entrada publicada:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Escribir el borrador en Notion&lt;/strong&gt; Empecé usando Google Docs, pero desde que retomé el blog me migré a Notion. Maneja bien los bloques de código y las tablas, y exporta a Markdown de una forma que realmente se traduce correctamente, lo que hace que el siguiente paso sea mucho más fluido.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Generar el archivo Markdown listo para Astro con frontmatter&lt;/strong&gt; Una vez que el borrador está listo, lo convierto en un archivo Markdown con los campos de frontmatter correctos —title, description, pubDate, author, image y categories— asegurándome de que todo se alinee con el esquema del RSS.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Desplegar en ilean.me el domingo por la mañana&lt;/strong&gt; El domingo es mi día de publicación. Subo la nueva entrada y despliego el sitio.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Dev.to la recoge vía RSS&lt;/strong&gt; Dev.to consulta periódicamente la URL del feed RSS, así que la entrada aparecerá como borrador en mi panel de Dev.to en unas horas, dependiendo de cuándo revisó por última vez.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Publicar en Mastodon el mismo día&lt;/strong&gt; Comparto la entrada en Mastodon con hashtags, normalmente los mismos que uso en el frontmatter de la entrada para mantener la consistencia.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;6. Programar LinkedIn para el lunes por la mañana&lt;/strong&gt; LinkedIn tiene una función para programar publicaciones que ha sido un cambio total. La programo para que salga el lunes por la mañana entre las 8 y 9 AM, cuando los reclutadores y profesionales están más activos, dejando que el algoritmo de LinkedIn la haga circular por mi red y mi red extendida durante los días siguientes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;7. Revisar y publicar el borrador de Dev.to&lt;/strong&gt; Una vez que llega a Dev.to, lo reviso, agrego un nombre de serie si forma parte de una entrada de varias partes, configuro las etiquetas, agrego una imagen de portada de Pexels y cambio &lt;code&gt;published&lt;/code&gt; a &lt;code&gt;true&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Resultados hasta ahora
&lt;/h2&gt;

&lt;p&gt;Reviví el blog en febrero y marzo de 2026. En Dev.to, en febrero la mayor cantidad de vistas que recibí en un solo día fue 16. Desde entonces ese número subió a 36 vistas en un solo día. Al 25 de abril de 2026, Dev.to muestra 435 vistas totales, aunque con una baja del 24 % respecto a los 7 días anteriores, un buen recordatorio de que la consistencia importa.&lt;/p&gt;

&lt;p&gt;En ilean.me, Umami muestra 48 visitantes únicos hasta ahora. Lo que me sorprendió fue la distribución geográfica. Una buena parte de los visitantes viene de Asia, lo cual tiene sentido dado que los sistemas embebidos y C++ son un stack común allá. La mayoría viene de Estados Unidos, con California apareciendo con frecuencia, lo que cuadra dada la concentración de la industria tecnológica ahí.&lt;/p&gt;

&lt;p&gt;Los números son pequeños, pero la tendencia es alentadora. El tráfico de los motores de búsqueda se acumula con el tiempo; cada entrada indexada es otro punto de entrada para que alguien encuentre tu trabajo. El objetivo nunca fue volverse viral de la noche a la mañana, sino construir una presencia en línea de forma lenta y constante, y los datos muestran que es exactamente lo que está pasando.&lt;/p&gt;

&lt;h2&gt;
  
  
  Qué haría diferente
&lt;/h2&gt;

&lt;p&gt;Algunas lecciones honestas de poner este pipeline en marcha.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Verifica dos veces tus fechas de publicación.&lt;/strong&gt; Una entrada no apareció a tiempo en Dev.to y, tras investigar un poco, me di cuenta de que la fecha en el frontmatter estaba desfasada por un día. Dev.to toma del feed RSS según el campo pubDate, así que si está mal, la entrada se omite o se retrasa. Verifica siempre la fecha antes de desplegar.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ten paciencia con los tiempos de obtención del RSS.&lt;/strong&gt; Dev.to no consulta tu feed al instante. Dependiendo de cuándo revisó por última vez, una entrada nueva puede tardar varias horas en aparecer como borrador. No entres en pánico ni asumas que algo está roto; solo dale tiempo.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Filtra tus propias visitas.&lt;/strong&gt; Al principio me emocioné al ver un visitante de Houston en Umami, antes de darme cuenta de que era yo haciendo clic en mis propios enlaces. El plan gratuito de Umami no tiene una forma integrada de excluir tu propia IP, así que tenlo en cuenta al leer tus primeros números y tómalos con cautela.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Encuentra tu comunidad.&lt;/strong&gt; Algo que quiero hacer de aquí en adelante es encontrar servidores de Discord tech de Houston con ingenieros de software y desarrolladores donde compartir mi contenido. Publicar en comunidades relevantes puede acelerar el crecimiento de una forma que el RSS pasivo y las publicaciones en redes por sí solos no pueden.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No revises las analíticas con demasiada frecuencia.&lt;/strong&gt; Es tentador al principio, pero los números se mueven despacio y revisarlos constantemente solo genera ansiedad innecesaria. Establece una frecuencia, tal vez una vez por semana, y mejor concéntrate en escribir la siguiente entrada.&lt;/p&gt;

&lt;p&gt;Todo el propósito de construir este pipeline era eliminar la fricción, y funcionó. Ya no hay un hueco de un año esperando a repetirse; solo está la siguiente entrada.&lt;/p&gt;

</description>
      <category>spanish</category>
      <category>astro</category>
      <category>blogging</category>
      <category>devto</category>
    </item>
    <item>
      <title>Building a Terminal Text Editor: The View (Part 3)</title>
      <dc:creator>Ilean Monterrubio Jr</dc:creator>
      <pubDate>Mon, 06 Apr 2026 00:00:00 +0000</pubDate>
      <link>https://dev.to/elcapitan/building-a-terminal-text-editor-the-view-part-3-12gd</link>
      <guid>https://dev.to/elcapitan/building-a-terminal-text-editor-the-view-part-3-12gd</guid>
      <description>&lt;h2&gt;
  
  
  Recap
&lt;/h2&gt;

&lt;p&gt;In &lt;a href="https://www.ilean.me/blog/building-a-terminal-text-editor-the-model-part-1/" rel="noopener noreferrer"&gt;Part 1&lt;/a&gt;, I talked about the origin of this project, the early architectural decisions, and why I picked the GapBuffer with &lt;code&gt;IBuffer&lt;/code&gt; as the contract for all future buffer implementations. In &lt;a href="https://www.ilean.me/blog/building-a-terminal-text-editor-the-presenter-part-2/" rel="noopener noreferrer"&gt;Part 2&lt;/a&gt;, we got into the details of the Presenter, how it wires the components together, handles user input, manages state, and keeps the Model and View in sync. Now we can move on to the View.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is the View?
&lt;/h2&gt;

&lt;p&gt;The View is the component that the user interacts with directly. All user inputs are collected and passed to the Presenter, the View doesn't decide what to do with them. It is also responsible for rendering the text the user types, along with any warnings, statistics like word count, and whether the file is unsaved. The View doesn't calculate any of this, it just displays whatever the Presenter sends it. Another example of how all three parts of the Model-View-Presenter separate concerns.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why FTXUI instead of ncurses
&lt;/h2&gt;

&lt;p&gt;In the proof of concept testing, I used ncurses, the de facto standard library for building terminal interfaces. It worked well for the initial testing, and I see a lot of projects still using it; it's tried and true. While ncurses would have been a great choice, I did a bit more research and found FTXUI, a modern C++ library with components to build interactive terminal user interfaces. I wanted to leverage that it uses modern C++, matching the project's early defined specification of targeting C++20.&lt;/p&gt;

&lt;h2&gt;
  
  
  The IView Interface
&lt;/h2&gt;

&lt;p&gt;While implementing the other components (Model and Presenter), I mocked up a thin interface for the View, &lt;code&gt;IView&lt;/code&gt;. At first, it was only taking character input and not accounting for special characters, and it didn't handle rendering any data. The proof of concept View just took the input, sent it to the Presenter, which would send it to the buffer and log it so I could see the data was actually flowing through the system.&lt;/p&gt;

&lt;p&gt;I was thinking of removing it, but after doing more research, I decided to expand the interface instead. Keeping &lt;code&gt;IView&lt;/code&gt; as an abstract class means that if someone wants to write their own View for an OS that isn't supported by FTXUI, they can implement the interface and plug it in without touching the Presenter or Model.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;IView&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;public:&lt;/span&gt;
    &lt;span class="k"&gt;virtual&lt;/span&gt; &lt;span class="o"&gt;~&lt;/span&gt;&lt;span class="n"&gt;IView&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;virtual&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;function&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;InputEvent&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;onInput&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;virtual&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="n"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;ViewState&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;virtual&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="n"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;virtual&lt;/span&gt; &lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;pair&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;getTerminalSize&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;virtual&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="n"&gt;showMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;isError&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A quick explanation, &lt;code&gt;run()&lt;/code&gt; starts the event loop and takes a callback for handling input, this is what the Presenter calls in its main loop. &lt;code&gt;render()&lt;/code&gt; takes the &lt;code&gt;ViewState&lt;/code&gt; we saw in Part 2 and draws it to the screen. &lt;code&gt;exit()&lt;/code&gt; and &lt;code&gt;getTerminalSize()&lt;/code&gt; do what they say, and &lt;code&gt;showMessage()&lt;/code&gt; is how the Presenter displays warnings like the "unsaved changes" prompt from the exit flow.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rendering the UI
&lt;/h2&gt;

&lt;p&gt;As we showed in Part 2, the &lt;code&gt;ViewState&lt;/code&gt; struct defines the contract for how the Presenter sends data to the View:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="nc"&gt;ViewState&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Content&lt;/span&gt;
    &lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt; &lt;span class="n"&gt;visibleText&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Text currently visible in viewport&lt;/span&gt;
    &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;cursorPosition&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Linear position of cursor in visibleText&lt;/span&gt;

    &lt;span class="c1"&gt;// Status bar information&lt;/span&gt;
    &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;wordCount&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Total word count (primary metric for writers)&lt;/span&gt;
    &lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt; &lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Current file name (or "Untitled")&lt;/span&gt;
    &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;isDirty&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Unsaved changes indicator&lt;/span&gt;

    &lt;span class="c1"&gt;// UI state&lt;/span&gt;
    &lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt; &lt;span class="n"&gt;statusMessage&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Temporary status message (e.g., "Saved", "Error: ...")&lt;/span&gt;
    &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;showHelp&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Whether to show help overlay&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There are three major types of data in the &lt;code&gt;ViewState&lt;/code&gt; contract: &lt;strong&gt;Content&lt;/strong&gt; , which tells us the &lt;code&gt;cursorPosition&lt;/code&gt; and the &lt;code&gt;visibleText&lt;/code&gt;. &lt;strong&gt;Status bar information&lt;/strong&gt; : &lt;code&gt;wordCount&lt;/code&gt;, &lt;code&gt;filename&lt;/code&gt;, and &lt;code&gt;isDirty&lt;/code&gt; which indicates whether the file is unsaved. Finally, the &lt;strong&gt;UI state&lt;/strong&gt; : &lt;code&gt;statusMessage&lt;/code&gt; and &lt;code&gt;showHelp&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;One of the biggest challenges was how to deal with the cursor, how to move it, and be able to add more to the buffer when the cursor moves. One of the simplest solutions is to break the &lt;code&gt;visibleText&lt;/code&gt; into three components: beforeCursor, cursor, and afterCursor.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="n"&gt;ftxui&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Element&lt;/span&gt; &lt;span class="n"&gt;FtxuiView&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;renderEditor&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="k"&gt;auto&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;currentState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;visibleText&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;size_t&lt;/span&gt; &lt;span class="n"&gt;pos&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;static_cast&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;size_t&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;currentState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cursorPosition&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;auto&lt;/span&gt; &lt;span class="n"&gt;before&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ftxui&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;substr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

    &lt;span class="c1"&gt;// Character at cursor with cyan background&lt;/span&gt;
    &lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt; &lt;span class="n"&gt;cursorChar&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pos&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;" "&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;auto&lt;/span&gt; &lt;span class="n"&gt;cursor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ftxui&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cursorChar&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;ftxui&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;bgcolor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ftxui&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Color&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Cyan&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;ftxui&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;color&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ftxui&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Color&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Black&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;auto&lt;/span&gt; &lt;span class="n"&gt;after&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pos&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="n"&gt;ftxui&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;substr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ftxui&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;ftxui&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;hbox&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="n"&gt;before&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;after&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The cursor is highlighted with a cyan background so the user can see where they are in the text. This challenge existed in both ncurses and FTXUI, neither library gives you a built-in cursor for a custom text editor. You have to simulate it yourself.&lt;/p&gt;

&lt;h2&gt;
  
  
  Translating Keyboard Input
&lt;/h2&gt;

&lt;p&gt;The View is also responsible for translating raw keyboard events into the &lt;code&gt;InputEvent&lt;/code&gt; structs that the Presenter understands. FTXUI has its own event system, so the View needs a translation layer between what FTXUI gives us and what our Presenter expects. The &lt;code&gt;translateEvent()&lt;/code&gt; method handles this mapping:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="n"&gt;InputEvent&lt;/span&gt; &lt;span class="n"&gt;FtxuiView&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;translateEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;ftxui&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Event&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;ftxui&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Event&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;ArrowLeft&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;InputEvent&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Type&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;ARROW_LEFT&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;ftxui&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Event&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;ArrowRight&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;InputEvent&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Type&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;ARROW_RIGHT&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;ftxui&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Event&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Backspace&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;InputEvent&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Type&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;BACKSPACE&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;ftxui&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Event&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Return&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;InputEvent&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Type&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;ENTER&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;ftxui&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Event&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;CtrlQ&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;InputEvent&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Type&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;CTRL_Q&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;ftxui&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Event&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;CtrlS&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;InputEvent&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Type&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;CTRL_S&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;

    &lt;span class="c1"&gt;// Printable character&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;is_character&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;InputEvent&lt;/span&gt; &lt;span class="n"&gt;ie&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;InputEvent&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Type&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;CHARACTER&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
        &lt;span class="n"&gt;ie&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;character&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;character&lt;/span&gt;&lt;span class="p"&gt;()[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;ie&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;InputEvent&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Type&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;UNKNOWN&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The pattern is straightforward, each FTXUI event maps to one of our &lt;code&gt;InputEvent&lt;/code&gt; types. If it's a printable character, we extract the character value. If we don't recognize the event, it returns &lt;code&gt;UNKNOWN&lt;/code&gt; and gets ignored. This is the same translation layer that would need to be reimplemented if someone wrote a new View using a different library, another benefit of the &lt;code&gt;IView&lt;/code&gt; interface.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the View Can Do So Far
&lt;/h2&gt;

&lt;p&gt;The View works well for the current state of the project. It renders text as the user types, displays the status bar with the filename, word count, and unsaved changes indicator, captures all input events and routes them to the Presenter, and toggles the help overlay with F1. It is not perfect, newlines don't render yet and the arrow keys can't fully navigate up and down through the text. But for Phase 1, it does its job as the passive layer in the MVP pattern.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping Up the Series
&lt;/h2&gt;

&lt;p&gt;This wraps up the three-part series on building wordNebula's Phase 1. Across these posts, we went from the origin of the project and why I chose Model-View-Presenter, through the Model with the GapBuffer and IBuffer interface, the Presenter that orchestrates everything and manages state, and finally the View that renders it all to the terminal.&lt;/p&gt;

&lt;p&gt;Building each layer independently and connecting them through contracts like &lt;code&gt;IBuffer&lt;/code&gt; and &lt;code&gt;IView&lt;/code&gt; made the project manageable. I could work on one piece at a time without worrying about breaking the others. That separation is the whole point of MVP, and it paid off.&lt;/p&gt;

&lt;p&gt;Phase 1 is complete but far from finished. There's more to build, but for now I'm stepping away to focus on other projects. If you want to explore the code, check out the project on &lt;a href="https://github.com/ileanmjr88/wordNebula" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>cpp</category>
      <category>terminal</category>
      <category>architecture</category>
      <category>programming</category>
    </item>
    <item>
      <title>Construyendo un editor de texto de terminal: la Vista (Parte 3)</title>
      <dc:creator>Ilean Monterrubio Jr</dc:creator>
      <pubDate>Sun, 05 Apr 2026 00:00:00 +0000</pubDate>
      <link>https://dev.to/elcapitan/construyendo-un-editor-de-texto-de-terminal-la-vista-parte-3-2d1e</link>
      <guid>https://dev.to/elcapitan/construyendo-un-editor-de-texto-de-terminal-la-vista-parte-3-2d1e</guid>
      <description>&lt;h2&gt;
  
  
  Resumen
&lt;/h2&gt;

&lt;p&gt;En la &lt;a href="https://dev.to/es/blog/construyendo-un-editor-de-texto-de-terminal-el-modelo-parte-1/"&gt;Parte 1&lt;/a&gt; hablé del origen de este proyecto, las primeras decisiones de arquitectura y por qué elegí el GapBuffer con &lt;code&gt;IBuffer&lt;/code&gt; como el contrato para todas las futuras implementaciones de búfer. En la &lt;a href="https://dev.to/es/blog/construyendo-un-editor-de-texto-de-terminal-el-presentador-parte-2/"&gt;Parte 2&lt;/a&gt; entramos en los detalles del Presentador, cómo conecta los componentes, maneja la entrada del usuario, gestiona el estado y mantiene el Modelo y la Vista sincronizados. Ahora podemos pasar a la Vista.&lt;/p&gt;

&lt;h2&gt;
  
  
  ¿Qué es la Vista?
&lt;/h2&gt;

&lt;p&gt;La Vista es el componente con el que el usuario interactúa directamente. Todas las entradas del usuario se recopilan y se pasan al Presentador; la Vista no decide qué hacer con ellas. También es responsable de renderizar el texto que el usuario escribe, junto con cualquier advertencia, estadísticas como el conteo de palabras y si el archivo está sin guardar. La Vista no calcula nada de esto, solo muestra lo que el Presentador le envía. Otro ejemplo de cómo las tres partes de Modelo-Vista-Presentador separan las responsabilidades.&lt;/p&gt;

&lt;h2&gt;
  
  
  Por qué FTXUI en lugar de ncurses
&lt;/h2&gt;

&lt;p&gt;En las pruebas de la prueba de concepto usé ncurses, la biblioteca estándar de facto para construir interfaces de terminal. Funcionó bien para las pruebas iniciales, y veo muchos proyectos que todavía la usan; es confiable y probada. Aunque ncurses habría sido una gran elección, investigué un poco más y encontré FTXUI, una biblioteca de C++ moderna con componentes para construir interfaces de usuario de terminal interactivas. Quería aprovechar que usa C++ moderno, en línea con la especificación que definí al inicio del proyecto de apuntar a C++20.&lt;/p&gt;

&lt;h2&gt;
  
  
  La interfaz IView
&lt;/h2&gt;

&lt;p&gt;Mientras implementaba los otros componentes (el Modelo y el Presentador), armé una interfaz ligera para la Vista, &lt;code&gt;IView&lt;/code&gt;. Al principio solo tomaba la entrada de caracteres y no contemplaba los caracteres especiales, ni se encargaba de renderizar dato alguno. La Vista de la prueba de concepto solo tomaba la entrada, la enviaba al Presentador, que la mandaba al búfer y la registraba para que yo pudiera ver que los datos realmente fluían por el sistema.&lt;/p&gt;

&lt;p&gt;Pensaba en eliminarla, pero después de investigar más, decidí más bien expandir la interfaz. Mantener &lt;code&gt;IView&lt;/code&gt; como una clase abstracta significa que, si alguien quiere escribir su propia Vista para un sistema operativo que FTXUI no soporta, puede implementar la interfaz y conectarla sin tocar el Presentador ni el Modelo.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;IView&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;public:&lt;/span&gt;
    &lt;span class="k"&gt;virtual&lt;/span&gt; &lt;span class="o"&gt;~&lt;/span&gt;&lt;span class="n"&gt;IView&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;virtual&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;function&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;InputEvent&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;onInput&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;virtual&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="n"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;ViewState&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;virtual&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="n"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;virtual&lt;/span&gt; &lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;pair&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;getTerminalSize&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;virtual&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="n"&gt;showMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;isError&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Una explicación rápida: &lt;code&gt;run()&lt;/code&gt; inicia el bucle de eventos y recibe un callback para manejar la entrada; esto es lo que el Presentador llama en su bucle principal. &lt;code&gt;render()&lt;/code&gt; toma el &lt;code&gt;ViewState&lt;/code&gt; que vimos en la Parte 2 y lo dibuja en la pantalla. &lt;code&gt;exit()&lt;/code&gt; y &lt;code&gt;getTerminalSize()&lt;/code&gt; hacen lo que dicen, y &lt;code&gt;showMessage()&lt;/code&gt; es la forma en que el Presentador muestra advertencias como el aviso de "cambios sin guardar" del flujo de salida.&lt;/p&gt;

&lt;h2&gt;
  
  
  Renderizando la interfaz
&lt;/h2&gt;

&lt;p&gt;Como mostramos en la Parte 2, la estructura &lt;code&gt;ViewState&lt;/code&gt; define el contrato de cómo el Presentador envía datos a la Vista:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="nc"&gt;ViewState&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Content&lt;/span&gt;
    &lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt; &lt;span class="n"&gt;visibleText&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Text currently visible in viewport&lt;/span&gt;
    &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;cursorPosition&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Linear position of cursor in visibleText&lt;/span&gt;

    &lt;span class="c1"&gt;// Status bar information&lt;/span&gt;
    &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;wordCount&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Total word count (primary metric for writers)&lt;/span&gt;
    &lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt; &lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Current file name (or "Untitled")&lt;/span&gt;
    &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;isDirty&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Unsaved changes indicator&lt;/span&gt;

    &lt;span class="c1"&gt;// UI state&lt;/span&gt;
    &lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt; &lt;span class="n"&gt;statusMessage&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Temporary status message (e.g., "Saved", "Error: ...")&lt;/span&gt;
    &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;showHelp&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Whether to show help overlay&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Hay tres grandes tipos de datos en el contrato de &lt;code&gt;ViewState&lt;/code&gt;: &lt;strong&gt;Contenido&lt;/strong&gt; , que nos dice la &lt;code&gt;cursorPosition&lt;/code&gt; y el &lt;code&gt;visibleText&lt;/code&gt;. &lt;strong&gt;Información de la barra de estado&lt;/strong&gt; : &lt;code&gt;wordCount&lt;/code&gt;, &lt;code&gt;filename&lt;/code&gt; e &lt;code&gt;isDirty&lt;/code&gt;, que indica si el archivo está sin guardar. Por último, el &lt;strong&gt;estado de la interfaz&lt;/strong&gt; : &lt;code&gt;statusMessage&lt;/code&gt; y &lt;code&gt;showHelp&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Uno de los mayores retos fue cómo lidiar con el cursor, cómo moverlo y poder agregar más al búfer cuando el cursor se mueve. Una de las soluciones más simples es dividir el &lt;code&gt;visibleText&lt;/code&gt; en tres componentes: beforeCursor, cursor y afterCursor.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="n"&gt;ftxui&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Element&lt;/span&gt; &lt;span class="n"&gt;FtxuiView&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;renderEditor&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="k"&gt;auto&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;currentState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;visibleText&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;size_t&lt;/span&gt; &lt;span class="n"&gt;pos&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;static_cast&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;size_t&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;currentState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cursorPosition&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;auto&lt;/span&gt; &lt;span class="n"&gt;before&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ftxui&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;substr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

    &lt;span class="c1"&gt;// Character at cursor with cyan background&lt;/span&gt;
    &lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt; &lt;span class="n"&gt;cursorChar&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pos&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;" "&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;auto&lt;/span&gt; &lt;span class="n"&gt;cursor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ftxui&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cursorChar&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;ftxui&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;bgcolor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ftxui&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Color&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Cyan&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;ftxui&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;color&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ftxui&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Color&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Black&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;auto&lt;/span&gt; &lt;span class="n"&gt;after&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pos&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="n"&gt;ftxui&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;substr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ftxui&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;ftxui&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;hbox&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="n"&gt;before&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;after&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;El cursor se resalta con un fondo cian para que el usuario pueda ver dónde está en el texto. Este reto existía tanto en ncurses como en FTXUI; ninguna biblioteca te da un cursor integrado para un editor de texto personalizado. Tienes que simularlo tú mismo.&lt;/p&gt;

&lt;h2&gt;
  
  
  Traduciendo la entrada del teclado
&lt;/h2&gt;

&lt;p&gt;La Vista también es responsable de traducir los eventos crudos del teclado a las estructuras &lt;code&gt;InputEvent&lt;/code&gt; que el Presentador entiende. FTXUI tiene su propio sistema de eventos, así que la Vista necesita una capa de traducción entre lo que FTXUI nos da y lo que nuestro Presentador espera. El método &lt;code&gt;translateEvent()&lt;/code&gt; se encarga de este mapeo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="n"&gt;InputEvent&lt;/span&gt; &lt;span class="n"&gt;FtxuiView&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;translateEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;ftxui&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Event&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;ftxui&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Event&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;ArrowLeft&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;InputEvent&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Type&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;ARROW_LEFT&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;ftxui&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Event&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;ArrowRight&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;InputEvent&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Type&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;ARROW_RIGHT&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;ftxui&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Event&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Backspace&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;InputEvent&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Type&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;BACKSPACE&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;ftxui&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Event&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Return&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;InputEvent&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Type&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;ENTER&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;ftxui&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Event&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;CtrlQ&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;InputEvent&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Type&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;CTRL_Q&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;ftxui&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Event&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;CtrlS&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;InputEvent&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Type&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;CTRL_S&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;

    &lt;span class="c1"&gt;// Printable character&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;is_character&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;InputEvent&lt;/span&gt; &lt;span class="n"&gt;ie&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;InputEvent&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Type&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;CHARACTER&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
        &lt;span class="n"&gt;ie&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;character&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;character&lt;/span&gt;&lt;span class="p"&gt;()[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;ie&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;InputEvent&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Type&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;UNKNOWN&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;El patrón es sencillo: cada evento de FTXUI se mapea a uno de nuestros tipos &lt;code&gt;InputEvent&lt;/code&gt;. Si es un carácter imprimible, extraemos el valor del carácter. Si no reconocemos el evento, devuelve &lt;code&gt;UNKNOWN&lt;/code&gt; y se ignora. Esta es la misma capa de traducción que habría que reimplementar si alguien escribiera una nueva Vista usando una biblioteca distinta, otro beneficio de la interfaz &lt;code&gt;IView&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lo que la Vista puede hacer hasta ahora
&lt;/h2&gt;

&lt;p&gt;La Vista funciona bien para el estado actual del proyecto. Renderiza el texto conforme el usuario escribe, muestra la barra de estado con el nombre del archivo, el conteo de palabras y el indicador de cambios sin guardar, captura todos los eventos de entrada y los enruta al Presentador, y alterna la superposición de ayuda con F1. No es perfecta: los saltos de línea todavía no se renderizan y las teclas de flecha no pueden navegar del todo hacia arriba y hacia abajo por el texto. Pero para la Fase 1, cumple su trabajo como la capa pasiva del patrón MVP.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cerrando la serie
&lt;/h2&gt;

&lt;p&gt;Con esto cerramos la serie de tres partes sobre la construcción de la Fase 1 de wordNebula. A lo largo de estas entradas fuimos desde el origen del proyecto y por qué elegí Modelo-Vista-Presentador, pasando por el Modelo con el GapBuffer y la interfaz IBuffer, el Presentador que orquesta todo y gestiona el estado, y finalmente la Vista que lo renderiza todo en la terminal.&lt;/p&gt;

&lt;p&gt;Construir cada capa de forma independiente y conectarlas mediante contratos como &lt;code&gt;IBuffer&lt;/code&gt; e &lt;code&gt;IView&lt;/code&gt; hizo que el proyecto fuera manejable. Podía trabajar en una pieza a la vez sin preocuparme por romper las demás. Esa separación es justamente el propósito de MVP, y valió la pena.&lt;/p&gt;

&lt;p&gt;La Fase 1 está completa pero lejos de estar terminada. Hay más por construir, pero por ahora me alejo para enfocarme en otros proyectos. Si quieres explorar el código, échale un vistazo al proyecto en &lt;a href="https://github.com/ileanmjr88/wordNebula" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>spanish</category>
      <category>cpp</category>
      <category>terminal</category>
      <category>architecture</category>
    </item>
  </channel>
</rss>
