Question

AKA - Quelle est cette obsession des pointeurs?

N'ayant que de véritables langages orientés objet tels qu'ActionScript, Java et C #, je ne comprends pas vraiment l'importance des pointeurs et de leur utilisation. Qu'est-ce qui me manque ici?

Était-ce utile?

La solution

Tout est simplement indirectionnel: possibilité de ne pas traiter les données, mais de dire "je vous dirigerai vers certaines données, là-bas". Vous avez le même concept en Java et en C #, mais uniquement au format de référence.

Les principales différences sont que les références sont en réalité des panneaux indicateurs immuables - elles indiquent toujours quelque chose. Ceci est utile et facile à comprendre, mais moins flexible que le modèle de pointeur C. Les pointeurs C sont des balises que vous pouvez aisément réécrire. Vous savez que la chaîne que vous recherchez se trouve juste à côté de la chaîne pointée? Eh bien, modifiez légèrement le poteau indicateur.

Ceci se couple bien avec les C "proches de l'os, une connaissance de bas niveau est requise" approche. Nous savons qu’un char * foo consiste en un ensemble de caractères commençant à l’emplacement indiqué par le panneau foo. Si nous savons également que la chaîne contient au moins 10 caractères, nous pouvons modifier le panneau en (foo + 5) pour qu'il pointe sur la même chaîne, mais commence par la moitié de sa longueur.

Cette flexibilité est utile lorsque vous savez ce que vous faites et la mort si vous ne le savez pas (où "savoir", c'est plus que "connaître la langue", c'est "connaître l'état exact du programme". ;). Ne vous y trompez pas, et votre poteau indicateur vous oriente du bord d'une falaise. Les références ne vous laissent pas tripoter, vous êtes donc beaucoup plus confiant que vous pouvez les suivre sans risque (surtout lorsqu'elles sont associées à des règles telles que "Un objet référencé ne disparaîtra jamais", comme dans la plupart des langages récupérés par Garbage).

Autres conseils

Vous manquez beaucoup! Comprendre le fonctionnement de l'ordinateur aux niveaux inférieurs est très utile dans plusieurs situations. C et l’assembleur le feront pour vous.

En gros, un pointeur vous permet d’écrire des éléments en un point quelconque de la mémoire de votre ordinateur. Sur un matériel / système d'exploitation plus primitif ou dans des systèmes embarqués, cela peut en réalité être utile. Dites allumer et éteindre les blinkenlichts.

Bien sûr, cela ne fonctionne pas sur les systèmes modernes. Le système d'exploitation est le Seigneur et le Maître de la mémoire principale. Si vous essayez d’accéder à un mauvais emplacement mémoire, votre processus paiera pour son orgueil avec sa vie.

En C, les pointeurs sont le moyen de transmettre des références à des données. Lorsque vous appelez une fonction, vous ne souhaitez pas copier un million de bits dans une pile. Au lieu de cela, vous indiquez simplement où se trouvent les données dans la mémoire principale. En d’autres termes, vous attribuez un pointeur aux données.

Dans une certaine mesure, c'est ce qui se passe même avec Java. Vous transmettez des références à des objets, pas aux objets eux-mêmes. Rappelez-vous qu’en fin de compte, chaque objet est un ensemble de bits dans la mémoire principale de l’ordinateur.

Les pointeurs servent à manipuler directement le contenu de la mémoire.

C'est à vous de décider si c'est une bonne chose à faire, mais c'est la base sur laquelle tout est fait en C ou en assembleur.

Les langages de haut niveau cachent les pointeurs dans les coulisses: par exemple, une référence en Java est implémentée en tant que pointeur dans presque toutes les machines virtuelles que vous rencontrez. C'est pourquoi elle s'appelle NullPointerException plutôt que NullReferenceException. Mais il ne laisse pas le programmeur accéder directement à l'adresse de la mémoire vers laquelle il pointe, et il ne peut pas être modifié pour prendre une valeur autre que l'adresse d'un objet du type correct. Il n’offre donc pas le même pouvoir (et la même responsabilité) que les pointeurs des langages de bas niveau.

