Contactos

C Selección de memoria para una cadena. Asignación de memoria dinámica. ¿Cómo asignar memoria por el nuevo operador con la intercepción de una situación crítica en la que la memoria no puede destacar? Situación excepcional BAD_ALLOC. Ejemplo

Trabajar con memoria dinámica es a menudo un cuello de botella en muchos algoritmos, si no está aplicando trucos especiales.

En el artículo, consideraré un par de tales técnicas. Los ejemplos en el artículo difieren (por ejemplo, de) en que la sobrecarga de los nuevos y eliminar operadores se usa y debido a esto, las estructuras de sintaxis serán minimalistas, y el remake del programa es simple. También se describen las piedras submarinas que se describen en el proceso (por supuesto, el Gurú, que leyó el estándar de la corteza a cáscara, no se sorprenderá).

0. ¿Necesitamos memoria manual con memoria?

En primer lugar, verifique cómo un asignador inteligente puede acelerar el trabajo con la memoria.

Escribiremos pruebas simples para C ++ y C # (C # (C # para un excelente administrador de memoria, que divide los objetos para generaciones, utiliza diferentes grupos para objetos diferentes tamaños etc.).

Nodo de clase (público: nodo * siguiente;); // ... para (int i \u003d 0; i< 10000000; i++) { Node* v = new Node(); }

Nodo de clase (nodo público siguiente;) // ... para (int l \u003d 0; l< 10000000; l++) { var v = new Node(); }

A pesar de todo el "vacío esférico" del ejemplo, la diferencia en el tiempo resultó 10 veces (62 ms contra 650 ms). Además, se completa C #, y de acuerdo con las reglas de buen tono en C ++, deben eliminarse objetos dedicados, lo que aumentará aún más la separación (hasta 2580 ms).

1. Objetos de la piscina

La solución obvia es recoger un gran bloque de memoria del sistema operativo y dividirlo en los bloques iguales de tamaño de tamaño (nodo), cuando asigna la memoria, tome un bloque de la piscina, cuando se libere, regrese a la piscina. La piscina es más fácil de organizar con una lista de un solo conectado (pila).

Debido a que vale la pena la tarea de una interferencia mínima en el programa, todo lo que se puede hacer es agregar una mezcla de BlockAlloc a la clase de nodo:
Nodo de clase: BlockAlloc público

En primer lugar, necesitaremos una piscina de bloques grandes (páginas), que se quita de OS o C-RUNNIME. Puede organizarse en la parte superior de las funciones malloc y gratis, pero para una mayor eficiencia (para saltar el nivel de abstracción adicional), utilizamos VirtualAlloc / VirtualFree. Estas funciones asignan bloques de memoria, varios 4k, así como reserva el espacio de direcciones del proceso por bloques, múltiples 64k. Al mismo tiempo, especificando las opciones de confirmación y reserva, saltamos otro nivel de abstracción, reservando el espacio de direcciones y resaltamos las páginas de memoria con una llamada.

Pálido

inline STORSE_T ALIGN (TEsams_T X, STORE_T A) (Retorno ((X-1) | (A-1)) + 1;) // # Defina Alinee (x, a) ((((x) -1) | (a) -1)) + 1) plantilla Clase PagePool (Público: Void * GetPage () (Void * Page \u003d VirtualAlloc (NULL, Pagese, MEM_COMMIT | MEM_RERSERVE, PAGE_READWRITE); PAGES.PUSH_BACK (PAGO); Página de retorno;) ~ PagePool () (para (Vector :: iterator i \u003d páginas.begin (); I! \u003d Pages.End (); ++ I) (VirtualFree (* i, 0, mem_release);) privado: vector Páginas; )

Luego organice la piscina de los bloques del tamaño especificado.

Clase de Blockpool

