Qu'est-ce qu'un pointeur C sinon une adresse mémoire?


206

Dans une source réputée sur C, les informations suivantes sont données après avoir discuté de l' &opérateur:

... Il est un peu malheureux que la terminologie [adresse de] reste, car elle confond ceux qui ne savent pas de quoi il s'agit et induit en erreur ceux qui le font: penser aux pointeurs comme s'ils étaient des adresses mène généralement au chagrin .. .

D'autres documents que j'ai lus (à partir de sources également réputées, je dirais) ont toujours fait référence aux pointeurs et à l' &opérateur comme donnant des adresses mémoire. Je serais ravi de continuer à chercher l'actualité de la question, mais c'est un peu difficile lorsque des sources réputées GENÈRES sont en désaccord.

Maintenant , je suis un peu confus - ce exactement est un pointeur, puis, sinon une adresse mémoire?

PS

L'auteur dira plus tard: ... Je continuerai cependant à utiliser le terme «adresse de», car en inventer un autre [terme] serait encore pire.


118
Un pointeur est une variable qui contient une adresse. Il a également sa propre adresse. Il s'agit de la différence fondamentale entre un pointeur et un tableau. Un tableau est effectivement une adresse (et implicitement, son adresse est elle - même ).
WhozCraig

7
Quelle est votre "source fiable" pour le devis?
Cornstalks

22
La source ultime de bonne réputation est la norme linguistique et non les livres semi-dérivés et semi-tirés de l'auteur. Je l'ai appris à la dure, faisant presque toutes les erreurs que je pouvais et construisant lentement un modèle mental de C quelque peu proche de celui décrit par la norme, puis en remplaçant finalement ledit modèle par le modèle de la norme.
Alexey Frunze

9
@thang Les gens pensent que pointer = integer parce que c'est souvent le cas (x86 Linux et Windows nous "l'enseignent"), parce que les gens aiment généraliser, parce que les gens ne connaissent pas bien la norme de langue et parce qu'ils ont peu d'expérience avec des différences radicalement différentes plates-formes. Ces mêmes personnes sont susceptibles de supposer qu'un pointeur vers des données et un pointeur vers une fonction peuvent être convertis l'un à l'autre et que les données peuvent être exécutées sous forme de code et que le code est accessible en tant que données. Bien que cela puisse être vrai sur les architectures von Neuman (avec 1 espace d'adressage), mais pas nécessairement vrai sur les architectures Harvard (avec code et espaces de données).
Alexey Frunze

6
Les normes @exebook ne sont pas destinées aux débutants (en particulier, les normes complètes). Ils ne sont pas censés fournir des introductions douces et une multitude d'exemples. Ils définissent formellement quelque chose, afin qu'il puisse être correctement mis en œuvre par des professionnels.
Alexey Frunze

Réponses:


148

La norme C ne définit pas ce qu'est un pointeur en interne et comment il fonctionne en interne. Ceci est intentionnel afin de ne pas limiter le nombre de plateformes, où C peut être implémenté comme un langage compilé ou interprété.

Une valeur de pointeur peut être une sorte d'ID ou de poignée ou une combinaison de plusieurs ID (dites bonjour aux segments et décalages x86) et pas nécessairement une véritable adresse mémoire. Cet ID peut être n'importe quoi, même une chaîne de texte de taille fixe. Les représentations sans adresse peuvent être particulièrement utiles pour un interprète C.


34
Il n'y a pas grand-chose à expliquer. Chaque variable a son adresse en mémoire. Mais vous n'avez pas besoin de stocker leurs adresses dans des pointeurs vers eux. Au lieu de cela, vous pouvez numéroter vos variables de 1 à n'importe quoi et stocker ce nombre dans le pointeur. C'est parfaitement légal selon la norme de langage tant que l'implémentation sait comment transformer ces nombres en adresses et comment faire l'arithmétique des pointeurs avec ces nombres et toutes les autres choses requises par la norme.
Alexey Frunze

4
je voudrais ajouter que sur x86, une adresse mémoire se compose d'un sélecteur de segment et d'un décalage, représentant ainsi un pointeur comme segment: le décalage utilise toujours l'adresse mémoire.
thang

6
@Lundin Je n'ai aucun problème à ignorer la nature générique du standard et l'inapplicable quand je connais ma plateforme et mon compilateur. Cependant, la question d'origine est générique, vous ne pouvez donc pas ignorer la norme lorsque vous y répondez.
Alexey Frunze

8
@Lundin Vous n'avez pas besoin d'être révolutionnaire ou scientifique. Supposons que vous souhaitiez émuler une machine 32 bits sur une machine physique 16 bits et que vous étendez vos 64 Ko de RAM jusqu'à 4 Go en utilisant le stockage sur disque et implémentez des pointeurs 32 bits comme décalages dans un fichier volumineux. Ces pointeurs ne sont pas de véritables adresses mémoire.
Alexey Frunze

6
Le meilleur exemple que j'aie jamais vu de ceci était l'implémentation C pour Symbolics Lisp Machines (vers 1990). Chaque objet C a été implémenté en tant que tableau Lisp et les pointeurs ont été implémentés en tant que paire d'un tableau et d'un index. En raison de la vérification des limites du tableau de Lisp, vous ne pourriez jamais déborder d'un objet à un autre.
Barmar

62

Je ne suis pas sûr de votre source, mais le type de langage que vous décrivez provient de la norme C:

6.5.3.2 Opérateurs d'adresse et d'indirection
[...]
3. L'opérateur unaire & renvoie l'adresse de son opérande. [...]

Alors ... oui, les pointeurs pointent vers des adresses mémoire. C'est du moins ce que la norme C suggère que cela signifie.

Pour le dire un peu plus clairement, un pointeur est une variable contenant la valeur d'une adresse . L'adresse d'un objet (qui peut être stockée dans un pointeur) est retournée avec l' &opérateur unaire .

Je peux stocker l'adresse "42 Wallaby Way, Sydney" dans une variable (et cette variable serait en quelque sorte un "pointeur", mais comme ce n'est pas une adresse mémoire, ce n'est pas quelque chose que nous appellerions correctement un "pointeur"). Votre ordinateur possède des adresses pour ses compartiments de mémoire. Les pointeurs stockent la valeur d'une adresse (c'est-à-dire qu'un pointeur stocke la valeur "42 Wallaby Way, Sydney", qui est une adresse).

Edit: Je veux développer le commentaire d'Alexey Frunze.

Qu'est-ce qu'un pointeur exactement? Regardons la norme C:

6.2.5 Types
[...]
20. [...]
Un type de pointeur peut être dérivé d'un type de fonction ou d'un type d'objet, appelé type référencé . Un type de pointeur décrit un objet dont la valeur fournit une référence à une entité du type référencé. Un type de pointeur dérivé du type référencé T est parfois appelé «pointeur vers T». La construction d'un type de pointeur à partir d'un type référencé est appelée «dérivation de type de pointeur». Un type de pointeur est un type d'objet complet.

Essentiellement, les pointeurs stockent une valeur qui fournit une référence à un objet ou une fonction. En quelque sorte. Les pointeurs sont destinés à stocker une valeur qui fournit une référence à un objet ou une fonction, mais ce n'est pas toujours le cas:

6.3.2.3 Pointeurs
[...]
5. Un entier peut être converti en n'importe quel type de pointeur. Sauf indication contraire, le résultat est défini par l'implémentation, peut ne pas être correctement aligné, peut ne pas pointer vers une entité du type référencé et peut être une représentation d'interruption.

