Notas de estudio sobre arquitectura ARM y lenguaje C (Wei Dongshan) (2) - variables globales, montón y pila


1. Por qué las variables globales no se inicializan

#include "stdio.h"
int g_a=123;
int add_val(volatile int v){
    
    
    volatile int a=321;
    v=v+a;
    return v;
}
int main(){
    
    
    static volatile int s_a=1;
    volatile int b;
    b=add_val(s_a);
    return 0;
}

En esta función, como se mencionó en la sección anterior, la variable local a en la función add_val(volatile int v) se inicializa y asigna al espacio de la pila, pero ¿por qué las variables globales definidas int g_a=123 y static volatile int s_a=1 ; comando para inicializar?

2. Cómo inicializar variables globales

Para las variables locales, la inicialización consiste en asignar el espacio de datos de los bytes especificados y el espacio de direcciones de los bytes especificados. Las variables locales se almacenan en la RAM. Para las variables globales, se puede acceder a las variables globales en cualquier parte del programa, por lo que generalmente se almacenan en la RAM. Cuando se inicia el programa, las variables globales se inicializan y se asigna espacio de memoria. Sin embargo, si usamos la palabra clave const cuando definimos variables globales, o definimos variables globales como tipos estáticos, entonces estas variables generalmente se almacenan en Flash en lugar de RAM. Esto se debe a que las variables de tipo const generalmente se consideran constantes y sus valores no se modificarán, por lo que se pueden almacenar en Flash. La variable de tipo estático se inicializa una sola vez durante la ejecución del programa y existe durante toda la ejecución del programa, por lo que también se puede almacenar en Flash.
Para las variables globales en flash, se copiarán a la dirección especificada en RAM al leer y escribir , esta dirección es determinada por el compilador y el sistema.
inserte la descripción de la imagen aquí
En KEIL, el enlazador LINKER establecerá la dirección base de RAM y la dirección base de FLASH. Por lo tanto, la CPU primero lee la dirección de datos de la memoria flash y copia los datos en el registro R0, luego esta variable global se guarda en la dirección 0x20000000 de la RAM.

3. ¿Qué es la pila?

Según el estudio de variables locales y variables globales, probablemente conozca el concepto de pila:

Stack (pila) es una estructura de datos en la memoria de la computadora, que sigue el principio de "Last In First Out (LIFO)". En una pila, agregar y eliminar datos solo puede ocurrir en la parte superior de la pila, por lo que los últimos datos agregados a la pila se eliminarán primero.
En una computadora, la pila ** generalmente se usa para almacenar datos temporales, como parámetros, variables locales y direcciones de retorno cuando se llama a una función. **Cuando se llama a una función, sus parámetros y variables locales se asignan en la pila, y cuando la función regresa, estos datos se eliminan de la pila. Por lo tanto, la pila proporciona una forma conveniente de administrar los datos durante las llamadas y devoluciones de funciones.
El sistema operativo suele gestionar automáticamente la pila. Utiliza punteros para rastrear la posición de la parte superior de la pila. Cuando es necesario insertar datos en la pila, el puntero se mueve hacia abajo; cuando es necesario extraer datos de la parte superior de la pila. la pila, el puntero se mueve hacia arriba. Dado que el tamaño de la pila suele ser fijo, el programa se bloqueará cuando la pila se desborde. Por lo tanto, al escribir un programa, debe administrar cuidadosamente el tamaño de la pila para evitar problemas de desbordamiento de la pila.

(1) La estructura de la pila

Para el núcleo cortex-m3 de la arquitectura ARM, la pila es una pila completa que crece hacia abajo, es decir, el puntero de la pila sp apunta inicialmente a la dirección alta configurada por el usuario, como 0x20001000. Debido al crecimiento hacia abajo , la dirección a la que apunta el puntero superior de la pila es la dirección donde se encuentran los datos más recientes, y la dirección a la que apunta el puntero inferior de la pila es la dirección donde se encuentran los datos más antiguos. Cuando la pila está llena, el puntero superior de la pila apuntará a la dirección donde se encuentra el puntero inferior de la pila, y agregar datos en este momento hará que la pila se desborde.
El valor del registro de "puntero de pila" de la pila que crece hacia abajo apunta a la dirección de la parte superior de la pila, y su valor restará el tamaño de los datos empujados cada vez que se empuje la pila, de modo que crezca hacia abajo. Cuando se extraen los datos en la parte superior de la pila, el valor del puntero de la pila aumentará y, por lo tanto, se moverá hacia arriba.

