Campo de bits en C

Abdul Mateen 12 octubre 2023
  1. Campo de bits en C
  2. Almacenamiento de campos de bits en C
  3. Campo de bits con el tipo de datos short en C
  4. Campo de bits con múltiples tipos de datos en C
  5. Conclusión
Campo de bits en C

En este tutorial, aprenderemos sobre el campo de bits en el lenguaje C.

Comenzaremos la discusión desde el campo de bits. A continuación, analizaremos el almacenamiento de campos de bits, seguido de la sintaxis del campo de bits en lenguaje C.

Por último, veremos un campo de bits con diferentes tipos de datos para comprender el espacio mínimo y máximo consumido por la estructura que tiene un campo de bits dentro.

Campo de bits en C

Un campo de bits en programación es una estructura de datos única que ayuda al programador a ahorrar memoria. El campo de bits permite la asignación de memoria a estructuras en bits.

Consideremos una imagen en blanco y negro puro que tiene solo dos colores, blanco y negro. Necesitamos almacenar solo dos valores: 0 o 1.

Considere una imagen pequeña de 100x100 comparando la pantalla con miles de píxeles en cada dimensión. Habrá diez mil píxeles en la imagen.

En los tipos de datos estándar, tiene una opción de tipo de datos unsigned char en lenguaje C, que ocupa solo un byte; sin embargo, para guardar una imagen en blanco y negro puro, necesita 10000 bytes de memoria.

Con el campo de bits, puede guardar ocho píxeles en un byte (8 bits en 1 byte). Esto significa que necesitará alrededor de 1250 bytes en lugar de 10000.

Este es solo un ejemplo; esto no significa que pueda ahorrar espacio solo en el caso de las imágenes. En algunos exámenes, donde aparecen miles de candidatos, solo necesita un bit para almacenar la información de aprobación/reprobación; de lo contrario, la opción es utilizar un byte para cada candidato.

Almacenamiento de campos de bits en C

Considere la siguiente estructura para iniciar una discusión sobre el almacenamiento de campos de bits:

struct {
  unsigned char is_married;
  unsigned char is_graduated;
} status0;

Esta estructura requiere dos bytes de espacio de memoria; sin embargo, tenemos que almacenar 0 o 1 en ambos campos. Avancemos hacia una mejor manera de ahorrar espacio.

Veremos el campo de bits desde la sintaxis hasta el código en detalle.

En lenguaje C, tenemos una sintaxis específica para decir la cantidad de bits necesarios con cada variable:

struct {
  type[variable_name] : size;  // Size will be in bits
}

Es importante tener en cuenta que esta sintaxis está disponible con estructura. Aquí, type es cualquier tipo de datos como int, char, short, unsigned char, etc.

Ya conoce bien los nombres de variables legales en lenguaje C. Los dos puntos son parte de la sintaxis y se requieren entre el nombre y el tamaño de la variable y, por último, size es la cantidad de bits necesarios.

Lo siguiente que podría sorprenderte es el tamaño de la estructura con el tamaño de bits. Ver el siguiente código:

#include <stdio.h>

struct {
  unsigned char is_married;
  unsigned char is_graduated;
} status0;

struct {
  unsigned char is_married : 1;
  unsigned char is_graduated : 1;
} status1;

int main() {
  printf("Memory size occupied by status1 : %ld bytes\n", sizeof(status0));
  printf("Memory size occupied by status1 : %ld  bytes\n", sizeof(status1));
  return 0;
}

La salida es:

Memory size occupied by status1 : 2 bytes
Memory size occupied by status1 : 1 bytes

En la salida, la primera estructura ocupa 2 bytes (es decir, 1 byte para cada campo), lo cual es muy lógico. Sin embargo, la segunda salida ocupa 1 byte; puede esperar 2 bytes o bits.

La pregunta es, ¿por qué un byte? La lógica es que el tipo de datos consumirá un byte por definición de rutina del lenguaje C.

Sin embargo, especificar el tamaño en bits permite al programador declarar 7 campos más de 1 bit cada uno.

El total de bits debe permanecer inferior a 8 bits por 1 byte. De lo contrario, se consumirán 2 bytes de almacenamiento.

Consulte los siguientes códigos:

#include <stdio.h>

struct {
  unsigned char a : 1;
  unsigned char b : 7;
} status0;

struct {
  unsigned char a : 1;
  unsigned char b : 1;
  unsigned char c : 1;
  unsigned char d : 1;
  unsigned char e : 1;
  unsigned char f : 1;
  unsigned char g : 1;
  unsigned char h : 1;
} status1;

int main() {
  printf("Memory size occupied by status1 : %ld\n", sizeof(status0));
  printf("Memory size occupied by status1 : %ld\n", sizeof(status1));
  return 0;
}

La salida es:

Memory size occupied by status1 : 1
Memory size occupied by status1 : 1

Veamos el siguiente ejemplo, donde se incrementa el número de bits:

#include <stdio.h>

struct {
  unsigned char a : 3;
  unsigned char b : 7;
  unsigned char c : 7;
} status0;

struct {
  unsigned char a : 1;
  unsigned char b : 1;
  unsigned char c : 1;
  unsigned char d : 1;
  unsigned char e : 1;
  unsigned char f : 1;
  unsigned char g : 1;
  unsigned char h : 1;
  unsigned char i : 1;
  unsigned char j : 1;
} status1;

