Notas de lectura de "C y punteros" (Capítulo 11 Asignación dinámica de memoria)

0 Introducción

En el desarrollo real (lenguaje C), los elementos de la matriz se almacenan en ubicaciones consecutivas en la memoria. Sin embargo, existe una desventaja de usar matrices para almacenar datos, es decir, necesitamos saber su tamaño antes de ejecutar el programa. En el desarrollo real, no siempre podemos captar con precisión la memoria que se debe solicitar. Si no lo hacemos Si se toman otros medios, el desarrollo mejorará. El personal se encuentra en una situación desesperada.

Como hermano mayor del lenguaje C, C++ obviamente puede sentarse y relajarse. Cuando se enfrenta a muchos escenarios complejos, a menudo puede utilizar contenedores para afrontarlos con calma y facilidad. Para obtener más información, consulte el enlace: C++ Common Containers Catch All

El lenguaje C también tiene su propia respuesta a este tipo de problemas. Para salir de este punto muerto, el protagonista de hoy viene a nosotros con cariño y ligereza: la asignación dinámica de memoria (lo mismo se aplica a C++).

Resumen del contenido de este artículo:Insertar descripción de la imagen aquí


1 ¿Por qué utilizar la asignación de memoria dinámica?

Como se mencionó anteriormente, muchas veces no sabemos cuánta memoria necesitamos solicitar para almacenar datos, si es demasiado grande desperdicia espacio y si es demasiado pequeña, no es suficiente.

2 malloc y gratis

Malloc y free son hermanos, el primero se encarga de solicitar la memoria y el segundo se encarga de liberar la memoria. División clara del trabajo, sencilla y eficiente. Los prototipos de estas dos funciones son los siguientes:

void *malloc(size_t  size);
void free(void *pointer);

Malloc asigna una parte de la memoria contigua. Al mismo tiempo, la memoria asignada real puede ser ligeramente mayor de la que solicitamos y el tamaño específico depende del compilador .

Si el grupo de memoria está vacío o la memoria disponible no puede satisfacer la solicitud, mallocla función solicita al sistema operativo más memoria y realiza la tarea de asignación en esta nueva memoria. Si el sistema operativo no puede mallocproporcionar más memoria, NULLse devolverá un puntero.

freeEl argumento to debe ser NULLo ser un valor devuelto previamente por malloc, calloco . reallocPasar freeuno NULLa no tiene ningún efecto.

Los casos específicos se mencionarán en contenidos posteriores.

3 calloc y realloc

También hay dos funciones de asignación de memoria callocy realloc. Sus prototipos se ven así:

void *calloc(size_t num_elements, size_t element_size);
void *realloc(void *ptr, size_t new_size);

callocHay mallocdos diferencias entre:

  • Formalmente, malloclo que se pasa es el número total de bytes y calloclo que se pasa es el número de elementos y el número de bytes ocupados por cada elemento.
  • Desde un punto de vista funcional, mallocsolo es responsable de solicitar espacio de memoria, callocno solo solicitar espacio de memoria, sino también inicializarlo 0.

También se puede ver en el nombre: calloc = clear + malloc , que significa borrar y solicitar memoria .


realloc modifica/vuelve a aplicar una parte de la memoria.

  1. Si hay suficiente espacio para agregar después del espacio señalado por p, se agregará directamente y se devolverá pla dirección inicial original .
  2. Si no hay suficiente espacio para agregar después del espacio señalado por p , reallocla función encontrará una nueva área de memoria, volverá a abrir new_sizeel espacio de memoria dinámica uno por uno y copiará los datos del espacio de memoria original para liberar el espacio de memoria anterior. Devuélvalo al sistema operativo y finalmente devuelva la dirección inicial del espacio de memoria recién abierto .

El primer caso se muestra en la siguiente figura:
Insertar descripción de la imagen aquí
El segundo caso se muestra en la siguiente figura:
Insertar descripción de la imagen aquí

También se puede ver en el nombre: realloc = re + malloc , que significa volver a solicitar memoria .

4 Uso de memoria asignada dinámicamente

Hay un ejemplo en el libro, como sigue:

	int  *pi;
	pi = malloc(100);
	if (pi == NULL)
	{
    
    
		printf("Out of memory!\n");
		exit(1);
	}