(2) Desbordamiento de pila

inserte la descripción de la imagen aquí
La pila completa generada hacia abajo, si la variable definida en la función inicializada es demasiado grande, el puntero de la pila sigue bajando. Cuando la pila está llena, el puntero superior de la pila apuntará a la dirección del puntero inferior de la pila, y agregar datos en este momento provocará un desbordamiento de la pila.

Cuando la pila se desborda, ocurren las siguientes consecuencias:
Bloqueo del programa: cuando la pila se desborda, el programa inevitablemente se bloquea, lo que puede provocar la pérdida de datos o la incapacidad del programa para continuar ejecutándose.
Corrupción de datos: dado que el desbordamiento de la pila provocará datos fuera de los límites, los datos de la pila pueden sobrescribirse o dañarse, lo que hará que el programa no funcione correctamente.
Vulnerabilidades de seguridad: los desbordamientos de pila también pueden provocar vulnerabilidades de seguridad, donde un atacante puede controlar el flujo de ejecución de un programa agregando datos maliciosos a la pila y, por lo tanto, ejecutando código malicioso.
Pérdidas de memoria: si hay objetos de pila en el programa, es posible que estos objetos no se liberen correctamente cuando la pila se desborda, lo que provoca pérdidas de memoria.

(3) Cómo evitar el desbordamiento de la pila: calcule el tamaño de la pila

El objetivo es encontrar la cadena de llamadas que " utiliza la mayor cantidad de variables locales ". Aquí está la respuesta de GPT:
1. Observe la cadena de llamada de función: el tamaño de la pila depende de la profundidad de la llamada de función y del tamaño de las variables locales de cada función. Por lo tanto, observar la cadena de llamadas de función y el tamaño de las variables locales en el programa puede estimar aproximadamente el tamaño de la pila.
2. Calcule el tamaño de los parámetros de la función y los valores devueltos: los parámetros de la función y los valores devueltos también ocuparán espacio en la pila. Por lo tanto, estimar el tamaño de los parámetros de la función y los valores devueltos puede ayudar a estimar el tamaño de la pila.
3. Considere la recursión: si hay funciones recursivas en el programa, debe considerar la profundidad de la recursión y el tamaño de las variables locales para estimar el tamaño de la pila.
4. Considere el manejo de excepciones: si se usa el manejo de excepciones en el programa, debe considerar el tamaño de la pila del manejo de excepciones para estimar el tamaño de la pila.
5. Use herramientas de detección de desbordamiento de pila: algunos compiladores y herramientas de depuración proporcionan funciones de detección de desbordamiento de pila, y puede usar estas herramientas para detectar problemas de desbordamiento de pila que pueden ocurrir en el programa, optimizando así el tamaño de la pila.

4. ¿Qué es un montón?

