Maison > développement back-end > C++ > Comprendre la gestion de la mémoire, les pointeurs et les pointeurs de fonction en C

Comprendre la gestion de la mémoire, les pointeurs et les pointeurs de fonction en C

王林
Libérer: 2024-07-17 00:10:11
original
1272 Les gens l'ont consulté

Understanding Memory Management, Pointers, and Function Pointers in C

En programmation C, une gestion efficace de la mémoire et l'utilisation de pointeurs sont cruciales pour créer des applications robustes et performantes. Ce guide fournit un aperçu complet des différents types de mémoire, des pointeurs, des références, de l'allocation dynamique de mémoire et des pointeurs de fonction, accompagné d'exemples pour vous aider à maîtriser ces concepts. Que vous soyez nouveau en C ou que vous cherchiez à approfondir votre compréhension, ce guide couvre des sujets essentiels pour améliorer vos compétences en codage.

Différents types de mémoire.

Il existe 5 types de mémoire différents que vous rencontrerez lors de la programmation en C.

1. Segment de texte

Votre code compilé est stocké ici. Les instructions du code machine pour votre programme. Le segment de texte est en lecture seule, vous ne pouvez donc modifier aucune donnée mais vous pouvez y accéder. Plus loin, nous parlerons des pointeurs de fonctions. Ces pointeurs pointent vers une fonction dans ce segment.

2. Segment de données initialisé

Les variables globales et statiques sont stockées ici, avec des valeurs spécifiques avant l'exécution du programme et restent accessibles tout au long du programme.

La différence entre les variables statiques et globales est la portée, les variables statiques sont accessibles dans la fonction ou le bloc, elles sont définies mais les variables globales sont accessibles depuis n'importe où dans votre programme.

Les variables normales sont supprimées une fois l'exécution d'une fonction terminée, tandis que les variables statiques restent.

3. Segment de données unitialisées (BSS)

Essentiellement identique au segment de données initialisé mais constitué de variables sans valeur spécifique qui lui est attribuée. Ils ont la valeur 0 par défaut.

4. Tas

Ici, vous disposez d'une mémoire dynamique que vous, en tant que programmeur, pouvez gérer au moment de l'exécution. Vous pouvez allouer de la mémoire et libérer de la mémoire avec des fonctions telles que malloc, calloc, realloc et free.

5. Pile

Vous connaissez probablement un peu la pile. La mémoire de pile gère les exécutions de fonctions en stockant les variables locales, les arguments et les valeurs de retour. La mémoire dans la pile est supprimée une fois l'exécution de la fonction terminée.

Types de données et quantité de stockage.

En C, vous avez différents types de données et les plus courants sont int, float, double et char. Je ne parlerai pas beaucoup des types de données mais l'important est de savoir combien d'octets un type de données spécifique a en mémoire. Voici une liste, gardez-la à l'esprit.

Int : 4 octets,
Flottant : 4 octets,
Double : 8 octets,
Caractère : 1 octet.

Vous pouvez vérifier la taille d'un type de données avec la méthode sizeof().

Pointeurs et références.

Lorsque vous attribuez une variable comme ;

int number = 5; 
Copier après la connexion

Le système stockera la valeur de 5 en mémoire. Mais où en mémoire ?

En mémoire, il y a en fait des adresses, et c'est ainsi que vous pouvez garder une trace des valeurs que vous avez stockées.

Une référence est l’adresse d’une variable. Plutôt cool, non ?

Pour accéder à la référence d'une variable utilisez & suivi du nom de la variable.

Pour imprimer la référence à la console, nous utilisons le spécificateur de format p.

int number = 5;

printf(“%d”, number);
// prints out 5 

printf(“%p”, &number);
// prints out the ref: 0x7ffd8379a74c
Copier après la connexion

Vous recevrez probablement une autre adresse imprimée.

Maintenant, pour garder une trace de cette référence, utilisez un pointeur pour stocker la référence.