[Edit: voici une réponse à la question "Quelle est cette obsession des pointeurs?". Tout ce que j'ai comparé, ce sont des pointeurs d'assembleur / style C avec des références Java. Le titre de la question a depuis changé: si j’avais voulu répondre à la nouvelle question, j’aurais peut-être mentionné des références dans des langues autres que Java.

Cela revient à demander: «Quelle est cette obsession des instructions de la CPU? Est-ce que je manque quelque chose en ne saupoudrant pas d'instructions x86 MOV partout? "

Vous avez juste besoin de pointeurs pour programmer à un niveau bas. Dans la plupart des implémentations de langage de programmation de niveau supérieur, les pointeurs sont utilisés de la même manière qu'en C, mais sont cachés de l'utilisateur par le compilateur.

Alors ... Ne t'inquiète pas. Vous utilisez déjà des pointeurs - et sans les dangers de le faire incorrectement également. :)

Je vois les pointeurs comme une transmission manuelle dans une voiture. Si vous apprenez à conduire avec une voiture à transmission automatique, cela ne fera pas pour un mauvais conducteur. Et vous pouvez toujours faire presque tout ce que les conducteurs qui ont appris sur une transmission manuelle peuvent faire. Il y aura juste un trou dans votre connaissance de la conduite. Si vous deviez conduire un manuel, vous auriez probablement des problèmes. Bien sûr, il est facile de comprendre le concept de base, mais une fois que vous devez faire un départ en pente, vous êtes foutu. Mais, il y a toujours une place pour les transmissions manuelles. Par exemple, les pilotes de voitures de course doivent pouvoir changer de vitesse pour que la voiture réagisse de la manière la plus optimale possible aux conditions de course actuelles. Avoir une transmission manuelle est très important pour leur succès.

Cela ressemble beaucoup à la programmation en ce moment. Certains logiciels nécessitent un développement en C / C ++. Certains exemples sont les jeux 3D haut de gamme, les logiciels embarqués de bas niveau, les choses dans lesquelles la rapidité est une partie essentielle de la fonction du logiciel, et un langage de niveau inférieur qui vous permet un accès plus proche aux données réelles devant être traitées est la clé de cette performance. . Cependant, pour la plupart des programmeurs, ce n'est pas le cas et le fait de ne pas connaître les pointeurs n'est pas invalidant. Cependant, je pense que tout le monde peut bénéficier de l’apprentissage du C, des pointeurs et des transmissions manuelles.

Puisque vous programmez dans des langages orientés objet, laissez-moi le formuler de cette façon.

L'objet A instancié dans l'objet B, vous le transmettez sous la forme d'un paramètre de méthode à l'objet C. L'objet C modifie certaines valeurs de l'objet B. Lorsque vous revenez au code de l'objet A, vous pouvez voir la valeur modifiée dans Objet B. Pourquoi est-ce vrai?

Etant donné que vous avez passé une référence d'objet B à l'objet C, vous n'avez pas créé une autre copie de l'objet B. L'objet A et l'objet C conservent tous deux des références au même objet B en mémoire. Changements d'un endroit et être vu dans un autre. Ceci est appelé par référence.

Maintenant, si vous utilisez plutôt des types primitifs, tels que int ou float, et les transmettez en tant que paramètres de méthode, les modifications dans Object C ne peuvent pas être vues par Object A, car Object A a simplement passé une copie d'une référence de sa propre copie de la variable. Ceci est appelé par valeur.

Vous le saviez probablement déjà.

Pour revenir au langage C, la fonction A transmet à la fonction B des variables. Ces paramètres de fonction sont des copies natives, par valeur. Pour que la fonction B puisse manipuler la copie appartenant à la fonction A, la fonction A doit transmettre un pointeur à la variable, afin qu'elle devienne une passe par référence.

"Hé, voici l'adresse mémoire de ma variable entière. Mettez la nouvelle valeur à cette adresse et je la récupérerai plus tard. "

Notez que le concept est similaire mais pas 100% analogue. Les pointeurs peuvent faire beaucoup plus que simplement passer "par référence". Les pointeurs permettent aux fonctions de manipuler des emplacements de mémoire arbitraires à la valeur requise. Les pointeurs sont également utilisés pour pointer sur de nouvelles adresses du code d'exécution afin d'exécuter de manière dynamique une logique arbitraire, pas seulement des variables de données. Les pointeurs peuvent même pointer vers d'autres pointeurs (double pointeur). C’est puissant, mais aussi assez facile à introduire, il est difficile de détecter les bogues et les failles de sécurité.

Si vous n'avez jamais vu de pointeur, vous ratez sûrement ce mini-bijou:

void strcpy(char *dest, char *src)
{    
        while(*dest++ = *src++);
}

Historiquement, la programmation était possible grâce à la prise de conscience que les emplacements de mémoire pouvaient contenir des instructions informatiques, pas uniquement des données.

Les pointeurs découlaient de la prise de conscience que les emplacements de mémoire pouvaient également contenir l’adresse d’autres emplacements de mémoire, nous donnant ainsi un sens indirect. Sans pointeurs (à un niveau bas), les structures de données les plus compliquées seraient impossibles. Pas de listes chaînées, d'arbres binaires ou de tables de hachage. Pas de passage par référence, uniquement par valeur. Puisque les pointeurs peuvent pointer sur du code, sans eux, nous n’aurions pas non plus de fonctions virtuelles ni de tables de recherche de fonctions.

J'utilise beaucoup de pointeurs et de références dans mon travail quotidien ... en code managé (C #, Java) et non managé (C ++, C). J'ai appris à maîtriser les indicateurs et à les définir par le maître lui-même ... [Binky !!] [1] Il n'y a rien d'autre à dire;)

