Serie A Blog Society (2): tipos personalizados en lenguaje C: estructura, segmento de bits, enumeración, unión

Tabla de contenido

 Prefacio

1. Estructura

1.1 Declaración del tipo de estructura

1.2 Declaración de tipo de estructura especial

1.3 Autorreferencia de la estructura

1.4 Definición e inicialización de estructura.

1.5 Llamar a variables miembro de estructura

1.6 Estructura de alineación de la memoria 

1.6.1, compensación de

1.6.2 Cálculo del tamaño de la estructura.

1.6.3 ¿Por qué existe la alineación de la memoria? 

1.7 Modificar el número de alineación predeterminado

1.8 Paso de parámetros de estructura

2. segmento de bits 

2.1 ¿Qué es un segmento de bits?

2.2 Asignación de memoria de segmentos de bits

2.3 Problemas multiplataforma en segmentos de bits

2.4 Aplicación de segmentos de bits 

3. Enumeración

3.1 Definición de tipos de enumeración

3.2 Ventajas de la enumeración 

4. Consorcio (comunidad)

4.1 Definición de tipo de unión

4.2 Características del consorcio

4.3 Cálculo del tamaño de la unión

 Prefacio

Los tipos integrados del lenguaje C son: char, short, int, long, long long, float, double.

Estos tipos integrados no pueden resolver todos los problemas y habrá algunos objetos complejos en la vida.

Por ejemplo, describe una persona, nombre, sexo, edad, altura, peso...

Describe un libro, título, autor, editorial...

Dado que existen objetos complejos, el lenguaje C admite tipos personalizados.Esta es la estructura, el campo de bits, la enumeración y la unión de los que hablará este blog .

1. Estructura

        Una estructura es un tipo de datos definido por el usuario que se utiliza para combinar múltiples elementos de datos relacionados para formar una colección de datos completa.

        Una matriz es una colección de elementos del mismo tipo , mientras que una estructura puede contener diferentes tipos de datos , como números enteros, caracteres, tipos de punto flotante, matrices, punteros, etc. Cada dato en la estructura se llama variable miembro .

1.1 Declaración del tipo de estructura

  • Estructura estructura de palabras clave
  • Etiqueta de nombre de tipo personalizado
  • lista de miembros lista de miembros
  • Estructura nombre de variable lista de variables
struct tag
{
	member-list;
}variable-list;

Por ejemplo, describa a un estudiante: 

struct Stu
{
	char name[20];//名字
	int age;//年龄
	char sex[5];//性别
	char id[20];//学号
}s1, s2, s3;//分号不能丢 s1,s2,s3是三个结构体变量,为全局变量

int main()
{
	struct Stu s4, s5, s6; //s4,s5,s6是三个结构体变量,为局部变量
	return 0;
}

1.2 Declaración de tipo de estructura especial

El tipo de estructura anónima no tiene un nombre de tipo personalizado cuando se define y la variable de estructura (s1) se crea cuando se define.

Características: debido a que no hay un nombre de tipo personalizado, no se puede crear a través de un nombre de tipo personalizado más adelante, por lo que las variables de estructura solo se pueden crear al definir.

struct 
{
	char name[20];//名字
	int age;//年龄
	char sex[5];//性别
	char id[20];//学号
}s1;//分号不能丢

[Recordatorio propenso a errores] 

 ¿Es factible el siguiente código?

struct 
{
	char name[20];
	int age;
	char sex[5];
	char id[20];
}s1;

struct
{
	char name[20];
	int age;
	char sex[5];
	char id[20];
}* p;

int main()
{
	p = &s1;  //是否可行?
	return 0;
}

【Respuesta】

No es factible . Desde la perspectiva del compilador, aunque las variables miembro de las dos estructuras son las mismas, aún se considerarán dos tipos de estructuras, por lo que el compilador emitirá una advertencia .

1.3 Autorreferencia de la estructura

¿Está bien incluir un miembro en una estructura que sea del tipo de la estructura misma?

struct Node
{
	int data;
	struct Node next;
};

De hecho, puede cambiar su forma de pensar: si puede incluirse directamente en la estructura, entonces el tamaño de la estructura debe calcularse usando sizeof (), porque si es factible, se almacenará en la memoria, y si es almacenado en la memoria, debe haber un tamaño. Por el contrario, si no se puede calcular el tamaño, demuestra que el método de autorreferencia no es factible.

Luego, cuando ejecutamos sizeof para calcular el tamaño de la estructura, encontraremos que el compilador informa un error , lo que demuestra que el método de autorreferencia es incorrecto.

[Autorreferencia de estructura correcta] 