Este ejemplo es fácil de entender. Asignamos una memoria de 100 bytes . Si la asignación falla, imprimimos el error y salimos del programa que se está ejecutando actualmente.
Por supuesto, también podemos escribir un programa simple nosotros mismos, de la siguiente manera:

#include<stdio.h>
#include<stdlib.h>
//定义返回值类型
typedef enum res
{
    
    
	FASLE,
	TRUE
}res_bool;
//分配内存并初始化、打印输出
res_bool fun_malloc(int const size)
{
    
    
	int *p;
	p = malloc(sizeof(int) * 25);
	if(p == NULL)
		return FASLE;
	else
	{
    
    
		for (int i = 0; i < size; i++)
			p[i] = i;
	}
	for (int i = 0; i < size; i++)
		printf("%d\t", p[i]);
	free(p);
	p = NULL;
	return TRUE;
}
int main()
{
    
    
	if (fun_malloc(25) == TRUE)
	{
    
    
		printf("内存分配成功!");
	}
	else
	{
    
    
		printf("内存分配失败!");
	}
	system("pause");
	return 0;
}

Este es un caso relativamente completo: asigna memoria, inicializa y verifica si la asignación de memoria es exitosa.

5 errores comunes de memoria dinámica

Hay dos tipos comunes de errores de memoria dinámica:

  1. Una es usarlo directamente sin juzgar si la aplicación de la memoria se realizó correctamente, lo que puede causar problemas inesperados.
  2. Una es que la operación excede los límites de la memoria asignada y también pueden ocurrir problemas inesperados.

Es difícil escribir casos específicos de errores de memoria, solo es necesario prestarles atención en la programación diaria.

6 ejemplos de asignación de memoria

6.1 Ordenar una lista de valores enteros

Los algoritmos de clasificación son los algoritmos más comunes y clásicos en el desarrollo de ingeniería. Hay diez algoritmos de clasificación comunes. Si está interesado, vaya a: Los diez mejores algoritmos de clasificación clásicos (
implementados en lenguaje C).
Los ejemplos que se dan a continuación se dan en el libro. Usar Es una función de biblioteca qsortpara ordenar, y se dice que la capa inferior usa un algoritmo de clasificación rápida.

#include <stdlib.h>
#include <stdio.h>
//该函数由qsort调用,用于比较整型值
int compare_integers(void const *a, void const *b)
{
    
    
	register int const *pa = a;
	register int const *pb = b;
	return *pa > *pb ? 1 : *pa < *pb ? -1 : 0;
}
//主函数
int main()
{
    
    
	int *array;
	int n_values;
	int i;
	//观察共有多少个值
	printf("How many values are there?");
	if (scanf_s("%d", &n_values) != 1 || n_values <= 0)
	{
    
    
		printf("Illegal number of values.\n");
		exit(EXIT_FAILURE);
	}
	//分配内存,用于存储这些值
	array = malloc(n_values * sizeof(int));
	if (array == NULL)
	{
    
    
		printf("Can't get memory for that many values.\n");
		exit(EXIT_FAILURE);
	}
	//读取这些值
	for (i = 0; i < n_values; i += 1)
	{
    
    
		printf("?");
		if (scanf_s("%d", array + i) != 1)
		{
    
    
			printf("error.\n");
			free(array);
			exit(EXIT_FAILURE);
		}
	}
	//对这些值排序
	qsort(array, n_values, sizeof(int), compare_integers);
	//打印这些值
	for (i = 0; i < n_values; i += 1)
		printf("%d\n",array[i]);
	//释放内存并推出
	free(array);
	system("pause");
	return EXIT_SUCCESS;
}

Ejecutar e imprimir la salida:
Insertar descripción de la imagen aquí
básicamente no hay dificultad. La única dificultad es que compare_integersel valor de retorno de la función utiliza una expresión condicional anidada. La expresión condicional es una versión simplificada de la declaración condicional (no todos los casos se pueden "simplificar"), un poco Es un poco complicado. Para expresiones condicionales, puede consultar la Sección 2.1.8 de las notas de lectura (Capítulo 5 Operadores y expresiones) en "C y punteros" .

6.2 Copiar cadena