La citation ci-dessus dit que nous pouvons transformer un entier en un pointeur. Si nous faisons cela (c'est-à-dire, si nous remplissons une valeur entière dans un pointeur au lieu d'une référence spécifique à un objet ou une fonction), alors le pointeur "pourrait ne pas pointer vers une entité de type référence" (c'est-à-dire qu'il peut ne pas fournir un référence à un objet ou une fonction). Cela pourrait nous fournir autre chose. Et c'est un endroit où vous pouvez coller une sorte de poignée ou d'ID dans un pointeur (c'est-à-dire que le pointeur ne pointe pas vers un objet; il stocke une valeur qui représente quelque chose, mais cette valeur peut ne pas être une adresse).

Alors oui, comme le dit Alexey Frunze, il est possible qu'un pointeur ne stocke pas d'adresse sur un objet ou une fonction. Il est possible qu'un pointeur stocke à la place une sorte de "handle" ou d'ID, et vous pouvez le faire en affectant une valeur entière arbitraire à un pointeur. Ce que ce descripteur ou ID représente dépend du système / environnement / contexte. Tant que votre système / implémentation peut donner un sens à la valeur, vous êtes en bonne forme (mais cela dépend de la valeur spécifique et du système / implémentation spécifique).

Normalement , un pointeur stocke une adresse à un objet ou une fonction. S'il ne stocke pas une adresse réelle (vers un objet ou une fonction), le résultat est défini par l'implémentation (ce qui signifie que ce qui se passe exactement et ce que le pointeur représente maintenant dépend de votre système et de votre implémentation, il peut donc s'agir d'un identifiant ou d'un identificateur sur un système particulier, mais l'utilisation du même code / valeur sur un autre système peut faire planter votre programme).

Cela a fini par être plus long que je ne le pensais ...


3
Dans un interpréteur C, un pointeur peut contenir un ID / handle / etc non-adresse.
Alexey Frunze

4
@exebook La norme n'est en aucun cas limitée à la compilation de C.
Alexey Frunze

7
@Lundin Bravo! Ignorons davantage la norme! Comme si nous ne l'avions pas déjà suffisamment ignoré et que nous n'avions pas produit de logiciels buggés et mal portables à cause de cela. Veuillez également noter que la question d'origine est générique et nécessite donc une réponse générique.
Alexey Frunze

3
Lorsque d'autres disent qu'un pointeur peut être une poignée ou autre chose qu'une adresse, cela ne signifie pas seulement que vous pouvez contraindre des données dans un pointeur en transtypant un entier en un pointeur. Ils signifient que le compilateur peut utiliser autre chose que des adresses mémoire pour implémenter des pointeurs. Sur le processeur Alpha avec l'ABI de DEC, un pointeur de fonction n'était pas l'adresse de la fonction mais l'adresse d'un descripteur de fonction, et le descripteur contenait l'adresse de la fonction et quelques données sur les paramètres de la fonction. Le fait est que la norme C est très flexible.
Eric Postpischil

5
@Lundin: L'affirmation selon laquelle les pointeurs sont implémentés en tant qu'adresses entières sur 100% des systèmes informatiques existants dans le monde réel est fausse. Les ordinateurs existent avec l'adressage de mots et l'adressage à décalage de segment. Les compilateurs existent toujours avec le support des pointeurs proches et lointains. Il existe des ordinateurs PDP-11, avec RSX-11 et le Task Builder et ses superpositions, dans lesquels un pointeur doit identifier les informations nécessaires pour charger une fonction à partir du disque. Un pointeur ne peut pas avoir l'adresse mémoire d'un objet si l'objet n'est pas en mémoire!
Eric Postpischil

39

Pointeur vs variable

Sur cette photo,

pointer_p est un pointeur qui se trouve à 0x12345 et pointe vers une variable variable_v à 0x34567.


16
Non seulement cela n'aborde pas la notion d'adresse par opposition au pointeur, mais il manque intégralement le point qu'une adresse n'est pas seulement un entier.
Gilles 'SO- arrête d'être méchant'

19
-1, cela explique simplement ce qu'est un pointeur. Ce ne fut pas le question-- et vous êtes en écartant toute la complexité que la question est à propos.
alexis

34

Penser un pointeur comme une adresse est une approximation . Comme toutes les approximations, c'est assez bon pour être utile parfois, mais ce n'est pas non plus exact, ce qui signifie que le fait de s'y fier cause des problèmes.

Un pointeur est comme une adresse dans la mesure où il indique où trouver un objet. Une limitation immédiate de cette analogie est que tous les pointeurs ne contiennent pas réellement une adresse. NULLest un pointeur qui n'est pas une adresse. Le contenu d'une variable pointeur peut en fait être de trois types:

  • l' adresse d'un objet, qui peut être déréférencée (si pcontient l'adresse de xalors l'expression *pa la même valeur que x);
  • un pointeur nul , dont NULLest un exemple;
  • contenu invalide , qui ne pointe pas vers un objet (s'il pne contient pas de valeur valide, il *ppourrait alors faire n'importe quoi («comportement indéfini»), avec le plantage du programme une possibilité assez courante).

De plus, il serait plus précis de dire qu'un pointeur (s'il est valide et non nul) contient une adresse: un pointeur indique où trouver un objet, mais il y a plus d'informations qui lui sont liées.