Debido a que se determina el tamaño de la dirección (puntero), puede pasar el puntero para realizar la autorreferencia de la estructura.

struct Node
{
	int data;
	struct Node* next; //结构体指针
};

int main()
{
	printf("%d\n", sizeof(struct Node));
	return 0;
}

1.4 Definición e inicialización de estructura.

struct Point
{
	int x;
	int y;
}p1;
//声明类型的同时定义变量p1
struct Point p2;
//定义结构体变量p2
//初始化:定义变量的同时赋初值。
struct Point p3 = { 1, 2 };

struct Stu    //类型声明
{
	char name[15];//名字
	int age;  //年龄
};
struct Stu s = { "zhangsan", 20 };//初始化

struct Node
{
	int data;
	struct Point p;
	struct Node* next;
}n1 = { 10, {4,5}, NULL };
//结构体嵌套初始化
struct Node n2 = { 20, {5, 6}, NULL };//结构体嵌套初始化

1.5 Llamar a variables miembro de estructura

  • Nombre de variable de estructura. Nombre de variable miembro
  • Puntero de estructura- > nombre de variable miembro

1.6 Estructura de alineación de la memoria 

  • Hemos dominado el uso básico de las estructuras.
  • Profundicemos ahora en un problema: calcular el tamaño de una estructura.
  • Este es también un punto de prueba particularmente popular: alineación de la memoria de la estructura.

Si las variables miembro de dos estructuras son iguales, ¿sus tamaños serán los mismos?

struct S1
{
	char c1;
	int i;
	char c2;
};

struct S2
{
	char c1;
	char c2;
	int i;
};

int main()
{
	printf("%d\n", sizeof(struct S1));   //结构体大小为多少?
	printf("%d\n", sizeof(struct S2));   //结构体大小为多少?
	return 0;
}

【resultado de la operación】 

Inesperadamente, el tamaño de S1 es 12 y el tamaño de S2 es 8. Sus tamaños son inconsistentes, ¿a qué se debe esto? A continuación, presentamos una macro offsetof . Utilice esta macro para explorar las causas de los diferentes tamaños de S1 y S2.

1.6.1, compensación de

La macro offsetof se utiliza para calcular el desplazamiento de los miembros de la estructura en comparación con la posición inicial, y se devuelve el desplazamiento.

【Calcular S1】

Primero, c1 y c2 ocupan un byte e i ocupa cuatro bytes. Luego use offsetof para calcular las compensaciones como 0, 4 y 8.

Y el tamaño total de S1 es 12, luego después de almacenar c1, i, c2 en la memoria de acuerdo con el desplazamiento, se puede observar que si S1 es 12, se desperdiciarán 6 bytes de espacio (área roja), este ¿Por qué? ?

【Calcular S2】

 El tamaño calculado por S2 es 8. Luego, después de almacenar las variables miembro en la memoria de acuerdo con el desplazamiento, se puede observar que se desperdician 2 bytes de espacio (área roja), ¿por qué hay espacio desperdiciado y el espacio desperdiciado todavía? ¿Es diferente? A continuación se explicará a todos la alineación de la memoria estructural.

1.6.2 Cálculo del tamaño de la estructura.

Primero, debes dominar las reglas de alineación de estructuras:

  1. El primer miembro está en el desplazamiento 0 de la variable de estructura.
  2. Otras variables miembro deben alinearse con direcciones que sean múltiplos enteros de un determinado número (número de alineación).
  • Número de alineación = El menor entre el número de alineación predeterminado del compilador y el tamaño del miembro .
  • El valor predeterminado en VS es 8
  • No hay un número de alineación predeterminado en Linux, el número de alineación es el tamaño del propio miembro.

     3. El tamaño total de la estructura es un múltiplo entero del número de alineación máximo (cada variable miembro tiene un número de alineación).

     4. Si una estructura está anidada y la estructura anidada está alineada con un múltiplo entero de su propio número de alineación máximo, el tamaño total de la estructura es un número entero de todos los números de alineación máximos (incluido el número de alineación de las estructuras anidadas) veces.

