Avantages des pointeurs?

J’ai récemment développé un intérêt pour la programmation en C, alors je me suis acheté un livre (K & R) et j’ai commencé à étudier.

Venant d’un cours universitaire en Java (notions de base), les pointeurs constituent un chapitre totalement nouveau et, d’après ce que j’ai lu en ligne, c’est un concept plutôt difficile à comprendre. Avant d’arriver au chapitre sur les pointeurs, j’avais l’impression que les pointeurs constituent une partie importante de C et offrent d’importants avantages.

En lisant le chapitre et en obtenant une idée de base de ce que sont les indicateurs et leur fonctionnement, les avantages ne me sont pas évidents.

Par exemple (corrigez-moi si je me trompe complètement) dans l’introduction des pointeurs dans le livre K & R, il est dit que puisque nous appelons par valeur, lors du passage d’une variable dans un appel de fonction, nous passons à peu près une copie de la variable pour la fonction à gérer et donc la fonction ne peut rien faire à la variable d’origine et nous pouvons le surmonter avec des pointeurs.

Dans un exemple ultérieur qui utilise un pointeur de caractère, le livre indique que l’incrémentation du pointeur de caractère est légale car la fonction possède une copie privée du pointeur. Les «copies privées» ne sont-elles pas une raison pour utiliser des pointeurs?

Je suppose que je suis un peu confus sur l’utilisation des pointeurs. Si on me le demande, je peux utiliser des pointeurs au lieu d’utiliser des indices de tableau, par exemple, mais je doute que ce soit l’utilisation principale des pointeurs.

La programmation open source et Linux était la raison principale pour laquelle je me suis mis à utiliser C. J’ai reçu le code source d’un projet C à étudier (Geany IDE) et je peux voir que les pointeurs sont utilisés dans tout le code source.

J’ai aussi fait quelques recherches dans les forums et j’ai trouvé quelques articles avec des questions similaires. Une réponse a été (je cite):

Si vous ne savez pas quand utiliser des pointeurs, ne les utilisez pas.

Cela deviendra évident quand vous aurez besoin de les utiliser, chaque situation est différente.

Est-il prudent pour moi d’éviter d’utiliser des pointeurs sur le moment et de ne les utiliser que dans des situations spécifiques (où le besoin de pointeurs sera évident?)

Votre règle surlignée est très sage. Cela vous évitera des ennuis, mais tôt ou tard, vous devrez apprendre des indicateurs.

Alors, pourquoi voulons-nous utiliser des pointeurs?

Supposons que j’ouvre un fichier texte et que je le lise dans une chaîne géante. Je ne peux pas vous passer la ficelle géante en valeur car elle est trop grosse pour tenir sur la stack (disons 10 Mo). Je vous dis donc où se trouve la chaîne et dites “Allez regarder ma chaîne”.

Un tableau est un pointeur (enfin presque).

int [] et int * sont légèrement différents mais interchangeables pour la plupart.

int[] i = new int[5]; // garbage data int* j = new int[5] // more garbage data but does the same thing std::cout << i[3] == i + 3 * sizeof(int); // different syntax for the same thing 

Une utilisation plus avancée des pointeurs extrêmement utile est l'utilisation des pointeurs de fonction. En C et C ++, les fonctions ne sont pas des types de données de première classe, mais les pointeurs le sont. Ainsi, vous pouvez passer un pointeur sur une fonction que vous souhaitez appeler et ils peuvent le faire.

Espérons que cela aide mais plus que probable sera déroutant.

L’un des avantages des pointeurs est que lorsque vous les utilisez dans des arguments de fonction, vous n’avez pas besoin de copier de gros morceaux de mémoire, et vous pouvez également modifier l’état en déréférencant le pointeur.