La différence entre un pointeur et une référence est la suivante. Un pointeur est une adresse vers un bloc de mémoire. Il peut être réécrit ou, en d'autres termes, réaffecté à un autre bloc de mémoire. Une référence est simplement un renommage d'un objet. Il ne peut être attribué qu'une seule fois! Une fois assigné à un objet, il ne peut plus être assigné à un autre. Une référence n'est pas une adresse, c'est un autre nom pour la variable. Pour plus d'informations à ce sujet, consultez la FAQ C ++ .

Link1

LInk2

Je suis en train de concevoir des logiciels d'entreprise de haut niveau dans lesquels des blocs de données (stockés dans une base de données SQL, dans ce cas) sont référencés par une ou plusieurs autres entités. S'il reste un bloc de données alors qu'aucune autre entité ne le référence, nous gaspillons de la mémoire. Si une référence pointe vers des données qui ne sont pas présentes, c'est également un gros problème.

Il existe une forte analogie entre nos problèmes et ceux de la gestion de la mémoire dans un langage qui utilise des pointeurs. Il est extrêmement utile de pouvoir parler à mes collègues de cette analogie. Ne pas supprimer des données non référencées est une "fuite de mémoire". Une référence qui ne mène nulle part est un "pointeur suspendu". Nous pouvons choisir explicitement "libère" ou implémentons le "ramassage des ordures". en utilisant le "comptage de références".

Alors, ici, comprendre la gestion de la mémoire de bas niveau aide à concevoir des applications de haut niveau.

En Java, vous utilisez tout le temps des pointeurs. La plupart des variables sont des pointeurs sur des objets. C'est pourquoi:

StringBuffer x = new StringBuffer("Hello");
StringBuffer y = x;
x.append(" boys");
System.out.println(y);

... imprime "Bonjour les garçons" et non "Bonjour".

La seule différence en C réside dans le fait qu’il est courant d’ajouter et de soustraire des pointeurs. Si vous vous trompez de logique, vous risquez de perdre votre temps avec des données que vous ne devriez pas toucher.

Les chaînes sont fondamentales pour le langage C (et d’autres langages apparentés). Lors de la programmation en C, vous devez gérer votre mémoire. Vous ne dites pas simplement "d'accord, il me faut un tas de chaînes"; vous devez réfléchir à la structure des données. De combien de mémoire avez-vous besoin? Quand allez-vous l'allouer? Quand allez-vous le libérer? Supposons que vous vouliez 10 chaînes, chacune ne comportant pas plus de 80 caractères.

