Notes d'étude sur l'architecture ARM et le langage C (Wei Dongshan) (2) - variables globales, tas et pile


1. Pourquoi les variables globales ne sont pas initialisées

#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;
}

Dans cette fonction, comme mentionné dans la section précédente, la variable locale a dans la fonction add_val(volatile int v) est initialisée et allouée à l'espace de pile, mais pourquoi les variables globales définies int g_a=123 et static volatile int s_a=1 ; commande pour initialiser ?

2. Comment initialiser les variables globales

Pour les variables locales, l'initialisation consiste à allouer l'espace de données des octets spécifiés et l'espace d'adressage des octets spécifiés. Les variables locales sont stockées dans la RAM. Pour les variables globales, les variables globales sont accessibles n'importe où dans le programme, elles sont donc généralement stockées dans la RAM. Lorsque le programme démarre, les variables globales sont initialisées et de l'espace mémoire est alloué. Cependant, si nous utilisons le mot-clé const lors de la définition de variables globales ou si nous définissons des variables globales comme des types statiques, ces variables sont généralement stockées dans Flash au lieu de RAM. En effet, les variables de type const sont généralement considérées comme des constantes et leurs valeurs ne seront pas modifiées, elles peuvent donc être stockées dans Flash. La variable de type statique n'est initialisée qu'une seule fois lors de l'exécution du programme et existe durant toute l'exécution du programme, elle peut donc également être stockée en Flash.
Pour les variables globales en flash, elles seront copiées à l'adresse spécifiée en RAM lors de la lecture et de l'écriture , cette adresse étant déterminée par le compilateur et le système.
insérez la description de l'image ici
Dans KEIL, l'éditeur de liens LINKER définira l'adresse de base RAM et l'adresse de base FLASH. Par conséquent, la CPU lit d'abord l'adresse de données de la mémoire flash et copie les données dans le registre R0, puis cette variable globale est enregistrée à l'adresse 0x20000000 de la RAM.

3. Quelle est la pile

D'après l'étude des variables locales et des variables globales, je connais probablement la notion de pile :

Stack (stack) est une structure de données en mémoire informatique, qui suit le principe du "Last In First Out (LIFO)". Dans une pile, l'ajout et la suppression de données ne peuvent se produire qu'en haut de la pile, de sorte que les dernières données ajoutées à la pile seront supprimées en premier.
Dans un ordinateur, la pile ** est généralement utilisée pour stocker des données temporaires, telles que des paramètres, des variables locales et des adresses de retour lorsqu'une fonction est appelée. **Lorsqu'une fonction est appelée, ses paramètres et variables locales sont alloués sur la pile, et lorsque la fonction revient, ces données sont supprimées de la pile. Par conséquent, la pile offre un moyen pratique de gérer les données lors des appels et des retours de fonction.
La pile est généralement gérée automatiquement par le système d'exploitation. Il utilise des pointeurs pour suivre la position du haut de la pile. Lorsque des données doivent être insérées dans la pile, le pointeur se déplace vers le bas ; lorsque des données doivent être extraites du haut de la pile, le pointeur se déplace vers le haut. Étant donné que la taille de la pile est généralement fixe, le programme plantera lorsque la pile débordera. Par conséquent, lors de l'écriture d'un programme, vous devez gérer avec soin la taille de la pile pour éviter les problèmes de débordement de pile.

(1) La structure de la pile

Pour le cœur cortex-m3 de l'architecture ARM, la pile est une pile complète qui croît vers le bas, c'est-à-dire que le pointeur de pile sp pointe initialement vers l'adresse haute définie par l'utilisateur, telle que 0x20001000. En raison de la croissance vers le bas , l'adresse pointée par le pointeur supérieur de la pile est l'adresse où se trouvent les dernières données, et l'adresse pointée par le pointeur inférieur de la pile est l'adresse où se trouvent les premières données. Lorsque la pile est pleine, le pointeur supérieur de la pile pointera vers l'adresse où se trouve le pointeur inférieur de la pile, et l'ajout de données à ce moment entraînera un débordement de la pile.
La valeur du registre "pointeur de pile" de pile croissant vers le bas pointe vers l'adresse du haut de la pile, et sa valeur soustraira la taille des données poussées à chaque fois que la pile est poussée, de sorte qu'elle croît vers le bas. Lorsque les données en haut de la pile sont dépilées, la valeur du pointeur de pile augmentera, se déplaçant ainsi vers le haut.