Entonces, cuando sepamos el número de alineación, intentemos calcular los tamaños de s1 y s2 nosotros mismos. 

 [Cálculo manual de S1]

  • El primer miembro c1 se coloca directamente en el desplazamiento 0.
  • El tamaño de i es 4 y el número de alineación predeterminado de vs es 8. El valor más pequeño es 4 , por lo que debe colocarse en un múltiplo entero de 4, es decir, se omiten (desperdician) 3 bytes y se colocan en el desplazamiento. 4. Ocupa 4 bytes.
  • El tamaño de c2 en sí es 1. El número de alineación predeterminado de vs es 8. El valor más pequeño es 1 , por lo que debe colocarse en un múltiplo entero de 1. Cualquier número es un múltiplo entero de 1, por lo que se puede colocar directamente después de i. .
  • Aún no ha terminado.El tamaño total de la estructura es un múltiplo entero del número máximo de alineación ( cada variable miembro tiene un número de alineación). El número de alineación de c1 es 1, el número de alineación de i es 4 y el número de alineación de c2 es 1. Por lo tanto, el número de alineación máximo es 4. En este momento, el tamaño es 9. Es necesario desperdiciar 3 más. espacios, de modo que el tamaño total de la estructura llega a 12 y se convierte en múltiplo de 4. Esto se completa. Se realizó un cálculo de la estructura.

[Cálculo manual de S2] 

  • El primer miembro c1 se coloca directamente en el desplazamiento 0.
  • El tamaño de c2 en sí es 1. El número de alineación predeterminado de vs es 8. El valor más pequeño es 1 , por lo que debe colocarse en un múltiplo entero de 1. Cualquier número es un múltiplo entero de 1, por lo que se puede colocar directamente detrás de c1.
  • El tamaño de i es 4 y el número de alineación predeterminado de vs es 8. El valor más pequeño es 4 , por lo que debe colocarse en un múltiplo entero de 4, es decir, se omiten (desperdician) 2 bytes y se colocan en el desplazamiento. 4. Ocupa 4 bytes.
  • Aún no ha terminado. El tamaño total de la estructura es un múltiplo entero del número máximo de alineación (cada variable miembro tiene un número de alineación). c1 tiene un número de alineación de 1, i tiene un número de alineación de 4 y c2 tiene un número de alineación de 1, por lo que el número de alineación máximo es 4. Pero el tamaño en este momento es solo un múltiplo de 4, por lo que no hay necesidad de desperdiciar otro espacio, y el tamaño de la estructura es 8.

1.6.3 ¿Por qué existe la alineación de la memoria? 

        Una vez que entendemos la estructura de la alineación de la memoria, todavía tenemos una pregunta: ¿Por qué existe la alineación de la memoria?

La mayoría de las referencias mencionan dos razones :

1. Motivos de plataforma (motivos de trasplante):
        no todas las plataformas de hardware pueden acceder a datos en cualquier dirección; algunas plataformas de hardware solo pueden recuperar ciertos tipos de datos en determinadas direcciones; de lo contrario, se generará una excepción de hardware.

2. Razones de rendimiento:
        las estructuras de datos (especialmente las pilas) deben estar alineadas con límites naturales tanto como sea posible. La razón es que para acceder a la memoria no alineada, el procesador necesita realizar dos accesos a la memoria; el acceso a la memoria alineada requiere solo un acceso.

En general: 

La alineación de la memoria de las estructuras intercambia espacio por tiempo .

 

 Al diseñar una estructura no sólo debemos satisfacer la alineación sino también ahorrar espacio.Cómo hacer esto:

Mantenga juntos tanto como sea posible a los miembros que ocupan menos espacio . Por ejemplo, S1 y S2 utilizados como ejemplos anteriores tienen exactamente los mismos miembros, pero existen algunas diferencias en el tamaño del espacio ocupado por S1 y S2, porque S2 junta los miembros pequeños.

struct S1
{
	char c1;
	int i;
	char c2;
};                      //结构体大小12

struct S2
{
	char c1;
	char c2;
	int i;
};                      //结构体大小8

1.7 Modificar el número de alineación predeterminado

Hemos visto la directiva de preprocesamiento #pragma antes y aquí la usamos nuevamente para cambiar nuestro número de alineación predeterminado.

#include <stdio.h>
#pragma pack(8)//设置默认对齐数为8

struct S1
{
	char c1;
	int i;
	char c2;
};
#pragma pack()//取消设置的默认对齐数,还原为默认


#pragma pack(1)//设置默认对齐数为1
struct S2
{
	char c1;
	int i;
	char c2;
};
#pragma pack()//取消设置的默认对齐数,还原为默认

int main()
{	//输出的结果是什么?
	printf("%d\n", sizeof(struct S1));
	printf("%d\n", sizeof(struct S2));
	return 0;
}

 【resultado de la operación】

Es muy fácil de entender aquí. Ya lo hemos calculado arriba cuando el número de alineación se establece en 8. Luego, cuando se establece en 1, significa que no hay alineación. Debido a que cualquier número es un múltiplo entero de 1, es directamente igual a 1+4+ 1 = 6.

1.8 Paso de parámetros de estructura

 ¿Cuál de las siguientes funciones print1 o print2 es mejor?

