# Capítulo 17 — El mundo binario: bits, bytes y máscaras > Capítulo avanzado. Se apoya en variables y tipos (`02`), operadores (`03`), `if` y bucles (`04`), > funciones (`05`), punteros (`07`), `struct` (`08`) y tipos de ancho exacto (`16`). > > Acá bajás un piso: dejás de mirar **números** y empezás a mirar los **bits** que los componen. Con > eso vas a poder meter ocho banderas en un solo byte, extraer campos de un archivo binario, hacer > multiplicaciones y divisiones gratis, y entender de una vez qué son `BYTE`, `WORD`, `DWORD` y > `QWORD`. --- ## Cómo leer este capítulo Este es el capítulo que convierte "sé que existen los bits" en "los uso todos los días". No es teórico: cada idea viene con **código para mirar**, y cada bloque cierra con **práctica** para que lo manejes con las manos. Está dividido en **seis partes**. Después de cada concepto importante hay uno o varios ejercicios cortos. **No los saltees**: son la mitad del capítulo. | Parte | De qué habla | La idea en una frase | |---|---|---| | **1** | El sustrato | Un bit es un interruptor; un byte son ocho interruptores en fila. | | **2** | Los operadores `&`, `\|`, `^`, `~` | Cuatro formas de combinar dos filas de interruptores. | | **3** | Máscaras | Una plantilla que deja pasar solo los bits que te interesan. | | **4** | `<<` y `>>` | Correr los interruptores y, de yapa, multiplicar y dividir. | | **5** | `BYTE`, `WORD`, `DWORD`, `QWORD` | Cajas de 8, 16, 32 y 64 bits, y cómo leer campos dentro de ellas. | | **6** | Conceptos extra | Contar bits, potencia de dos, rotar, endianness, `bitfields` y más. | Los **ejercicios** se numeran en orden: `17.1`, `17.2`, `17.3`, y así. Cada uno trae su **escenario de prueba** (qué entra y qué tiene que salir, nunca el algoritmo) para que **compiles y compruebes**. > **Regla de oro del libro:** el capítulo explica y muestra **cómo funciona** cada cosa (con código > ilustrativo), pero **los ejercicios no traen la solución**. El enunciado te da el problema; la > respuesta la escribís vos. --- # PARTE 1 — El sustrato: bit, byte y bases Antes de operar con bits, hay que verlos. Esta parte es corta y es la base de todo lo demás. ## 1.1 El bit: un interruptor Pensá en un **interruptor de luz**. Tiene dos posiciones: **apagado** o **encendido**. No hay matices. Un **bit** es exactamente eso: un interruptor que puede valer **0** (apagado) o **1** (encendido). Con **un** interruptor solo podés representar dos cosas. Con **dos**, cuatro combinaciones (`00`, `01`, `10`, `11`). Con **ocho**, doscientas cincuenta y seis (`2⁸`). Esa regla es la única que tenés que recordar: ```text n bits → 2ⁿ combinaciones distintas ``` ## 1.2 El byte: ocho interruptores en una caja Un **byte** es, en la práctica de hoy, **8 bits**. Los lenguajes modernos garantizan que `sizeof(char) == 1`, pero **no** que un carácter tenga 8 bits: eso lo dice otra constante, `CHAR_BIT`, que en todas las máquinas que vas a tocar vale **8**. ```c #include #include /* define CHAR_BIT */ int main(void) { /* sizeof(char) SIEMPRE es 1: un char ocupa "un byte". Pero "un byte" tiene CHAR_BIT bits. En x86/x64/ARM, CHAR_BIT == 8. */ printf("sizeof(char) = %zu\n", sizeof(char)); printf("CHAR_BIT = %d\n", CHAR_BIT); /* casi siempre: 8 */ return 0; } ``` Los bits de un byte se numeran **de derecha a izquierda**, empezando en **0**. Es una convención, no un capricho: el bit 0 es el **menos significativo** (el que vale `2⁰ = 1`) y el bit 7, el **más significativo** (el que vale `2⁷ = 128`). ```text bit: 7 6 5 4 3 2 1 0 ┌────┬────┬────┬────┬────┬────┬────┬────┐ byte │ 0 │ 1 │ 0 │ 1 │ 1 │ 0 │ 0 │ 1 │ └────┴────┴────┴────┴────┴────┴────┴────┘ valor: 128 64 32 16 8 4 2 1 (los que están en 1 se suman) = 0+64+0+16+8+0+0+1 = 89 ``` ## 1.3 Bases: por qué escribimos `0x` y `0b` Tu computadora piensa en **binario**, pero nadie escribe `0b01011001` a mano todo el tiempo: es larguísimo y un dígito de más te arruina todo. Por eso usamos **atajos**: | Base | Prefijo | Ejemplo | Cuánto vale | |---|---|---|---| | Decimal | (ninguno) | `89` | 89 | | Hexadecimal | `0x` | `0x59` | 89 | | Octal | `0` | `0131` | 89 | | Binario | `0b` | `0b01011001` | 89 | El **hexadecimal** es el atajo estrella porque **cada dígito hex son exactamente 4 bits** (un *nibble*, del inglés *nibble*, "mordisco"). Entonces un byte entra justo en **dos dígitos hex**. Pasás de binario a hex **de a cuatro bits**, sin cuentas: ```text 0101 1001 ← el byte partido en dos nibbles 5 9 ← cada nibble, su dígito hex = 0x59 ``` > **Momento ajá.** Los dígitos hex van del `0` al `9` y después de la `A` a la `F` (`A=10`, `B=11`, > … `F=15`). ¿Por qué letras? Porque con un solo símbolo hay que representar **16** valores y solo > tenemos **10** dígitos. Así que se usan seis letras. Nada más. > **El prefijo binario `0b` es de C23.** Se estandarizó recién ahí (y con él, el separador `'` para > agrupar dígitos: `0b0101'1001`). En compiladores viejos, `0b` era una **extensión** de GCC/Clang > que quizá no estaba activada. Si te sale un error raro con `0b`, revisá el estándar: compilá con > `-std=c23` (o usá `0x`). ## 1.4 El byte también guarda negativos: complemento a dos Un byte sin signo (`unsigned char`) va de `0` a `255`. Pero un byte **con signo** (`signed char`, y `char` en la mayoría de las plataformas) va de **`-128` a `127`**. ¿Cómo entra un número negativo en una caja de bits que no tiene signo? C dijo "el bit **más alto** decide": si el bit 7 vale **1**, el número es **negativo**, y su valor se lee con la regla del **complemento a dos**: para saber cuánto vale, **invertís todos los bits y sumás 1**. ```text 0b11111111 → invertir: 00000000 → +1 → -1 0b11111110 → invertir: 00000001 → +1 → -2 0b10000000 → invertir: 01111111 → +1 → -128 (el más negativo) ``` Esa es la razón de que el rango sea asimétrico: el `0` ocupa una sola combinación (`00000000`), así que del lado negativo sobra un lugar: hay un `-128` pero no un `+128`. > **Ojo con esto.** El **mismo byte** `0xFF` es **255** si lo tratás como `unsigned char` y **-1** si > lo tratás como `signed char`. Los bits no cambian: cambia **cómo los interpretás**. Por eso importa > tanto el **tipo** (capítulo `16`): es el que le dice a C cómo leer esos bits. ## 1.5 Extensión de signo: el detalle que muerde Cuando un número "chico" se convierte en uno "grande", C tiene que rellenar los bits nuevos. Y ahí se comporta distinto según el signo: - **Sin signo**: rellena con **0**. `(unsigned)0x9` → `0x00000009`. - **Con signo**: **copia el bit de signo** (extensión de signo). `(int)-1` → `0xFFFFFFFF`. ```c #include int main(void) { signed char sc = -1; /* 0xFF en 8 bits */ unsigned char uc = 255; /* 0xFF en 8 bits */ /* %x imprime en hexadecimal (sin signo). Mirá los ceros de relleno: sc (con signo) propaga el 1 del signo; uc (sin signo) rellena con 0. */ printf("sc = %08x\n", (unsigned)sc); /* ffffffff */ printf("uc = %08x\n", (unsigned)uc); /* 000000ff */ return 0; } ``` > **Para guardar.** Antes de operar con bits, preguntate: **¿este valor es un número o un montón de > bits?** Si es un montón de bits (una máscara, un byte de imagen), usá **tipos sin signo** > (`uint8_t`, `uint32_t`). Evitás sorpresas como las de arriba. La regla corta: **bits se manipulan > en `unsigned`.** ## Práctica de la Parte 1 ### Ejercicio 17.1 — Radiografía de un byte **Conceptos** · bits, valor posicional, `uint8_t`, `printf` (`%x`, `%b` o `%u`) **Contexto** · Para entender los bits hay que verlos. Simplificación: mostrás un byte en decimal, hexadecimal y binario, y desglosás qué bits están encendidos. **Consigna** · Pedí (o fijá) un valor de `0` a `255` en un `uint8_t`. Imprimí: 1. el valor en **decimal**; 2. el mismo valor en **hexadecimal** (`%x`), con dos dígitos; 3. el mismo valor en **binario**. Podés usar el `%b` de C23 o, si compilás con un estándar anterior, construir el texto bit a bit con un bucle; 4. la lista de **posiciones** de los bits que están en `1` (de mayor a menor o al revés, a tu gusto). **Entrada** · Un valor de `0` a `255`. **Salida** · El valor en las tres bases y la lista de bits encendidos. **Escenario de prueba** ```text valor = 89 decimal : 89 hexadecimal : 0x59 binario : 01011001 bits en 1 : 6, 4, 3, 0 (64 + 16 + 8 + 1 = 89) ``` ```text valor = 255 decimal : 255 hexadecimal : 0xff binario : 11111111 bits en 1 : 7, 6, 5, 4, 3, 2, 1, 0 ``` **Identificadores sugeridos** · `value`, `mask`, `bitIndex`, `printBinary` **Criterios de aceptación** - [ ] El valor se guarda en un `uint8_t` (sin signo). - [ ] Las tres bases coinciden para los dos escenarios. - [ ] La lista de bits encendidos suma exactamente el valor original. - [ ] El binario se muestra con **ocho** dígitos (con ceros a la izquierda). --- ### Ejercicio 17.2 — ¿Cuánto vale ese bit de signo? **Conceptos** · complemento a dos, `int8_t` vs `uint8_t`, `0xFF`, interpretación según el tipo **Contexto** · El mismo byte significa dos cosas distintas según lo leas con o sin signo, y eso confunde a cualquiera la primera vez. Simplificación: mostrás las dos interpretaciones de los mismos bits. **Consigna** · Tomá el byte `0xFF` y guardalo en un `uint8_t` y en un `int8_t`. Imprimí **ambos** valores y el **mismo** byte en hex. Después explicá en un comentario **por qué** el mismo patrón de bits da dos números distintos. **Entrada** · Ninguna (el byte es fijo, `0xFF`). **Salida** · Los dos valores y el hex. **Escenario de prueba** ```text bits : 11111111 (0xff) uint8_t : 255 int8_t : -1 explicación: el tipo decide si el bit 7 es "128" (sin signo) o el signo (complemento a dos, con signo). ``` **Identificadores sugeridos** · `rawByte`, `asUnsigned`, `asSigned` **Criterios de aceptación** - [ ] El mismo patrón de bits se imprime igual en hex. - [ ] Sin signo da `255`; con signo da `-1`. - [ ] La explicación menciona el bit de signo y el complemento a dos. - [ ] No se cambian los bits: solo la **interpretación**. --- # PARTE 2 — Los operadores de bits: `&`, `|`, `^` y `~` Acá está el corazón del capítulo. Son **cuatro** operadores (más dos de desplazamiento, que vienen en la Parte 4), y se aplican **bit por bit**, columna por columna. Se parecen a los lógicos, pero **no** son los mismos: `&` no es `&&`, y `|` no es `||`. > **No los confundas.** > - `&` (un solo ampersand) = AND **de bits**, opera sobre cada bit. > - `&&` (dos) = AND **lógico**, devuelve `0` o `1` según si ambos son "verdaderos". > - `|` (una barra) = OR **de bits**. `||` (dos) = OR **lógico**. > La diferencia es la misma que entre *"multiplicá cada dígito"* y *"¿es el número distinto de cero?"*. ## 2.1 `&` — AND: "solo si los dos están encendidos" El AND bit a bit compara **dos bits** y devuelve `1` **solo si ambos son `1`**. Si alguno es `0`, el resultado es `0`. Pensalo como **dos amigos que tienen que estar de acuerdo**: si uno dice que no, no hay trato. | `a` | `b` | `a & b` | |---|---|---| | 0 | 0 | 0 | | 0 | 1 | 0 | | 1 | 0 | 0 | | 1 | 1 | **1** | ```c #include int main(void) { uint8_t a = 0b11001100; /* 0xCC */ uint8_t b = 0b10101010; /* 0xAA */ /* & compara cada bit con su vecino de la otra fila. Solo queda en 1 donde AMBAS tienen 1. */ uint8_t r = a & b; /* 0b10001000 = 0x88 */ printf("a = %08b (0x%02x)\n", a, a); /* 11001100 (0xcc) */ printf("b = %08b (0x%02x)\n", b, b); /* 10101010 (0xaa) */ printf("a & b = %08b (0x%02x)\n", r, r); /* 10001000 (0x88) */ return 0; } ``` ## 2.2 `|` — OR: "si alguno está encendido" El OR bit a bit devuelve `1` si **al menos uno** de los dos bits es `1`. Solo da `0` cuando **los dos** son `0`. Es el operador de **encender**: sirve para "prender" bits concretos. | `a` | `b` | `a \| b` | |---|---|---| | 0 | 0 | 0 | | 0 | 1 | **1** | | 1 | 0 | **1** | | 1 | 1 | **1** | ```c uint8_t a = 0b11001100; uint8_t b = 0b10101010; uint8_t r = a | b; /* 0b11101110 = 0xEE */ ``` ## 2.3 `^` — XOR: "encendido solo si son DISTINTOS" El XOR (OR exclusivo) devuelve `1` **solo si los dos bits son distintos**. Si son iguales, da `0`. Es un **detector de diferencias** y, además, el único operador que **invierte** un bit cuando le aplicás un `1` (por eso es el operador de *toggle*, ver Parte 3). | `a` | `b` | `a ^ b` | |---|---|---| | 0 | 0 | 0 | | 0 | 1 | **1** | | 1 | 0 | **1** | | 1 | 1 | 0 | ```c uint8_t a = 0b11001100; uint8_t b = 0b10101010; uint8_t r = a ^ b; /* 0b01100110 = 0x66 */ ``` > **Dato de color.** El XOR tiene una propiedad mágica que lo hace famoso: **aplicarlo dos veces > devuelve el original** (`x ^ k ^ k == x`). Por eso se usa en cifrados simples de juguete y en el > truco de "intercambiar dos variables sin variable temporal". Es lindo, pero **`^` con la misma > variable dos veces da 0**: no te olvides de eso. ## 2.4 `~` — NOT: "invertí todo" El complemento (`~`) es el único que trabaja con **un** operando: agarra cada bit y lo **da vuelta** (`0`→`1`, `1`→`0`). Es el operador de la **negación**, y es la pieza clave para **apagar** bits (Parte 3). | `a` | `~a` | |---|---| | 0 | 1 | | 1 | 0 | ```c #include int main(void) { uint8_t a = 0b11001100; /* ~ invierte cada bit. Ojo: se evalúa promoviendo a int, por eso conviene asignar a un uint8_t para "recortar" a 8 bits. */ uint8_t r = (uint8_t)~a; /* 0b00110011 = 0x33 */ printf("a = %08b (0x%02x)\n", a, a); /* 11001100 (0xcc) */ printf("~a = %08b (0x%02x)\n", r, r); /* 00110011 (0x33) */ return 0; } ``` > **La identidad que no se olvida.** Con números enteros, `~x == -x - 1`. Por eso `~0` es `-1` > (`0xFFFFFFFF`) y `~5` es `-6`. No es una regla aparte: es el complemento a dos otra vez. ## 2.5 La tabla de un solo vistazo | Operador | Nombre | Hace… | Regla corta | Uso típico | |---|---|---|---|---| | `&` | AND | deja pasar si ambos son 1 | **filtrar / testear** | "¿está encendido el bit?" | | `\|` | OR | enciende si alguno es 1 | **prender** | "encendé estos bits" | | `^` | XOR | enciende si son distintos | **invertir / comparar** | "cambiá este bit" | | `~` | NOT | da vuelta todo | **negar** | "apagá este bit" (`& ~`) | | `<<` | shift izq | corre a la izquierda | **×2ᵏ** | "movete k lugares" | | `>>` | shift der | corre a la derecha | **÷2ᵏ** | "movete k lugares" | ## Práctica de la Parte 2 ### Ejercicio 17.3 — La calculadora de bits **Conceptos** · `&`, `|`, `^`, operaciones bit a bit, entrada de dos bytes **Contexto** · Antes de usar máscaras hay que ver los operadores "en crudo". Simplificación: una calculadora que combina dos bytes con cada operador. **Consigna** · Leé **dos** valores de `0` a `255` (por ejemplo con `strtoul` en base 0, para aceptar `0x` y `0b`, o con `scanf`). Para ese par, mostrá el resultado de `&`, `|`, `^` y `~` **de cada uno**, en decimal, hex y binario. Mostrá también **la tabla de verdad** (los cuatro casos) para cada operador, como comprobación. **Entrada** · Dos bytes (adminsitrá base `0x`, `0b` y decimal). **Salida** · Una fila por operación con los tres formatos. **Escenario de prueba** ```text a = 0xCC b = 0xAA a & b = 0x88 (10001000) a | b = 0xEE (11101110) a ^ b = 0x66 (01100110) ~a = 0x33 (00110011) ~b = 0x55 (01010101) ``` **Identificadores sugeridos** · `left`, `right`, `andResult`, `orResult`, `xorResult` **Criterios de aceptación** - [ ] Se manejan valores **sin signo**. - [ ] Cada resultado se imprime en decimal, hex y binario. - [ ] La tabla de verdad de cada operador se muestra y coincide con la teoría. - [ ] Los resultados del escenario salen exactamente como se espera. --- ### Ejercicio 17.4 — El mapa de diferencias **Conceptos** · `^` como detector de diferencias, contar bits distintos **Contexto** · Comparar dos versiones de un mismo byte (por ejemplo, dos fotos de un estado) y saber **qué bits cambiaron** es un caso real de XOR. Simplificación: contás y ubicás las diferencias entre dos bytes. **Consigna** · Leé dos bytes. Usando `^`, mostrá: 1. una **máscara** con `1` en cada posición donde los dos bytes **difieren**; 2. **cuántos** bits difieren (la cantidad de `1` de esa máscara); 3. la lista de las **posiciones** que difieren. **Entrada** · Dos bytes. **Salida** · La máscara, el conteo y las posiciones. **Escenario de prueba** ```text a = 0b11001100 b = 0b11000011 a ^ b = 0b00001111 (0x0f) bits distintos : 4 posiciones : 0, 1, 2, 3 ``` **Identificadores sugeridos** · `a`, `b`, `diffMask`, `diffCount` **Criterios de aceptación** - [ ] La máscara de diferencias sale de **un** `^` entre los dos bytes. - [ ] El conteo coincide con el número de posiciones listadas. - [ ] Dos bytes iguales dan máscara `0` y conteo `0`. - [ ] Se usa un tipo sin signo para los bytes. --- # PARTE 3 — Máscaras: un byte, ocho banderas Esta es **la** parte que pediste, y es la aplicación más común de todo lo anterior. Una **máscara** es un valor con **un `1` en el/los bits que te interesan** y `0` en el resto. Es como un **calco** (*stencil*, la plantilla con agujeros): la ponés encima y solo pasa la luz por los agujeros. ## 3.1 La idea: ocho fuentes de luz, un solo interruptor general Imaginate una sala con **ocho lámparas** y **un tablero con ocho interruptores**. No querés ocho cajas separadas: querés **una** caja de 8 bits donde cada bit sea el estado de una lámpara. Eso es exactamente un byte usado como **conjunto de banderas** (*flags*). ```text bit: 7 6 5 4 3 2 1 0 ┌──────┬──────┬──────┬──────┬──────┬──────┬──────┬──────┐ flags │música│ wifi │avión │ linterna│ dato │ dato │ dato │ dato │ └──────┴──────┴──────┴──────┴──────┴──────┴──────┴──────┘ 128 64 32 16 8 4 2 1 ``` Con **un byte**, ocho banderas booleanas. Con un `uint32_t` (**DWORD**), **32**. Con un `uint64_t` (**QWORD**), **64**. La técnica es siempre la misma; cambia el ancho de la caja. ## 3.2 El truco del `1 << n`: fabricar una máscara Para una bandera en la posición `n`, la máscara es un `1` corrido `n` lugares a la izquierda. Eso se escribe `1u << n` (el `u` la hace `unsigned`, para no meter signo en la fiesta). ```text n = 0 → 1u << 0 = 0b00000001 (0x01) n = 3 → 1u << 3 = 0b00001000 (0x08) n = 7 → 1u << 7 = 0b10000000 (0x80) ``` Y como cada bandera tiene su nombre, lo natural es **definir máscaras con nombre** en vez de repetir el `1u << n` por todo el código: ```c #include /* Cada bandera es un bit propio. Los nombres dicen QUÉ son, no cuánto valen. */ enum DeviceFlags { FLAG_ON = 1u << 0, /* 0x01 bit 0 */ FLAG_CONNECTED = 1u << 1, /* 0x02 bit 1 */ FLAG_ERROR = 1u << 2, /* 0x04 bit 2 */ FLAG_UPDATE_READY = 1u << 3, /* 0x08 bit 3 */ FLAG_LOW_BATTERY = 1u << 4, /* 0x10 bit 4 */ FLAG_MUTED = 1u << 5, /* 0x20 bit 5 */ FLAG_CHARGING = 1u << 6, /* 0x40 bit 6 */ FLAG_SAFE_MODE = 1u << 7 /* 0x80 bit 7 */ }; ``` > **Por qué esto es mejor que ocho `bool`.** Ocho `bool` ocupan **8 bytes** (cada uno, aunque valga > un bit, ocupa un byte entero). Las ocho banderas en **un `uint8_t` ocupan 1 byte**. Cuando eso se > repite en un millón de registros, la diferencia deja de ser un detalle. Es el mismo motivo del > capítulo `16` (sección 16.9): tipos chicos ahorran **cuando hay muchos**. ## 3.3 Las cuatro operaciones sobre una bandera Con las cuatro herramientas de la Parte 2, hacés todo lo que necesitás con una bandera: **testear**, **prender**, **apagar** e **invertir**. Esta es la tabla que vas a usar el resto de tu vida. | Quiero… | Escribo… | Operador | Por qué | |---|---|---|---| | **Testear** si está encendida | `flags & MASK` | `&` | solo queda `MASK` si el bit estaba en 1 | | **Prender** (poner en 1) | `flags \| MASK` | `\|` | fuerza ese bit a 1, deja el resto igual | | **Apagar** (poner en 0) | `flags & ~MASK` | `& ~` | el `~MASK` es "todos menos ese bit" | | **Invertir** (toggle) | `flags ^ MASK` | `^` | cambia 1↔0 solo en ese bit | Mirá cómo queda cada una en código: ```c #include #include enum { FLAG_ERROR = 1u << 2 }; /* 0x04 */ int main(void) { uint8_t flags = 0; /* PRENDER: OR con la máscara fuerza el bit 2 a 1. */ flags |= FLAG_ERROR; /* flags = 0b00000100 */ printf("prendida : %08b\n", flags); /* TESTEAR: AND con la máscara. Si el resultado NO es 0, el bit estaba encendido. */ if (flags & FLAG_ERROR) { /* el & da 0x04, distinto de 0 → verdadero */ printf("la bandera esta encendida\n"); } /* INVERTIR: XOR con la máscara da vuelta solo ese bit. */ flags ^= FLAG_ERROR; /* flags = 0b00000000 */ printf("invertida: %08b\n", flags); /* VOLVER A PRENDER y luego APAGAR: AND con el complemento del bit. */ flags |= FLAG_ERROR; /* 0b00000100 */ flags &= ~FLAG_ERROR; /* ~0x04 = 0xFB → apaga el bit 2 */ printf("apagada : %08b\n", flags); return 0; } ``` > **La trampa N.º 1: testear con `==`.** No escribas `if (flags == FLAG_ERROR)`: eso pregunta si > **todas** las banderas valen exactamente eso, no si **una** está encendida. Se testea con `&`, y la > condición es "el resultado es distinto de cero". La forma idiomática es > `if (flags & FLAG_ERROR)`, no `if ((flags & FLAG_ERROR) == FLAG_ERROR)` (aunque también funciona). > **La trampa N.º 2: el `~` y el ancho.** `~FLAG_ERROR` sobre un `int` de 32 bits da > `0xFFFFFFFB`, no `0xFB`. Al hacer `flags &= ~FLAG_ERROR` con `flags` de 8 bits, los bits de arriba > del `~` se recortan al asignar, así que **funciona igual**. Pero si operás con tipos de distinto > ancho en un mismo cálculo, tenelo presente. ## 3.4 Varias banderas de una: preguntar por un grupo Un `&` te deja preguntar por **varias** banderas juntas. Combinando máscaras con `|` armás "grupos": ```c /* ¿Están las DOS banderas encendidas a la vez? (AND con la suma de máscaras == la suma) */ if ((flags & (FLAG_ERROR | FLAG_LOW_BATTERY)) == (FLAG_ERROR | FLAG_LOW_BATTERY)) { /* ambas encendidas */ } /* ¿Está encendida ALGUNA de las dos? (el AND no es 0) */ if (flags & (FLAG_ERROR | FLAG_LOW_BATTERY)) { /* al menos una encendida */ } ``` > **Para guardar.** `flags & GRUPO` = "¿alguna de estas?" (intersección no vacía). > `(flags & GRUPO) == GRUPO` = "¿todas estas?" (todas presentes). Son dos preguntas **distintas**, y > confundirlas es de los bugs más comunes. ## Práctica de la Parte 3 ### Ejercicio 17.5 — El panel de control **Conceptos** · máscaras, `1u << n`, `&`, `|`, `~`, `^`, banderas **Contexto** · Todo panel de control (un router, un reproductor, una consola de juegos) mantiene su estado en un conjunto de banderas. Simplificación: un panel de 8 banderas en un `uint8_t`. **Consigna** · Definí **ocho** banderas con nombre, una por bit, para un panel de control (por ejemplo: `POWER`, `RECORDING`, `MUTED`, `NETWORK`, `ERROR`, `PAUSED`, `OVERHEATED`, `BATTERY_LOW`). Implementá **cinco** funciones que reciban el estado (`uint8_t`) y: 1. **prender** una bandera dada; 2. **apagar** una bandera dada; 3. **invertir** una bandera dada; 4. **testear** una bandera dada (devolver un `bool`); 5. **contar** cuántas banderas están encendidas. Demostralas encendiendo unas cuantas, invirtiendo una, apagando otra y mostrando el estado en binario después de cada paso. **Entrada** · Una secuencia de operaciones (o fija, en el `main`). **Salida** · El estado del panel en binario después de cada operación, y el conteo final. **Escenario de prueba** ```text inicio : 00000000 (0 encendidas) prender RECORDING : 00000100 (1 encendida) prender NETWORK : 00010100 (2 encendidas) invertir MUTED : 00110100 (3 encendidas) apagar RECORDING : 00110000 (2 encendidas) total encendidas : 2 ``` **Identificadores sugeridos** · `PanelState`, `setFlag`, `clearFlag`, `toggleFlag`, `isFlagSet`, `countFlags` **Criterios de aceptación** - [ ] Las ocho banderas se definen con `1u << n` y con nombres significativos. - [ ] Prender usa `|`; apagar usa `& ~`; invertir usa `^`; testear usa `&`. - [ ] El conteo recorre los bits (no cuenta a mano). - [ ] El escenario sale igual, y dos banderas nunca comparten el mismo bit. --- ### Ejercicio 17.6 — Permisos al estilo Unix **Conceptos** · máscaras, grupos de banderas, `&` con grupo, `==` con grupo **Contexto** · Los permisos de archivo de Unix (`rwx` para dueño, grupo y otros) se guardan en bits justamente para operarlos con máscaras. Simplificación: modelás **solo** los permisos de **un** grupo sobre un `uint8_t` (aunque los reales son 9 bits en total). **Consigna** · Definí tres banderas: `READ` (leer), `WRITE` (escribir), `EXEC` (ejecutar). Implementá funciones que digan: 1. si se puede **leer** (`READ`); 2. si se puede **escribir** (`WRITE`); 3. si se puede **ejecutar** (`EXEC`); 4. si **todos** los permisos están dados (testear con grupo y `==`); 5. si **alguno** de leer/escribir está dado (testear con grupo). Mostrá el resultado para varios conjuntos de permisos. **Entrada** · Varios valores de permisos (por ejemplo `0b111`, `0b100`, `0b010`, `0b000`). **Salida** · El veredicto de cada función para cada conjunto. **Escenario de prueba** ```text permisos = rwx (111): leer=sí escribir=sí ejecutar=sí todos=sí leer-o-escribir=sí permisos = r-- (100): leer=sí escribir=no ejecutar=no todos=no leer-o-escribir=sí permisos = -w- (010): leer=no escribir=sí ejecutar=no todos=no leer-o-escribir=sí permisos = --- (000): leer=no escribir=no ejecutar=no todos=no leer-o-escribir=no ``` **Identificadores sugeridos** · `Permissions`, `canRead`, `canWrite`, `canExec`, `canAll`, `canReadOrWrite` **Criterios de aceptación** - [ ] "Todos" usa `(p & GRUPO) == GRUPO`; "alguno" usa `(p & GRUPO) != 0`. - [ ] Cada permiso es un bit distinto. - [ ] Los cuatro escenarios dan exactamente lo esperado. - [ ] El código distingue claramente "todos" de "alguno". --- ### Ejercicio 17.7 — El registro de opciones de arranque **Conceptos** · banderas de 32 bits (`uint32_t`), máscaras altas, `1u << n` con `n` grande **Contexto** · Los programas serios reciben muchas opciones (verbose, debug, dry-run, sin color, …) y las guardan como banderas. Con 8 bits te quedás corto enseguida, así que se usa un `uint32_t`. Simplificación: **treinta y dos** opciones, aunque solo uses algunas. **Consigna** · Definí al menos **diez** banderas en un `uint32_t`, incluyendo algunas en **bits altos** (por ejemplo el bit 31). Implementá una función que reciba el registro y **liste por nombre** cada opción que esté encendida. Comprobá que una bandera en el bit 31 **no** se pierde ni se confunde con las de abajo. **Entrada** · Un registro con varias opciones (incluida una del bit alto). **Salida** · La lista de opciones activas, por nombre. **Escenario de prueba** ```text registro = 0x80000005 (bit 31 + bit 2 + bit 0) opciones activas: - VERBOSE (bit 0) - DRY_RUN (bit 2) - EXPERIMENTAL (bit 31) ``` **Identificadores sugeridos** · `StartupOptions`, `optionMask`, `listActiveOptions` **Criterios de aceptación** - [ ] El registro es `uint32_t` (sin signo, para que el bit 31 no sea "signo"). - [ ] La bandera del bit 31 se define como `1u << 31` (o `0x80000000u`). - [ ] El bit 31 se detecta correctamente (no aparece como negativo). - [ ] Las banderas altas y bajas conviven sin pisarse. --- # PARTE 4 — Desplazamientos: `<<` y `>>` Los otros dos operadores. Mueven **toda la fila de bits** a la izquierda o a la derecha. Es como correr un tren: los vagones que salen por un extremo se pierden, y por el otro extremo entran vagones nuevos (que son **ceros**). ## 4.1 `<<` y `>>`: correr los bits - `x << k`: cada bit se corre **k lugares a la izquierda**. Los bits que se salen por la izquierda se **pierden**, y por la derecha entran **ceros**. - `x >> k`: cada bit se corre **k lugares a la derecha**. Los bits que se salen por la derecha se **pierden**, y por la izquierda entran... depende del signo (ver 4.4). ```text 0b00001111 << 2 = 0b00111100 (se corrió a la izquierda; entran dos ceros) └ izquierda derecha ┘ 0b11000000 >> 3 = 0b00011000 (se corrió a la derecha; entran tres ceros) ``` ```c #include int main(void) { uint8_t x = 0b00001111; /* 0x0F */ printf("%08b << 1 = %08b\n", x, (uint8_t)(x << 1)); /* 00011110 */ printf("%08b << 4 = %08b\n", x, (uint8_t)(x << 4)); /* 11110000 */ printf("%08b >> 2 = %08b\n", x, (uint8_t)(x >> 2)); /* 00000011 */ return 0; } ``` > **El `<<` también es la fábrica de máscaras.** Todas las máscaras de la Parte 3 (`1u << n`) son un > desplazamiento. Por eso el `<<` aparece en todos lados: es tanto "mover" como "fabricar". ## 4.2 Multiplicar por 2, 4, 8… con `<<` Correr **un** bit a la izquierda **duplica el valor**. Correr **k** lugares lo multiplica por `2ᵏ`. No es magia: es que cada bit vale el **doble** que el de su derecha. ```text 0b00000101 = 5 5 << 1 = 0b00001010 = 10 (×2) 5 << 2 = 0b00010100 = 20 (×4) 5 << 3 = 0b00101000 = 40 (×8) ``` ## 4.3 Dividir (hacia abajo) con `>>` Igual pero al revés: `x >> k` **divide** entre `2ᵏ`, **truncando** hacia el entero de abajo (para valores sin signo). ```text 0b00101000 = 40 40 >> 1 = 0b00010100 = 20 (÷2) 40 >> 2 = 0b00001010 = 10 (÷4) 40 >> 3 = 0b00000101 = 5 (÷8) ``` ## 4.4 Las cuatro trampas del desplazamiento Acá es donde el `<<` y el `>>` dejan de ser "gratis". Leé esta lista dos veces. **Trampa 1 — corrimiento por el ancho o más es indefinido.** Correr `k >= ancho` (por ejemplo `x << 32` sobre un `uint32_t`) es **comportamiento indefinido**: el estándar no dice qué pasa y cada CPU puede dar algo distinto. Corré **siempre** con `0 <= k < ancho`. **Trampa 2 — `>>` de un valor con signo es dependiente de la implementación.** Para valores **sin signo**, `>>` rellena con ceros (es "lógico"). Para valores **con signo negativos**, el estándar deja que el compilador elija: la mayoría copia el bit de signo (corrimiento **aritmético**, `-8 >> 1` da `-4`), pero **no está garantizado**. Regla: desplazá a la derecha **valores sin signo**. **Trampa 3 — multiplicar puede desbordar silenciosamente.** `5 << 30` sobre un `uint32_t` no da `5 × 2³⁰`: los bits que se salen se pierden. El desplazamiento **no avisa** del desborde. **Trampa 4 — `<<` de un valor con signo que desborda es indefinido.** Mover a la izquierda un `signed` de forma que el resultado no entre en el tipo es UB. Por eso, otra vez: **bits se manipulan en `unsigned`**. > **¿Entonces el truco de ×2 es inútil?** No. Es **rapidísimo** y lo usan a fondo los compiladores. > Vos, además, lo vas a necesitar para leer y escribir **campos** en formatos binarios (Parte 5), > donde el desplazamiento no es una optimización sino la forma correcta de pararse sobre los bits. ## Práctica de la Parte 4 ### Ejercicio 17.8 — El multiplicador sin `*` **Conceptos** · `<<`, `>>`, multiplicación y división por potencias de dos **Contexto** · Los procesadores antiguos (y algunos chips chicos de hoy) no tenían multiplicador de hardware: multiplicaban a fuerza de sumar y correr bits. Simplificación: multiplicás y dividís **solo** con desplazamientos. **Consigna** · Dado un `uint32_t` `x` y un exponente `k` (de `0` a `30`), calculá `x * 2ᵏ` y `x / 2ᵏ` **usando únicamente** `<<` y `>>` (nada de `*`, `/` ni `pow`). Mostrá el resultado en decimal y binario. Comprobá con `k = 0` (no cambia nada) y con un `k` que haga perder bits. **Entrada** · Un valor `x` y un exponente `k`. **Salida** · `x * 2ᵏ` y `x / 2ᵏ`, en decimal y binario. **Escenario de prueba** ```text x = 5, k = 3 x * 2^3 = 40 (0b00000000000000000000000000101000) x / 2^3 = 0 (5 >> 3, trunca a 0) x = 40, k = 2 x * 2^2 = 160 x / 2^2 = 10 ``` > **Ojo:** `5 / 8` "de verdad" es `0.625`, pero con enteros y `>>` da **0**. La división con `>>` > **trunca**. **Identificadores sugeridos** · `value`, `shift`, `multiplyByShift`, `divideByShift` **Criterios de aceptación** - [ ] No se usa `*`, `/` ni `pow` en ninguna parte. - [ ] `k = 0` devuelve el mismo valor. - [ ] La división trunca hacia abajo (como `/` entre enteros). - [ ] Se usa `uint32_t` (sin signo) para que `>>` sea confiable. --- ### Ejercicio 17.9 — Empaquetar dos valores en uno **Conceptos** · `<<`, `|`, `&`, máscaras, empaquetado **Contexto** · A veces dos datos chicos tienen que viajar juntos dentro de uno más grande (por ejemplo, dos coordenadas de 16 bits en un solo `uint32_t`). Simplificación: empaquetás y desempaquetás dos valores. **Consigna** · Tomá dos `uint16_t` (`high` y `low`) y **empaquetálos** en **un** `uint32_t` con el `high` en los 16 bits de arriba y el `low` en los 16 de abajo, usando `<<` y `|`. Después **desempaquetálos** de vuelta con `>>` y `&`, y comprobá que recuperás los valores originales. **Entrada** · Dos valores de 16 bits. **Salida** · El paquete de 32 bits (en hex y binario) y los dos valores recuperados. **Escenario de prueba** ```text high = 0xABCD low = 0x1234 paquete = 0xABCD1234 recuperado high = 0xABCD recuperado low = 0x1234 ``` **Identificadores sugeridos** · `pack`, `unpackHigh`, `unpackLow`, `packed` **Criterios de aceptación** - [ ] El paquete se arma con `<<` y `|` (sin sumas ni multiplicaciones). - [ ] `high` se extrae con `>>`; `low`, con una máscara de 16 bits. - [ ] Los valores recuperados coinciden **exactamente** con los originales. - [ ] Se comprueba que `high` y `low` no se pisaron. --- # PARTE 5 — `BYTE`, `WORD`, `DWORD`, `QWORD` y los paquetes de bits Ya viste que una caja puede tener 8, 32 o 64 bits. En el mundo de Windows (y en el de los archivos binarios, como el BMP del capítulo `15`) esas cajas tienen **nombres clásicos**. Conocerlos te permite leer APIs y formatos de archivo sin traducir mentalmente todo el tiempo. ## 5.1 Los nombres de los anchos | Nombre | Bits | Bytes | Equivalente portable (``) | De dónde viene | |---|---|---:|---|---| | **BYTE** | 8 | 1 | `uint8_t` | `` | | **WORD** | 16 | 2 | `uint16_t` | `` | | **DWORD** | 32 | 4 | `uint32_t` | `` (*double word*) | | **QWORD** | 64 | 8 | `uint64_t` | convención (*quad word*) | > **¿Qué significa "word"?** Originalmente, la **palabra** (*word*) era el tamaño con el que la CPU > trabajaba de una: en las CPUs de 16 bits, una *word* eran 16 bits. De ahí el nombre. Con el tiempo > las CPUs crecieron, y aparecieron *double word* (32) y *quad word* (64). Hoy "word" es un nombre > **histórico**: por eso `DWORD` son 32 bits aunque tu máquina sea de 64. > **La versión portable existe y es mejor.** Si no estás en Windows, o querés código que compile en > cualquier lado, usá `uint8_t` / `uint16_t` / `uint32_t` / `uint64_t` del capítulo `16`. `DWORD` es > un `typedef` de 32 bits **atado a ``**. El **ancho** es lo importante; el nombre es > cosmético. ## 5.2 Un `DWORD` no es "un número": es una caja de 32 bits La clave de esta parte es cambiar el chip. Un `DWORD` (o un `uint32_t`) puede ser **dos cosas**: - **un número** (por ejemplo, una suma, un contador), o - **una caja de 32 bits** donde cada tramo guarda un dato distinto. Cuando lo usás como caja, el **truco de máscaras y desplazamientos** es cómo entrás y salís de cada tramo. Es lo mismo que hiciste en la Parte 4 con el paquete de 16+16, pero con más tabiques. ## 5.3 Extraer campos: el patrón `(x >> shift) & mask` Para leer un campo de `w` bits que empieza en la posición `s`, el patrón es siempre el mismo: ```text campos = (packed >> s) & ((1u << w) - 1) ``` - `>> s` **trae** el campo hasta la posición 0. - `(1u << w) - 1` es una **máscara de `w` unos** (por ejemplo, `w = 5` → `0b11111`), que **borra** todo lo que no sea el campo. Ejemplo con los bytes de un color `0x00RRGGBB` (como en un BMP de 24 bits, capítulo `15`): ```c #include #include int main(void) { uint32_t color = 0x00336699; /* 00 RR GG BB */ /* Cada canal son 8 bits. Extraigo con >> y recorto con & 0xFF. */ uint8_t r = (color >> 16) & 0xFF; /* 0x33 */ uint8_t g = (color >> 8) & 0xFF; /* 0x66 */ uint8_t b = (color ) & 0xFF; /* 0x99 */ printf("R=%02x G=%02x B=%02x\n", r, g, b); /* R=33 G=66 B=99 */ /* Y al revés: armar el color desde los canales (el | combina, el << posiciona). */ uint32_t rebuilt = ((uint32_t)r << 16) | ((uint32_t)g << 8) | b; printf("rebuilt = 0x%06x\n", rebuilt); /* 0x336699 */ return 0; } ``` > **Pensalo como un edificio.** El `>>` es el **ascensor** que baja el campo hasta la planta baja; la > máscara `& 0xFF` es el **portero** que deja pasar solo a los ocho bits de ese campo y echa al resto. > Ese par —**bajar y filtrar**— es el 90% de leer formatos binarios. ## 5.4 Empaquetar campos: el patrón `| (v << s)` Armar el valor es el espejo: se **sube** cada campo con `<<` a su posición y se **combina** todo con `|`. Es exactamente lo que ya viste en 5.3, y lo que hace la función que arma el color. > **Un detalle que salva horas.** Cuando un campo tiene **1 bit**, es una bandera (Parte 3). Cuando > tiene **varios bits**, es un **número** dentro de la caja. Antes de escribir una máscara, preguntate > cuántos bits tiene el campo: un campo de 3 bits (por ejemplo, un nivel de `0` a `7`) necesita > máscara `0b111`, no `0b1`. ## Práctica de la Parte 5 ### Ejercicio 17.10 — El decodificador de registros **Conceptos** · `uint32_t` (`DWORD`), extracción de campos, `>>`, `&`, máscara de `w` bits, `|` **Contexto** · Los protocolos de red y los archivos binarios empaquetan varios campos en un solo entero (a veces de tamaño fijo, como un `DWORD`). Simplificación: un registro de 32 bits con cuatro campos de distinto ancho que tenés que leer. **Consigna** · Definí un registro de **32 bits** con esta distribución: ```text 31 24 23 16 15 12 11 0 ┌────────────────┬──────────┬──────┬───────────┐ │ versión (8) │ flags (8)│ tipo │ valor (12) │ │ │ │ (4) │ │ └────────────────┴──────────┴──────┴───────────┘ ``` Implementá: 1. una función que **arme** el registro a partir de los cuatro valores (con `<<` y `|`); 2. cuatro funciones que **extraigan** cada campo (con `>>` y máscara de `w` bits); 3. una función que, dado el registro, imprima los cuatro campos. Comprobá que armar y extraer son inversos: el valor que metiste es el que sale. **Entrada** · Cuatro valores (versión `0`–`255`, flags `0`–`255`, tipo `0`–`15`, valor `0`–`4095`). **Salida** · El registro en hex y los cuatro campos extraídos. **Escenario de prueba** ```text version = 2, flags = 0x84, type = 5, value = 2730 registro = 0x02845AAA (hex) version = 2 flags = 0x84 type = 5 value = 2730 ``` > **Cómo comprobar el hex:** `version << 24` = `0x02000000`, `flags << 16` = `0x00840000`, > `type << 12` = `0x00005000`, `value` = `0x00000AAA`. Sumados con `|`: `0x02845AAA`. **Identificadores sugeridos** · `packRecord`, `getVersion`, `getFlags`, `getType`, `getValue` **Criterios de aceptación** - [ ] Ningún campo se solapa con otro (cada uno tiene sus bits). - [ ] Las máscaras se construyen como `(1u << w) - 1` (por ejemplo, `type` con 4 bits → `0xF`). - [ ] El registro se maneja como `uint32_t` (el bit 31 no es signo). - [ ] Armar y extraer devuelven exactamente los valores de entrada. --- # PARTE 6 — Conceptos extra, de fácil a difícil Todo lo de abajo es de la misma familia: operar bits. Están **ordenados por dificultad**, para que vayas sumando herramientas. ## 6.1 (Fácil) Limpiar el bit más bajo: `x & (x - 1)` `x & (x - 1)` **apaga el bit `1` más a la derecha** que tenga `x`. Es la forma canónica de recorrer solo los bits encendidos: repetís esta operación y cada vuelta consume un bit. ```text x = 0b10110000 x - 1 = 0b10101111 x & (x-1) = 0b10100000 ← se apagó el bit 4 (el más bajo encendido) ``` ## 6.2 (Fácil) ¿Es potencia de dos? `x && !(x & (x - 1))` Un número es potencia de dos si tiene **exactamente un bit** en `1`. Combinando 6.1: `x && !(x & (x - 1))` es verdadero solo para `1, 2, 4, 8, …`. El `x &&` inicial excluye el `0`, que técnicamente apagaría un bit que no existe. ## 6.3 (Media) Aislar el bit más bajo: `x & -x` `x & -x` **deja solo** el bit `1` más a la derecha y apaga el resto. Funciona gracias al complemento a dos (Parte 1). Es la base de estructuras de datos como el *Fenwick tree*. ```text x = 0b10110100 -x = 0b01001100 (complemento a dos) x & -x = 0b00000100 ← quedó solo el bit 2 ``` ## 6.4 (Media) Contar bits encendidos: *popcount* **Popcount** es cuántos bits valen `1`. El camino a pie usa un bucle con `x &= x - 1` (6.1), que da una vuelta por cada bit encendido. Pero desde **C23** hay soporte **estándar** en ``: ```c #include /* C23 */ unsigned n = 0b10110100u; unsigned ones = stdc_count_ones(n); /* 4 */ ``` > **Antes de C23** se usaban extensiones del compilador: `__builtin_popcount(x)` (GCC/Clang). No son > estándar, pero están por todos lados. ## 6.5 (Media) Intercambiar sin variable temporal (XOR *swap*) El XOR tiene la propiedad de que `a ^ b ^ b == a`. Encadenando tres XOR se intercambian dos variables **sin** una tercera: ```text a = a ^ b; b = a ^ b; /* (a^b)^b = a */ a = a ^ b; /* (a^b)^a = b */ ``` > **Ojo.** Es un truco **didáctico**, no una buena práctica: con la variable temporal el compilador > hace lo mismo o mejor, y el XOR-swap **falla si `a` y `b` son la misma variable** (se anula a cero). > Conocelo por cultura, no lo uses en producción. ## 6.6 (Media) Rotar: correr sin perder bits Con `<<` y `>>` los bits que salen se **pierden**. **Rotar** es un desplazamiento circular: lo que sale por un lado **entra por el otro**. No hay operador estándar (los procesadores tienen `ROL`/`ROR`, no C), así que se combina un `<<` y un `>>`: ```text rotar 8 bits a la izquierda en 1: 0b10000001 → 0b00000011 (el 1 que salió por la izquierda reaparece a la derecha) ``` C23 también lo trae en ``: `stdc_rotate_left(x, n)` y `stdc_rotate_right(x, n)`. ## 6.7 (Difícil) Endianness: ¿por qué `0x1234` no siempre se guarda igual? **Endianness** (el "orden de los bytes") es cómo se **almacenan** los bytes de un número multi-byte en memoria: - **Little-endian** (x86, ARM en su modo normal): el byte **menos** significativo va **primero**. El `uint32_t 0x12345678` se guarda como los bytes `78 56 34 12`. - **Big-endian** (muchas redes, el protocolo TCP/IP): el byte **más** significativo va **primero**: `12 34 56 78`. ```c #include #include int main(void) { uint32_t n = 0x12345678; /* Miramos los bytes crudos del entero, uno por uno (unsigned char sí puede aliasar). */ unsigned char *p = (unsigned char *)&n; for (size_t i = 0; i < sizeof n; i++) { printf("%02x ", p[i]); } printf("\n"); /* little-endian: 78 56 34 12 | big-endian: 12 34 56 78 */ return 0; } ``` > **Por qué te importa.** Si leés un `DWORD` de un archivo o de la red en una máquina del signo > opuesto, los **bytes vienen al revés**. Por eso existen `htonl`/`ntohl` (host-to-network) y por eso > los formatos de archivo **fijan** su endianness en la especificación (el BMP del capítulo `15` es > little-endian). Los bits **dentro** de un byte, en cambio, no cambian: el desorden es **entre > bytes**. ## 6.8 (Difícil) `bitfields`: dejar que C haga las máscaras por vos En vez de escribir `>>` y `&` a mano, C tiene una forma declarativa de empaquetar campos dentro de un `struct`: los **campos de bits** (*bit-fields*). Después de los `:` va cuántos bits tiene el campo. ```c #include /* Un struct de 1 byte (según el compilador) repartido en 3 campos. */ struct Color5 { unsigned r : 5; /* 5 bits: 0..31 */ unsigned g : 6; /* 6 bits: 0..63 */ unsigned b : 5; /* 5 bits: 0..31 */ }; int main(void) { struct Color5 c = { 12, 40, 20 }; printf("r=%u g=%u b=%u\n", c.r, c.g, c.b); printf("sizeof = %zu\n", sizeof c); /* típicamente 4 (con relleno) */ return 0; } ``` > **El lado oscuro de los bitfields.** El **orden** de los bits dentro del `struct` **no está > garantizado** por el estándar: cambia entre compiladores y plataformas. Además, meten relleno > (capítulo `12`) que arruina el cálculo. Por eso, **para leer un formato binario externo usá `>>` y > `&`**, no bitfields. Los bitfields sirven cuando el "paquete" es **interno** y no sale nunca de tu > programa. ## 6.9 (Difícil) Uniones para reinterpretar bits: *type punning* Una **unión** (`union`) hace que varios tipos **compartan la misma memoria**. Escribís como uno y leés como otro: eso se llama *type punning*. ```c #include #include union Word { uint32_t full; /* vista como número de 32 bits */ uint8_t bytes[4]; /* la MISMA memoria vista como 4 bytes */ }; int main(void) { union Word w; w.full = 0x11223344; /* en little-endian, w.bytes[] = { 0x44, 0x33, 0x22, 0x11 } */ printf("%02x %02x %02x %02x\n", w.bytes[0], w.bytes[1], w.bytes[2], w.bytes[3]); return 0; } ``` > **Con cuidado.** Reinterpretar por unión es **dependiente de la implementación** (funciona en GCC, > Clang y MSVC, pero el estándar no lo garantiza al 100%). La forma **segura** de reinterpretar bytes > sin violar el *aliasing* es `memcpy`, con `unsigned char` (como en 6.7). Conocelo, pero preferí > `memcpy` para código serio. ## Práctica de la Parte 6 ### Ejercicio 17.11 — La caja de herramientas de bits **Conceptos** · `x & (x-1)`, potencia de dos, `x & -x`, popcount, tipos sin signo **Contexto** · Los trucos de bits son herramientas sueltas que conviene tener probadas. Simplificación: una librería de funciones que recibe un número y responde preguntas sobre sus bits. **Consigna** · Implementá, con `uint32_t`: 1. `countBits(x)`: cuántos bits en `1` tiene (contalos recorriendo con `x &= x - 1`); 2. `isPowerOfTwo(x)`: verdadero solo si `x` es una potencia de dos (`1, 2, 4, 8, …`); 3. `lowestBit(x)`: el **valor** del bit encendido más bajo (con `x & -x`); 4. `highestBit(x)`: la **posición** del bit encendido más alto. Probá cada una con varios valores, incluido `0` y un valor con un solo bit (por ejemplo `1u << 31`). **Entrada** · Varios valores de 32 bits. **Salida** · La respuesta de cada función para cada valor. **Escenario de prueba** ```text x = 0b10110100 (180) countBits = 4 isPowerOfTwo= no lowestBit = 4 (0b100) highestBit = 7 (bit 7 = 128) x = 0b01000000 (64) countBits = 1 isPowerOfTwo= sí lowestBit = 64 highestBit = 6 x = 0 countBits = 0 isPowerOfTwo= no lowestBit = 0 ``` **Identificadores sugeridos** · `countBits`, `isPowerOfTwo`, `lowestBit`, `highestBit` **Criterios de aceptación** - [ ] Todo se hace con operadores de bits y tipos **sin signo**. - [ ] `isPowerOfTwo(0)` devuelve **falso** (ojo con el `0`). - [ ] El `1u << 31` se maneja bien (no aparece como negativo). - [ ] Los resultados del escenario salen exactos. --- ### Ejercicio 17.12 — El ejercicio jefe: un archivo de paquetes binarios **Conceptos** · todo lo anterior — `uintN_t`, máscaras, `<<`, `>>`, `&`, `|`, endianness, archivos **Contexto** · Un sistema de telemetría escribe cada medición como un **DWORD** de 32 bits donde se empaquetan varios campos. Para que este ejercicio entre en el capítulo, podés mantener los registros en un **arreglo** en memoria (sin archivos); si querés, guardalos con `fwrite` (capítulo `15`) como extra. **Consigna** · Definí el siguiente registro de 32 bits: ```text 31 20 19 16 15 4 3 0 ┌────────────────┬────────┬───────────┬──────────┐ │ sensorId (12) │ temp │ presion │ status │ │ │ (4) │ (12) │ (4) │ └────────────────┴────────┴───────────┴──────────┘ ``` 1. Escribí una función que **empaquete** los cuatro campos en un `uint32_t`. 2. Escribí funciones que **extraigan** cada campo. 3. Cargá un arreglo con **al menos cinco** mediciones (con valores a elección, incluyendo `temp` negativa codificada en 4 bits con complemento a dos, y ambos extremos de `sensorId`). 4. Mostrá cada medición con sus cuatro campos **desempaquetados**. 5. Añadí una función que **cuente cuántas mediciones** tienen el `status` con el bit 0 encendido. **Entrada** · Un arreglo de registros `uint32_t` (a elección). **Salida** · Cada registro en hex con sus campos, y el conteo final. **Escenario de prueba** ```text registro = 0x054A4A32 sensorId = 0x054 (84) temp = 0xA (4 bits, complemento a dos = -6) presion = 0x4A3 (1187) status = 0x2 ... (cuatro mediciones más) mediciones con status bit0 = 3 ``` > **Cómo comprobar el hex del escenario:** `sensorId << 20` = `0x05400000`, > `temp << 16` = `0x000A0000`, `presion << 4` = `0x00004A30`, `status` = `0x00000002`. Con `|`: > `0x054A4A32`. Y `0xA` en 4 bits con signo es `-6` (complemento a dos, Parte 1). **Identificadores sugeridos** · `TelemetryRecord`, `packRecord`, `getSensorId`, `getTemp`, `getPressure`, `getStatus`, `countWithBit0` **Criterios de aceptación** - [ ] Las máscaras se construyen con `(1u << w) - 1`, nunca "a ojo". - [ ] `temp` se extrae como número **con signo** de 4 bits (ver Parte 1) y puede dar negativo. - [ ] Todos los campos se recuperan sin pisarse entre sí. - [ ] El conteo del `status & 1` es correcto. - [ ] Se usa `uint32_t` (el bit 31 no es signo). --- ## 17.13 Errores comunes - **Confundir `&` con `&&`** (y `|` con `||`): los primeros operan bit a bit; los segundos, sobre la verdad completa. - **Testear una bandera con `==`** en vez de `&`. - **Confundir "alguna" con "todas"** al testear un grupo: `flags & GRUPO` vs `(flags & GRUPO) == GRUPO`. - **Desplazar por el ancho o más** (`x << 32`): comportamiento indefinido. - **Desplazar a la derecha un `signed` negativo**: dependiente de la implementación. - **Operar bits con tipos con signo**: usá `unsigned`. - **Enteros multi-byte crudos**: acordate de la **endianness** al leer archivos o redes. - **Bitfields para formatos externos**: el orden de bits no es portable; usá `>>` y `&`. --- ## 17.14 Cierre Bajaste un piso y ya no ves números: ves **filas de interruptores**. Con cuatro operadores (`&`, `|`, `^`, `~`) y dos desplazamientos (`<<`, `>>`) tenés todo lo necesario para: - meter **ocho banderas en un byte** (y 32 en un `DWORD`) en vez de ocho `bool`; - **testear, prender, apagar e invertir** cada bandera con una máscara; - **multiplicar y dividir** por potencias de dos corriendo bits; - **leer y escribir campos** dentro de un `uint32_t` (`BYTE`/`WORD`/`DWORD`/`QWORD`) con el par `>>` y `&`; - entender por qué el mismo `0xFF` es `255` o `-1`, y por qué un `0x1234` no siempre se guarda igual (endianness). **Regla mental.** Cada vez que veas un montón de datos que son "sí/no" repetidos, un conjunto de opciones, o un formato binario con campos, preguntate: **"¿esto es un número, o es una caja de bits?"**. Si es una caja de bits, ya sabés abrirla. **Resumen de la caja de herramientas.** | Quiero… | Escribo… | |---|---| | Fabricar la máscara del bit `n` | `1u << n` | | Prender el bit `n` | `x \|= (1u << n)` | | Apagar el bit `n` | `x &= ~(1u << n)` | | Invertir el bit `n` | `x ^= (1u << n)` | | Testear el bit `n` | `x & (1u << n)` | | Máscara de `w` bits | `(1u << w) - 1` | | Extraer un campo | `(x >> s) & ((1u << w) - 1)` | | Armar un campo | `x \|= (v & ((1u << w) - 1)) << s` | | Multiplicar por `2ᵏ` | `x << k` | | Dividir por `2ᵏ` (trunca) | `x >> k` | | Apagar el bit más bajo | `x & (x - 1)` | | Aislar el bit más bajo | `x & -x` | | ¿Potencia de dos? | `x && !(x & (x - 1))` | | Contar bits en 1 (C23) | `stdc_count_ones(x)` | > **El puente al capítulo siguiente.** Ahora que ves los bits, el mundo binario se te abre: leer > imágenes (BMP, capítulo `15`), escribir compresores, entender protocolos de red y hasta mirar cómo > un `float` guarda su signo, su exponente y su mantisa. Todo eso son **máscaras y desplazamientos** > aplicados a `uintN_t`. Lo difícil ya lo tenés.