En particulier, un pointeur a un type. Sur la plupart des plates-formes, le type du pointeur n'a aucune influence au moment de l'exécution, mais il a une influence qui va au-delà du type au moment de la compilation. Si pest un pointeur vers int( int *p;), alors p + 1pointe vers un entier qui est sizeof(int)octets après p(en supposant qu'il p + 1s'agit toujours d'un pointeur valide). Si qest un pointeur vers charqui pointe vers la même adresse que p( char *q = p;), alors q + 1n'est pas la même adresse que p + 1. Si vous considérez le pointeur comme des adresses, il n'est pas très intuitif que l '«adresse suivante» soit différente pour différents pointeurs vers le même emplacement.

Il est possible dans certains environnements d'avoir plusieurs valeurs de pointeur avec différentes représentations (différents modèles de bits en mémoire) qui pointent vers le même emplacement en mémoire. Vous pouvez les considérer comme des pointeurs différents détenant la même adresse ou comme des adresses différentes pour le même emplacement - la métaphore n'est pas claire dans ce cas. L' ==opérateur vous indique toujours si les deux opérandes pointent vers le même emplacement, donc sur ces environnements, vous pouvez les avoir p == qmême pet qavoir des modèles de bits différents.

Il existe même des environnements où les pointeurs portent d'autres informations au-delà de l'adresse, telles que des informations de type ou d'autorisation. Vous pouvez facilement passer par votre vie de programmeur sans les rencontrer.

Il existe des environnements où différents types de pointeurs ont des représentations différentes. Vous pouvez le considérer comme différents types d'adresses ayant des représentations différentes. Par exemple, certaines architectures ont des pointeurs d'octet et des pointeurs de mot, ou des pointeurs d'objet et des pointeurs de fonction.

Dans l'ensemble, penser aux pointeurs comme des adresses n'est pas si mal tant que vous gardez à l'esprit que

  • ce ne sont que des pointeurs valides et non nuls qui sont des adresses;
  • vous pouvez avoir plusieurs adresses pour le même emplacement;
  • vous ne pouvez pas faire d'arithmétique sur les adresses, et il n'y a pas d'ordre sur elles;
  • le pointeur porte également des informations de type.

Faire l'inverse est beaucoup plus gênant. Tout ce qui ressemble à une adresse ne peut pas être un pointeur . Quelque part au fond, tout pointeur est représenté comme un motif binaire qui peut être lu comme un entier, et vous pouvez dire que cet entier est une adresse. Mais dans l'autre sens, tous les nombres entiers ne sont pas des pointeurs.

Il y a d'abord quelques limitations bien connues; par exemple, un entier qui désigne un emplacement en dehors de l'espace d'adressage de votre programme ne peut pas être un pointeur valide. Une adresse mal alignée ne fait pas un pointeur valide pour un type de données qui nécessite un alignement; par exemple, sur une plate-forme intnécessitant un alignement sur 4 octets, 0x7654321 ne peut pas être une int*valeur valide .

Cependant, cela va bien au-delà, car lorsque vous transformez un pointeur en entier, vous êtes dans un monde de problèmes. Une grande partie de ce problème est que l'optimisation des compilateurs est bien meilleure en microoptimisation que la plupart des programmeurs ne le pensent, de sorte que leur modèle mental de fonctionnement d'un programme est profondément erroné. Ce n'est pas parce que vous avez des pointeurs avec la même adresse qu'ils sont équivalents. Par exemple, considérez l'extrait de code suivant:

unsigned int x = 0;
unsigned short *p = (unsigned short*)&x;
p[0] = 1;
printf("%u = %u\n", x, *p);

Vous pouvez vous attendre à ce que sur une machine ordinaire où sizeof(int)==4et sizeof(short)==2, cela imprime 1 = 1?(petit-boutien) ou 65536 = 1?(gros-boutien). Mais sur mon PC Linux 64 bits avec GCC 4.4:

$ c99 -O2 -Wall a.c && ./a.out 
a.c: In function main’:
a.c:6: warning: dereferencing pointer p does break strict-aliasing rules
a.c:5: note: initialized from here
0 = 1?

GCC est assez aimable pour nous avertir de ce qui ne va pas dans cet exemple simple - dans des exemples plus complexes, le compilateur pourrait ne pas le remarquer. Depuis pa un type différent de &x, le fait de changer les ppoints vers ne peut pas affecter ceux &xvers lesquels (en dehors de certaines exceptions bien définies). Par conséquent, le compilateur est libre de conserver la valeur de xdans un registre et de ne pas mettre à jour ce registre en tant que *pmodifications. Le programme déréférence deux pointeurs vers la même adresse et obtient deux valeurs différentes!

La morale de cet exemple est que penser à un pointeur (valide non nul) comme une adresse est très bien, tant que vous restez dans les règles précises du langage C. Le revers de la médaille est que les règles du langage C sont complexes et difficiles à obtenir une sensation intuitive à moins que vous ne sachiez ce qui se passe sous le capot. Et ce qui se passe sous le capot, c'est que le lien entre les pointeurs et les adresses est quelque peu lâche, à la fois pour prendre en charge les architectures de processeur «exotiques» et pour optimiser les compilateurs.

Pensez donc aux pointeurs comme étant des adresses comme une première étape dans votre compréhension, mais ne suivez pas cette intuition trop loin.


5
+1. D'autres réponses semblent manquer qu'un pointeur vienne avec des informations de type. C'est beaucoup plus important que l'adresse / ID / quelle que soit la discussion.
undur_gongor

+1 Excellents points sur les informations de type. Je ne suis pas sûr que les exemples du compilateur soient corrects ... Il semble très peu probable, par exemple, que la *p = 3réussite soit garantie lorsque p n'a pas été initialisé.
LarsH

@LarsH Vous avez raison, merci, comment ai-je écrit cela? Je l'ai remplacé par un exemple qui démontre même le comportement surprenant de mon PC.
Gilles 'SO- arrête d'être méchant'

1
euh, NULL est ((void *) 0) ..?
Aniket Inge

1
@ gnasher729 Le pointeur nul est un pointeur. NULLn'est pas, mais pour le niveau de détail requis ici, c'est une distraction non pertinente. Même pour la programmation au jour le jour, le fait que cela NULLpuisse être implémenté comme quelque chose qui ne dit pas «pointeur» ne revient pas souvent (principalement en passant NULLà une fonction variadique - mais même là, si vous ne le diffusez pas , vous faites déjà l'hypothèse que tous les types de pointeurs ont la même représentation).
Gilles 'SO- arrête d'être méchant'

19

Un pointeur est une variable qui HOLDS adresse en mémoire, pas l'adresse elle-même. Cependant, vous pouvez déréférencer un pointeur - et accéder à l'emplacement de la mémoire.

Par exemple:

int q = 10; /*say q is at address 0x10203040*/
int *p = &q; /*means let p contain the address of q, which is 0x10203040*/
*p = 20; /*set whatever is at the address pointed by "p" as 20*/

C'est tout. C'est si simple.

entrez la description de l'image ici

Un programme pour démontrer ce que je dis et sa sortie est ici:

http://ideone.com/rcSUsb

Le programme:

#include <stdio.h>

int main(int argc, char *argv[])
{
  /* POINTER AS AN ADDRESS */
  int q = 10;
  int *p = &q;

  printf("address of q is %p\n", (void *)&q);
  printf("p contains %p\n", (void *)p);

  p = NULL;
  printf("NULL p now contains %p\n", (void *)p);
  return 0;
}

5
Cela peut créer encore plus de confusion. Alice, tu vois un chat? Non, je ne vois qu'un sourire de chat. Donc, dire que le pointeur est une adresse ou que le pointeur est une variable qui contient une adresse ou dire que le pointeur est le nom d'un concept qui se réfère à l'idée d'une adresse, jusqu'où les écrivains peuvent-ils aller pour confondre les novices?
exebook

@exebook à ceux chevronnés en pointeurs, c'est assez simple. Peut-être qu'une photo vous aidera?
Aniket Inge

5
Un pointeur ne contient pas nécessairement une adresse. Dans un interpréteur C, cela pourrait être autre chose, une sorte d'ID / handle.
Alexey Frunze

Le "label" ou nom de variable est un compilateur / assembleur et n'existe pas au niveau de la machine, donc je ne pense pas qu'il devrait apparaître dans la mémoire.
Ben

1
@Aniket Une variable de pointeur peut contenir une valeur de pointeur. Vous n'avez besoin de stocker le résultat de fopendans une variable que si vous devez l'utiliser plus d'une fois (ce qui, pour fopen, est à peu près tout le temps).
Gilles 'SO- arrête d'être méchant'

16

Il est difficile de dire exactement ce que les auteurs de ces livres veulent dire exactement. Le fait qu'un pointeur contienne ou non une adresse dépend de la façon dont vous définissez une adresse et de la façon dont vous définissez un pointeur.

À en juger par toutes les réponses qui sont écrites, certaines personnes supposent que (1) une adresse doit être un entier et (2) un pointeur n'a pas besoin d'être virtuel pour ne pas être dit ainsi dans la spécification. Avec ces hypothèses, il est clair que les pointeurs ne contiennent pas nécessairement d'adresses.

Cependant, nous voyons que si (2) est probablement vrai, (1) ne doit probablement pas être vrai. Et que faire du fait que le & est appelé l' adresse de l' opérateur selon la réponse de @ CornStalks? Est-ce à dire que les auteurs de la spécification ont l'intention qu'un pointeur contienne une adresse?

Alors pouvons-nous dire que le pointeur contient une adresse, mais une adresse ne doit pas nécessairement être un entier? Peut être.

Je pense que tout cela est un discours sémantique pédant jibberish. Cela ne vaut pratiquement rien. Pouvez-vous penser à un compilateur qui génère du code de telle manière que la valeur d'un pointeur ne soit pas une adresse? Si oui, quoi? C'est ce que je pensais...

Je pense que l'auteur du livre (le premier extrait qui prétend que les pointeurs ne sont pas nécessairement des adresses) fait probablement référence au fait qu'un pointeur est accompagné des informations de type inhérentes.

Par exemple,

 int x;
 int* y = &x;
 char* z = &x;

y et z sont tous deux des pointeurs, mais y + 1 et z + 1 sont différents. s'il s'agit d'adresses mémoire, ces expressions ne vous donneraient-elles pas la même valeur?

Et c'est là que réside la réflexion sur les pointeurs comme s'ils étaient des adresses mène généralement au chagrin . Des bogues ont été écrits parce que les gens pensent aux pointeurs comme s'ils étaient des adresses , ce qui mène généralement à la peine .

55555 n'est probablement pas un pointeur, bien qu'il puisse s'agir d'une adresse, mais (int *) 55555 est un pointeur. 55555 + 1 = 55556, mais (int *) 55555 + 1 est 55559 (+/- différence en termes de taille de (int)).


1
+1 pour indiquer l'arithmétique du pointeur n'est pas la même chose que l'arithmétique sur les adresses.
kutschkem

Dans le cas du 8086 16 bits, une adresse mémoire est décrite par une base segment + offset, toutes deux 16 bits. Il existe de nombreuses combinaisons de segment base + offset qui donnent la même adresse en mémoire. Ce farpointeur n'est pas seulement "un entier".
vonbrand

@vonbrand je ne comprends pas pourquoi vous avez posté ce commentaire. cette question a été discutée sous forme de commentaires dans d'autres réponses. à peu près toutes les autres réponses supposent que address = integer et tout ce qui n'est pas entier n'est pas address. je le signale simplement et je note qu'il peut ou non être correct. tout mon argument dans la réponse est qu'il n'est pas pertinent. tout cela n'est que pédant, et le problème principal n'est pas abordé dans les autres réponses.
thang

@tang, l'idée "pointer == address" est fausse . Que tout le monde et sa tante préférée continuent de le dire, cela ne fait pas l'affaire.
vonbrand

@vonbrand, et pourquoi avez-vous fait ce commentaire sous mon post? Je n'ai pas dit que c'était bien ou mal. En fait, c'est vrai dans certains scénarios / hypothèses, mais pas toujours. Permettez-moi de résumer à nouveau le point du poste (pour la deuxième fois). tout mon argument dans la réponse est qu'il n'est pas pertinent. tout cela n'est que pédant, et le problème principal n'est pas abordé dans les autres réponses. il serait plus approprié de commenter les réponses qui affirment que le pointeur == adresse ou l'adresse == entier. voir mes commentaires sous le post d'Alexey concernant le segment: offset.
thang

15

Eh bien, un pointeur est une abstraction représentant un emplacement mémoire. Notez que la citation ne dit pas que penser aux pointeurs comme s'il s'agissait d'adresses de mémoire est faux, mais simplement que cela "mène généralement au chagrin". En d'autres termes, cela vous conduit à avoir des attentes incorrectes.

La source de deuil la plus probable est certainement l' arithmétique des pointeurs, qui est en fait l'une des forces de C. Si un pointeur était une adresse, vous vous attendriez à ce que l'arithmétique du pointeur soit une arithmétique d'adresse; mais ce n'est pas. Par exemple, ajouter 10 à une adresse devrait vous donner une adresse plus grande de 10 unités d'adressage; mais l'ajout de 10 à un pointeur l'incrémente de 10 fois la taille du type d'objet vers lequel il pointe (et même pas la taille réelle, mais arrondie à une limite d'alignement). Avec une int *architecture ordinaire avec des entiers 32 bits, l'ajout de 10 l'incrémenterait de 40 unités d'adressage (octets). Les programmeurs C expérimentés en sont conscients et vivent avec, mais votre auteur n'est évidemment pas fan des métaphores bâclées.

Il y a la question supplémentaire de savoir comment le contenu du pointeur représente l'emplacement de la mémoire: Comme beaucoup de réponses l'ont expliqué, une adresse n'est pas toujours un int (ou long). Dans certaines architectures, une adresse est un "segment" plus un décalage. Un pointeur peut même contenir uniquement l'offset dans le segment en cours (pointeur "proche"), qui en soi n'est pas une adresse mémoire unique. Et le contenu du pointeur peut n'avoir qu'une relation indirecte avec une adresse mémoire telle que le matériel la comprend. Mais l'auteur de la citation citée ne mentionne même pas la représentation, donc je pense que c'était l'équivalence conceptuelle, plutôt que la représentation, qu'ils avaient en tête.


12

Voici comment je l'ai expliqué à certaines personnes confuses dans le passé: Un pointeur a deux attributs qui affectent son comportement. Il a une valeur , qui est (dans des environnements typiques) une adresse mémoire, et un type , qui vous indique le type et la taille de l'objet vers lequel il pointe.

Par exemple, étant donné:

union {
    int i;
    char c;
} u;

Vous pouvez avoir trois pointeurs différents pointant tous vers ce même objet:

void *v = &u;
int *i = &u.i;
char *c = &u.c;

Si vous comparez les valeurs de ces pointeurs, ils sont tous égaux:

v==i && i==c

Cependant, si vous incrémentez chaque pointeur, vous verrez que le type vers lequel ils pointent devient pertinent.

i++;
c++;
// You can't perform arithmetic on a void pointer, so no v++
i != c

Les variables iet cauront des valeurs différentes à ce stade, parce que les i++causes ide contenir l'adresse du nombre entier suivant accessible, et c++provoque cle point sur le caractère suivant adressable. En règle générale, les entiers occupent plus de mémoire que les caractères, ils ise retrouveront donc avec une valeur plus élevée cqu'après avoir tous les deux été incrémentés.


2
+1 Merci. Avec des pointeurs, la valeur et le type sont aussi inséparables que l'on peut séparer le corps de l'homme de son âme.
Aki Suihkonen

i == cest mal formé (vous ne pouvez comparer des pointeurs à différents types qu'en cas de conversion implicite de l'un à l'autre). De plus, la correction de ceci avec un cast signifie que vous avez appliqué une conversion, et il est alors discutable que la conversion change la valeur ou non. (Vous pouvez affirmer que ce n'est pas le cas, mais c'est simplement affirmer la même chose que vous essayiez de prouver avec cet exemple).
MM

8

Mark Bessey l'a déjà dit, mais cela doit être souligné de nouveau jusqu'à ce qu'il soit compris.

Le pointeur a autant à voir avec une variable qu'avec un littéral 3.

Le pointeur est un tuple d'une valeur (d'une adresse) et d'un type (avec des propriétés supplémentaires, telles que la lecture seule). Le type (et les paramètres supplémentaires le cas échéant) peut définir ou restreindre davantage le contexte; par exemple. __far ptr, __near ptr: quel est le contexte de l'adresse: pile, tas, adresse linéaire, décalage de quelque part, mémoire physique ou quoi.

C'est la propriété de type qui rend l'arithmétique du pointeur un peu différente de l'arithmétique entière.

Les contre-exemples d'un pointeur de non-variable sont trop nombreux pour être ignorés

  • fopen renvoyant un pointeur FILE. (où est la variable)

  • pointeur de pile ou pointeur de trame étant généralement des registres non adressables

    *(int *)0x1231330 = 13; - transtyper une valeur entière arbitraire en un type pointer_of_integer et écrire / lire un entier sans jamais introduire de variable

Au cours de la durée de vie d'un programme C, il y aura de nombreuses autres instances de pointeurs temporaires qui n'ont pas d'adresse - et donc ce ne sont pas des variables, mais des expressions / valeurs avec un type associé à l'heure de compilation.


8

Tu as raison et sensé. Normalement, un pointeur n'est qu'une adresse, vous pouvez donc le convertir en entier et faire n'importe quelle arithmétique.

Mais parfois, les pointeurs ne sont qu'une partie d'une adresse. Sur certaines architectures, un pointeur est converti en une adresse avec ajout de base ou un autre registre CPU est utilisé.

Mais de nos jours, sur une architecture PC et ARM avec un modèle de mémoire plat et un langage C compilés nativement, il est normal de penser qu'un pointeur est une adresse entière à un endroit dans la RAM adressable unidimensionnelle.


PC ... modèle de mémoire plate? que sont les sélecteurs?
thang

Riight. Et lorsque le prochain changement d'architecture surviendra, peut-être avec des espaces de données de code et d'adn séparés, ou que quelqu'un reviendra à l'architecture de segment vénérable (ce qui est très logique pour la sécurité, pourrait même ajouter une clé au numéro de segment + décalage pour vérifier les autorisations) votre de jolis "pointeurs ne sont que des entiers" s'écroulent.
vonbrand

7

Un pointeur, comme toute autre variable en C, est fondamentalement une collection de bits qui peuvent être représentés par une ou plusieurs unsigned charvaleurs concaténées (comme avec tout autre type de cariable, sizeof(some_variable)indiquera le nombre de unsigned charvaleurs). Ce qui différencie un pointeur des autres variables, c'est qu'un compilateur C interprétera les bits d'un pointeur comme identifiant, d'une manière ou d'une autre, un endroit où une variable peut être stockée. En C, contrairement à certains autres langages, il est possible de demander de l'espace pour plusieurs variables, puis de convertir un pointeur vers n'importe quelle valeur de cet ensemble en un pointeur vers n'importe quelle autre variable de cet ensemble.

De nombreux compilateurs implémentent des pointeurs en utilisant leurs bits pour stocker les adresses réelles des machines, mais ce n'est pas la seule implémentation possible. Une implémentation peut conserver un tableau - non accessible au code utilisateur - répertoriant l'adresse matérielle et la taille allouée de tous les objets de mémoire (ensembles de variables) qu'un programme utilisait, et chaque pointeur contient un index dans un tableau le long avec un décalage par rapport à cet indice. Une telle conception permettrait à un système de restreindre non seulement le code à la mémoire qu'il possédait, mais également de s'assurer qu'un pointeur vers un élément de mémoire ne pouvait pas être accidentellement converti en pointeur vers un autre élément de mémoire (dans un système qui utilise du matériel adresse, si fooet barsont des tableaux de 10 éléments qui sont stockés consécutivement en mémoire, un pointeur vers le "onzième" élément defoopourrait plutôt pointer vers le premier élément de bar, mais dans un système où chaque "pointeur" est un ID d'objet et un décalage, le système pourrait intercepter si le code essayait d'indexer un pointeur au- foodelà de sa plage allouée). Il serait également possible pour un tel système d'éliminer les problèmes de fragmentation de la mémoire, car les adresses physiques associées à tout pointeur pourraient être déplacées.

Notez que bien que les pointeurs soient quelque peu abstraits, ils ne sont pas assez abstraits pour permettre à un compilateur C entièrement conforme aux normes d'implémenter un garbage collector. Le compilateur C spécifie que chaque variable, y compris les pointeurs, est représentée comme une séquence de unsigned charvaleurs. Étant donné n'importe quelle variable, on peut la décomposer en une séquence de nombres et plus tard reconvertir cette séquence de nombres en une variable du type d'origine. Par conséquent, un programme pourraitcallocun peu de stockage (recevoir un pointeur sur celui-ci), y stocker quelque chose, décomposer le pointeur en une série d'octets, les afficher à l'écran, puis effacer toute référence à ceux-ci. Si le programme acceptait ensuite certains chiffres du clavier, les reconstituait en un pointeur, puis tentait de lire les données de ce pointeur, et si l'utilisateur saisissait les mêmes chiffres que le programme avait précédemment affichés, le programme serait obligé de sortir les données qui avait été stocké dans la callocmémoire 'ed. Puisqu'il n'y a aucun moyen concevable que l'ordinateur puisse savoir si l'utilisateur a fait une copie des nombres qui ont été affichés, il ne serait pas concevable que l'ordinateur puisse savoir si la mémoire susmentionnée pourrait être accessible à l'avenir.


Au-dessus des frais généraux, vous pourriez peut-être détecter toute utilisation de la valeur du pointeur qui pourrait "divulguer" sa valeur numérique et épingler l'allocation de sorte que le garbage collector ne la recueille pas ou ne la déplace pas (sauf si elle freeest appelée explicitement, bien sûr). Que l'implémentation résultante soit tout aussi utile est une autre question, car sa capacité à collecter est peut-être trop limitée, mais vous pouvez au moins l'appeler un garbage collector :-) L'affectation de pointeurs et l'arithmétique ne "fuiront" pas la valeur, mais tout accès à une char*origine inconnue devra être vérifié.
Steve Jessop

@SteveJessop: Je pense qu'une telle conception serait pire qu'inutile, car il serait impossible pour le code de savoir quels pointeurs devaient être libérés. Les ramasse-miettes qui supposent que tout ce qui ressemble à un pointeur en est un peuvent être trop conservateurs, mais généralement les choses qui ressemblent - mais ne le sont pas - aux pointeurs ont la possibilité de changer, évitant ainsi les fuites de mémoire "permanentes". Avoir une action qui ressemble à décomposer un pointeur en octets gèle définitivement le pointeur est une recette garantie pour les fuites de mémoire.
supercat

Je pense que cela échouerait de toute façon pour des raisons de performances - si vous voulez que votre code s'exécute aussi lentement car chaque accès est vérifié, alors ne l'écrivez pas en C ;-) J'ai plus d'espoir pour l'ingéniosité des programmeurs C que vous, car je pense que même si cela n'est pas pratique, il n'est probablement pas invraisemblable d'éviter d'épingler inutilement les allocations. Quoi qu'il en soit, C ++ définit des "pointeurs dérivés en toute sécurité" précisément pour traiter ce problème, nous savons donc quoi faire si nous voulons augmenter l'abstrait des pointeurs C au niveau où ils prennent en charge la collecte de déchets raisonnablement efficace.
Steve Jessop

