Question Quels sont les obstacles à la compréhension des indicateurs et que peut-on faire pour les surmonter? [fermé]


Pourquoi les pointeurs sont-ils un facteur de confusion important pour de nombreux nouveaux et même anciens étudiants de niveau C ou C ++? Existe-t-il des outils ou des processus de réflexion qui vous ont aidé à comprendre comment les pointeurs fonctionnent au niveau de la variable, de la fonction et au-delà?

Quelles sont les bonnes pratiques qui peuvent être mises en œuvre pour amener quelqu'un au niveau "Ah-hah, je l'ai compris", sans pour autant les enliser dans le concept global? Fondamentalement, percer des scénarios similaires.


443


origine


Réponses:


Les pointeurs sont un concept qui, pour beaucoup, peut être déroutant au début, en particulier lorsqu'il s'agit de copier des valeurs de pointeur autour du même bloc de mémoire et de s'y référer.

J'ai trouvé que la meilleure analogie est de considérer le pointeur comme un morceau de papier sur lequel se trouve une adresse de maison, et le bloc de mémoire fait référence à la maison réelle. Toutes sortes d'opérations peuvent ainsi être facilement expliquées.

J'ai ajouté du code Delphi ci-dessous, et quelques commentaires le cas échéant. J'ai choisi Delphi car mon autre langage de programmation principal, C #, ne présente pas les mêmes choses que les fuites de mémoire.

Si vous souhaitez uniquement apprendre le concept de pointeur de haut niveau, vous devez ignorer les parties intitulées "Disposition de la mémoire" dans l'explication ci-dessous. Ils sont destinés à donner des exemples de ce que la mémoire pourrait ressembler après les opérations, mais ils sont de nature plus bas niveau. Cependant, afin d’expliquer avec précision le fonctionnement des dépassements de tampon, il était important d’ajouter ces diagrammes.

Disclaimer: À toutes fins utiles, cette explication et l'exemple de mémoire les mises en page sont grandement simplifiées. Il y a plus de frais généraux et beaucoup plus de détails que vous voudriez besoin de savoir si vous avez besoin de gérer la mémoire de bas niveau. Cependant, pour le Les intentions d'expliquer la mémoire et les pointeurs sont assez précises.


Supposons que la classe THouse utilisée ci-dessous ressemble à ceci:

type
    THouse = class
    private
        FName : array[0..9] of Char;
    public
        constructor Create(name: PChar);
    end;

Lorsque vous initialisez l'objet maison, le nom donné au constructeur est copié dans le champ privé FName. Il y a une raison pour laquelle il est défini comme un tableau de taille fixe.

En mémoire, il y aura des frais généraux associés à l'allocation de la maison, je vais illustrer ceci ci-dessous comme ceci:

--- [ttttNNNNNNNNNN] ---
     ^ ^
     | |
     | + - le tableau FName
     |
     + - frais généraux

La zone "tttt" est surchargée, il y en aura généralement plus pour différents types d’exécutions et de langages, comme 8 ou 12 octets. Il est impératif que les valeurs stockées dans cette zone ne soient modifiées par rien d'autre que l'allocateur de mémoire ou les routines du système principal, ou vous risquez de planter le programme.


Allouer de la mémoire

Demandez à un entrepreneur de construire votre maison et de vous donner l'adresse de la maison. Contrairement au monde réel, l’allocation de mémoire ne peut pas être définie sur l’endroit où allouer, mais trouvera un emplacement approprié avec suffisamment de place et restituera l’adresse à la mémoire allouée.

En d'autres termes, l'entrepreneur choisira l'endroit.

THouse.Create('My house');

Disposition de la mémoire:

--- [ttttNNNNNNNNNN] ---
    1234Ma maison

Garder une variable avec l'adresse

Écrivez l'adresse de votre nouvelle maison sur un morceau de papier. Ce papier servira de référence à votre maison. Sans ce morceau de papier, vous êtes perdu et vous ne pouvez pas trouver la maison, à moins que vous y soyez déjà.

var
    h: THouse;
begin
    h := THouse.Create('My house');
    ...

Disposition de la mémoire:

    h
    v
--- [ttttNNNNNNNNNN] ---
    1234Ma maison

Copier la valeur du pointeur 

