Où les valeurs nulles sont-elles stockées ou sont-elles stockées?

39

Je veux en savoir plus sur les valeurs nulles ou les références nulles.

Par exemple, j'ai une classe appelée Apple et j'en ai créé une instance.

Apple myApple = new Apple("yummy"); // The data is stored in memory

Ensuite, j'ai mangé cette pomme et maintenant elle doit être nulle, donc je l'ai définie comme nulle.

myApple = null;

Après cet appel, j'ai oublié que je l'avais mangé et maintenant je veux vérifier.

bool isEaten = (myApple == null);

Avec cet appel, où se trouve myApple? Est-ce que null est une valeur de pointeur spéciale? Si c'est le cas, si j'ai 1000 objets nuls, occupent-ils 1000 espaces de mémoire d'objets ou 1000 int d'espace de mémoire si nous pensons qu'un type de pointeur est de type int?

Mert Akcakaya
la source

Réponses:

45

Dans votre exemple, il myApplea la valeur spéciale null(généralement tous les bits nuls) et ne fait donc référence à rien. L'objet auquel il faisait référence à l'origine est maintenant perdu sur le tas. Il n'y a aucun moyen de récupérer son emplacement. Ceci est connu comme une fuite de mémoire sur les systèmes sans récupération de place.

Si vous définissez initialement 1 000 références sur null, vous ne disposez que de 1 000, généralement 1 000 * 4 octets (sur un système 32 bits, deux fois plus que sur 64). Si ces 1 000 références pointaient à l'origine sur des objets réels, vous avez alloué 1 000 fois la taille de chaque objet, plus un espace pour les 1 000 références.

Dans certaines langues (comme C et C ++), les pointeurs pointent toujours sur quelque chose, même "non initialisé". La question est de savoir si l'adresse qu'ils détiennent est légale pour votre programme. L'adresse spéciale zéro (alias null) n'est délibérément pas mappée dans votre espace d'adressage, de sorte qu'une erreur de segmentation est générée par l'unité de gestion de la mémoire (MMU) lors de son accès et que votre programme se bloque. Mais puisque l'adresse zéro n'est délibérément pas mappée, elle devient une valeur idéale à utiliser pour indiquer qu'un pointeur ne pointe rien, d'où son rôle null. Pour compléter l’histoire, lorsque vous allouez de la mémoire avec newoumalloc(), le système d’exploitation configure le MMU pour mapper les pages de RAM dans votre espace adresse et les rendre utilisables. Il existe encore généralement de vastes plages d'espace d'adressage non mappées, ce qui entraîne également des erreurs de segmentation.

Randall Cook
la source
Très bonne explication.
NoChance
6
C'est légèrement faux sur la partie "fuite de mémoire". C'est une fuite de mémoire dans les systèmes sans gestion automatique de la mémoire. Cependant, GC n'est pas le seul moyen possible de mettre en œuvre une gestion automatique de la mémoire. C ++ std::shared_ptr<Apple>est un exemple qui n'est ni GC, ni les fuites Applelorsqu'il est mis à zéro.
MSalters
1
@MSalters - N'est-ce pas shared_ptrsimplement un formulaire de base pour la collecte des ordures? GC n’exige pas qu’il y ait un "ramasse-miettes" séparé, seulement cette poubelle a lieu.
Rétablir Monica
5
@Brendan: Le terme "ramasse-miettes" est presque universellement compris comme désignant une collection non déterministe indépendante du chemin de code normal. La destruction déterministe basée sur le comptage de références est quelque chose de complètement différent.
Mason Wheeler
1
Bonne explication. Un point légèrement trompeur est l'hypothèse selon laquelle l'allocation de mémoire est mappée sur la RAM. La RAM est un mécanisme pour le stockage en mémoire à court terme, mais le mécanisme de stockage réel est résumé par le système d'exploitation. Dans Windows (pour les applications non-ring-zero), les pages de mémoire sont virtualisées et peuvent mapper vers la RAM, un fichier d'échange de disque ou peut-être un autre périphérique de stockage.
Simon Gillbee
13

La réponse dépend de la langue que vous utilisez.

C / C ++

En C et C ++, le mot clé était NULL, et ce qui était vraiment NULL était 0. Il a été décidé que "0x0000" ne serait jamais un pointeur valide sur un objet. Il s'agit donc de la valeur qui est assignée pour indiquer qu'il n'est pas un pointeur valide. Cependant, c'est complètement arbitraire. Si vous tentiez d'y accéder comme un pointeur, il se comporterait exactement comme un pointeur vers un objet qui n'existe plus en mémoire, ce qui provoquerait la génération d'une exception de pointeur non valide. Le pointeur lui-même occupe de la mémoire, mais pas plus qu'un objet entier. Par conséquent, si vous avez 1000 pointeurs nuls, cela équivaut à 1000 entiers. Si certains de ces pointeurs pointent sur des objets valides, l'utilisation de la mémoire équivaudrait à 1 000 entiers plus la mémoire contenue dans ces pointeurs valides. Rappelez-vous qu'en C ou C ++,n'implique pas que la mémoire a été libérée, vous devez donc explicitement supprimer cet objet à l'aide de dealloc (C) ou delete (C ++).

Java