@SteveJessop: Pour qu'un système GC soit utile, il doit être en mesure de libérer de manière fiable la mémoire sur laquelle il freen'a pas été appelé, ou d'empêcher toute référence à un objet libéré de devenir une référence à un objet vivant [même lors de l'utilisation de ressources qui nécessitent gestion explicite de la durée de vie, GC peut encore utilement remplir cette dernière fonction]; un système GC qui considère parfois faussement des objets comme ayant des références réelles à eux peut être utilisable si la probabilité que N objets soient inutilement épinglés simultanément approche de zéro lorsque N devient grand . À moins que quelqu'un ne
veuille signaler

... pour du code qui est valide en C ++, mais pour lequel le compilateur serait incapable de prouver qu'un pointeur ne peut jamais être converti en une forme méconnaissable, je ne vois pas comment on pourrait éviter le risque qu'un programme qui en fait ne utilise des pointeurs car les nombres entiers peuvent être considérés à tort comme tels.
supercat

6

Un pointeur est un type de variable qui est nativement disponible en C / C ++ et contient une adresse mémoire. Comme toute autre variable, elle a une adresse qui lui est propre et prend de la mémoire (le montant est spécifique à la plate-forme).

Un problème que vous verrez à la suite de la confusion est d'essayer de changer le référent dans une fonction en passant simplement le pointeur par valeur. Cela fera une copie du pointeur à la portée de la fonction et toute modification à l'endroit où ce nouveau pointeur "pointe" ne changera pas le référent du pointeur à la portée qui a appelé la fonction. Afin de modifier le pointeur réel dans une fonction, on devrait normalement passer un pointeur à un pointeur.