Il suffit d'écrire l'adresse sur un nouveau morceau de papier. Vous avez maintenant deux morceaux de papier qui vous mèneront à la même maison, pas deux maisons séparées. Toute tentative de suivre l'adresse d'un papier et réorganiser les meubles à cette maison fera paraître que l'autre maison a été modifié de la même manière, à moins que vous ne puissiez explicitement détecter qu'il s'agit en fait d'une seule maison.

Remarque C'est généralement le concept que j'ai le plus de problèmes à expliquer aux gens, deux pointeurs ne signifient pas deux objets ou des blocs de mémoire.

var
    h1, h2: THouse;
begin
    h1 := THouse.Create('My house');
    h2 := h1; // copies the address, not the house
    ...
    h1
    v
--- [ttttNNNNNNNNNN] ---
    1234Ma maison
    ^
    h2

Libérer la mémoire 

Démolir la maison. Vous pourrez ensuite réutiliser le papier pour une nouvelle adresse si vous le souhaitez, ou l'effacer pour oublier l'adresse à la maison qui n'existe plus.

var
    h: THouse;
begin
    h := THouse.Create('My house');
    ...
    h.Free;
    h := nil;

Ici, je construis d'abord la maison et je saisis son adresse. Ensuite, je fais quelque chose à la maison (utilisez-le, le ... code, laissé comme un exercice pour le lecteur), puis je le libère. Enfin, j'efface l'adresse de ma variable.

Disposition de la mémoire:

    h <- +
    v + - avant libre
--- [ttttNNNNNNNNNN] --- |
    1234Ma maison <- +

    h (pointe maintenant nulle part) <- +
                                + - après gratuit
---------------------- | (note, la mémoire pourrait encore
    xx34Ma maison <- + contient des données)

Pointeurs pendants

Vous dites à votre entrepreneur de détruire la maison, mais vous oubliez d'effacer l'adresse de votre feuille de papier. Lorsque vous regardez plus tard le morceau de papier, vous avez oublié que la maison n'est plus là et va la visiter, avec des résultats ratés (voir également la partie concernant une référence non valide ci-dessous).

var
    h: THouse;
begin
    h := THouse.Create('My house');
    ...
    h.Free;
    ... // forgot to clear h here
    h.OpenFrontDoor; // will most likely fail

En utilisant h après l'appel à .Free  pourrait travail, mais c'est juste de la chance. Il est fort probable que cela échouera chez un client, en pleine opération critique.

    h <- +
    v + - avant libre
--- [ttttNNNNNNNNNN] --- |
    1234Ma maison <- +

    h <- +
    v + - après libre
---------------------- |
    xx34Ma maison <- +

Comme vous pouvez le voir, h pointe toujours vers les restes des données en mémoire, mais dans la mesure où il pourrait ne pas être complet, l’utiliser comme avant peut échouer.


Fuite de mémoire 

Vous perdez le morceau de papier et ne pouvez pas trouver la maison. La maison est encore debout quelque part, et quand vous voudrez plus tard construire une nouvelle maison, vous ne pouvez pas réutiliser cet endroit.

var
    h: THouse;
begin
    h := THouse.Create('My house');
    h := THouse.Create('My house'); // uh-oh, what happened to our first house?
    ...
    h.Free;
    h := nil;

Ici nous avons écrasé le contenu de la h variable avec l'adresse d'une nouvelle maison, mais l'ancien est toujours debout ... quelque part. Après ce code, il n'y a aucun moyen d'atteindre cette maison, et il restera debout. En d'autres termes, la mémoire allouée restera allouée jusqu'à ce que l'application se ferme, à quel point le système d'exploitation va l'arrêter.

Disposition de la mémoire après la première attribution:

    h
    v
--- [ttttNNNNNNNNNN] ---
    1234Ma maison

Disposition de la mémoire après la deuxième attribution:

                       h
                       v
--- [ttttNNNNNNNNNN] --- [ttttNNNNNNNNNN]
    1234Ma maison 5678Ma maison

Une façon plus commune d'obtenir cette méthode est d'oublier de libérer quelque chose, au lieu de l'écraser comme ci-dessus. En termes Delphi, cela se produira avec la méthode suivante:

procedure OpenTheFrontDoorOfANewHouse;
var
    h: THouse;
begin
    h := THouse.Create('My house');
    h.OpenFrontDoor;
    // uh-oh, no .Free here, where does the address go?