struct S
{
	int data[1000];
	int num;
};
struct S s = { {1,2,3,4}, 1000 };
//结构体传参
void print1(struct S s)
{
	printf("%d\n", s.num);
}
//结构体地址传参
void print2(struct S* ps)
{
	printf("%d\n", ps->num);
}
int main()
{
	print1(s);  //传结构体
	print2(&s); //传地址
	return 0;
}

【Respuesta】

Se prefiere  la función print2  porque cuando la función pasa parámetros, los parámetros deben insertarse en la pila, lo que provocará una sobrecarga del sistema en tiempo y espacio.
Si se pasa un objeto de estructura y la estructura es demasiado grande, la sobrecarga del sistema al insertar parámetros en la pila será relativamente grande, lo que provocará una degradación del rendimiento.

2. segmento de bits 

 Los segmentos de bits parecen ahorrar espacio.

2.1 ¿Qué es un segmento de bits?

El "bit" del campo de bits es el "bit" del bit binario. La declaración y estructura de los campos de bits son similares, con dos diferencias:

  1. Los miembros de un campo de bits deben ser int, int sin signo o int con signo. Después de C99, también pueden ser otros tipos, pero básicamente son todos los tipos de la familia de enteros, como int y char.
  2. El nombre de miembro del campo de bits va seguido de dos puntos y un número .
struct A
{
	int _a : 2;  //_a占用2个bit位的空间
	int _b : 5;  //_b占用5个bit位的空间
	int _c : 10; //_c占用10个bit位的空间
	int _d : 30; //_d占用30个bit位的空间
};

int main()
{
	printf("%d\n", sizeof(struct A));
	return 0;
}


//提示:1个字节等于8个bit位

 

En circunstancias normales, el tamaño abierto por cuatro tipos int es de 16 bytes, pero si se usa el código anterior para implementarlo, solo se usan 8 bytes.Expliquemos la asignación de memoria de los segmentos de bits.

2.2 Asignación de memoria de segmentos de bits

  1. Los miembros del campo de bits pueden ser del tipo int unsigned int firmado int o char (perteneciente a la familia de enteros)
  2. El espacio del campo de bits se asigna en 4 bytes (int) o 1 byte (char), según sea necesario.
  3. Los segmentos de bits implican muchas incertidumbres. Los segmentos de bits no son multiplataforma. Los programas que se centran en la portabilidad deben evitar el uso de segmentos de bits .
struct S
{
	char a : 3;
	char b : 4;
	char c : 5;
	char d : 4;
};


int main()
{
	struct S s = { 0 };

	s.a = 10;
	s.b = 12;
	s.c = 3;
	s.d = 4;

	int ret = sizeof(struct S);
	printf("%d\n", ret);
	return 0;
}

 [Resultados de la ejecución] Resultados de la prueba en el entorno Visual Studio 2022

El resultado son 3 bytes.

Pregunta : 3+4+5+4 = 16 bits, 1 byte equivale a 8 bits, ¿por qué no abrir 2 bytes?

La respuesta la podemos encontrar en los valores almacenados en la memoria .

Del diagrama se puede concluir: cuando no hay suficiente espacio para almacenar el siguiente miembro, el espacio restante no se utilizará, sino que se abrirá otro espacio y el contenido se almacenará en el espacio recién abierto. Por lo tanto, el resultado del escódigo

2.3 Problemas multiplataforma en segmentos de bits

  1. No está definido si un campo de bits int se trata como un número con o sin signo.
  2. No se puede determinar el número máximo de bits en un campo de bits. (El número máximo para una máquina de 16 bits es 16 y el número máximo para una máquina de 32 bits es 32. Escribirlo como 27 causará problemas en una máquina de 16 bits).
  3. Los criterios para determinar si los miembros de un campo de bits se asignan de izquierda a derecha o de derecha a izquierda en la memoria no están definidos.
  4. Cuando una estructura contiene dos campos de bits y los miembros del segundo campo de bits son más grandes y no pueden caber en los bits restantes del primer campo de bits, no está claro si descartar los bits restantes o usarlos.

Resumir:

En comparación con la estructura, el segmento de bits puede lograr el mismo efecto, pero puede ahorrar mucho espacio, pero existen problemas multiplataforma.

2.4 Aplicación de segmentos de bits 

Pila de protocolos de red, la capa inferior de la red transmite datos.