1
Généralement, c'est une poignée / ID. Habituellement, c'est une adresse simple.
Alexey Frunze

J'ai ajusté ma réponse pour être un peu plus PC à la définition de Handle dans wikipedia. J'aime faire référence aux pointeurs comme une instance particulière d'un handle, car un handle peut simplement être une référence à un pointeur.
Matthew Sanders

6

BREF RÉSUMÉ (que je mettrai également en haut):

(0) Le fait de considérer les pointeurs comme des adresses est souvent un bon outil d'apprentissage et constitue souvent l'implémentation réelle des pointeurs vers des types de données ordinaires.

(1) Mais sur de nombreux compilateurs, peut-être la plupart, les pointeurs vers les fonctions ne sont pas des adresses, mais sont plus gros qu'une adresse (généralement 2x, parfois plus), ou sont en fait des pointeurs vers une structure en mémoire qui contient les adresses de fonction et des trucs comme une piscine constante.

(2) Les pointeurs vers les membres de données et les pointeurs vers les méthodes sont souvent encore plus étranges.

(3) Code x86 hérité avec problèmes de pointeurs FAR et NEAR

(4) Plusieurs exemples, notamment l'IBM AS / 400, avec des "gros pointeurs" sécurisés.

Je suis sûr que vous pouvez en trouver plus.

DÉTAIL:

UMMPPHHH !!!!! Jusqu'à présent, la plupart des réponses sont des réponses "programmeur weenie" assez typiques - mais pas le compilateur weenie ni le matériel weenie. Puisque je fais semblant d'être un weenie matériel et que je travaille souvent avec des weenies de compilateur, permettez-moi de mettre mes deux cents:

Sur de nombreux compilateurs C, probablement la plupart, un pointeur vers des données de type Test, en fait, l'adresse de T.

Bien.

Mais, même sur beaucoup de ces compilateurs, certains pointeurs ne sont PAS des adresses. Vous pouvez le constater en regardant sizeof(ThePointer).

Par exemple, les pointeurs vers les fonctions sont parfois beaucoup plus gros que les adresses ordinaires. Ou, ils peuvent impliquer un niveau d'indirection. Cet articlefournit une description, impliquant le processeur Intel Itanium, mais j'en ai vu d'autres. En règle générale, pour appeler une fonction, vous devez connaître non seulement l'adresse du code de la fonction, mais également l'adresse du pool constant de la fonction - une région de mémoire à partir de laquelle les constantes sont chargées avec une seule instruction de chargement, plutôt que le compilateur doive générer une constante 64 bits sur plusieurs instructions Load Immediate et Shift et OR. Ainsi, plutôt qu'une seule adresse 64 bits, vous avez besoin de 2 adresses 64 bits. Certains ABI (Application Binary Interfaces) le déplacent sur 128 bits, tandis que d'autres utilisent un niveau d'indirection, le pointeur de fonction étant en fait l'adresse d'un descripteur de fonction qui contient les 2 adresses réelles qui viennent d'être mentionnées. Ce qui est mieux? Dépend de votre point de vue: performances, taille du code, et certains problèmes de compatibilité - le code suppose souvent qu'un pointeur peut être converti en un long ou un long long, mais peut également supposer que le long long est exactement 64 bits. Ce code n'est peut-être pas conforme aux normes, mais les clients peuvent néanmoins souhaiter qu'il fonctionne.

Beaucoup d'entre nous ont des souvenirs douloureux de l'ancienne architecture segmentée Intel x86, avec NEAR POINTERs et FAR POINTERS. Heureusement, ils sont presque éteints maintenant, donc seulement un bref résumé: en mode réel 16 bits, l'adresse linéaire réelle était

LinearAddress = SegmentRegister[SegNum].base << 4 + Offset

Alors qu'en mode protégé, il peut être

LinearAddress = SegmentRegister[SegNum].base + offset

l'adresse résultante étant vérifiée par rapport à une limite définie dans le segment. Certains programmes n'utilisaient pas vraiment les déclarations de pointeur C / C ++ FAR et NEAR standard, mais beaucoup venaient de le dire *T--- mais il y avait des commutateurs de compilation et de l'éditeur de liens donc, par exemple, les pointeurs de code pouvaient être des pointeurs proches, juste un décalage de 32 bits par rapport à tout ce qui se trouve dans le registre CS (Code Segment), tandis que les pointeurs de données peuvent être des pointeurs FAR, spécifiant à la fois un numéro de segment de 16 bits et un décalage de 32 bits pour une valeur de 48 bits. Maintenant, ces deux quantités sont certainement liées à l'adresse, mais comme elles ne sont pas de la même taille, laquelle est l'adresse? De plus, les segments comportaient également des autorisations - lecture seule, lecture-écriture, exécutable - en plus des éléments liés à l'adresse réelle.