Par exemple, vous pouvez avoir une énorme struct MyStruct et une fonction a() .

 void a (struct MyStruct* b) { // You didn't copy the whole `b` around, just passed a pointer. } 

Venant de Java, votre perspective est légèrement différente de celle présentée dans K & R (K & R ne suppose pas que le lecteur connaisse un autre langage de programmation moderne).

Un pointeur en C ressemble à une version légèrement plus performante d’une référence en Java. Vous pouvez voir cette similarité via l’exception Java nommée NullPointerException . Un aspect important des pointeurs en C est que vous pouvez changer leur sharepoint vue en incrémentant et en décrémentant.

En C, vous pouvez stocker plusieurs objects en mémoire dans un tableau, et vous savez qu’ils sont assis côte à côte en mémoire. Si vous avez un pointeur sur l’un d’eux, vous pouvez le faire pointer vers le “suivant” en l’incrémentant. Par exemple:

 int a[5]; int *p = a; // make p point to a[0] for (int i = 0; i < 5; i++) { printf("element %d is %d\n", i, *p); p++; // make `p` point to the next element } 

Le code ci-dessus utilise le pointeur p pour pointer en séquence chacun des éléments successifs du tableau et les affiche.

(Remarque: le code ci-dessus n'est qu'un exemple de référentiel, et vous n'écririez généralement pas une boucle simple de cette façon. Il serait plus facile d'accéder aux éléments du tableau en tant a[i] et de ne pas utiliser de pointeur.)

Dans un exemple ultérieur qui utilise un pointeur de caractère, le livre indique que l’incrémentation du pointeur de caractère est légale car la fonction possède une copie privée du pointeur.

Je dirais que cela signifie qu’ils incrémentent le pointeur lui-même, ce qui signifie changer l’adresse (et donc le faire pointer vers une valeur différente). Cela pourrait être utile s’ils recevaient le premier élément d’un tableau et souhaitaient continuer dans le tableau sans modifier les valeurs.

Rappelez-vous que C (et le livre K & R) est très vieux, probablement plus vieux que tout ce que vous avez appris auparavant (certainement plus vieux que Java). Les pointeurs ne sont pas une fonctionnalité supplémentaire de C, ils constituent une partie très élémentaire du fonctionnement des ordinateurs.

La théorie des pointeurs n’est pas particulièrement difficile à maîsortingser, ils sont très puissants, ce qui fait qu’une erreur plantera votre application et les compilateurs ont du mal à saisir les erreurs liées aux pointeurs. L’une des grandes nouveautés concernant Java était d’avoir “presque” autant de puissance que C sans les pointeurs.

Donc, à mon avis, essayer d’écrire C en évitant les pointeurs, c’est comme essayer de faire du vélo sans une pédale. Oui, c’est faisable mais vous travaillerez deux fois plus fort.

Ignorez cette réponse s’il vous plaît.

Si vous ne savez pas comment utiliser les pointeurs, apprenez à les utiliser. Simple.

Les pointeurs que vous dites vous permettent de transmettre plusieurs variables à une fonction via un pointeur sur celle-ci, comme vous l’avez fait remarquer à juste titre. Une autre utilisation des pointeurs consiste à faire référence à des tableaux de données, que vous pouvez parcourir à l’aide de l’arithmétique de pointeur. Enfin, les pointeurs vous permettent d’allouer de la mémoire de manière dynamic. Par conséquent, vous conseiller de ne pas utiliser de pointeur va limiter considérablement ce que vous pouvez faire.

Les pointeurs sont le moyen utilisé par C pour parler des adresses de mémoire. Pour cette raison, ils sont critiques. Comme vous avez K & R, lisez ça.

Pour un exemple d’utilisation, voir ma réponse à cela . Comme je l’ai dit dans cette réponse, ce n’est pas nécessairement la façon de procéder, étant donné la question.

Cependant, cette technique correspond exactement au fonctionnement de bibliothèques telles que MPIR et GMP. Si vous ne l’avez pas déjà rencontré, libgmp fait fonctionner Mathematica, maple, etc., et constitue la bibliothèque d’arithmétique en précision arbitraire. Vous constaterez que mpn_t est un typedef à un pointeur; en fonction du système d’exploitation dépend de ce qu’il pointe. Vous trouverez également beaucoup d’arithmétique de pointeur dans les versions lentes, C, de ce code.

Enfin, j’ai mentionné la gestion de la mémoire. Si vous voulez allouer un tableau de quelque chose, vous avez besoin de malloc et free qui traitent des pointeurs vers des espaces mémoire; malloc précisément, malloc renvoie un pointeur sur une mémoire allouée ou NULL en cas d’échec.

Une de mes utilisations préférées des pointeurs consiste à faire en sorte qu’une fonction membre de la classe C ++ agisse comme un thread sur des fenêtres à l’aide de l’API win32, c’est-à-dire que la classe contienne un thread. Malheureusement, CreateThread sous Windows n’accepte pas les fonctions de classe C ++ pour des raisons évidentes – CreateThread est une fonction C qui ne comprend pas les instances de classe. donc vous devez lui passer une fonction statique. Voici le truc:

 DWORD WINAPI CLASSNAME::STATICFUNC(PVOID pvParam) { return ((CLASSNAME*)pvParam)->ThreadFunc(); } 

(PVOID est void * )

Ce qui se passe, c’est qu’il retourne ThreadFunc qui est exécuté “au lieu de” (en fait, STATICFUNC l’appelle) STATICFUNC et peut en effet accéder à toutes les variables de membre privées de CLASSNAME . Ingénieux, hein?

Si cela ne suffit pas à vous convaincre que les pointeurs sont un peu C, je ne sais pas ce que c’est. Ou peut-être qu’il n’y a pas de point en C sans pointeurs. Ou…

Étant donné que vous venez d’un environnement Java, voici le moyen le plus simple de vous faire une idée de l’utilisation des pointeurs.

Disons que vous avez une classe comme celle-ci en Java:

 public class MyClass { private int myValue; public int getMyValue() { return myValue; } public void setMyValue(int value) { myValue = value; } } 

Voici votre fonction principale, qui crée un de vos objects et appelle une fonction dessus.

 public static void Main(Ssortingng[] args) { MyClass myInstance = new MyClass(); myInstance.setMyValue(1); System.out.printLn(myInstance.getMyValue()); // prints 1 DoSomething(myInstance); System.out.printLn(myInstance.getMyValue()); // prints 2 } public static void DoSomething(MyClass instance) { instance.setMyValue(2); } 

La variable myInstance vous avez déclarée dans Main est une référence. Il s’agit essentiellement d’un descripteur utilisé par la machine virtuelle Java pour garder un œil sur votre instance d’object. Maintenant, faisons la même chose en C.

 typedef struct _MyClass { int myValue; } MyClass; void DoSomething(MyClass *); int main() { MyClass myInstance; myInstance.myValue = 1; printf("i", myInstance.myValue); // prints 1 MyClass *myPointer = &myInstance; // creates a pointer to the address of myInstance DoSomething(myPointer); printf("i", myInstance.myValue); // prints 2 return 0; } void DoSomething(MyClass *instance) { instance->myValue = 2; } 

Bien sûr, les pointeurs sont beaucoup plus flexibles en C, mais c’est l’essentiel de leur fonctionnement en Java.

Si vous utilisez de la mémoire allouée dynamicment, vous devez utiliser des pointeurs pour accéder à l’espace alloué. De nombreux programmes traitent de la mémoire allouée, de sorte que de nombreux programmes doivent utiliser des pointeurs.

Dans cet exemple:

 int f1(int i); f1(x); 

le paramètre i étant passé par valeur, la fonction f1 n’a pas pu changer la valeur de la variable x de l’appelant.

Mais dans ce cas:

 int f2(int* i); int x; int* px = &x; f2(px); 

Ici, nous passons toujours le paramètre px par valeur, mais dans le même temps, nous passons x par référence !. Ainsi, si l’appelé ( f2 ) change d’ int* i il n’aura aucun effet sur px dans l’appelant. Cependant, en modifiant *i l’appelé modifiera la valeur de x dans l’appelant.

Permettez-moi de l’expliquer davantage en termes de références Java (comme l’a souligné la réponse de @ Greg)

En Java, il existe des types de référence (référence d’une classe) et valeur (type int ). Comme C, Java est transmis par valeur, uniquement . Si vous transmettez un type primitif à une fonction, vous transmettez en réalité la valeur de la valeur (après tout, il s’agit d’un “type de valeur”) et, par conséquent, toute modification de cette valeur dans cette fonction n’est pas reflétée dans le code appelant. Si vous transmettez un type de référence à une fonction, vous pouvez modifier la valeur de cet object, car lorsque vous transmettez le type de référence, vous transmettez la référence à ce type de référence par valeur.

Les pointeurs permettent d’envoyer dynamicment du code en fonction des conditions ou de l’état du programme. Un moyen simple de comprendre ce concept consiste à imaginer une structure arborescente dans laquelle chaque nœud représente un appel de fonction, une variable ou un pointeur sur un nœud de niveau inférieur. Une fois que vous avez compris cela, vous utilisez ensuite des pointeurs pour pointer vers des emplacements de mémoire établis que le programme peut référencer à volonté pour comprendre l’état initial et donc le premier déréférencement et décalage. Ensuite, chaque nœud contiendra ses propres pointeurs pour mieux comprendre l’état dans lequel une déréférence supplémentaire peut avoir lieu, une fonction peut être appelée ou une valeur saisie.

Bien sûr, ce n’est qu’un moyen de visualiser comment les pointeurs peuvent être utilisés puisqu’un pointeur n’est rien de plus que l’adresse d’un emplacement mémoire. Vous pouvez également utiliser des pointeurs pour la transmission de messages, les appels de fonctions virtuelles, la récupération de place, etc. En fait, je les ai utilisés pour recréer des appels de fonctions virtuelles de style c ++. Le résultat est que la fonction virtuelle c et les fonctions virtuelles c ++ s’exécutent à la même vitesse. Cependant, l’implémentation c était beaucoup plus petite (66% moins en Ko) et légèrement plus portable. Toutefois, la réplication d’entités à partir d’autres langages en C ne sera pas toujours avantageuse. Bien entendu, d’autres langages pourraient être mieux équipés pour rassembler des informations que le compilateur peut utiliser pour optimiser cette structure de données.

En résumé, beaucoup de choses peuvent être faites avec des pointeurs. Le langage C et les pointeurs sont dévalorisés de nos jours car la plupart des langages de niveau supérieur sont équipés des structures de données / implémentations les plus utilisées que vous auriez dû utiliser vous-même avec des pointeurs. Mais il arrive toujours qu’un programmeur ait besoin d’implémenter une routine complexe et dans ce cas, savoir comment utiliser les pointeurs est une très bonne chose.