Créez une variable de pointeur en utilisant *.

int number; 

int* pointer = &number;

printf(“%p”, pointer);
// 0x7ffd8379a74c
Copier après la connexion

Pour obtenir la valeur d'un pointeur, vous pouvez utiliser le déréférencement. Pour déréférencer une valeur, vous utilisez * avant le pointeur. Ainsi * est utilisé à la fois pour créer un pointeur et le déréférencer, mais dans des contextes différents.

int number = 5; // create variable.

int* pointer = &number //store the reference in the pointer

printf(“%p”, pointer); // print the reference
// 0x7ffd8379a74c

printf(“%d”, *pointer); // dereference the value. 
// 5
Copier après la connexion

C'est puissant car avec les pointeurs, vous pouvez transmettre des valeurs par référence et non copier des valeurs. C’est une mémoire efficace et performante.

Lorsque vous transmettez des valeurs à une fonction comme arguments dans un langage de haut niveau, vous copiez les valeurs mais en C vous pouvez envoyer une référence et manipuler la valeur directement en mémoire.

#include <stdio.h>

void flipValues(int *a, int *b) { // receives two pointers of type int
  int temp = *a; // dereference pointer a and store it’s value in temp
  *a = *b; // dereference pointer b and store in pointer a
  *b = temp; // dereference b and change value to the value of temp
}

int main(void) {
  int a = 20;
  int b = 10;
  flipValues(&a, &b); // pass the references of a and b
  printf("a is now %d and b is %d", a, b);
  // a is now 10 and b is 20
  return 0;
}
Copier après la connexion

Un pointeur peut être déclaré comme nom int* ; ou int *nom; les deux styles sont corrects et interchangeables.

Allouer et désallouer de la mémoire dynamique.

Lorsque vous déclarez une variable comme int num = 5; à l'intérieur d'une fonction comprenant la fonction principale, cette variable est stockée dans la pile et lorsque l'exécution de la fonction est terminée, la variable est supprimée.

Mais maintenant, nous allons allouer de la mémoire dynamiquement dans le tas. Nous avons alors un contrôle total sur la quantité de mémoire dont nous avons besoin, et elle persistera jusqu'à ce que nous la libérions.

Malloc et Calloc.

Nous pouvons utiliser les fonctions malloc ou calloc pour allouer de la mémoire.

Malloc prend un paramètre représentant la taille de la mémoire à allouer en octets.

Calloc prend deux paramètres, la quantité d'éléments et la quantité de mémoire en octets occupée par chaque élément.

malloc(taille)
calloc(montant, taille)

calloc initializes all allocated memory to 0, while malloc leaves the memory uninitialized, making malloc slightly more efficient.

You can use sizeof to indicate how much memory you need. In the example we use malloc to allocate space for 4 integers.

int* data; 
data = malloc(sizeof(int) * 4);
Copier après la connexion

Here is a visualization:

Pointer->[],[],[],[] [],[],[],[] [],[],[],[] [],[],[],[]

  1. Each bracket is a byte so here we see 16 bytes in memory.
  2. The malloc function allocates 4 x 4 bytes in memory, resulting in 16 bytes.
  3. int* data is a pointer of type int, so it points to the 4 first bytes in memory.

Therefore, pointer + 1 moves the pointer one step to the right, referring to the next integer in memory, which is 4 bytes away.

int* data; 
  data = malloc(sizeof(int) * 4);

  *data = 10; // change first value to 10.
  *(data + 1) = 20; // change second value to 20.
  *(data + 2) = 30;
  *(data + 3) = 40;

  for(int i = 0; i < 4; i++) {
      printf("%d\n", *(data + i));
  }
Copier après la connexion

This is how an array work!

When you declare an array, the array name is a pointer to its first element.

int numbers[] = { 10, 20, 30, 40 };
  printf("%p\n", &numbers); 
  printf("%p", &numbers[0]);
  // 0x7ffe91c73c80
  // 0x7ffe91c73c80