Explicación de GPT:
Heap (Heap) es una forma de asignar memoria dinámicamente. Es un área donde el sistema operativo asigna memoria para que se ejecute el programa, y ​​se utiliza para almacenar datos que deben asignarse dinámicamente cuando el programa se está ejecutando. La memoria del montón es administrada por el propio programador, y el programador puede asignar y liberar memoria del montón llamando a las funciones de administración del montón (como malloc, new, etc.) proporcionadas por el sistema operativo o el lenguaje de programación . El tamaño de la memoria del montón suele ser dinámico y puede expandirse y contraerse según las necesidades del programa.
Las características de la memoria en montón son las siguientes:
Asignación dinámica : la memoria en montón se puede asignar dinámicamente de acuerdo con las necesidades del programa, y ​​el programador puede asignar el tamaño de memoria requerido de acuerdo con la situación real.
Gestión del programador : la gestión de la memoria del montón es responsabilidad del programador. El programador necesita llamar manualmente a la función de gestión del montón para asignar y liberar la memoria del montón.
Uso compartido de varios subprocesos : varios subprocesos pueden compartir la memoria del montón, por lo que se debe tener en cuenta la seguridad de los subprocesos.
Fugas de memoria : dado que la gestión de la memoria del montón es responsabilidad del programador, existe el riesgo de fugas de memoria.Los programadores deben prestar atención para liberar a tiempo la memoria del montón que ya no se utiliza.
Fragmentación de la memoria : la asignación dinámica y la liberación de la memoria del montón pueden conducir fácilmente a problemas de fragmentación de la memoria, y los programadores deben tomar algunas medidas de optimización para evitar problemas de fragmentación de la memoria.
La memoria de almacenamiento dinámico suele utilizarse para almacenar estructuras de datos dinámicas (como listas vinculadas, árboles, etc.) y grandes cantidades de datos (como imágenes, audio, etc.), porque el tamaño de estos datos suele ser incierto y la memoria necesita para ser asignado dinámicamente para el almacenamiento.

1. Función de muestra

#include "stdio.h"
#include "stdlib.h"

int heap(){
    
    
char *str;
str = malloc(100);
strcpy(str,"helloworld");
free(str);
}

Sabemos que para una función, cuando se ejecuta la instrucción de definición de variable que se encuentra dentro, se libera el espacio de la variable. Todavía hay mucho espacio para la pila especificada de arriba a abajo de la pila. Cuando no se crea la pila, es un espacio libre , por lo que puede usar malloc() para especificar un espacio libre, y este espacio libre no afecta La asignación de espacio de pila no se superpondrá con las variables globales en la parte inferior, por lo que se pueden completar algunas operaciones de asignación de espacio de memoria dinámica .

2. La diferencia entre char *str y char str

char str declara una variable de puntero de carácter , es decir, str apunta a una dirección de memoria y almacena un puntero a una matriz de caracteres. Es decir, str es un puntero.

Y char str declara una variable de matriz de caracteres , es decir, str almacena el contenido de una matriz de caracteres.

Específicamente, **char *str generalmente se usa cuando se requiere una asignación dinámica de espacio de memoria, como usar la función malloc para asignar un espacio de cadena, asignar su dirección a str y luego procesar la cadena a través de la operación de puntero str**. Y char str generalmente se usa para declarar una matriz de caracteres de longitud fija, por ejemplo, char str[100] significa declarar una matriz de caracteres con una longitud de 100, que puede almacenar hasta 100 caracteres.
Por ejemplo:

// 使用 char *str 定义字符串变量
char *str = (char*) malloc(100 * sizeof(char)); // 动态分配内存空间
strcpy(str, "Hello, world!"); // 复制字符串
printf("%s\n", str); // 输出字符串
free(str); // 释放内存空间

// 使用 char str[100] 定义字符串变量
char str[100] = "Hello, world!"; // 直接初始化字符数组
printf("%s\n", str); // 输出字符串

3. malloc() y libre()

malloc y free son funciones dinámicas de asignación y liberación de memoria en lenguaje C.

La función malloc se utiliza para asignar dinámicamente un espacio de memoria de un tamaño específico en la memoria del montón y devolver un puntero a este espacio . Su prototipo de función es el siguiente:

void *malloc(size_t size);

Entre ellos, el parámetro de tamaño indica el tamaño del espacio de memoria a asignar, en bytes. La función malloc devuelve un puntero de tipo void*, que apunta a la dirección inicial del espacio de memoria asignado. Si falla la asignación, se devuelve un puntero nulo NULL.

La función free se usa para liberar el espacio de memoria asignado por la función malloc antes , y su prototipo de función es el siguiente:

void free(void *ptr);

Entre ellos, el parámetro ptr representa el puntero del espacio de memoria a liberar, es decir, el puntero devuelto por la función malloc. Después de llamar a la función free, el espacio de memoria se liberará y se puede reasignar para que lo usen otras variables.

En resumen, el montón es una parte de la memoria libre, que se puede administrar mediante malloc o funciones gratuitas.

5. ¿Por qué a menudo se juntan las pilas? ¿Diferencia entre montón y pila?