Un exemple plus intéressant, à mon humble avis, est (ou était peut-être) la famille IBM AS / 400. Cet ordinateur a été l'un des premiers à implémenter un OS en C ++. Les pointeurs sur ce machime étaient généralement 2X la taille réelle de l'adresse - par exemple, comme cette présentationdit, des pointeurs de 128 bits, mais les adresses réelles étaient de 48 à 64 bits, et, encore une fois, des informations supplémentaires, ce qu'on appelle une capacité, qui fournissait des autorisations telles que la lecture, l'écriture, ainsi qu'une limite pour éviter le débordement de la mémoire tampon. Oui: vous pouvez le faire de manière compatible avec C / C ++ - et si cela était omniprésent, le PLA chinois et la mafia slave ne pirateraient pas autant de systèmes informatiques occidentaux. Mais historiquement, la plupart des programmes C / C ++ ont négligé la sécurité pour les performances. Plus intéressant encore, la famille AS400 a permis au système d'exploitation de créer des pointeurs sécurisés, qui pourraient être attribués à du code non privilégié, mais que le code non privilégié ne pouvait ni falsifier ni altérer. Encore une fois, la sécurité et, bien que conforme aux normes, le code C / C ++ non conforme aux normes, très bâclé, ne fonctionnera pas dans un système aussi sécurisé. Encore une fois, il existe des normes officielles,

Maintenant, je vais quitter ma boîte à savon de sécurité et mentionner quelques autres façons dont les pointeurs (de différents types) ne sont souvent pas vraiment des adresses: pointeurs vers les membres de données, pointeurs vers les méthodes de fonctions membres et leurs versions statiques sont plus grandes qu'un adresse ordinaire. Comme le dit ce post :

Il existe de nombreuses façons de résoudre ce problème [problèmes liés à l'héritage simple ou multiple et à l'héritage virtuel]. Voici comment le compilateur Visual Studio décide de le gérer: Un pointeur vers une fonction membre d'une classe à héritage multiplié est vraiment une structure. "Et ils continuent en disant" Casting un pointeur de fonction peut changer sa taille! ".

Comme vous pouvez probablement le deviner à partir de mon pontificat sur la (in) sécurité, j'ai été impliqué dans des projets matériels / logiciels C / C ++ où un pointeur était traité plus comme une capacité qu'une adresse brute.

Je pourrais continuer, mais j'espère que vous comprenez l'idée.

BREF RÉSUMÉ (que je mettrai également en haut):

(0) considérer les pointeurs comme des adresses est souvent un bon outil d'apprentissage et constitue souvent la mise en œuvre réelle des pointeurs vers des types de données ordinaires.

(1) Mais sur de nombreux compilateurs, peut-être la plupart, les pointeurs vers des fonctions ne sont pas des adresses, mais sont plus gros qu'une adresse (généralement 2X, parfois plus), ou sont en fait des pointeurs vers une structure en mémoire qui contient les adresses de fonction et des trucs comme une piscine constante.

(2) Les pointeurs vers les membres de données et les pointeurs vers les méthodes sont souvent encore plus étranges.

(3) Code x86 hérité avec problèmes de pointeurs FAR et NEAR

(4) Plusieurs exemples, notamment l'IBM AS / 400, avec des "gros pointeurs" sécurisés.

Je suis sûr que vous pouvez en trouver plus.


En mode réel 16 bits LinearAddress = SegmentRegister.Selector * 16 + Offset(notez les temps 16, pas de décalage de 16). En mode protégé LinearAddress = SegmentRegister.base + offset(aucune multiplication d'aucune sorte; la base de segment est stockée dans le GDT / LDT et mise en cache dans le registre de segment telle quelle ).
Alexey Frunze

Vous avez également raison sur la base du segment. J'avais mal mémorisé. C'est la limite de segment qui est éventuellement multiple de 4K. La base de segment doit simplement être débrouillée par le matériel lorsqu'il charge un descripteur de segment de la mémoire dans un registre de segment.
Krazy Glew

4

Un pointeur est juste une autre variable qui est utilisée pour contenir l'adresse d'un emplacement mémoire (généralement l'adresse mémoire d'une autre variable).


Donc, la pointe est en fait une adresse mémoire? Vous n'êtes pas d'accord avec l'auteur? J'essaye juste de comprendre.
d0rmLife

La fonction principale du pointeur est de pointer vers quelque chose. Comment exactement cela est réalisé et s'il existe une adresse réelle ou non, n'est pas défini. Un pointeur pourrait être juste un ID / handle, pas une vraie adresse.
Alexey Frunze

4

Vous pouvez le voir de cette façon. Un pointeur est une valeur qui représente une adresse dans l'espace mémoire adressable.


2
Un pointeur ne doit pas nécessairement contenir la véritable adresse mémoire. Voir ma réponse et le (s) commentaire (s) en dessous.
Alexey Frunze

quoi .... le pointeur sur la première variable de la pile n'imprime pas 0. il imprime le haut (ou le bas) du cadre de la pile selon la façon dont il est implémenté.
thang

@thang Pour la première variable, le haut et le bas sont identiques. Et quelle est l'adresse du haut ou du bas dans ce cas de la pile?
Valentin Radu

@ValentinRadu, pourquoi ne l'essayez-vous pas? Évidemment vous ne l'avez pas essayé.
thang

2
@thang Tu as raison, j'ai fait de très mauvaises hypothèses, à ma défense il est 5h du matin ici.
Valentin Radu

3

Un pointeur est juste une autre variable qui peut contenir l'adresse mémoire d'une autre variable. Un pointeur étant une variable, il a lui aussi une adresse mémoire.


1
Pas nécessairement une adresse. Btw, avez-vous lu les réponses et commentaires existants avant de poster votre réponse?
Alexey Frunze

3

Le pointeur AC est très similaire à une adresse mémoire mais avec des détails dépendants de la machine abstraits, ainsi que certaines fonctionnalités non trouvées dans le jeu d'instructions de niveau inférieur.

Par exemple, un pointeur C est relativement richement typé. Si vous incrémentez un pointeur à travers un tableau de structures, il passe bien d'une structure à l'autre.

Les pointeurs sont soumis à des règles de conversion et fournissent une vérification du type de compilation.

Il existe une valeur spéciale "pointeur nul" qui est portable au niveau du code source, mais dont la représentation peut différer. Si vous affectez une constante entière dont la valeur est zéro à un pointeur, ce pointeur prend la valeur de pointeur nulle. Idem si vous initialisez un pointeur de cette façon.

Un pointeur peut être utilisé comme une variable booléenne: il teste true s'il est différent de null et false s'il est null.

Dans un langage machine, si le pointeur nul est une adresse amusante comme 0xFFFFFFFF, vous devrez peut-être avoir des tests explicites pour cette valeur. C te le cache. Même si le pointeur nul est 0xFFFFFFFF, vous pouvez le tester en utilisant if (ptr != 0) { /* not null! */}.

Les utilisations de pointeurs qui subvertissent le système de types conduisent à un comportement indéfini, alors qu'un code similaire en langage machine peut être bien défini. Les assembleurs assembleront les instructions que vous avez écrites, mais les compilateurs C seront optimisés en supposant que vous n'avez rien fait de mal. Si un float *ppointeur pointe sur une long nvariable et *p = 0.0est exécuté, le compilateur n'est pas requis pour gérer cela. Une utilisation ultérieure de nne lira pas nécessairement la configuration binaire de la valeur flottante, mais peut-être, ce sera un accès optimisé qui est basé sur l'hypothèse du "strict aliasing" qui nn'a pas été touché! Autrement dit, l'hypothèse selon laquelle le programme se comporte bien et pne devrait donc pas être pointée n.

En C, les pointeurs vers le code et les pointeurs vers les données sont différents, mais sur de nombreuses architectures, les adresses sont les mêmes. Des compilateurs C peuvent être développés qui ont des pointeurs "gras", même si l'architecture cible n'en a pas. Les gros pointeurs signifient que les pointeurs ne sont pas seulement des adresses de machine, mais contiennent d'autres informations, telles que des informations sur la taille de l'objet pointé, pour la vérification des limites. Les programmes écrits de manière portative seront facilement portés sur ces compilateurs.

Vous pouvez donc voir qu'il existe de nombreuses différences sémantiques entre les adresses des machines et les pointeurs C.


Les pointeurs NULL ne fonctionnent pas comme vous le pensez sur toutes les plates-formes - veuillez consulter ma réponse à CiscoIPPhone ci-dessus. NULL == 0 est une hypothèse qui ne s'applique qu'aux plates-formes x86. La convention dit que les nouvelles plates-formes devraient correspondre à x86, mais ce n'est pas le cas en particulier dans le monde intégré. Edit: De plus, C ne fait rien pour abstraire la valeur d'un pointeur du matériel - "ptr! = 0" ne fonctionnera pas comme un test NULL sur une plate-forme où NULL! = 0.
DX-MON

1
DX-MON, c'est complètement faux pour le standard C. NULL est conçu pour être 0, et ils peuvent être utilisés de manière interchangeable dans les instructions. Que la représentation du pointeur NULL dans le matériel soit sur 0 bit ou non est sans importance pour la façon dont elle est représentée dans le code source.
Mark Bessey

@ DX-MON J'ai bien peur que vous ne travailliez pas avec les faits corrects. En C, une expression constante intégrale sert de constante de pointeur nul, que le pointeur nul soit ou non l'adresse. Si vous connaissez un compilateur C où ptr != 0n'est pas un test nul, veuillez révéler son identité (mais avant cela, envoyez un rapport de bogue au vendeur).
Kaz

Je vois où vous en êtes, mais vos commentaires sur les pointeurs nuls sont incohérents parce que vous confondez pointeurs et adresses mémoire - exactement ce que la citation citée dans la question conseille d'éviter! L'instruction correcte: C définit le pointeur nul à zéro, que l'adresse mémoire au décalage zéro soit légale ou non.
alexis

1
@alexis Chapter and verset, please. C ne définit pas le pointeur nul comme étant nul. C définit zéro (ou toute expression de constante intégrale dont la valeur est zéro) comme syntaxe pour désigner une constante de pointeur nul. faqs.org/faqs/C-faq/faq (section 5).
Kaz

3

Avant de comprendre les pointeurs, nous devons comprendre les objets. Les objets sont des entités qui existent et ont un spécificateur d'emplacement appelé une adresse. Un pointeur est juste une variable comme toutes les autres variables Cavec un type appelé pointerdont le contenu est interprété comme l'adresse d'un objet qui prend en charge l'opération suivante.

+ : A variable of type integer (usually called offset) can be added to yield a new pointer
- : A variable of type integer (usually called offset) can be subtracted to yield a new pointer
  : A variable of type pointer can be subtracted to yield an integer (usually called offset)
* : De-referencing. Retrieve the value of the variable (called address) and map to the object the address refers to.
++: It's just `+= 1`
--: It's just `-= 1`

Un pointeur est classé en fonction du type d'objet auquel il se réfère actuellement. La seule partie des informations qu'elle importe est la taille de l'objet.

Tout objet prend en charge une opération, &(adresse de), qui récupère le spécificateur d'emplacement (adresse) de l'objet en tant que type d'objet pointeur. Cela devrait atténuer la confusion entourant la nomenclature, car il serait logique d'appeler &comme une opération d'un objet plutôt que comme un pointeur dont le type résultant est un pointeur du type d'objet.

Remarque Tout au long de cette explication, j'ai laissé de côté le concept de mémoire.


J'aime votre explication sur la réalité abstraite d'un pointeur général dans un système général. Mais, peut-être que discuter de la mémoire serait utile. En fait, parlant pour moi, je sais que ce serait ...! Je pense que discuter de la connexion peut être très utile pour comprendre la situation dans son ensemble. +1
quand même

@ d0rmLife: Vous avez suffisamment d'explications dans les autres réponses qui couvrent la situation dans son ensemble. Je voulais juste donner une explication mathématique abstraite comme une autre vue. À mon humble avis, cela créerait moins de confusion en appelant &comme «adresse de» car cela est plus lié à un objet plutôt qu'au pointeur en soi »
Abhijit

Pas d'offense, mais je déciderai moi-même de ce qu'est une explication suffisante. Un seul manuel n'est pas suffisant pour expliquer pleinement les structures de données et l'allocation de mémoire. ;) .... de toute façon, votre réponse est toujours utile , même si elle n'est pas nouvelle.
d0rmLife