(2) Débordement de pile

insérez la description de l'image ici
La pile pleine générée vers le bas, si la variable définie dans la fonction initialisée est trop grande, le pointeur de pile continue de descendre. Lorsque la pile est pleine, le pointeur du haut de la pile pointera sur l'adresse du pointeur du bas de la pile, et l'ajout de données à ce moment entraînera un débordement de pile.

Lorsque la pile déborde, les conséquences suivantes se produisent :
Plantage du programme : Lorsque la pile déborde, le programme plante inévitablement, ce qui peut entraîner une perte de données ou l'incapacité du programme à continuer à s'exécuter.
Corruption des données : étant donné que le débordement de la pile entraînera des données hors limites, les données de la pile peuvent être écrasées ou endommagées, ce qui empêchera le programme de fonctionner correctement.
Vulnérabilités de sécurité : les débordements de pile peuvent également entraîner des vulnérabilités de sécurité, où un attaquant peut contrôler le flux d'exécution d'un programme en ajoutant des données malveillantes à la pile, exécutant ainsi un code malveillant.
Fuites de mémoire : s'il y a des objets de pile dans le programme, ces objets peuvent ne pas être libérés correctement lorsque la pile déborde, ce qui entraîne des fuites de mémoire.

(3) Comment éviter le débordement de pile : estimer la taille de la pile

Le but est de trouver la chaîne d'appel qui « utilise le plus de variables locales ». Voici la réponse de GPT :
1. Observez la chaîne d'appel de fonction : la taille de la pile dépend de la profondeur de l'appel de fonction et de la taille des variables locales de chaque fonction. Par conséquent, l'observation de la chaîne d'appel de fonction et de la taille des variables locales dans le programme peut estimer approximativement la taille de la pile.
2. Estimez la taille des paramètres de fonction et des valeurs de retour : les paramètres de fonction et les valeurs de retour occuperont également de l'espace dans la pile. Par conséquent, l'estimation de la taille des paramètres de fonction et des valeurs de retour peut aider à estimer la taille de la pile.
3. Tenez compte de la récursivité : s'il existe des fonctions récursives dans le programme, vous devez tenir compte de la profondeur de la récursivité et de la taille des variables locales pour estimer la taille de la pile.
4. Tenez compte de la gestion des exceptions : si la gestion des exceptions est utilisée dans le programme, vous devez tenir compte de la taille de la pile de gestion des exceptions pour estimer la taille de la pile.
5. Utilisez des outils de détection de débordement de pile : certains compilateurs et outils de débogage fournissent des fonctions de détection de débordement de pile, et vous pouvez utiliser ces outils pour détecter les problèmes de débordement de pile qui peuvent survenir dans le programme, optimisant ainsi la taille de la pile.

4. Qu'est-ce qu'un tas ?