D'accord, chaque chaîne est un tableau de caractères (81 caractères - vous ne devez pas oublier le null ou vous serez désolé!), puis chaque chaîne est elle-même dans un tableau. Le résultat final sera un tableau multidimensionnel du type

.
char dict[10][81];

Notez, incidemment, que dict n'est pas une "chaîne". ou un "tableau" ou un "caractère". C'est un pointeur. Lorsque vous essayez d'imprimer une de ces chaînes, vous ne faites que transmettre l'adresse d'un seul caractère. C suppose que s'il commence juste à imprimer des caractères, il finira par frapper un zéro. Et cela suppose que si vous êtes au début d'une chaîne et que vous avancez de 81 octets, vous serez au début de la chaîne suivante. Et, en fait, prendre votre pointeur et y ajouter 81 octets est le seul moyen possible de passer à la chaîne suivante.

Alors, pourquoi les pointeurs sont-ils importants? Parce que vous ne pouvez rien faire sans eux. Vous ne pouvez même pas faire quelque chose de simple, comme imprimer un tas de ficelles; certainement vous ne pouvez rien faire d'intéressant, comme implémenter des listes chaînées, des hachages, des files d'attente, des arbres, un système de fichiers, un code de gestion de la mémoire, un noyau ou autre. Vous devez les comprendre parce que C vous donne simplement un bloc de mémoire et vous laisse faire le reste. Tout faire avec un bloc de mémoire brute nécessite des pointeurs.

De plus, de nombreuses personnes suggèrent que la capacité de comprendre les pointeurs est étroitement liée aux compétences en programmation. Joel a présenté cet argument, entre autres. Par exemple

  

Maintenant, j'admets volontiers que 90% du code écrit aujourd'hui ne nécessite pas de programmation avec des pointeurs. En fait, c'est vraiment dangereux dans le code de production. D'ACCORD. C'est très bien. Et la programmation fonctionnelle n’est tout simplement pas très utilisée dans la pratique. D'accord.

     

Mais cela reste important pour certains des travaux de programmation les plus intéressants. Sans pointeurs, par exemple, vous ne pourriez jamais travailler sur le noyau Linux. Vous ne pouvez pas comprendre une ligne de code sous Linux, ni même aucun système d’exploitation, sans vraiment comprendre les pointeurs.

De ici . Excellent article.

Pour être honnête, la plupart des développeurs chevronnés s’amuseront (heureusement amicalement) si vous ne connaissez pas les indicateurs. Lors de mon dernier emploi, nous avons eu deux nouvelles recrues l'année dernière (récemment diplômées) qui ne connaissaient pas les indicateurs, et cela seul a été le sujet de conversation avec eux pendant environ une semaine. Personne ne pouvait croire que quelqu'un puisse obtenir son diplôme sans connaître les indicateurs ...

Les références en C ++ sont fondamentalement différentes des références en langage Java ou .NET; Les langages .NET ont des types spéciaux appelés "byrefs " qui se comportent comme des "références" C ++.

Une référence C ++ ou .NET byref (je vais utiliser ce dernier terme pour distinguer des références .NET) est un type spécial qui ne contient pas de variable, mais contient plutôt des informations suffisantes pour identifier une variable (ou quelque chose comme ça). pouvant se comporter comme un seul, tel qu’un emplacement de tableau) détenu ailleurs. Les byrefs ne sont généralement utilisés que comme paramètres / arguments de fonctions et sont destinés à être éphémères. Le code qui transmet un byref à une fonction garantit que la variable ainsi identifiée existera au moins jusqu’à ce que cette fonction revienne, et les fonctions garantissent généralement de ne conserver aucune copie d’un byref après leur retour (notez que cette dernière restriction n’est pas utilisée en C ++). forcée). Ainsi, les références ne peuvent pas survivre aux variables ainsi identifiées.

Dans les langages Java et .NET, une référence est un type qui identifie un objet tas. chaque objet de tas a une classe associée et le code de la classe d'objet de tas peut accéder aux données stockées dans l'objet. Les objets de segment de mémoire peuvent accorder un accès limité ou complet au code extérieur aux données qui y sont stockées et / ou autoriser un code extérieur à appeler certaines méthodes au sein de leur classe. Utiliser une référence pour appeler une méthode de sa classe rendra cette référence disponible pour cette méthode, qui pourra ensuite l’utiliser pour accéder à des données (même privées) de l’objet tas.