Contrairement à C et C ++, en Java, null n’est qu’un mot-clé. Plutôt que de gérer null comme un pointeur sur un objet, il est géré en interne et traité comme un littéral. Cela éliminait la nécessité de lier des pointeurs sous forme de types entiers et permettait à Java d’abstraire totalement les pointeurs Cependant, même si Java le cache mieux, ce sont toujours des pointeurs, ce qui signifie que 1000 pointeurs nuls consomment toujours l'équivalent de 1000 entiers. Évidemment, quand ils pointent sur des objets, un peu comme C et C ++, la mémoire est consommée par ces objets jusqu'à ce qu'aucun autre pointeur ne les référence, mais contrairement à C et C ++, le ramasse-miettes ramasse dessus lors de son prochain passage et libère de la mémoire, sans exiger que vous deviez suivre quels objets sont libérés et quels objets ne le sont pas, dans la plupart des cas (sauf si vous avez des raisons de faire référence à des objets avec une faible faiblesse, par exemple).

Neil
la source
9
Votre distinction n'est pas correcte: en fait, en C et C ++, le pointeur null n'a pas du tout besoin de pointer sur l'adresse mémoire 0 (bien qu'il s'agisse d'une implémentation naturelle, identique à celle en Java et en C #). Il peut pointer littéralement n'importe où. Ceci est légèrement confondu par le fait que le littéral-0 peut être implicitement converti en un pointeur nul. Mais le modèle de bits stocké pour un pointeur null n'a toujours pas besoin d'être composé de zéros.
Konrad Rudolph
1
Non tu as tort. La sémantique est complètement transparente… dans le programme, les pointeurs nuls et la macro NULL( pas un mot-clé, en passant) sont traités comme s'il s'agissait de bits nuls. Mais ils ne doivent pas être mises en œuvre en tant que telle, et en fait certaines implémentations obscures font utiliser les pointeurs nuls non nuls. Si j'écris, if (myptr == 0)le compilateur fera ce qu'il faut, même si le pointeur null est représenté en interne par 0xabcdef.
Konrad Rudolph
3
@Neil: une constante de pointeur nulle (valeur du type entier évaluée à zéro) est convertible en une valeur de pointeur nulle . (§4.10 C ++ 11.) Il n'est pas garanti que la valeur du pointeur null ait tous les bits nuls. 0est une constante de pointeur nulle, mais cela ne signifie pas que myptr == 0vérifie si tous les bits de myptrsont zéro.
Mat
5
@Neil: Vous voudrez peut-être vérifier cette entrée dans le faq C ou cette question SO
hugomg
1
@Neil C'est pourquoi j'ai pris soin de ne pas mentionner la NULLmacro, mais plutôt de parler du «pointeur nul» et de mentionner explicitement que «le littéral-0 peut être implicitement converti en un pointeur nul».
Konrad Rudolph
5

Un pointeur est simplement une variable qui est principalement de type entier. Il spécifie une adresse mémoire où l'objet réel est stocké.

La plupart des langues permettent d'accéder aux membres de l'objet via cette variable de pointeur:

int localInt = myApple.appleInt;

Le compilateur sait comment accéder aux membres d’un Apple. Il "suit" le pointeur vers myApplel'adresse de et récupère la valeur duappleInt

Si vous affectez le pointeur null à une variable de pointeur, vous faites en sorte que le pointeur ne pointe pas sur une adresse mémoire. (Ce qui rend l'accès des membres impossible.)

Pour chaque pointeur, vous avez besoin de mémoire pour contenir la valeur entière de l'adresse mémoire (généralement 4 octets sur des systèmes 32 bits, 8 octets sur des systèmes 64 bits). Ceci est également vrai pour les pointeurs nuls.

Stephan
la source
Je pense que les variables / objets de référence ne sont pas exactement des pointeurs. Si vous les imprimez, ils contiennent ClassName @ Hashcode. JVM utilise en interne Hashtable pour stocker Hashcode avec l'adresse réelle et utilise un algorithme de hachage pour extraire l'adresse réelle lorsque cela est nécessaire.
minuseven
@minusSeven C'est correct pour ce qui concerne les objets littéraux tels que les entiers. Sinon, la table de hachage contient des pointeurs sur d'autres objets contenus dans la classe Apple elle-même.
Neil
@ minusSeven: Je suis d'accord. Les détails de la mise en œuvre du pointeur dépendent fortement de la langue / de l'exécution. Mais je pense que ces détails ne sont pas très pertinents pour la question spécifique.
Stephan
4

Exemple rapide (les noms de variables ne sont pas enregistrés):

void main()
{
  int X = 3;
  int *Y = X;
  int *Z = null;
} // void main(...)


...........................
....+-----+--------+.......
....|     |   X    |.......
....+-----+--------+.......
....| 100 |   3    |<---+..
....+-----+--------+....|..
........................|..
....+-----+--------+....|..
....|     |   Y    |....|..
....+-----+--------+....|..
....| 102 |  100   +----+..
....+-----+--------+.......
...........................
....+-----+--------+.......
....|     |   z    |.......
....+-----+--------+.......
....| 104 |   0    |.......
....+-----+--------+.......
...........................

À votre santé.

Umlcat
la source