Heap y stack son dos estructuras de datos importantes en la administración de la memoria de la computadora. A menudo se mencionan juntas porque se usan para administrar el espacio de la memoria.
Sin embargo, creo que el montón y la pila son diferentes y no deberían llamarse pila juntos. Un montón es un montón y una pila es una pila.

La diferencia entre el montón y la pila
1. Método de asignación: el sistema asigna y administra automáticamente el espacio de la pila, mientras que el programador debe asignar y liberar manualmente el espacio del montón.
2. Tamaño del espacio: el sistema fija y preestablece el tamaño del espacio de pila, mientras que el tamaño del espacio de almacenamiento dinámico no es fijo y puede asignarse y liberarse dinámicamente.
3. Eficiencia de asignación: dado que el sistema realiza automáticamente la asignación y liberación de espacio de pila, la eficiencia de asignación y liberación es mayor que la del espacio de montón.
4. Método de almacenamiento: el espacio de pila utiliza el método Last In First Out (LIFO) para almacenar datos, que se utiliza principalmente para almacenar información como parámetros, variables locales y direcciones de retorno de función cuando se llama a funciones, mientras que el espacio de almacenamiento dinámico no tiene Modo de almacenamiento específico, utilizado para almacenar estructuras de datos asignadas dinámicamente, como matrices dinámicas, listas vinculadas, etc.
5. Ciclo de vida: el ciclo de vida del espacio de la pila es el mismo que el de la llamada a la función. Una vez que finaliza la llamada a la función, los datos en el espacio de la pila se liberarán automáticamente, mientras que el ciclo de vida del espacio del montón se administra manualmente. por el programador Libere espacio manualmente cuando use espacio de almacenamiento dinámico.

6. ¿Se insertarán variables estáticas en la pila?

No.
La ubicación de almacenamiento de las variables estáticas en la memoria está en el segmento de datos del programa (segmento de datos), no en la pila. Por lo tanto, las variables estáticas no se colocan en la pila.
**El área de datos del programa es un espacio de memoria asignado por el sistema cuando el programa se está ejecutando y se utiliza para almacenar datos como variables globales, variables estáticas y constantes. **El tamaño del área de datos es fijo y se ha determinado en tiempo de compilación, por lo que el tamaño del espacio de la variable estática también es fijo y no cambiará dinámicamente con la llamada a la función.
Por el contrario, la pila es un espacio de memoria dinámica que se utiliza para almacenar información como parámetros, variables locales y direcciones de retorno cuando se llama a una función . El tamaño del espacio de la pila es limitado, por lo general, solo de unos pocos cientos de KB a unos pocos MB, por lo que el tamaño del espacio y la cantidad de variables almacenadas en la pila están limitados hasta cierto punto.

7. ¿Cómo especificar que el montón está en un espacio determinado?

Si necesita asignar el espacio de almacenamiento dinámico a un segmento de dirección específico, puede utilizar técnicas como la conversión de tipo de puntero y la aritmética de puntero.

El siguiente es un código de muestra que demuestra cómo asignar manualmente un espacio de montón con un tamaño de 10 variables enteras y asignarlo al segmento de dirección especificado:

#include <stdio.h>
#include <stdlib.h>

int main() {
    
    
    int *ptr, *start_addr;
    int size = 10;
    // 分配堆空间
    ptr = (int*)malloc(size * sizeof(int));
    
    // 将堆空间分配到指定的地址段
    start_addr = (int*)0x10000; // 假设指定地址为 0x10000
    ptr = start_addr;
    
    // 释放堆空间
    free(ptr);
    return 0;
}

En el código anterior, primero se asigna un espacio de almacenamiento dinámico de 10 variables enteras a través de la función malloc, y luego el puntero ptr apunta a la dirección inicial de este espacio de almacenamiento dinámico. Luego, el espacio de almacenamiento dinámico se asigna al segmento de dirección especificado apuntando el puntero ptr al segmento de dirección especificado. El espacio de almacenamiento dinámico se libera mediante la función libre.

Supongo que te gusta

Origin blog.csdn.net/qq_53092944/article/details/131029319
Recomendado
Clasificación