int main() {
  printf("Memory size occupied by status1 : %ld\n", sizeof(status0));
  printf("Memory size occupied by status1 : %ld\n", sizeof(status1));
  return 0;
}

La salida es:

Memory size occupied by status1 : 3
Memory size occupied by status1 : 2

En la primera estructura, los bits totales son 3+7+7=17, que es mayor que 16 bits (2 bytes). Por lo tanto, se consumirán 3 bytes.

Lo mismo es visible en la primera línea de salida.

En la segunda estructura, tenemos 10 campos de 1 bit cada uno, lo que requiere 10 bits; por lo tanto, consumirá dos bytes. Nuevamente, la segunda línea de salida confirma el concepto.

Campo de bits con el tipo de datos short en C

Uno podría obtener que se requiere un unsigned char para usar el campo de bits. También podemos usar el campo de bits con otros tipos de datos.

Aquí, estamos considerando el tipo de datos short para demostrar y comprender mejor el concepto del campo de bits.

Considere el siguiente código:

#include <stdio.h>

struct {
  unsigned short a : 3;
} status0;

struct {
  unsigned short a : 3;
  unsigned short b : 9;
  unsigned short c : 4;
} status1;

int main() {
  printf("Memory size occupied by status1 : %ld\n", sizeof(status0));
  printf("Memory size occupied by status1 : %ld\n", sizeof(status1));
  return 0;
}

La salida es:

Memory size occupied by status1 : 2
Memory size occupied by status1 : 2

En ambas estructuras la memoria consumida es de 2 bytes porque short ocupa 2 bytes. Por lo tanto, la memoria mínima requerida es de 2 bytes.

Sin embargo, varios campos pueden consumir un total de 16 bits (como se muestra en la segunda estructura), el tamaño permanecerá en 2 bytes.

Si el número de bits aumenta de 16, los siguientes dos bytes del tipo de datos short se cubren automáticamente y, como resultado, se consumirán cuatro 4.

Veamos el siguiente programa en C:

#include <stdio.h>

struct {
  unsigned short a : 6;
  unsigned short b : 6;
  unsigned short c : 7;
} status1;

int main() {
  printf("Memory size occupied by status1 : %ld\n", sizeof(status1));
  return 0;
}

Aquí, los bits totales son 6+6+7=19, por lo que la salida es:

Memory size occupied by status1 : 4

Observe que la memoria aumenta byte a byte porque char tiene almacenamiento de un byte; mientras que, en el caso de short, la memoria aumenta en dos bytes porque el short, por definición, tiene dos bytes de almacenamiento.

Campo de bits con múltiples tipos de datos en C

Ahora es el momento adecuado para analizar los requisitos de almacenamiento de campos de bits en caso de que la estructura tenga dos o más tipos de datos. En tal caso, los bytes mínimos requeridos son el espacio máximo requerido por el tipo de datos más grande en la estructura.

Por ejemplo, si una estructura tiene miembros short y char, el tipo más grande es short y requiere 2 bytes; toda la estructura requerirá un mínimo de dos bytes.

En el caso de int & short, int es el tipo más grande; por lo tanto, toda la estructura requiere 4 bytes. Demostremos el concepto con la ayuda del siguiente código de ejemplo:

#include <stdio.h>

struct {
  unsigned short a : 6;
  unsigned int b : 6;
} status1;

struct {
  unsigned long long a : 6;
  unsigned int b : 6;
  unsigned short c : 6;
} status2;

int main() {
  printf("Memory size occupied by status1 : %ld\n", sizeof(status1));
  printf("Memory size occupied by status1 : %ld\n", sizeof(status2));
  return 0;
}

La salida es:

Memory size occupied by status1 : 4 Memory size occupied by status1 : 8

En la primera estructura, int es el tipo de datos más grande; por lo tanto, la primera línea de salida muestra un tamaño de estructura de 4 bytes.

En la segunda estructura, el long es el tipo dominante, por lo que la segunda línea de salida muestra el tamaño de ocho bytes de la estructura.

Por último, el incremento en caso de múltiples tamaños de datos será simple; el paso de incremento es el tamaño es igual al número de bytes en el tipo de estructura más grande.

Veamos el siguiente código para obtener una mejor comprensión:

#include <stdio.h>

struct {
  unsigned short a : 6;
  unsigned int b : 30;
  unsigned long long c : 50;
} status1;

int main() {
  printf("Memory size occupied by status1 : %ld\n", sizeof(status1));
  return 0;
}

La estructura tiene 50+30+6=86 bits, mientras que 8 bytes tienen 64 bits. Por lo tanto, esta estructura requería doble espacio, como se muestra en el siguiente resultado:

Memory size occupied by status1 : 16

Necesitamos 16 bytes que id 8+8 bytes.

Conclusión

Podemos ahorrar espacio si estamos usando campos de múltiples bits en nuestro programa. Podemos especificar el número de bits para una variable miembro de estructura en el momento de la declaración.

El tamaño mínimo de la estructura es el máximo de bytes necesarios para el tipo de datos más grande de la estructura.

Si los campos únicos o múltiples consumen colectivamente más bits que el tamaño del tipo más grande en la estructura, la memoria consumida será múltiplo del tamaño del tipo de memoria máximo.

Artículo relacionado - C Struct