end;

Après que cette méthode a été exécutée, il n'y a pas de place dans nos variables que l'adresse à la maison existe, mais la maison est toujours là.

Disposition de la mémoire:

    h <- +
    v + - avant de perdre le pointeur
--- [ttttNNNNNNNNNN] --- |
    1234Ma maison <- +

    h (pointe maintenant nulle part) <- +
                                + - après avoir perdu le pointeur
--- [ttttNNNNNNNNNN] --- |
    1234Ma maison <- +

Comme vous pouvez le voir, les anciennes données sont laissées intactes dans la mémoire, et ne seront pas être réutilisé par l'allocateur de mémoire. L'allocateur conserve la trace de zones de mémoire ont été utilisées, et ne les réutiliseront pas à moins que vous libérez le.


Libérer la mémoire mais garder une référence (maintenant invalide) 

Démolissez la maison, effacez l'un des morceaux de papier, mais vous avez aussi un autre morceau de papier avec l'ancienne adresse, quand vous allez à l'adresse, vous ne trouverez pas de maison, mais vous pourriez trouver quelque chose qui ressemble aux ruines d'un.

Peut-être que vous trouverez même une maison, mais ce n'est pas la maison à laquelle vous avez reçu l'adresse à l'origine, et donc toute tentative pour l'utiliser comme si elle vous appartenait pourrait échouer horriblement.

Parfois, vous pourriez même trouver une adresse plutôt grande sur une adresse voisine qui occupe trois adresses (rue principale 1-3), et votre adresse va au milieu de la maison. Toute tentative de traiter cette partie de la grande maison à trois adresses comme une seule petite maison pourrait aussi échouer horriblement.

var
    h1, h2: THouse;
begin
    h1 := THouse.Create('My house');
    h2 := h1; // copies the address, not the house
    ...
    h1.Free;
    h1 := nil;
    h2.OpenFrontDoor; // uh-oh, what happened to our house?

Ici, la maison a été démolie, par la référence dans h1, et tandis que h1 a été effacé aussi, h2 a toujours l'ancienne adresse, obsolète. L'accès à la maison qui n'est plus debout pourrait ou non fonctionner.

Ceci est une variation du pointeur suspendu ci-dessus. Voir sa disposition en mémoire.


Saturation de la mémoire tampon 

Vous déplacez plus de choses dans la maison que vous ne pouvez le faire, en vous répandant dans la maison ou la cour des voisins. Quand le propriétaire de la maison voisine reviendra plus tard, il trouvera toutes sortes de choses qu'il considérera comme les siennes.

C'est la raison pour laquelle j'ai choisi un tableau de taille fixe. Pour préparer le terrain, supposez que la deuxième maison que nous allouerons sera, pour une raison quelconque, placée devant le le premier en mémoire. En d’autres termes, la deuxième maison aura un adresse que la première. En outre, ils sont attribués les uns à côté des autres.

Ainsi, ce code:

var
    h1, h2: THouse;
begin
    h1 := THouse.Create('My house');
    h2 := THouse.Create('My other house somewhere');
                         ^-----------------------^
                          longer than 10 characters
                         0123456789 <-- 10 characters

Disposition de la mémoire après la première attribution:

                        h1
                        v
----------------------- [ttttNNNNNNNNNN]
                        5678Ma maison

Disposition de la mémoire après la deuxième attribution:

    h2 h1
    v v
--- [ttttNNNNNNNNNN] ---- [ttttNNNNNNNNNN]
    1234Mon autre maison quelque part
                        ^ --- + - ^
                            |
                            + - écrasé

La partie qui causera le plus souvent un crash est lorsque vous écrasez des parties importantes des données que vous avez stockées ne devraient pas être modifiées au hasard. Par exemple il pourrait ne pas être un problème que des parties du nom de la maison de la h1 a été changé, en termes de crash du programme, mais en écrasant la surcharge de la l'objet sera probablement tomber en panne lorsque vous essayez d'utiliser l'objet cassé, comme écrase les liens stockés dans autres objets dans l'objet.


Listes liées 

Lorsque vous suivez une adresse sur une feuille de papier, vous arrivez à une maison et, à cette maison, il y a une autre feuille de papier sur laquelle est inscrite une nouvelle adresse, la prochaine maison de la chaîne, etc.

