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.
la source
Réponses:
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.
la source
Je ne suis pas sûr de votre source, mais le type de langage que vous décrivez provient de la norme C:
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:
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:
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 ...
la source
Sur cette photo,
pointer_p est un pointeur qui se trouve à 0x12345 et pointe vers une variable variable_v à 0x34567.
la source
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.
NULL
est un pointeur qui n'est pas une adresse. Le contenu d'une variable pointeur peut en fait être de trois types:p
contient l'adresse dex
alors l'expression*p
a la même valeur quex
);NULL
est un exemple;p
ne contient pas de valeur valide, il*p
pourrait 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
p
est un pointeur versint
(int *p;
), alorsp + 1
pointe vers un entier qui estsizeof(int)
octets aprèsp
(en supposant qu'ilp + 1
s'agit toujours d'un pointeur valide). Siq
est un pointeur verschar
qui pointe vers la même adresse quep
(char *q = p;
), alorsq + 1
n'est pas la même adresse quep + 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 avoirp == q
mêmep
etq
avoir 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
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
int
nécessitant un alignement sur 4 octets, 0x7654321 ne peut pas être uneint*
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:
Vous pouvez vous attendre à ce que sur une machine ordinaire où
sizeof(int)==4
etsizeof(short)==2
, cela imprime1 = 1?
(petit-boutien) ou65536 = 1?
(gros-boutien). Mais sur mon PC Linux 64 bits avec GCC 4.4: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
p
a un type différent de&x
, le fait de changer lesp
points vers ne peut pas affecter ceux&x
vers lesquels (en dehors de certaines exceptions bien définies). Par conséquent, le compilateur est libre de conserver la valeur dex
dans un registre et de ne pas mettre à jour ce registre en tant que*p
modifications. 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.
la source
*p = 3
réussite soit garantie lorsque p n'a pas été initialisé.NULL
n'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 celaNULL
puisse être implémenté comme quelque chose qui ne dit pas «pointeur» ne revient pas souvent (principalement en passantNULL
à 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).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:
C'est tout. C'est si simple.
Un programme pour démontrer ce que je dis et sa sortie est ici:
http://ideone.com/rcSUsb
Le programme:
la source
fopen
dans une variable que si vous devez l'utiliser plus d'une fois (ce qui, pourfopen
, est à peu près tout le temps).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,
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)).
la source
far
pointeur n'est pas seulement "un entier".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.
la source
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é:
Vous pouvez avoir trois pointeurs différents pointant tous vers ce même objet:
Si vous comparez les valeurs de ces pointeurs, ils sont tous égaux:
Cependant, si vous incrémentez chaque pointeur, vous verrez que le type vers lequel ils pointent devient pertinent.
Les variables
i
etc
auront des valeurs différentes à ce stade, parce que lesi++
causesi
de contenir l'adresse du nombre entier suivant accessible, etc++
provoquec
le point sur le caractère suivant adressable. En règle générale, les entiers occupent plus de mémoire que les caractères, ilsi
se retrouveront donc avec une valeur plus élevéec
qu'après avoir tous les deux été incrémentés.la source
i == c
est 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).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 variableAu 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.
la source
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.
la source
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 char
valeurs concaténées (comme avec tout autre type de cariable,sizeof(some_variable)
indiquera le nombre deunsigned char
valeurs). 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
foo
etbar
sont des tableaux de 10 éléments qui sont stockés consécutivement en mémoire, un pointeur vers le "onzième" élément defoo
pourrait plutôt pointer vers le premier élément debar
, 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-foo
delà 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 char
valeurs. É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 pourraitcalloc
un 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 lacalloc
mé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.la source
free
est 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 à unechar*
origine inconnue devra être vérifié.free
n'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 neUn 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.
la source
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
T
est, en fait, l'adresse deT
.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
Alors qu'en mode protégé, il peut être
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 :
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.
la source
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 ).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).
la source
Vous pouvez le voir de cette façon. Un pointeur est une valeur qui représente une adresse dans l'espace mémoire adressable.
la source
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.
la source
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 *p
pointeur pointe sur unelong n
variable et*p = 0.0
est exécuté, le compilateur n'est pas requis pour gérer cela. Une utilisation ultérieure den
ne 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" quin
n'a pas été touché! Autrement dit, l'hypothèse selon laquelle le programme se comporte bien etp
ne devrait donc pas être pointéen
.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.
la source
ptr != 0
n'est pas un test nul, veuillez révéler son identité (mais avant cela, envoyez un rapport de bogue au vendeur).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
C
avec un type appelépointer
dont le contenu est interprété comme l'adresse d'un objet qui prend en charge l'opération suivante.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.
la source
&
comme «adresse de» car cela est plus lié à un objet plutôt qu'au pointeur en soi »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:
(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 typeint*
contrechar*
.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
p
comme 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
object
signifie: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;
, celap
signifie "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
Notez que cette formulation est introduite par WG14 / N1256, c'est-à-dire ISO C99: TC3. Dans C99, il y a
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
ISO C ++ 11 5.19
(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::addressof
est 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.
la source
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!
la source
À 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.
la source
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.p
est un pointeur,p+1
l'adresse n'est pas toujours incrémentée de 1.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.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)
):Notez qu'il existe une exception pour
char*
, donc la manipulation des valeurs à l'aidechar*
est possible (bien que pas très portable).la source
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
int
valeur 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 .la source
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.
la source
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.
la source