Ce qui rend les références spéciales dans les langages Java et .NET, c’est qu’ils maintiennent, comme invariant absolu, que chaque référence non nulle continuera à identifier le même objet de tas tant que cette référence existe. Une fois qu’aucune référence à un objet tas n’existe nulle part dans l’univers, l’objet tas cessera tout simplement d’exister, mais il n’existe aucun moyen qu’un objet tas puisse cesser d’exister tant que la référence n’existe pas, et il n’existe aucun moyen pour un " normal " référence à un objet du tas devient spontanément autre chose qu'une référence à cet objet. Java et .NET ont tous les deux une "référence faible" spéciale. types, mais même ils soutiennent l'invariant. Si aucune référence non-faible à un objet n'existe nulle part dans l'univers, toute référence faible existante sera invalidée; une fois que cela se produit, il n'y aura plus de référence à l'objet et celui-ci pourra donc être invalidé.

Les pointeurs, comme les références C ++ et les références Java / .NET, identifient les objets, mais contrairement aux types de références susmentionnés, ils peuvent survivre aux objets qu’ils identifient. Si l'objet identifié par un pointeur cesse d'exister, mais pas le pointeur lui-même, toute tentative d'utilisation du pointeur entraînera un comportement non défini. Si un pointeur n'est pas connu pour être null ou pour identifier un objet qui existe actuellement, il n'y a pas de moyen standard de faire quoi que ce soit avec ce pointeur autre que l'écraser. avec autre chose. Il est parfaitement légitime qu'un pointeur continue d'exister une fois que l'objet identifié ainsi a cessé de le faire, à condition que rien ne l'utilise jamais, mais il est nécessaire que quelque chose en dehors du pointeur indique si l'utilisation est sûre ou non, car il n'y a aucun moyen de le faire. demander au pointeur lui-même.

La principale différence entre les pointeurs et les références (de chaque type) réside dans le fait que les références peuvent toujours être demandées si elles sont valides (elles seront soit valides, soit identifiables comme nulles), et si elles sont considérées comme valides, elles le resteront. tant qu'ils existent. On ne peut pas demander aux pointeurs s'ils sont valides, et le système ne fera rien pour s'assurer qu'ils ne deviennent pas invalides, et ne permettent pas aux pointeurs qui deviennent invalides d'être reconnus comme tels.

Pendant longtemps, je n'ai pas compris les pointeurs, mais j'ai compris l'adressage par matrice. Je mettais donc généralement en place une zone de stockage pour les objets d'un tableau, puis j'utilisais un index pour ce tableau en tant que concept de "pointeur".

SomeObject store[100];
int a_ptr = 20;
SomeObject A = store[a_ptr];

Un problème avec cette approche est qu’après avoir modifié "A", je devrais le réaffecter au tableau "store" pour que les modifications soient permanentes:

store[a_ptr] = A;

En coulisse, le langage de programmation effectuait plusieurs opérations de copie. La plupart du temps, cela n'affectait pas les performances. Le code était généralement source d'erreur et de répétition.

Après avoir appris à comprendre les pointeurs, je me suis écarté de la mise en œuvre de l'approche d'adressage par matrice. L'analogie est toujours valable. Il suffit de considérer que le tableau 'store' est géré par l'exécution du langage de programmation.

SomeObject A;
SomeObject* a_ptr = &A;
// Any changes to a_ptr's contents hereafter will affect
// the one-true-object that it addresses. No need to reassign.

De nos jours, je n'utilise des pointeurs que lorsque je ne peux pas légitimement copier un objet. Il existe de nombreuses raisons pour lesquelles cela pourrait être le cas:

  1. Pour éviter une copie d'objet coûteuse opération pour le bien de performances.
  2. Un autre facteur ne permet pas de opération de copie d'objet.
  3. Vous voulez qu'un appel de fonction ait effets secondaires sur un objet (ne pas passer l'objet, passer le pointeur à cet égard).
  4. Dans certaines langues, si vous voulez renvoyer plus d'une valeur d'un fonction (bien que généralement évité).