Explication GPT :
Heap (Heap) est un moyen d'allouer dynamiquement de la mémoire. C'est une zone dans laquelle le système d'exploitation alloue de la mémoire pour que le programme s'exécute et est utilisée pour stocker les données qui doivent être allouées dynamiquement lorsque le programme est en cours d'exécution. La mémoire de tas est gérée par le programmeur lui-même, et le programmeur peut allouer et libérer de la mémoire de tas en appelant les fonctions de gestion de tas (telles que malloc, new, etc.) fournies par le système d'exploitation ou le langage de programmation . La taille de la mémoire de tas est généralement dynamique et peut s'étendre et se contracter en fonction des besoins du programme.
Les caractéristiques de la mémoire tas sont les suivantes :
Allocation dynamique : La mémoire tas peut être allouée dynamiquement en fonction des besoins du programme, et le programmeur peut allouer la taille de mémoire requise en fonction de la situation réelle.
Gestion du programmeur : La gestion de la mémoire de tas est de la responsabilité du programmeur.Le programmeur doit appeler manuellement la fonction de gestion de tas pour allouer et libérer la mémoire de tas.
Partage multi-thread : la mémoire de tas peut être partagée par plusieurs threads, la sécurité des threads doit donc être prise en compte.
Fuites de mémoire : La gestion du tas de mémoire étant de la responsabilité du programmeur, il existe un risque de fuites de mémoire.Les programmeurs doivent faire attention à libérer en temps voulu le tas de mémoire qui n'est plus utilisé.
Fragmentation de la mémoire : L'allocation et la libération dynamiques de la mémoire de tas peuvent facilement entraîner des problèmes de fragmentation de la mémoire, et les programmeurs doivent prendre des mesures d'optimisation pour éviter les problèmes de fragmentation de la mémoire.
La mémoire de tas est généralement utilisée pour stocker des structures de données dynamiques (telles que des listes chaînées, des arbres, etc.) et de grandes quantités de données (telles que des images, de l'audio, etc.), car la taille de ces données est généralement incertaine et les besoins en mémoire à allouer dynamiquement pour le stockage.

1. Exemple de fonction

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

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

Nous savons que pour une fonction, lorsque la déclaration de définition de variable à l'intérieur est exécutée, l'espace de la variable est libéré. Il y a encore beaucoup d'espace pour le haut de la pile spécifié jusqu'au bas de la pile. Lorsque la pile n'est pas créée, c'est un espace libre , vous pouvez donc utiliser malloc() pour spécifier un espace libre, et cet espace libre sera pas d'incidence L'allocation d'espace de pile ne chevauchera pas les variables globales en bas, de sorte que certaines opérations d'allocation dynamique d'espace mémoire peuvent être effectuées .

2. La différence entre char *str et char str

char str déclare une variable de pointeur de caractère , c'est-à-dire que str pointe vers une adresse mémoire et stocke un pointeur vers un tableau de caractères. C'est-à-dire que str est un pointeur.

Et char str déclare une variable de tableau de caractères , c'est-à-dire que str stocke le contenu d'un tableau de caractères.

Plus précisément, **char *str est généralement utilisé lorsqu'une allocation dynamique d'espace mémoire est requise, comme l'utilisation de la fonction malloc pour allouer un espace de chaîne, attribuer son adresse à str, puis traiter la chaîne via l'opération de pointeur str**. Et char str est généralement utilisé pour déclarer un tableau de caractères de longueur fixe, par exemple, char str[100] signifie déclarer un tableau de caractères d'une longueur de 100, qui peut stocker jusqu'à 100 caractères.
Par exemple:

// 使用 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() et free()

malloc et free sont des fonctions dynamiques d'allocation et de libération de mémoire en langage C.

La fonction malloc est utilisée pour allouer dynamiquement un espace mémoire d'une taille spécifiée dans la mémoire tas et renvoyer un pointeur vers cet espace . Son prototype de fonction est le suivant :

void *malloc(size_t size);

Parmi eux, le paramètre size indique la taille de l'espace mémoire à allouer, en octets. La fonction malloc renvoie un pointeur de type void*, qui pointe sur l'adresse de début de l'espace mémoire alloué. Si l'allocation échoue, un pointeur nul NULL est retourné.

La fonction free est utilisée pour libérer l'espace mémoire alloué par la fonction malloc avant , et son prototype de fonction est le suivant :

void free(void *ptr);

Parmi eux, le paramètre ptr représente le pointeur de l'espace mémoire à libérer, c'est-à-dire le pointeur renvoyé par la fonction malloc. Après avoir appelé la fonction free, l'espace mémoire sera libéré et pourra être réalloué pour d'autres variables à utiliser.

Pour résumer, le tas est un morceau de mémoire libre, qui peut être géré à l'aide de malloc ou de fonctions libres.

5. Pourquoi les piles sont-elles souvent appelées ensemble ? Différence entre tas et pile ?

Le tas et la pile sont deux structures de données importantes dans la gestion de la mémoire des ordinateurs. Ils sont souvent mentionnés ensemble car ils sont utilisés pour gérer l'espace mémoire.
Cependant, je pense que le tas et la pile sont différents et ne devraient pas être appelés la pile ensemble. Un tas est un tas et une pile est une pile.

La différence entre le tas et la pile
1. Méthode d'allocation : L'espace de la pile est automatiquement alloué et géré par le système, tandis que l'espace du tas doit être manuellement alloué et libéré par le programmeur.
2. Taille de l'espace : la taille de l'espace de pile est fixe et prédéfinie par le système, tandis que la taille de l'espace de tas n'est pas fixe et peut être allouée et libérée dynamiquement.
3. Efficacité de l'allocation : étant donné que l'allocation et la libération de l'espace de la pile sont effectuées automatiquement par le système, l'efficacité de l'allocation et de la libération est supérieure à celle de l'espace du tas.
4. Méthode de stockage : l'espace de pile utilise la méthode LIFO (dernier entré, premier sorti) pour stocker des données, qui est principalement utilisée pour stocker des informations telles que des paramètres, des variables locales et des adresses de retour de fonction lors de l'appel de fonctions ; tandis que l'espace de tas n'a pas Mode de stockage spécifique, utilisé pour stocker des structures de données allouées dynamiquement, telles que des tableaux dynamiques, des listes chaînées, etc.
5. Cycle de vie : le cycle de vie de l'espace de la pile est le même que celui de l'appel de fonction. Après la fin de l'appel de la fonction, les données de l'espace de la pile seront libérées automatiquement ; tandis que le cycle de vie de l'espace du tas est géré manuellement. par le programmeur. Libérez manuellement de l'espace lors de l'utilisation de l'espace de tas.

6. Les variables statiques seront-elles poussées sur la pile ?

Ne le fera pas.
L'emplacement de stockage des variables statiques en mémoire se trouve dans le segment de données du programme (segment de données), et non dans la pile. Par conséquent, les variables statiques ne sont pas poussées sur la pile.
**La zone de données du programme est un morceau d'espace mémoire alloué par le système lorsque le programme est en cours d'exécution et est utilisé pour stocker des données telles que des variables globales, des variables statiques et des constantes. **La taille de la zone de données est fixe et a été déterminée au moment de la compilation, de sorte que la taille de l'espace de la variable statique est également fixe et ne changera pas dynamiquement avec l'appel de la fonction.
En revanche, la pile est un espace mémoire dynamique utilisé pour stocker des informations telles que des paramètres, des variables locales et des adresses de retour lorsqu'une fonction est appelée . La taille de l'espace de la pile est limitée, généralement seulement quelques centaines de Ko à quelques Mo, de sorte que la taille de l'espace et le nombre de variables stockées dans la pile sont limités dans une certaine mesure.

7. Comment spécifier que le tas est dans un certain espace ?

Si vous devez allouer l'espace de tas à un segment d'adresse spécifié, vous pouvez utiliser des techniques telles que la conversion de type de pointeur et l'arithmétique de pointeur.

Voici un exemple de code qui montre comment allouer manuellement un espace de tas avec une taille de 10 variables entières et l'allouer au segment d'adresse spécifié :

#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;
}

Dans le code ci-dessus, un espace de tas de 10 variables entières est d'abord alloué via la fonction malloc, puis le pointeur ptr pointe vers l'adresse de départ de cet espace de tas. Ensuite, l'espace de tas est alloué au segment d'adresse spécifié en pointant le pointeur ptr vers le segment d'adresse spécifié. L'espace du tas est libéré par la fonction free.

Je suppose que tu aimes

Origine blog.csdn.net/qq_53092944/article/details/131029319
conseillé
Classement