Copier après la connexion

As shown, the address of the array name is the same as the address of its first element.

Realloc

Sometimes you want to reallocate memory. A normal use case is when you need more memory then you initially allocated with malloc or calloc.

The realloc function takes 2 parameters. A pointer to where your data currently is located, and the size of memory you need.

int* pointer2 = realloc(*pointer1, size);
Copier après la connexion

The realloc function will first check if the size of data can be stored at the current address and otherwise it will find another address to store it.

It’s unlikely but if there is not enough memory to reallocate the function will return null.

int *ptr1, *ptr2;

// Allocate memory
ptr1 = malloc(4);

// Attempt to resize the memory
ptr2 = realloc(ptr1, 8);

// Check whether realloc is able to resize the memory or not
if (ptr2 == NULL) {
  // If reallocation fails
  printf("Failed. Unable to resize memory");
} else {
  // If reallocation is sucessful
  printf("Success. 8 bytes reallocated at address %p \n", ptr2);
  ptr1 = ptr2;  // Update ptr1 to point to the newly allocated memory
}
Copier après la connexion

Free memory

After you have allocated memory and don’t use it anymore. Deallocate it with the free() function with a pointer to the data to be freed.

After that, it is considered good practice to set the pointer to null so that the address is no longer referenced so that you don’t accidentally use that pointer again.

int *ptr;
ptr = malloc(sizeof(*ptr));

free(ptr);
ptr = NULL;
Copier après la connexion

Remember that the same memory is used in your whole program and other programs running on the computer.

If you don’t free the memory it’s called data leak and you occupy memory for nothing. And if you accidentally change a pointer you have freed you can delete data from another part of your program or another program.

Create a Vector (dynamic array).

You can use your current knowledge to create a dynamic array.

As you may know, you can use structs to group data and are powerful to create data structures.

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

struct List {
  int *data; // Pointer to the list data
  int elements; // Number  of elements in the list 
  int capacity; // How much memmory the list can hold
};

void append(struct List *myList, int item);
void print(struct List *myList);
void free_list(struct List *myList);

int main() {
    struct List list;
    // initialize the list with no elements and capazity of 5 elements
    list.elements = 0;
    list.capacity = 5;
    list.data = malloc(list.capacity * sizeof(int));
    // Error handeling for allocation data
    if (list.data == NULL) {
    printf("Memory allocation failed");
    return 1; // Exit the program with an error code
    }
    // append 10 elements
    for(int i = 0; i < 10; i++) {
      append(&list, i + 1);
    };
    // Print the list 
    print(&list);
    // Free the list data
    free_list(&list);

    return 0;
}

// This function adds an item to a list
void append(struct List *list, int item) {
    // If the list is full then resize the list with thedouble amount
    if (list->elements == list->capacity) {
    list->capacity *= 2;
    list->data = realloc( list->data, list->capacity * sizeof(int) );
    }
    // Add the item to the end of the list
    list->data[list->elements] = item;
    list->elements++;
}

void print(struct List *list) {
    for (int i = 0; i < list->elements; i++) {
    printf("%d ", list->data[i]);
    } 
}

void free_list(struct List *list) {
    free(list->data);
    list->data = NULL;
}
Copier après la connexion

Static data

Global variables.

When declaring a variable above the main function they are stored in the data segment and are allocated in memory before the program starts and persists throughout the program and is accessible from all functions and blocks.

#include <stdio.h>
int globalVar = 10; // Stored in initilized data segment
int unitilizedGlobalVar; // Stored in uninitialized data segment

int main() {
    printf("global variable %d\n", globalVar);            printf("global uninitilized variable %d", unitilizedGlobalVar);
    // global variable 10
    // global uninitilized variable 0
    return 0;
}
Copier après la connexion

Static variables

They work the same as global ones but are defined in a certain block but they are also allocated before the program starts and persist throughout the program. This is a good way of keeping the state of a variable. Use the keyword static when declaring the variable.