Les pointeurs sont le moyen le plus pragmatique de représenter l'indirection dans les langages de programmation de niveau inférieur.

Les pointeurs sont importants! Ils " point " à une adresse mémoire, et de nombreuses structures internes sont représentées par des pointeurs, IE, un tableau de chaînes est en fait une liste de pointeurs sur des pointeurs! Les pointeurs peuvent également être utilisés pour mettre à jour les variables transmises aux fonctions.

Vous en avez besoin si vous souhaitez générer des "objets". au moment de l'exécution sans préallouer de la mémoire sur la pile

Parameter efficency - passage d'un pointeur (Int - 4 octets) par opposition à la copie d'un objet entier (arbitrairement grand).

Les classes Java sont transmises via une référence (en gros un pointeur) également btw, c’est simplement que java est caché au programmeur.

En programmant dans des langages tels que C et C ++, vous êtes beaucoup plus proche du "métal". Les pointeurs contiennent un emplacement de mémoire où résident vos variables, données, fonctions, etc. Vous pouvez passer un pointeur au lieu de passer d'une valeur à une autre (en copiant vos variables et vos données).

Il y a deux choses difficiles avec les pointeurs:

  1. Les pointeurs sur les pointeurs, les adresses, etc. peuvent être très cryptés. Cela conduit à des erreurs et il est difficile à lire.
  2. La mémoire pointée par les pointeurs est souvent allouée à partir du tas, ce qui signifie que vous êtes responsable de la libération de cette mémoire. Plus votre application est grosse, plus il est difficile de satisfaire à cette exigence et vous vous retrouvez avec des fuites de mémoire difficiles à localiser.

Vous pouvez comparer le comportement du pointeur à la manière dont les objets Java sont transmis, à l'exception du fait qu'en Java, vous n'avez pas à vous soucier de la libération de la mémoire car celle-ci est gérée par un garbage collection. De cette façon, vous obtenez les avantages du pointeur, sans avoir à gérer les aspects négatifs. Bien sûr, vous pouvez toujours avoir des fuites de mémoire en Java si vous ne dé-référencez pas vos objets, mais c'est une autre affaire.

Autre chose à noter, vous pouvez utiliser des pointeurs en C # (par opposition aux références normales) en marquant un bloc de code comme non sécurisé. Ensuite, vous pouvez modifier directement les adresses de mémoire et faire de l'arithmétique de pointeur et tout ce qui est amusant. C'est parfait pour la manipulation d'image très rapide (le seul endroit où je l'ai personnellement utilisé).

Pour autant que je sache, Java et ActionScript ne prennent pas en charge le code et les pointeurs non sécurisés.

Je suis toujours troublé par l’attention portée à des éléments tels que les pointeurs ou les références dans les langages de haut niveau. Il est vraiment utile de penser à un niveau d'abstraction plus élevé en termes de comportement d'objets (ou même de fonctions) plutôt qu'en termes de "laissez-moi voir, si j'envoie l'adresse de cette chose là-bas, alors que chose va me renvoyer un pointeur vers quelque chose d'autre "

Envisagez même une simple fonction d'échange. Si vous avez

void swap (int & amp; a, int & amp; b)

ou

procédure Swap (var a, b: entier)

interprétez ensuite ces informations comme signifiant que les valeurs peuvent être modifiées. Le fait que cela soit mis en œuvre en passant les adresses des variables n’est qu’une distraction par rapport au but recherché.

Idem avec les objets: ne pensez pas que les identificateurs d’objet sont des pointeurs ou des références à des "éléments". Au lieu de cela, imaginez-les comme des objets auxquels vous pouvez envoyer des messages. Même dans les langages primitifs tels que C ++, vous pouvez aller beaucoup plus vite en pensant (et en écrivant) au plus haut niveau possible.

Écrivez plus de 2 lignes de c ou de c ++ et vous le saurez.

Ils sont des "pointeurs". à l'emplacement de mémoire d'une variable. C'est comme passer une variable par référence un peu.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top