var
    h1, h2: THouse;
begin
    h1 := THouse.Create('Home');
    h2 := THouse.Create('Cabin');
    h1.NextHouse := h2;

Ici, nous créons un lien de notre maison à notre cabine. Nous pouvons suivre la chaîne jusqu'à ce qu'une maison n'a pas NextHouse référence, ce qui signifie que c'est le dernier. Pour visiter toutes nos maisons, nous pourrions utiliser le code suivant:

var
    h1, h2: THouse;
    h: THouse;
begin
    h1 := THouse.Create('Home');
    h2 := THouse.Create('Cabin');
    h1.NextHouse := h2;
    ...
    h := h1;
    while h <> nil do
    begin
        h.LockAllDoors;
        h.CloseAllWindows;
        h := h.NextHouse;
    end;

Disposition de la mémoire (ajouté NextHouse comme un lien dans l'objet, noté avec les quatre LLLL dans le diagramme ci-dessous):

    h1 h2
    v v
--- [ttttNNNNNNNNNNLLLL] ---- [ttttNNNNNNNNNNLLLL]
    1234Accueil + 5678Cabine +
                   | ^ |
                   + -------- + * (pas de lien)

En termes simples, quelle est une adresse mémoire?

Une adresse mémoire est simplement un nombre. Si vous pensez à la mémoire comme un grand tableau d'octets, le premier octet a l'adresse 0, le suivant l'adresse 1 et ainsi de suite. Ceci est simplifié, mais suffisant.

Donc, cette disposition de la mémoire:

    h1 h2
    v v
--- [ttttNNNNNNNNNN] --- [ttttNNNNNNNNNN]
    1234Ma maison 5678Ma maison

Peut avoir ces deux adresses (le plus à gauche - est l'adresse 0):

  • h1 = 4
  • h2 = 23

Ce qui signifie que notre liste de liens ci-dessus pourrait ressembler à ceci:

    h1 (= 4) h2 (= 28)
    v v
--- [ttttNNNNNNNNNNLLLL] ---- [ttttNNNNNNNNNNLLLL]
    1234Home 0028 5678Cabine 0000
                   | ^ |
                   + -------- + * (pas de lien)

Il est typique de stocker une adresse qui "ne pointe nulle part" en tant qu'adresse zéro.


En termes simples, qu'est-ce qu'un pointeur?

Un pointeur est juste une variable contenant une adresse mémoire. Vous pouvez généralement demander à la programmation langue pour vous donner son numéro, mais la plupart des langages de programmation et des runtimes essaie de cacher le fait qu'il y a un nombre en dessous, simplement parce que le numéro lui-même ne vraiment avoir un sens pour vous. Il est préférable de penser à un pointeur comme une boîte noire, c'est à dire. vous ne savez pas vraiment ou ne vous souciez pas de la façon dont il est réellement mis en œuvre, aussi longtemps travaux.


725



Dans ma première classe Comp Sci, nous avons fait l'exercice suivant. Certes, il s'agissait d'une salle de conférence avec environ 200 étudiants dans ...

Le professeur écrit au tableau: int john;

John se lève

Le professeur écrit: int *sally = &john;

Sally se lève, pointe sur John

Professeur: int *bill = sally;

Bill se lève, pointe sur John

Professeur: int sam;

Sam se lève

Professeur: bill = &sam;

Bill pointe maintenant vers Sam.

Je pense que vous avez l'idée. Je pense que nous avons passé environ une heure à faire cela, jusqu'à ce que nous passions en revue les bases de l'assignation des pointeurs.


146



Une analogie que j’ai trouvée utile pour expliquer les indicateurs est l’hyperlien. La plupart des gens peuvent comprendre qu'un lien sur une page Web «pointe» vers une autre page sur Internet, et si vous pouvez copier et coller cet hyperlien, ils pointeront tous les deux vers la même page Web originale. Si vous allez modifier cette page d'origine, puis suivez l'un de ces liens (pointeurs), vous obtiendrez cette nouvelle page mise à jour.


121



La raison pour laquelle les pointeurs semblent confondre tant de gens est qu'ils viennent la plupart du temps avec peu ou pas d'expérience en architecture informatique. Comme beaucoup ne semblent pas avoir une idée de la manière dont les ordinateurs (la machine) sont réellement implémentés, travailler en C / C ++ semble étranger.

Un exercice consiste à leur demander d'implémenter une simple machine virtuelle basée sur un bytecode (dans n'importe quel langage, python fonctionne parfaitement) avec un jeu d'instructions axé sur les opérations de pointage (chargement, stockage, adressage direct / indirect). Demandez-leur ensuite d'écrire des programmes simples pour ce jeu d'instructions.

Tout ce qui nécessite un peu plus qu'une simple addition va impliquer des pointeurs et ils sont sûrs de l'obtenir.


48



Pourquoi les pointeurs sont-ils un facteur de confusion majeur pour de nombreux nouveaux étudiants, voire anciens, du niveau C / C ++?

Le concept d'un espace réservé pour une valeur - variables - correspond à quelque chose qu'on nous enseigne à l'école - l'algèbre. Il n'y a pas de parallèle existant que vous pouvez dessiner sans comprendre comment la mémoire est physiquement disposée dans un ordinateur, et personne ne pense à ce genre de choses avant d'avoir affaire à des niveaux bas - au niveau des communications C / C ++ / octets .

Existe-t-il des outils ou des processus de réflexion qui vous ont aidé à comprendre comment les pointeurs fonctionnent au niveau de la variable, de la fonction et au-delà?

Adresses boîtes. Je me souviens quand j'apprenais à programmer BASIC dans des micro-ordinateurs, il y avait ces beaux livres avec des jeux, et parfois vous deviez piquer des valeurs dans des adresses particulières. Ils avaient une image d'un groupe de boîtes, étiquetées de manière incrémentielle avec 0, 1, 2 ... et il a été expliqué qu'une seule petite chose (un octet) pouvait tenir dans ces boîtes, et il y en avait beaucoup - certains ordinateurs avait autant que 65535! Ils étaient côte à côte et ils avaient tous une adresse.

Quelles sont les bonnes pratiques qui peuvent être mises en œuvre pour amener quelqu'un au niveau "Ah-hah, je l'ai compris", sans pour autant les enliser dans le concept global? Fondamentalement, percer des scénarios similaires.

Pour une perceuse? Faire une structure:

struct {
char a;
char b;
char c;
char d;
} mystruct;
mystruct.a = 'r';
mystruct.b = 's';
mystruct.c = 't';
mystruct.d = 'u';

char* my_pointer;
my_pointer = &mystruct.b;
cout << 'Start: my_pointer = ' << *my_pointer << endl;
my_pointer++;
cout << 'After: my_pointer = ' << *my_pointer << endl;
my_pointer = &mystruct.a;
cout << 'Then: my_pointer = ' << *my_pointer << endl;
my_pointer = my_pointer + 3;
cout << 'End: my_pointer = ' << *my_pointer << endl;

Même exemple que ci-dessus, sauf en C:

// Same example as above, except in C:
struct {
    char a;
    char b;
    char c;
    char d;
} mystruct;

mystruct.a = 'r';
mystruct.b = 's';
mystruct.c = 't';
mystruct.d = 'u';

char* my_pointer;
my_pointer = &mystruct.b;

printf("Start: my_pointer = %c\n", *my_pointer);
my_pointer++;
printf("After: my_pointer = %c\n", *my_pointer);
my_pointer = &mystruct.a;
printf("Then: my_pointer = %c\n", *my_pointer);
my_pointer = my_pointer + 3;
printf("End: my_pointer = %c\n", *my_pointer);

Sortie:

Start: my_pointer = s
After: my_pointer = t
Then: my_pointer = r
End: my_pointer = u

Peut-être que cela explique certaines des bases par exemple?


28



La raison pour laquelle j'ai eu du mal à comprendre les pointeurs, au début, c'est que de nombreuses explications comprennent beaucoup de conneries sur le passage par référence. Tout cela ne fait qu'embrouiller la question. Lorsque vous utilisez un paramètre de pointeur, vous êtes encore passant par valeur; mais la valeur se trouve être une adresse plutôt que, disons, un int.

Quelqu'un d'autre a déjà lié à ce tutoriel, mais je peux mettre en évidence le moment où j'ai commencé à comprendre les pointeurs:

Un tutoriel sur les pointeurs et les tableaux dans C: Chapitre 3 - Pointeurs et chaînes

int puts(const char *s);

Pour le moment, ignorez le const. Le paramètre transmis à puts() est un pointeur, c'est la valeur d'un pointeur (puisque tous les paramètres de C sont passés par valeur), et la valeur d'un pointeur est l'adresse vers laquelle il pointe, ou simplement une adresse. Ainsi quand on écrit puts(strA); comme nous l'avons vu, nous passons l'adresse de strA [0].

Au moment où j'ai lu ces mots, les nuages ​​se sont séparés et un rayon de soleil m'a enveloppé de compréhension de pointeur.

Même si vous êtes un développeur VB .NET ou C # (comme je le suis) et que vous n'utilisez jamais de code dangereux, cela vaut la peine de comprendre comment les pointeurs fonctionnent, ou vous ne comprendrez pas comment fonctionnent les références d'objet. Ensuite, vous aurez la notion commune mais erronée que la transmission d'une référence d'objet à une méthode copie l'objet.


23



J'ai trouvé le «Tutorial on Pointers and Arrays in C» de Ted Jensen, une excellente source d'information sur les pointeurs. Il est divisé en 10 leçons, en commençant par une explication de ce que sont les pointeurs (et ce qu'ils sont pour) et en finissant avec des pointeurs de fonction. http://home.netcom.com/~tjensen/ptr/cpoint.htm

Partant de là, le Guide de programmation en réseau de Beej enseigne l'API Unix sockets, à partir de laquelle vous pouvez commencer à faire des choses vraiment amusantes. http://beej.us/guide/bgnet/


19



Les complexités des indicateurs vont au-delà de ce que nous pouvons facilement enseigner. Avoir des élèves qui se pointent les uns les autres et utiliser des morceaux de papier avec des adresses de maison sont deux excellents outils d'apprentissage. Ils font un excellent travail d'introduction des concepts de base. En effet, l'apprentissage des concepts de base est vital à utiliser avec succès des pointeurs. Cependant, dans le code de production, il est courant de se retrouver dans des scénarios beaucoup plus complexes que ces simples démonstrations peuvent encapsuler.

J'ai été impliqué dans des systèmes où nous avions des structures pointant vers d'autres structures pointant vers d'autres structures. Certaines de ces structures contenaient également des structures intégrées (plutôt que des pointeurs vers des structures supplémentaires). C'est là que les pointeurs deviennent vraiment confus. Si vous avez plusieurs niveaux d'indirection, et que vous commencez avec du code comme celui-ci:

widget->wazzle.fizzle = fazzle.foozle->wazzle;

cela peut devenir très confus très rapidement (imaginez beaucoup plus de lignes, et potentiellement plus de niveaux). Ajoutez des tableaux de pointeurs et des pointeurs de nœud à nœud (arbres, listes chaînées) et cela devient encore pire. J'ai vu de très bons développeurs se perdre une fois qu'ils ont commencé à travailler sur de tels systèmes, même des développeurs qui comprenaient très bien les bases.

Les structures complexes de pointeurs n'indiquent pas nécessairement un mauvais codage (même si elles le peuvent). La composition est un élément essentiel d'une bonne programmation orientée objet, et dans les langages avec des pointeurs bruts, elle conduira inévitablement à une indirection multicouche. De plus, les systèmes doivent souvent utiliser des bibliothèques tierces avec des structures qui ne correspondent pas entre elles en termes de style ou de technique. Dans de telles situations, la complexité va naturellement se produire (même si, bien sûr, nous devrions le combattre autant que possible).

Je pense que la meilleure chose que les collèges peuvent faire pour aider les étudiants à apprendre des conseils est d'utiliser de bonnes démonstrations, combinées à des projets nécessitant une utilisation de pointeur. Un projet difficile fera plus pour la compréhension du point de vue qu'un millier de manifestations. Les démonstrations peuvent vous amener à une compréhension superficielle, mais pour bien saisir les indications, vous devez vraiment les utiliser.


12



Je ne pense pas que les pointeurs en tant que concept soient particulièrement délicats - la plupart des modèles mentaux des élèves correspondent à quelque chose comme ça et quelques ébauches de boîtes rapides peuvent aider.

La difficulté, du moins celle que j'ai connue dans le passé et dont d'autres ont été confrontés, est que la gestion des pointeurs en C / C ++ peut être compliquée sans nécessité.


10