Le langage C et l’arithmétique des pointeurs
Pour commencer, oui, l’arithmétique des pointeurs dans la programmation actuelle, et globalement l’utilisation du langage C, est quelque chose de plus en plus rare, car la majorité des langages sont dits de haut niveau, ils font abstraction du code machine, et leur typage est dynamique.
Toutefois, le langage C a certains atouts (pas de démonstration, ce n’est pas le but de ce billet), et il est ainsi important de produire du code robuste et modulable.
Prenons le cas où vous devez manipuler à travers les paramètres d’une fonction :
- des listes (une dimension) ;
- des matrices/tableaux (deux dimensions) ;
- des volumes (trois dimensions) ;
- voire autant de profondeurs de pointeurs dont vous avez besoin (x dimensions).
(Exemple et démonstration plus bas)
Pour une allocation de mémoire sur une variable faites à partir de pointeurs d’objets, le comportement est :
mytype *my_variable;
my_variable = malloc(sizeof(mytype));
Mais ensuite, si ce n’est plus un seul niveau de pointeurs, mais plusieurs, il faut donc calculer le nombre de pointeurs à mettre : combien d’astérisques à écrire ? est-ce vraiment utile, i.e "void *" ne suffit-il pas ? ce nombre a-t-il un impact ?
Et si on change de type ? par exemple, à l’intérieur d’une structure, vous ne déclarez pas la variable, du coup, il faut aller chercher le type de la variable en question, ce qui n’est pas forcément facile, et si ça change, vous devez manipuler le code.
Voici une solution : utilisez l’idiomatique suivante :
my_var = malloc(sizeof(*my_var));
Il suffit de remarquer qu’il suffit simplement de rajouter un astéristique par rapport à la variable dans le paramètre de gauche, dans le contenu de l’appel sizeof, et c’est tout.
Exemple, vous avez une matrice de "choses", chose pouvant être un entier, un nombre à virgule, un pointeur, une structure, etc, mais que c’est une donnée ici d’un des paramètres de la fonction, voici le résultat :
void myFunction(mytype ***my_var) {
…
*my_var = malloc(sizeof(**my_var));
…
}
void main(void) {
mytype **myMatrix;
myFunction(&myMatrix);
…
}
Exemple et Situation :
Prenons le cas où vous avez déjà une liste d’objets, comme une liste de sommets (“vertex/vertices” en anglais) dans un graphe (“graph”) contenant un nombre important de paramètres et d’informations. Recopier le contenu alors que vous souhaitez manipuler l’objet pour lire et créer d’autres ensembles n’est pas le plus efficace, la mémoire s’en alourdi : il suffit d’appeler le pointeur sur l’objet en question.
Déterminons les enfants d’un sommets dans un graphe, avec les arêtes qui les relient (“edge” en anglais) ; on va donc à partir d’un graphe "g" et d’un sommet "v" recevoir une liste de sommets "listChildren", et le nombre d’éléments de cette liste. Comme indiqué en introduction, on ne cherche pas à recopier les données, mais on cherche à recevoir leurs pointeurs. Pareil pour les éléments passés en paramètres en tant que source d’informations.
Remarque : j’évite au maximum d’avoir des variables globales, et je mets en premier dans les paramètres d’une fonction les données qui seront modifiées.
Remarque : pour simplifier la lecture, on pourrait aussi utiliser un “typedef” pour parler de pointeurs sur les différents objets, comme "pgraph" = "graph *", "pvertex" = "vertex *", etc, mais ça devient lourd à utiliser, et ce n’est pas entièrement pratique si on ne l’utilise pas entièrement.
Nous avons en entrée :
- un "graph my_graph" ;
- un "vertex current_vertex" ;
- une liste de pointeurs de sommets "vertex **my_child" ;
- et une valeur de retour, "int nb_children".
La liste des paramètres de la fonction "childrenVertices" ainsi se compose :
- pour le graphe "g" : graph *g ;
- pour le sommet "v" : vertex *v ;
- pour la liste de sommets, nous allons modifier son contenu, il faut rajouter un pointeur dessus : "vertex ***list_children" ;
- et le paramètre de retour sera un entier "int".
int childrenVertices(vertex ***list_children, graph *g, vertex *v);
À l’intérieur, l’allocation mémoire pour "list_children" sera :
*list_children = malloc(nb_vertices * sizeof(**list_children));
et son appel sera :
nb_children = childrenVertices(&my_child, &my_graph, ¤t_vertex);
En conclusion, la fonction "childrenVertices" ne manipule que des pointeurs, ce qui économise beaucoup plus de place par rapport à des données dans des structures (sauf pour les petits types, comme les entiers, qui sont moins volumineux dans une architecture 64 bits (4 octets, soient 32 bits pour un entier relatif "int") que pour un pointeur, 8 octets soient 64 bits en archi 64 bits.
April 1, 2016 at 4:05:12 PM GMT+2
- permalink
-
-
https://links.thican.net/?-rSgaA