Cela n'a aucun sens de manipuler des pointeurs sans le concept de mémoire . Si l'objet existe sans mémoire, il doit se trouver dans un endroit où il n'y a pas d'adresse - par exemple dans les registres. Pouvoir utiliser «&» suppose de la mémoire.
Aki Suihkonen

3

Une adresse est utilisée pour identifier un élément de stockage de taille fixe, généralement pour chaque octet, sous forme d'entier. Ceci est précisément appelé adresse d'octet , qui est également utilisé par l'ISO C. Il peut y avoir d'autres méthodes pour construire une adresse, par exemple pour chaque bit. Cependant, seule l'adresse d'octet est si souvent utilisée, nous omettons généralement "octet".

Techniquement, une adresse n'est jamais une valeur en C, car la définition du terme "valeur" en (ISO) C est:

signification précise du contenu d'un objet lorsqu'il est interprété comme ayant un type spécifique

(Souligné par moi.) Cependant, il n'y a pas un tel "type d'adresse" en C.

Le pointeur n'est pas le même. Le pointeur est une sorte de type en langage C. Il existe plusieurs types de pointeurs distincts. Ils n'obéit pas nécessairement à même ensemble de règles de la langue, par exemple l'effet de ++sur une valeur de type int*contre char*.

Une valeur en C peut être de type pointeur. C'est ce qu'on appelle une valeur de pointeur . Pour être clair, une valeur de pointeur n'est pas un pointeur en langage C. Mais nous sommes habitués à les mélanger ensemble, car en C, il est peu probable qu'il soit ambigu: si nous appelons une expression pcomme un "pointeur", ce n'est qu'une valeur de pointeur mais pas un type, car un type nommé en C n'est pas exprimé par une expression , mais par un nom de type ou un nom de typedef .

Certaines autres choses sont subtiles. En tant qu'utilisateur C, tout d'abord, il faut savoir ce que objectsignifie:

région de stockage de données dans l'environnement d'exécution, dont le contenu peut représenter des valeurs

Un objet est une entité pour représenter des valeurs, qui sont d'un type spécifique. Un pointeur est un type d'objet . Donc, si nous déclarons int* p;, cela psignifie "un objet de type pointeur", ou un "objet pointeur".