En la era actual de Internet, la transmisión de datos a través de la red se ha vuelto muy común. Entonces, ¿alguna vez has pensado en cómo se procesa la parte de transmisión de la red cuando enviamos un mensaje de texto o un mensaje WeChat? Solo transmite el mensaje en sí. Por supuesto que no, el mensaje más simple contiene muchos otros datos, como la hora en que se envió el mensaje, la dirección IP del remitente, la dirección IP del remitente, etc. Un mensaje contiene tantos datos, por lo que si no hay segmentos de bits, el volumen de transmisión de un solo mensaje será demasiado grande, lo que provocará una carga excesiva de la red, lo que no favorece nuestro uso diario y el almacenamiento de datos del servidor. El uso de segmentos de bits puede comprimir muy bien el tamaño, haciendo que los mensajes sean más pequeños y livianos.

3. Enumeración

La enumeración, como su nombre indica, consiste en enumerar todos los valores posibles uno por uno.

Por ejemplo, en nuestra vida real:

  • Hay un número limitado de 7 días de lunes a domingo en una semana, que se pueden enumerar uno por uno.
  • El género incluye: masculino, femenino, confidencial o puede enumerarlos uno por uno.
  • Hay 12 meses en el mes y también puedes enumerarlos uno por uno.

3.1 Definición de tipos de enumeración

El día de enumeración, el sexo de enumeración y el color de enumeración definidos a continuación son todos tipos de enumeración.
Los contenidos en {} son valores posibles del tipo de enumeración, también llamados constantes de enumeración.

enum Day//星期
{
	Mon,   //枚举的可能取值是默认从0开始的。
	Tues,
	Wed,
	Thur,
	Fri,
	Sat,
	Sun
};

enum Sex//性别
{
	MALE,
	FEMALE,
	SECRET
};

enum Color//颜色
{
	RED,
	GREEN,
	BLUE
};

Todos estos valores posibles son valiosos, comenzando desde 0 de forma predeterminada y aumentando de a 1. Por supuesto, también se puede asignar un valor inicial al definir.

Por ejemplo: 

enum Color//颜色
{
    RED=1,
    GREEN=2,
    BLUE=4
};

3.2 Ventajas de la enumeración 

Podemos usar #define para definir constantes, ¿por qué tenemos que usar enumeraciones? Ventajas
de las enumeraciones :

  1. Aumentar la legibilidad y mantenibilidad del código
  2. En comparación con los identificadores definidos por #define, las enumeraciones tienen verificación de tipo, que es más rigurosa.
  3. Contaminación de nombres evitada (encapsulación)
  4. Fácil de depurar
  5. Fácil de usar, se pueden definir múltiples constantes al mismo tiempo

4. Consorcio (comunidad)

4.1 Definición de tipo de unión

La unión también es un tipo personalizado especial.
Las variables definidas por este tipo también contienen una serie de miembros. La característica es que estos miembros comparten el mismo espacio (por eso la unión también se llama unión).
Por ejemplo:

union Un
{
	char c;
	int i;
};

4.2 Características del consorcio

Los miembros de una unión comparten el mismo espacio de memoria , por lo que el tamaño de una variable de unión debe ser al menos el tamaño del miembro más grande (porque la unión debe poder almacenar al menos el miembro más grande). Al mismo tiempo, debido a que comparten un espacio de memoria, solo se puede utilizar uno al mismo tiempo.

union Un
{
	char c;
	int i;
};

int main()
{
	union Un un;
	printf("%d\n", sizeof(un));
	printf("%p\n", &(un));
	printf("%p\n", &(un.c));
	printf("%p\n", &(un.i));
	return 0;
}

 

4.3 Cálculo del tamaño de la unión

  • El tamaño del sindicato es al menos el tamaño del miembro más grande.
  • Cuando el tamaño máximo del miembro no es un múltiplo entero del número máximo de alineación, debe alinearse a un múltiplo entero del número máximo de alineación.
union Un
{
	char c[5];  //大小为5,对齐数为1
	int i;      //大小为4,对齐数为4
};

int main()
{
	printf("%zd\n", sizeof(union Un));
	return 0;
}

 【resultado de la operación】

El tamaño máximo de miembro es 5, pero el número máximo de alineaciones es 4, por lo que es necesario alinearlo a 8.

Si cree que la escritura del autor es buena, dele un gran me gusta y apoyo al blogger. ¡Su apoyo es mi mayor motivación para actualizar!

Si cree que la escritura del autor es buena, dele un gran me gusta y apoyo al blogger. ¡Su apoyo es mi mayor motivación para actualizar!

Si cree que la escritura del autor es buena, dele un gran me gusta y apoyo al blogger. ¡Su apoyo es mi mayor motivación para actualizar!

 

Supongo que te gusta

Origin blog.csdn.net/zzzzzhxxx/article/details/133302515
Recomendado
Clasificación