También hay funciones de biblioteca listas para usar que se pueden usar para copiar cadenas. Los ejemplos en el libro solo abren espacio para cadenas nuevas, nada más (ligeramente modificado).

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
char *my_strdup(char const *string)
{
    
    
	char *new_string;
	new_string = (char *)malloc(strlen(string) + 1);
	if (new_string != NULL)
		strcpy(new_string, string);
	return new_string;
}
int main()
{
    
    
	char *new_p;
	char base_char[] = "Hello World!";
	//复制字符串
	new_p = my_strdup(base_char);
	//检查是否顺利复制
	if (new_p == NULL)
	{
    
    
			printf("error.\n");
			free(new_p);
			exit(EXIT_FAILURE);
	}
	//检查复制结果
	for (int i = 0; i < (int)(strlen(base_char)); i++)
	{
    
    
		if (new_p[i] != base_char[i])
		{
    
    
			printf("new_p[%d] != base_char[%d]", i, i);
			free(new_p);
			exit(EXIT_FAILURE);
		}
			
	}
	printf("success.\n");
	free(new_p);
	return  0;
}

Ejecute e imprima el resultado:
Insertar descripción de la imagen aquí
puede ver que la cadena se copió correctamente. En este ejemplo, también podemos ver la conveniencia de la asignación dinámica de memoria en el desarrollo.

6.3 Creación y destrucción de registros de variantes

El último ejemplo ilustra cómo se puede utilizar la asignación de memoria dinámica para eliminar el espacio de memoria desperdiciado causado por el uso de registros variantes. El conocimiento de estructuras y uniones se utiliza en el programa, si desea conocer los conocimientos relevantes, vaya a: Notas de lectura "C y punteros" (Capítulo 10 Estructuras y uniones)

Primero cree un archivo de encabezado para definir las estructuras que necesita usar

#pragma once
//包含零件专用信息的结构
typedef struct {
    
    
	int cost;
	int supplier;
}Partinfo;
//存储配件专用信息的结构
typedef struct {
    
    
	int n_parts;
	struct SUBASSYPART{
    
    
		char partno[10];
		short quan;
	} *part;
}Subassyinfo;
//存货记录结构,一个变体记录
typedef struct {
    
    
	char partno[10];
	int quan;
	enum {
    
    PART, SUBASSY} type;
	union {
    
    
		Partinfo *part;
		Subassyinfo *subassy;
	}info;
}Invrec;

Luego escriba los procedimientos relevantes para crear registros variantes:

#include <stdio.h>
#include <stdlib.h>
#include "inventor.h"
Invrec *creat_subassy_record(int n_parts)
{
    
    
	Invrec *new_rec;
	//试图为Inverc部分分配内存
	new_rec = malloc(sizeof(Invrec));
	if (new_rec != NULL)
	{
    
    
		//内存分配成功,现在存储SUBASSYPART部分
		new_rec->info.subassy = malloc(sizeof(Subassyinfo));
		if (new_rec->info.subassy != NULL)
		{
    
    
			//为零件获取一个足够大的数组
			new_rec->info.subassy->part = malloc(n_parts * sizeof(struct SUBASSYPART));
			if (new_rec->info.subassy->part != NULL) 
			{
    
    
				//获取内存,填充我们已知道的字段,然后返回
				new_rec->type = SUBASSY;
				new_rec->info.subassy->n_parts = n_parts;
				return new_rec;
			}
			//内存已用完,释放我们原先分配的内存
			free(new_rec->info.subassy);
		}
		free(new_rec);
	}
	return NULL;
}

También existen procedimientos relacionados para la destrucción de registros variantes:

#include <stdlib.h>
#include "inventor.h"
void discard_inventory_record(Invrec *record)
{
    
    
	//删除记录中的变体部分
	switch (record->type)
	{
    
    
	case SUBASSY:
		free(record->info.subassy->part);
		free(record->info.subassy);
		break;
	case PART:
		free(record->info.part);
		break;
	}
	//删除记录的主体部分
	free(record);
}

Este ejemplo es relativamente complejo, ya que contiene estructuras anidadas, lo que implica solicitar memoria capa por capa y luego liberarla capa por capa.

7 Resumen

Este capítulo no tiene mucho contenido, pero es muy práctico. Cuando se declara una matriz, se debe conocer su longitud en el momento de la compilación. La asignación de memoria dinámica permite a un programa asignar espacio de memoria para una matriz cuya longitud se conoce sólo en tiempo de ejecución.

---FIN---

Supongo que te gusta

Origin blog.csdn.net/weixin_43719763/article/details/131176301
Recomendado
Clasificación