plantilla. Clase Blockpool: PagePool (Público: Blockpool (): Cabeza (NULL) (BlockSize \u003d Alinee (sizeof (t), alineación); cuenta \u003d Pagesize / BlockSize;) VOID * ALLOCBLOCK () (// TODO: Bloqueo (esto) if (! Head) FormatNewpage (); Void * tmp \u003d cabeza; cabeza \u003d * (void **) cabeza; devuelva tmp;) void libreblock (void * tmp) (// todot: bloqueo (esto) * (void **) tmp \u003d cabeza; Cabeza \u003d tmp;) privado: void * cabeza; size_t bloques de bloques; tess_t.t conteo; formato de vacío () (void * tmp \u003d getpage (); cabeza \u003d tmp; para (size_t i \u003d 0; i;< count-1; i++) { void* next = (char*)tmp + BlockSize; *(void**)tmp = next; tmp = next; } *(void**)tmp = NULL; } };

Comentario // todobir: bloqueo (esto) Los lugares están marcados, que requieren una sincronización interpotional (por ejemplo, use entercritticiticalsection o Boost :: mutex).

Explicaré por qué cuando "Formateo", la página no usa la abstracción de bloques gratuito para agregar un bloque a la piscina. Si algo como algo como estaba escrito.

Para (size_t i \u003d 0; i< PageSize; i += BlockSize) FreeBlock((char*)tmp+i);

Esa página sobre el principio de FIFO se marcaría "por el contrario":

Varios bloques solicitados desde la piscina en una fila tendrían una dirección decreciente. Y el procesador no le gusta volver, se rompe con Prefett ( UPP.: No es relevante para procesadores modernos). Si haces marcado en el ciclo.
Para (size_t i \u003d paysize- (BlockSize- (Pagesize% BlockSize); i! \u003d 0; i - \u003d BlockSize) FreeBlock ...
El ciclo de marcado iría a las direcciones hacia atrás.

Ahora que se hacen los preparativos, puede describir la clase de grado.
Plantilla. Clase BlockAlloc (público: OPERADOR DE VOID DE ESTÁTICO * NUEVO (size_t s) (si (s! \u003d Sizeof (t)) (devolución :: Operador Nuevo (s);) Volver a la piscina.Allocblock ();) Operador de vacío estático BORRAR (VOID * M, size_t s) (si (s! \u003d Sizeof (t)) (:: Operador Eliminar (M);) De lo contrario, si (M! \u003d NULL) (piscina .... ARQUERÍA DE ARQUIDAD * OPERADOR NUEVO (TEVER_T, VACE * M) (return M;) // ... y la advertencia sobre la colocación faltante Eliminar ... Operador de vacío estático Eliminar (Void *, Void *) () Privado: Estadístico Blockpool Piscina; ) Plantilla. Pozo de bloque BLOCKALLOC. :: Piscina;

Voy a explicar por qué necesitas cheques. si (s! \u003d sizeof (t))
¿Cuándo trabajan? Luego, cuando la clase se crea / elimina desde la base T.
Los herederos usarán los nuevos / eliminar los habituales, pero también puede mezclar BlockAlloc. Por lo tanto, somos fácilmente determinados y con seguridad qué clases deben usar las piscinas, sin temer romper algo en el programa. La herencia múltiple también funciona muy bien con esta mezcla.

Listo. Inhire el nodo de BlockAlloc y re-realizar una prueba.
El tiempo de prueba es ahora - 120 ms. 5 veces más rápido. Pero en C # alocator sigue siendo mejor. Probablemente, no hay solo una lista conectada. (Si inmediatamente después de nuevo, llame inmediatamente, llame a Borrar, y por lo tanto, no gaste mucha memoria, conozca los datos en el caché, obtenemos 62 ms. Excepte exactamente. Exactamente como un U.net CLR, como si devuelve las variables locales liberadas inmediatamente a la piscina relevante, sin esperar GC)

2. Contenedor y su contenido de petróleo.

¿A menudo se encuentra en las clases que almacenan muchas subsidiarias diferentes, de modo que la vida útil de la última no más larga que la vida del padre?

Por ejemplo, puede ser una clase XmlDocument llena con clases de nodo y atributos, así como cadenas C (char *) tomadas del texto dentro del nodo. O lista de archivos y directorios en administrador de archivosDescargado una vez cuando vuelva a leer el directorio y ya no cambia.

Como se mostró en la introducción, Eliminar es más caro que nuevo. La idea de la segunda parte del artículo es asignar memoria para objetos infantiles en un bloque grande asociado con el objeto principal. Al eliminar el objeto principal, las subsidiarias, como de costumbre, son causadas por destructores, pero la memoria no será devuelta, es gratuita para obtener un bloque grande.

Cree una clase de PointerBumpalocator, que puede morder de una pieza grande de piezas de diferentes tamaños y resaltar un nuevo bloque grande cuando se agotará los viejos.

Clase de PointerBumpalocator

plantilla. Clase PointerBumplollocator (Público: PointerBumpalocator (): GRATIS (0) () VOID * ALLOCBLOCK (BLOQUE DE TEAME_T) (// TODO: Bloqueo de bloqueo (este) Bloque \u003d Alinee (bloque, alineación); if (Block\u003e Libre) (gratis \u003d Alinee (Bloque, Pagese); cabeza \u003d getpage (libre);) Void * tmp \u003d cabeza; cabeza \u003d (char *) cabeza + bloque; libre - \u003d bloque; devolver tmp;) ~ punterobumpalocator () (para (vector :: iterator i \u003d páginas.begin (); I! \u003d Pages.End (); ++ i) (VirtualFree (* I, 0, MEM_RELEASE);)) Privado: Void * GetPage (tamaño Tamaño) (Vaquero * Página \u003d VirtualAlloc (NULL, TAMAÑO, MEM_COMMIT | MEM_RERVE, PAGE_READWRITE); PAGES.PUSH_BACK (PAGE) ; Página de retorno;) Vector Páginas; Nulo * cabeza; Size_t libre; ) Typedef pointerbumpalocator<> Fallo de infaeza;

Finalmente, describimos el aditivo infantil con la sobrecarga de nuevo y eliminar, dirigida al alooctor especificado:

Plantilla. Struct ChildObject (aloCator de retorno.AllockBlock (s);) Void Static New * OPERADOR NUEVO (ALOCIADOR DE DEVOLUCIÓN-\u003e ABLOCBLOCK (S);) Operador de vacío estático Eliminar (Void *, size_t) () // * 1 Operador de Vacío estático BORRAR (VOID *, A *) () Operador de vacío estático BORRAR (VOID *, A &) Privado: ARQUERÍA ARQUERÍA * OPERADOR NUEVO););

En este caso, además de agregar una impureza en la clase infantil, también será necesario corregir todas las llamadas a nuevo (o usar el patrón de fábrica). La nueva sintaxis del operador será la siguiente:

Nuevos (... Parámetros para el operador ...) ChildObject (... Parámetros de diseño ...)

Por conveniencia, fijo a dos nuevos operadores que reciben un & o A *.
Si el asignador se agrega a la clase principal como miembro, más conveniente para la primera opción:
Nodo \u003d nuevo (alocador) xmlnode (nodo);
Si el asignador se agrega como antepasado (aditivo), es más conveniente para el segundo:
Nodo \u003d nuevo (esto) xmlnode (nodo);

No se proporciona una sintaxis especial para la llamada Eliminar, el compilador causará un borrado estándar (marcado * 1), independientemente de cuál de las nuevas declaraciones se utilizará para crear un objeto. Es decir, sintaxis eliminar. normal:
ELIMINAR NODO;

Si hay una excepción en el diseñador infantil (o su heredero), se llama un borrado con una firma correspondiente a la firma del nuevo operador que se usa al crear este objeto (el primer parámetro TEVER_T será reemplazado por VOID *).

La nueva ubicación del operador en la sección privada protege contra el nuevo parámetro de total.

Daré un ejemplo completo de usar el par de alogador-ChildObject:

Ejemplo

clase XmlDocument: Public ValorAllocator (público: ~ Xmldocument () (para (Vector :: ITERATOR I \u003d NODES.BEGIN (); I! \u003d Nodos.end (); ++ i) (Eliminar (* i);)) Void AddNode (CHAR * contenido, nombre * nombre) (char * c \u003d (char *) allocblock (Strllen (contenido) +1); Strcpy (C, contenido); char * n \u003d (char *) allocblock (strlen (nombre) +1); strcpy (n, contenido); nodes.push_back (nuevo XMlNode (C, N));) Clase XMlNode: ChildObject público (Público: XmlNode (char * _content, char * _name): contenido (_content), nombre (_name) () Privado: char * contenido; CHAR * nombre); Privado: Vector nodos; )

Conclusión. El artículo fue escrito hace 1,5 años para la caja de arena, pero a Mas, no le gustó el moderador.

    almacena variables globales y constantes;

    el tamaño se determina al compilar.

    Pila (pila)

    almacena variables locales, argumentos de características y valores de cálculo intermedio;

    el tamaño se determina cuando se inicia el programa (4 MB se asigna generalmente).

    Montón (montón)

    memoria distribuida dinámicamente;

    OS destaca la memoria para las piezas (según sea necesario).

La memoria distribuida dinámicamente se debe utilizar si tenemos por adelantado (en el momento de escribir un programa), no sabemos cuánta memoria necesitaremos (por ejemplo, el tamaño de la matriz depende de lo que el usuario ingrese durante el programa) y Al trabajar con grandes volúmenes de datos.

La memoria dinámica, también llamada "pila", se asigna explícitamente a petición del programa de recursos sistema operativo y controlado por el puntero. No se inicializa automáticamente y debe ser liberado claramente. En contraste con la memoria estática y automática, la memoria dinámica prácticamente no está limitada (limitada solo por el tamaño memoria de acceso aleatorio) y puede cambiar durante el programa

Trabajando con memoria dinámica con

Para trabajar con memoria dinámica en el idioma, se utilizan las siguientes funciones: malloc, callo, gratis, realloc. Considerarlos con más detalle.

    Selección (captura de memoria): Void * malloc (tamaño Tamaño);

Como parámetro de entrada, la función toma el tamaño de la memoria que desea resaltar. El valor devuelto es el puntero al sitio de memoria asignado en una pila. Si el sistema operativo no pudo asignar la memoria (por ejemplo, la memoria no fue suficiente), entonces Malloc devuelve 0.

    Después del final del trabajo con la memoria dinámica asignada, debe liberarlo. Para este propósito, se usa la función libre, que devuelve la memoria para el sistema operativo controlado: sin efecto (VOID * PTR);

Si la memoria se libera antes del final del programa, está exento automáticamente al finalizar el programa. Sin embargo, la liberación explícita de la memoria innecesaria es un signo de un buen estilo de programación.

Ejemplo: // asignación de memoria bajo 1,000 elementos de tipo int

int * p \u003d (int *) malloc (1000 * sizeof (int));

if (p \u003d\u003d null) cout<< "\n память не выделена";

libre (P); // reembolso de la memoria en un grupo

2. Asignación (Captura de memoria): VOID * CALLOC (TAMAÑO_T NMEMB, TAMAÑO TEAME_T);

La función funciona de manera similar a malloc, pero se caracteriza por la sintaxis (en lugar del tamaño de la memoria asignada, debe especificar el número de elementos y el tamaño de un elemento) y el hecho de que se restablezca la memoria seleccionada. Por ejemplo, después de realizar la CALLOC INT * P \u003d (INT *) (1000, STEEDOF (INT)) P, P se apuntará al comienzo de una matriz de tipo INT de 1000 elementos inicializados con ceros.

3. Cambio del tamaño de la memoria: VOID * REALLOC (VOID * PTR, tamaño TAMAÑO_T);

La función cambia el tamaño de la memoria asignada (lo que indica pTR, Recibido de la llamada malloc, calloc o realloc). Si el tamaño especificado en el parámetro tamaño Más que el que se destacó bajo el puntero. pTR, Se verifica si existe la oportunidad de resaltar las células faltantes de la memoria en una fila con ya dedicadas. Si no hay espacio suficiente, entonces se distingue una nueva sección de la memoria. tamaño e indicadores de datos pTR. Copia en la parte superior de un nuevo sitio.

En el proceso de ejecución del programa, el sitio de memoria dinámico está disponible en todas partes donde el puntero aborda esta sección está disponible. Por lo tanto, las siguientes tres opciones son posibles con la memoria dinámica asignada en una determinada unidad (por ejemplo, en la función desenrollar).

    El puntero (al sitio de memoria dinámico) se define como un objeto de memoria automático local. En este caso, la memoria seleccionada no está disponible cuando el bloque de ubicación está enviando el bloque de ubicación, y es necesario soltarlo antes de salir del bloque.

(int * p \u003d (int *) calloc (n, sizeof (int))

libre (p); // Din Libere. Memoria

    El puntero se define como una memoria estática local. La memoria dinámica asignada una vez en el bloque está disponible a través de un puntero en cada retención en el bloque. La memoria debe ser liberada solo al final de su uso.

(Estático int * p \u003d (int *) calloc (N, sizeof (int));

p \u003d (int *) calloc (n, sizeof (int));

f (50); // Decan asignación. Memoria con su posterior liberación

f1 (100); // Decan asignación. Memoria (primera apelación)

f1 (100); // Trabajar con Dean. Memoria

f1 (0); // Din Libere. Memoria

    El puntero es un objeto global con respecto al bloque. La memoria dinámica está disponible en todos los bloques donde el puntero es "visible". La memoria debe ser liberada solo al final de su uso.

int * pg; // Puntero de trabajo para Dean. Memoria (variable global)

init nulo (tamaño int)

para (i \u003d 0; i< size; i++) //цикл ввода чисел

(Printf ("x [% d] \u003d", i);

sCANF ("% d", & pg [i]);

int sum sum (tamaño int)

para (i \u003d 0; i< size; i++) //цикл суммирования

// Asignación de memoria

pg \u003d (int *) calloc (n, sizeof (int));

// trabajar con Dean PAM

printf (\\ ns \u003d% d \\ n ", suma (n));

libre (pg); pg \u003d nulo; // liberación de memoria

Trabajar con memoria dinámica en C ++.

En C ++, hay un mecanismo para la asignación y la liberación de la memoria, estas son funciones. nuevo y borrar.Ejemplo de uso nuevo: int * p \u003d nuevo int; // selección de memoria bajo 1000 EL-PEDIDO I.E. Al usar la función nuevo No hay necesidad de dar un puntero y no necesita usar. tamaño de (). Liberación asignada por nuevo La memoria se realiza siguiendo la siguiente llamada: Eliminar P; Si desea seleccionar la memoria para un elemento, puede usar int * q \u003d nuevo int; o int * q \u003d nuevo int (10); // seleccionado INT está inicializado por el valor de 10 en este caso, la eliminación se verá así: Eliminar q;

tiempo de espera programas. Bajo las variables locales, el programa toma memoria del espacio de la pila. Sin embargo, las variables locales requieren una determinación preliminar de la cantidad de memoria asignada para cada situación. Aunque C ++ implementa efectivamente tales variables, requieren que un programador sepa de antemano qué cantidad de memoria es necesaria para cada situación.

La segunda forma en que C ++ puede almacenar información es usar el sistema de distribución dinámica. En este método, la memoria se distribuye para obtener información del área libre de la memoria según sea necesario. El área de memoria libre está entre el código del programa con su área de memoria constante y la pila (Fig. 24.1). El alojamiento dinámico es conveniente cuando se desconoce la cantidad de datos de datos.


Higo. 24.1.

A medida que el programa utiliza el área de la pila aumenta, es decir, el programa en sí determina el volumen de la memoria de la pila. Por ejemplo, un programa de gran número. funciones recursivas tomará más memoria de pila que un programa que no tiene funciones recursivasDado que las variables locales y las direcciones de devolución se almacenan en pilas. Memoria para el programa en sí y variables globales destacarse en todo tiempo de espera Los programas son constantes para un entorno específico.

La memoria asignada en el proceso de ejecución del programa se llama dinámica. Despues de la selección dinámica Se guarda en su liberación explícita, que se puede realizar solo con una función especial de operación o biblioteca.

Si la memoria dinámica no se libera antes del final del programa, está exento automáticamente al finalizar el programa. Sin embargo, la liberación explícita de la memoria innecesaria es un signo de un buen estilo de programación.

En el proceso de ejecución del programa, el sitio de memoria dinámico está disponible en todas partes donde el puntero aborda esta sección está disponible. Por lo tanto, son posibles los siguientes. tres opciones de trabajo con memoria dinámica.secretado en una determinada unidad (por ejemplo, en el cuerpo del UNCOAR).

  • El puntero (al sitio de memoria dinámico) se define como un objeto de memoria automático local. En este caso, la memoria seleccionada no está disponible cuando el bloque de ubicación está enviando el bloque de ubicación, y es necesario soltarlo antes de salir del bloque.
  • El puntero se define como una memoria estática local. La memoria dinámica asignada una vez en el bloque está disponible a través de un puntero en cada retención en el bloque. La memoria debe ser liberada solo al final de su uso.
  • El puntero es un objeto global con respecto al bloque. La memoria dinámica está disponible en todos los bloques donde el puntero es "visible". La memoria debe ser liberada solo al final de su uso.

Todas las variables declaradas en el programa se colocan en un área de memoria continua llamada segmento de datos. Tales variables no cambian su tamaño durante la ejecución del programa y se llaman estático. El tamaño del segmento de datos puede no ser suficiente para colocar grandes cantidades de información. La salida de esta situación es usar memoria dinámica. Memoria dinámica - Esta es la memoria asignada por el programa para su trabajo menos el segmento de datos, la pila en la que se encuentran las variables locales de las subrutinas y el programa en sí.

Los punteros usan punteros para trabajar con memoria dinámica. Con su ayuda, acceso a los sitios de memoria dinámica que se llaman variables dinámicas. Para almacenamiento variables dinámicas Se resalta un área de memoria especial, llamada "errores".

Variables dinámicas Creado con funciones y operaciones especiales. Existen hasta el final del programa, o hasta que la memoria asignada a ellos se libere utilizando funciones u operaciones especiales. Eso es, vida variables dinámicas - Desde el punto de creación hasta el final del programa o para explícito. memoria de lanzamiento.

C ++ usa dos formas de trabajar con memoria dinámica:

  1. uso de operaciones nuevo y eliminar;
  2. el uso de la familia de funciones Malcos (Calloc) (heredada de C).

Trabajar con memoria dinámica utilizando nuevas y eliminar operaciones.

En el lenguaje de programación de C ++ para dISTRIBUCIÓN DE MEMORIA DINÁMICA Hay operaciones nuevas y eliminan. Estas operaciones se utilizan para resaltar y liberar bloques de memoria. El área de memoria en la que se colocan estos bloques, llamados memoria libre.

La nueva operación le permite resaltar y hacer una parcela gratuita accesible en la memoria principal, cuyas dimensiones corresponden al tipo de datos definidas por el nombre del tipo.

Sintaxis:

nuevo NameType;

nuevo NameType [Inicializador];

El área resaltada se ingresa por el valor determinado por inicializadorque no es un elemento obligatorio. En el caso de una ejecución exitosa, la nueva devuelve la dirección del inicio del área seleccionada de la memoria. Si no se puede seleccionar la sección de las dimensiones deseadas (no hay memoria), la nueva operación devuelve el valor de dirección cero (NULL).

OPERACIÓN DE LA APLICACIÓN SINTAX:

Puntero \u003d Nuevo Nametype [Inicializador];

La nueva operación de flotación resalta el área de memoria de 4 bytes. La nueva operación INT (15) resalta el área de memoria de 4 bytes e inicializa este sitio con todo el valor 15. La sintaxis de uso de la red y el eliminar implica el uso de los punteros. Anteriormente, cada puntero debe ser anunciado:

tipo * Indicador de nombre;

Por ejemplo:

flotando * pi; // Anuncio de la PI PI \u003d nueva variable de flotador; // seleccionando la memoria para la variable pi * pi \u003d 2.25; // Asignación de valor

Como tal como tipo, como los tipos estándar se pueden utilizar. int, largo, flotando, doble, char.

El nuevo operador se usa con mayor frecuencia para alojar los tipos específicos de tipo en la memoria de datos, por ejemplo, estructuras:

nodo de estructura (CHAR * nombre; valor int; nodo * siguiente); Nodo * pNode; // anunció pNode \u003d nuevo puntero de nodo; // PNODE-\u003e NOMBRE \u003d "ATA" se asigna; // asigna pNode-\u003e Valor \u003d 1 valores; PNode-\u003e Siguiente \u003d NULL;

Como tipo de nombre en nueva operación, se puede usar una matriz:

nueva Timasiva

Cuando asigna memoria dinámica para la matriz, sus tamaños deben estar completamente definidos. Por ejemplo:

pTR \u003d NUEVO INT; // 10 Elementos de tipo INT o 40 bytes PTR \u003d NUEVO INT; // incorrectamente, porque Tamaño no definido

Dicha operación le permite resaltar el área en la memoria dinámica para colocar una matriz del tipo correspondiente, pero no le permite inicializarse. Como resultado de la ejecución, la nueva operación devolverá el puntero, cuyo valor es la dirección del primer elemento de la matriz. Por ejemplo:

int * n \u003d nuevo int;

La nueva operación realiza un tipo de memoria dinámica como suficiente para colocar el valor del INT, y registra la dirección del comienzo de este área en la variable N. La memoria bajo la variable N (tamaño suficiente para colocar un puntero) se resalta en la etapa de compilación.

Muy a menudo hay tareas de procesamiento de matrices de datos, cuya dimensión tiene por adelantado desconocida. En este caso, es posible utilizar uno de los dos enfoques:

  • selección de memoria para una matriz estática que contiene el máximo número posible de elementos, pero en este caso la memoria no se consume racional;
  • asignación de memoria dinámica para almacenar matriz de datos.

Para usar las funciones de asignación de memoria dinámica, es necesario describir el puntero, que es la dirección inicial del almacenamiento de los elementos de la matriz.

int * p; // puntero para escribir int

La dirección inicial de la matriz estática está determinada por el compilador en el momento de su anuncio y no se puede cambiar.

Para una matriz dinámica, la dirección inicial se asigna al puntero declarado a la matriz durante la ejecución del programa.

Funciones de asignación de memoria dinámica estándar

Las funciones de la memoria dinámica se encuentran en la RAM, una parte continua de la longitud requerida y devuelve la dirección inicial de esta área.

Funciones de distribución de memoria dinámica:

nulo * malloc (tamaño masivo);
void * Calloc (Naturallos, Tamaño Elementavabytes);

Para utilizar las funciones de distribución de memoria dinámica, debe conectar la biblioteca. :

#Incluir.

Dado que ambas funciones presentadas como un valor devuelto tienen un puntero a un tipo vacío vacío, se requiere una atribución explícita del tipo de valor de retorno.

Para determinar el tamaño de una matriz en bytes utilizados como el argumento de la función malloc (), se requiere el número de elementos para multiplicarse por un elemento. Dado que los elementos de la matriz pueden ser tanto estos datos de tipo y los tipos de compuestos (por ejemplo, estructuras), para determinar con precisión el tamaño del elemento, generalmente recomendó el uso de la función

int stemedof (tipo);


que define la cantidad de byte ocupada por el elemento del tipo especificado.

La memoria, resaltada dinámicamente utilizando las funciones Calloc (), malloc (), se puede liberar utilizando la función

libre (puntero);

"La regla de tono común" en la programación es la liberación de la memoria asignada dinámicamente en ausencia de su uso posterior. Sin embargo, si la memoria dedicada dinámicamente no está exenta, se liberará al finalizar la ejecución del programa.

Asignación de memoria dinámica para matrices unidimensionales

La forma de circulación a los elementos de la matriz utilizando los punteros es la siguiente forma:

int a, * p; // describe una matriz estática y un puntero
int b;
p \u003d a; // Asignar la dirección inicial de la matriz
... // ingresando los elementos de la matriz
b \u003d * p; // b \u003d a;
b \u003d * (p + i) // b \u003d a [i];

Ejemplo en C: organización de una matriz dinámica unidimensional y entrada de sus elementos.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27


#Incluir.
#Incluir.
#Incluir.
int main ()
{
int * a; // puntero a una matriz
int i, n;
Sistema ("CHCP 1251");
Sistema ("CLS");
Printf ( "Ingrese el tamaño de la matriz:");
SCANF ("% d", & n);
// Asignación de memoria
a \u003d (int *) malloc (n * sizeof (int));
// ingresando los elementos de la matriz
para (i \u003d 0; i {
Printf ("A [% D] \u003d", i);
SCANF ("% d", y a [i]);
}
// Conclusión de los elementos de la matriz.
para (i \u003d 0; i Printf ("% d", un [i]);
Libre (a);
getchar (); getchar ();
retorno 0;
}


El resultado del programa:

Asignación de memoria dinámica para matrices bidimensionales

Permítales que se realicen en la memoria dinámica una matriz que contiene n cadenas y columnas M. La matriz bidimensional se ubicará en RAM en forma de cinta que consiste en elementos de cadenas. En este caso, el índice de cualquier elemento de la matriz bidimensional puede obtenerse por la fórmula

índice \u003d i * m + j;

donde yo es el número de línea actual; J es el número de la columna actual.

Considere la matriz 3x4 (ver Fig.)

El índice del elemento dedicado se determinará como

índice \u003d 1 * 4 + 2 \u003d 6

La cantidad de memoria requerida para acomodar una matriz bidimensional se determina como

n · m · (tamaño del elemento)

Sin embargo, dado que con esta Declaración, el compilador claramente no indica claramente la cantidad de elementos en la línea y la columna de la matriz bidimensional, la apelación tradicional al artículo especificando el índice de la cadena y el índice de columna es incorrecto:

a [I] [J] - Incorrecto.

El atractivo correcto al elemento que usa el puntero se verá como

* (P + I * M + J),
Dónde

  • p es un puntero de matriz
  • m - Número de columnas,
  • i - Líneas índice,
  • j es el índice de columna.

Ejemplo en si

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38

#Define _crt_secure_no_warnings.
#Incluir.
#Incluir.
#Incluir.
int main ()
{
int * a; // puntero a una matriz
int i, j, n, m;
Sistema ("CHCP 1251");
Sistema ("CLS");
Printf ( "Ingrese el número de filas:");
SCANF ("% d", & n);
printf ();
SCANF ("% d", & m);
// Asignación de memoria
a \u003d (int *) malloc (n * m * sizeof (int));
// ingresando los elementos de la matriz
para (i \u003d 0; i // Ciclo de fila
{
para (j \u003d 0; j // ciclo en columnas
{
SCANF ("% d", (a + i * m + j));
}
}
// Conclusión de los elementos de la matriz.
para (i \u003d 0; i // Ciclo de fila
{
para (j \u003d 0; j // ciclo en columnas
{
Printf ("% 5d", * (A + I * M + J));
}
Printf ("\\ n");
}
Libre (a);
getchar (); getchar ();
retorno 0;
}

Resultado de la ejecución

Otro método de memoria dinámica también es posible para una matriz bidimensional, utilizando una matriz de punteros. Para esto necesitas:

  • seleccione la unidad RAM bajo la matriz de punteros;
  • seleccione los bloques de RAM para arreglos unidimensionales, que son las filas de la matriz deseada;
  • registre las direcciones de filas a la matriz de punteros.

Un método gráfico de este tipo de asignación de memoria puede representarse de la siguiente manera.


Con este método, el tamaño del compilador está claramente indicado por el número de filas y el número de columnas en la matriz.
Ejemplo en si

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43

#Define _crt_secure_no_warnings.
#Incluir.
#Incluir.
#Incluir.
int main ()
{
int ** a; // puntero al puntero a la línea de elementos
int i, j, n, m;
Sistema ("CHCP 1251");
Sistema ("CLS");
Printf ( "Ingrese el número de filas:");
SCANF ("% d", & n);
Printf ( "Ingrese el número de columnas:");
SCANF ("% d", & m);
// seleccionando la memoria para las líneas
// ingresando los elementos de la matriz
para (i \u003d 0; i // Ciclo de fila
{
// seleccionando la memoria para las cadenas de almacenamiento
a [I] \u003d (int *) malloc (m * sizeof (int));
para (j \u003d 0; j // ciclo en columnas
{
Printf ("a [% d] [% d] \u003d", i, j);
SCANF ("% d", y a [i] [j]);
}
}
// Conclusión de los elementos de la matriz.
para (i \u003d 0; i< n; i++) // Ciclo de fila
{
para (j \u003d 0; j< m; j++) // ciclo en columnas
{
Printf ("% 5d", a [i] [j]); // 5 conocido bajo el elemento de la matriz
}
Printf ("\\ n");
}
// Memoria de limpieza
para (i \u003d 0; i< n; i++) // Ciclo de fila
Gratis (a [i]); // liberación de la memoria para la línea
Libre (a);
getchar (); getchar ();
retorno 0;
}

El resultado del programa es similar al caso anterior.

Uso de la asignación de memoria dinámica bajo punteros de fila, puede colocar matrices gratuitas. Una matriz bidimensional (matriz) es gratuita, cuyo tamaño de cadena puede ser diferente. La ventaja de usar una matriz gratuita es que no es necesario quitar la memoria de la computadora con una reserva para colocar la cadena de la longitud máxima posible. De hecho, la matriz libre es una matriz de punteros unidimensionales a matrices de datos unidimensionales.

Para acomodar la matriz en la RAM con líneas de diferentes longitudes, debe ingresar una matriz adicional M, en la que se almacenarán el tamaño de las cadenas.

Ejemplo en si

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43

#Define _crt_secure_no_warnings.
#Incluir.
#Incluir.
int main ()
{
int ** a;
int i, j, n, * m;
Sistema ("CHCP 1251");
Sistema ("CLS");
Printf ( "Ingrese el número de filas:");
SCANF ("% d", & n);
a \u003d (int **) malloc (n * sizeof (int *));
m \u003d (int *) malloc (n * sizeof (int)); // Array de la cantidad de elementos en líneas de matriz A
// ingresando los elementos de la matriz
para (i \u003d 0; i {
Printf ( "Ingrese el número de columnas% D:", I);
SCANF ("% d", & m [i]);
a [I] \u003d (int *) malloc (m [i] * sizeof (int));
para (j \u003d 0; j Printf ("un [% d] [% d] \u003d", i, j);
SCANF ("% d", y a [i] [j]);
}
}
// Conclusión de los elementos de la matriz.
para (i \u003d 0; i {
para (j \u003d 0; j {
Printf ("% 3d", un [i] [j]);
}
Printf ("\\ n");
}
// liberación de memoria
para (i \u003d 0; i< n; i++)
{
Gratis (a [i]);
}
Libre (a);
Libre (m);
getchar (); getchar ();
retorno 0;
}


Resultado de la ejecución

Redistribución de la memoria.

Si el tamaño de la memoria de la memoria no se puede especificar de antemano, por ejemplo, al ingresar una secuencia de valores a un comando específico, para aumentar el tamaño de la matriz, cuando ingresa el siguiente valor, debe realizar Los siguientes pasos:

  • Seleccione la unidad de dimensión de N + 1 (1 más que el tamaño actual de la matriz)
  • Copie todos los valores almacenados en una matriz en un área recién dedicada de la memoria.
  • Memoria libre asignada anteriormente para el almacenamiento de la matriz
  • Mueva el puntero para iniciar la matriz al principio del área de memoria recién seleccionada.
  • Agregar una matriz Último valor introducido

Todos los pasos anteriores (excepto los últimos) realizan la función.

void * RealLOC (Void * PTR, Tamaño Tamaño_T);

  • pTR es un puntero a un bloque de memoria asignada previamente con funciones MALLOC (), Calloc () o para moverse a un nuevo lugar. Si este parámetro es nulo, se asigna la nueva unidad, y la función le devuelve un puntero.
  • el tamaño es un nuevo tamaño, en bytes del bloque de memoria asignado. Si el tamaño \u003d 0, se libera la memoria dedicada anteriormente y la función devuelve un puntero cero, el PTR se instala en NULL.

El tamaño del bloque de memoria al que se refiere el parámetro PTR cambia a los bytes de tamaño. El bloque de memoria puede disminuir o aumentar de tamaño. Los contenidos del bloque de memoria se conservan incluso si la nueva unidad tiene un tamaño más pequeño que el anterior. Pero esos datos que van más allá del nuevo bloque se descartan. Si el nuevo bloque de memoria es mayor que el anterior, los contenidos de la memoria recién asignada serán inciertos.
si (i\u003e 2) i - \u003d 2;
Printf ("\\ n");
A \u003d (int *) Realloc (A, I * STEYOF (INT)); // Reduciendo el tamaño de la matriz para 2
para (int j \u003d 0; j< i; j++)
Printf ("% d", a [j]);
getchar (); getchar ();
retorno 0;
}

El programa puede almacenar información en la memoria principal de la computadora de dos maneras principales. El primero de ellos utiliza variables globales y locales, incluidas matrices, estructuras y clases. En el caso de las variables locales globales y estáticas, la ubicación de almacenamiento se fija para todo el tiempo de ejecución del programa. En el caso de las variables locales, la memoria se resalta en la pila. Aunque en Borland C ++, el trabajo con estas variables se implementa de manera muy eficiente, su uso requiere que un programador sepa con anticipación el tamaño de la memoria que se requerirá durante la ejecución del programa.

El segundo método de almacenamiento de información es el uso de un sistema de memoria dinámica de la memoria Borland C ++. En este método, la memoria para almacenar información se asigna desde el área libre de la memoria según sea necesario y regresa, es decir, Se libera cuando la necesidad de ella desapareció. El área de memoria libre se encuentra entre el área de memoria donde se encuentra el programa y la pila. Esta área se llama un grupo (montón) y se usa para solicitudes de asignación de memoria dinámica.

La ventaja de usar la memoria dinámica es que la misma memoria se puede usar para almacenar diversas información en el proceso de ejecución del programa. Dado que la memoria se asigna para un propósito específico y se libera cuando se ha completado su uso, puede usar la misma memoria en otro punto de tiempo para otros fines en otra parte del programa. Otra ventaja de la asignación de memoria dinámica es la capacidad de crear listas relacionadas, árboles binarios y otras estructuras de datos dinámicos.

El núcleo de la asignación de memoria dinámica de la memoria del idioma con las funciones Funoc () y Free () que son partes de la biblioteca estándar. Cada vez que la función Malloc (), se recibe una memoria para asignar la memoria, se distingue una parte de la memoria libre disponible. Cada vez que se libera esta memoria utilizando la función libre (), esta memoria devuelve el sistema.

El idioma de C ++ define dos operadores de asignación de memoria dinámica: nuevo y eliminar.

El estándar ANSI C define solo cuatro funciones de asignación de memoria dinámica: Calloc (), Malloc (), Free () y RealLOC (). Sin embargo, Borland C ++ contiene varias otras funciones de asignación de memoria dinámica. Al compilar el código para un modelo de memoria moderno de 32 bits, la memoria es plana y generalmente se usa solo cuatro funciones de asignación de memoria estándar.

El estándar ANSI C define que la información del encabezado requerida para la asignación de memoria dinámica está contenida en el archivo STDLIB.H. Sin embargo, Borland C ++ le permite usar archivos de encabezado STDLIB.H o ALLOC.H. Aquí usamos el archivo de encabezado STDLIB.H, ya que proporciona la portabilidad. Algunas otras funciones de asignación de memoria dinámica requieren archivos de encabezado ALLOC.H, MALLOC.H o DOS.H. Es necesario prestar especial atención a qué archivo de encabezado se necesita para usar cada función.



¿Te gustó el artículo? Compártelo