Notez qu'il n'y a pas de "variable" normativement définie par la norme (en fait, elle n'est jamais utilisée comme nom par ISO C dans le texte normatif). Cependant, de manière informelle, nous appelons un objet une variable, comme le fait un autre langage. (Mais toujours pas si exactement, par exemple en C ++ une variable peut être de type de référence normativement, ce qui n'est pas un objet.) Les expressions "objet pointeur" ou "variable pointeur" sont parfois traitées comme une "valeur de pointeur" comme ci-dessus, avec un légère différence probable. (Un autre ensemble d'exemples est "tableau".)

Étant donné que le pointeur est un type et que l'adresse est effectivement "sans type" en C, une valeur de pointeur "contient" à peu près une adresse. Et une expression de type pointeur peut donner une adresse, par exemple

ISO C11 6.5.2.3

3 L' &opérateur unaire donne l'adresse de son opérande.

Notez que cette formulation est introduite par WG14 / N1256, c'est-à-dire ISO C99: TC3. Dans C99, il y a

3 L' &opérateur unaire renvoie l'adresse de son opérande.

Elle reflète l'avis du comité: une adresse n'est pas une valeur de pointeur renvoyée par l' &opérateur unaire .

Malgré le libellé ci-dessus, il y a encore du désordre, même dans les normes.

ISO C11 6.6

9 Une constante d'adresse est un pointeur nul, un pointeur vers une valeur l désignant un objet de durée de stockage statique ou un pointeur vers un désignateur de fonction

ISO C ++ 11 5.19

3 ... Une expression constante d'adresse est une expression constante de base de valeur de type pointeur qui évalue l'adresse d'un objet avec une durée de stockage statique, l'adresse d'une fonction, ou une valeur de pointeur nulle, ou une expression de constante de noyau de valeur de type std::nullptr_t. ...

(Le brouillon standard C ++ récent utilise une autre formulation donc il n'y a pas ce problème.)

En fait, "constante d'adresse" en C et "expression constante d'adresse" en C ++ sont des expressions constantes de types pointeurs (ou du moins de type "pointeur" depuis C ++ 11).

Et l' &opérateur unaire intégré est appelé "adresse de" en C et C ++; de même, std::addressofest introduit dans C ++ 11.

Ces noms peuvent apporter des idées fausses. L'expression résultat est de type pointeur, de sorte qu'ils seraient interprétés comme: le résultat contient / donne une adresse, plutôt que est une adresse.


2

Il dit "parce que cela confond ceux qui ne savent pas de quoi il s'agit" - aussi, c'est vrai: si vous apprenez de quoi il s'agit, vous ne serez pas confus. Théoriquement, le pointeur est une variable qui pointe vers une autre, contient pratiquement une adresse, qui est l'adresse de la variable vers laquelle il pointe. Je ne sais pas pourquoi devrait cacher ce fait, ce n'est pas une science sorcière. Si vous comprenez les pointeurs, vous aurez un pas de plus pour comprendre le fonctionnement des ordinateurs. Aller de l'avant!


2

À bien y penser, je pense que c'est une question de sémantique. Je ne pense pas que l'auteur ait raison, car la norme C fait référence à un pointeur comme détenant une adresse vers l'objet référencé comme d'autres l'ont déjà mentionné ici. Cependant, adresse! = Adresse mémoire. Une adresse peut être vraiment n'importe quoi selon la norme C bien qu'elle conduira finalement à une adresse mémoire, le pointeur lui-même peut être un identifiant, un sélecteur offset + (x86), vraiment n'importe quoi tant qu'il peut décrire (après mappage) n'importe quelle mémoire adresse dans l'espace adressable.


Un pointeur contient une adresse (ou pas, si elle est nulle). Mais c'est loin d' être une adresse: par exemple, deux pointeurs vers la même adresse mais avec un type différent ne sont pas équivalents dans de nombreuses situations.
Gilles 'SO- arrête d'être méchant'

@Gilles Si vous voyez "être", comme dans int i=5-> i vaut 5 alors, le pointeur est l'adresse oui. De plus, null a également une adresse. Habituellement, une adresse d'écriture invalide (mais pas nécessairement, voir le mode x86 réel), mais une adresse néanmoins. Il n'y a vraiment que 2 exigences pour null: il est garanti de comparer inégale à un pointeur à un objet réel et deux pointeurs null compareront égaux.
Valentin Radu

Au contraire, un pointeur nul est garanti de ne pas être égal à l'adresse d'un objet. Déréférencer un pointeur nul est un comportement non défini. Un gros problème pour dire que «le pointeur est l'adresse» est qu'ils fonctionnent différemment. Si pest un pointeur, p+1l'adresse n'est pas toujours incrémentée de 1.
Gilles 'SO- arrête d'être méchant'

Relisez le commentaire s'il vous plaît, it's guaranteed to compare unequal to a pointer to an actual object. Quant à l'arithmétique du pointeur, je ne vois pas le point, la valeur du pointeur est toujours une adresse, même si l'opération "+" n'y ajoutera pas nécessairement un octet.
Valentin Radu

1

Une autre façon dont un pointeur C ou C ++ diffère d'une simple adresse mémoire en raison des différents types de pointeurs que je n'ai pas vus dans les autres réponses (malgré leur taille totale, je l'ai peut-être ignoré). Mais c'est probablement le plus important, car même les programmeurs C / C ++ expérimentés peuvent trébucher dessus:

Le compilateur peut supposer que les pointeurs de types incompatibles ne pointent pas vers la même adresse même s'ils le font clairement, ce qui peut donner un comportement qui ne serait pas possible avec un modèle d'adresse simple pointeur ==. Considérez le code suivant (en supposant sizeof(int) = 2*sizeof(short)):

unsigned int i = 0;
unsigned short* p = (unsigned short*)&i;
p[0]=p[1]=1;

if (i == 2 + (unsigned short)(-1))
{
  // you'd expect this to execute, but it need not
}

if (i == 0)
{
  // you'd expect this not to execute, but it actually may do so
}

Notez qu'il existe une exception pour char*, donc la manipulation des valeurs à l'aide char*est possible (bien que pas très portable).


0

Résumé rapide: l'adresse AC est une valeur, généralement représentée comme une adresse mémoire au niveau de la machine, avec un type spécifique.

Le mot non qualifié "pointeur" est ambigu. C a des objets de pointeur (variables), des types de pointeur, des expressions de pointeur et des valeurs de pointeur .

Il est très courant d'utiliser le mot "pointeur" pour signifier "objet pointeur", et cela peut créer une certaine confusion - c'est pourquoi j'essaie d'utiliser "pointeur" comme adjectif plutôt que comme nom.

La norme C, au moins dans certains cas, utilise le mot "pointeur" pour signifier "valeur de pointeur". Par exemple, la description de malloc indique qu'il "renvoie soit un pointeur nul, soit un pointeur vers l'espace alloué".

Alors, quelle est une adresse en C? C'est une valeur de pointeur, c'est-à-dire une valeur d'un type de pointeur particulier. (Sauf qu'une valeur de pointeur nul n'est pas nécessairement appelée "adresse", car ce n'est l'adresse de rien).

La description standard de l' &opérateur unaire indique qu'il "donne l'adresse de son opérande". En dehors de la norme C, le mot "adresse" est couramment utilisé pour faire référence à une adresse de mémoire (physique ou virtuelle), généralement d'un mot (quel que soit le "mot" sur un système donné).

L '"adresse" AC est généralement implémentée en tant qu'adresse machine - tout comme une intvaleur C est généralement implémentée en tant que mot machine. Mais une adresse C (valeur de pointeur) est plus qu'une simple adresse de machine. C'est une valeur généralement représentée comme une adresse de machine, et c'est une valeur avec un type spécifique .


0

Une valeur de pointeur est une adresse. Une variable de pointeur est un objet qui peut stocker une adresse. C'est vrai parce que c'est ce que la norme définit comme un pointeur. Il est important de le dire aux novices C parce que les novices C ne savent souvent pas la différence entre un pointeur et la chose vers laquelle il pointe (c'est-à-dire qu'ils ne connaissent pas la différence entre une enveloppe et un bâtiment). La notion d'adresse (chaque objet a une adresse et c'est ce qu'un pointeur stocke) est importante car elle trie cela.

Cependant, la norme parle à un niveau d'abstraction particulier. Ces personnes dont l'auteur parle qui "savent de quoi parlent les adresses", mais qui sont nouvelles pour C, doivent nécessairement avoir appris les adresses à un niveau d'abstraction différent - peut-être en programmant un langage d'assemblage. Il n'y a aucune garantie que l'implémentation C utilise la même représentation pour les adresses que les opcodes des CPU (appelée "l'adresse de stockage" dans ce passage), que ces personnes connaissent déjà.

Il continue en parlant de "manipulation d'adresse parfaitement raisonnable". En ce qui concerne la norme C, il n'y a fondamentalement pas de "manipulation d'adresse parfaitement raisonnable". L'addition est définie sur des pointeurs et c'est tout. Bien sûr, vous pouvez convertir un pointeur en entier, effectuer des opérations au niveau du bit ou arithmétique, puis le reconvertir. Il n'est pas garanti que cela fonctionne selon la norme, donc avant d'écrire ce code, vous feriez mieux de savoir comment votre implémentation C particulière représente des pointeurs et effectue cette conversion. Il utilise probablement la représentation d'adresse que vous attendez, mais ce n'est pas votre faute car vous n'avez pas lu le manuel. Ce n'est pas de la confusion, c'est une procédure de programmation incorrecte ;-)

En bref, C utilise un concept d'adresse plus abstrait que l'auteur.

Le concept d'auteur d'une adresse n'est bien sûr pas non plus le mot le plus bas en la matière. En ce qui concerne les cartes de mémoire virtuelle et l'adressage RAM physique sur plusieurs puces, le nombre que vous dites au processeur est "l'adresse de magasin" à laquelle vous souhaitez accéder n'a pratiquement rien à voir avec l'emplacement des données que vous souhaitez dans le matériel. Ce sont toutes des couches d'indirection et de représentation, mais l'auteur en a choisi une à privilégier. Si vous allez le faire en parlant de C, choisissez le niveau C à privilégier !

Personnellement, je ne pense pas que les remarques de l'auteur soient très utiles, sauf dans le contexte de l'introduction de C aux programmeurs d'assemblage. Il n'est certainement pas utile pour ceux qui viennent de langues de niveau supérieur de dire que les valeurs de pointeur ne sont pas des adresses. Il vaudrait beaucoup mieux reconnaître la complexité que de dire que le CPU a le monopole de dire ce qu'est une adresse et donc que les valeurs du pointeur C "ne sont pas" des adresses. Ce sont des adresses, mais elles peuvent être écrites dans une langue différente des adresses qu'il veut dire. Distinguer les deux choses dans le contexte de C comme "adresse" et "adresse de magasin" serait suffisant, je pense.


0

Pour dire simplement que les pointeurs sont en fait une partie décalée du mécanisme de segmentation qui se traduit par une adresse linéaire après la segmentation, puis par une adresse physique après la pagination. Les adresses physiques sont en fait adressées depuis votre bélier.

       Selector  +--------------+         +-----------+
      ---------->|              |         |           |
                 | Segmentation | ------->|  Paging   |
        Offset   |  Mechanism   |         | Mechanism |
      ---------->|              |         |           |
                 +--------------+         +-----------+
        Virtual                   Linear                Physical
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.