Here is a silly example where you have a function keeping the state of how many fruits you find in a garden using a static variable.

#include <stdio.h>

void countingFruits(int n) {
    // the variable is static and will remain through function calls and you can store the state of the variable
    static int totalFruits;
    totalFruits += n;
    printf( "Total fruits: %d\n", totalFruits);
}

void pickingApples(int garden[], int size) {
    // search for apples
    int totalApples = 0;
    for(int i = 0; i < size; i++) {
        if(garden[i] == 1) {
            totalApples++;
        }
    }
    countingFruits(totalApples);
}

void pickingOranges(int garden[], int size) {
    // search for oranges
    int totalOranges = 0;
    for(int i = 0; i < size; i++) {
        if(garden[i] == 2) {
            totalOranges++;
        }
    }
    countingFruits(totalOranges);
}

int main() {
    // A garden where you pick fruits, 0 is no fruit and 1 is apple and 2 is orange
    int garden[] = {0, 0, 1, 0, 1, 2,
                    2, 0, 1, 1, 0, 0,
                    2, 0, 2, 0, 0, 1
                    };
    // the length of the  garden
    int size = sizeof(garden) / sizeof(garden[0]);
    pickingApples(garden, size); 
    // now the total fruits is 5
    pickingOranges(garden, size);
    // now the total fruits is 9
    return 0;
}
Copier après la connexion

Understanding Function Pointers in C.

Function pointers are a powerful feature in C that allow you to store the address of a function and call that function through the pointer. They are particularly useful for implementing callback functions and passing functions as arguments to other functions.

Function pointers reference functions in the text segment of memory. The text segment is where the compiled machine code of your program is stored.

Defining a Function Pointer

You define a function pointer by specifying the return type, followed by an asterisk * and the name of the pointer in parentheses, and finally the parameter types. This declaration specifies the signature of the function the pointer can point to.

int(*funcPointer)(int, int)
Copier après la connexion

Using a Function Pointer

To use a function pointer, you assign it the address of a function with a matching signature and then call the function through the pointer.

You can call a function of the same signature from the function pointer

#include <stdio.h>

void greet() {
    printf("Hello!\n");
}

int main() {
    // Declare a function pointer and initialize it to point to the 'greet' function
    void (*funcPtr)();
    funcPtr = greet;

    // Call the function using the function pointer
    funcPtr();

    return 0;
}
Copier après la connexion

Passing Function Pointers as Parameters

Function pointers can be passed as parameters to other functions, allowing for flexible and reusable code. This is commonly used for callbacks.

#include <stdio.h>

// Callback 1
void add(int a, int b) {
    int sum  = a + b;
    printf("%d + %d = %d\n", a, b, sum);
}

// Callback 2
void multiply(int a, int b) {
    int sum  = a * b;
    printf("%d x %d = %d\n", a, b, sum);
}

// Math function recieving a callback
void math(int a, int b, void (*callback)(int, int)) {
    // Call the callback function
    callback(a, b);
}

int main() {
    int a = 2;
    int b = 3;

    // Call math with add callback
    math(a, b, add);

    // Call math with multiply callback
    math(a, b, multiply);

    return 0;
}
Copier après la connexion

Explanation of the Callback Example

Callback Functions: Define two functions, add and multiply, that will be used as callbacks. Each function takes two integers as parameters and prints the result of their respective operations.

Math Function: Define a function math that takes two integers and a function pointer (callback) as parameters. This function calls the callback function with the provided integers.

Main Function: In the main function, call math with different callback functions (add and multiply) to demonstrate how different operations can be performed using the same math function.

The output of the program is:

2 + 3 = 5
2 x 3 = 6
Copier après la connexion

Thanks for reading and happy coding!

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

source:dev.to
Déclaration de ce site Web
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn
Tutoriels populaires
Plus>
Derniers téléchargements
Plus>
effets Web
Code source du site Web
Matériel du site Web
Modèle frontal