Pointeurs et tableaux

18 mai 2009 · Posté dans C / C + +

Dans le premier post de cette série, nous avons parlé un peu de pointeurs . Dans le second, on parle de références . Aujourd'hui, nous discutons de la relation intime (ui!) Entre les pointeurs et les tableaux (ou tableaux).

Rappelant Arrays

Un tableau, ou une matrice, ou un arrangement, est une abstraction mathématique utilisée pour représenter un ensemble de données homogènes, c'est à dire le même type (int, float, etc.) Cette abstraction est organisé sous forme de tableau, avec des lignes et colonnes. Chaque élément de la matrice des coordonnées uniques (ligne et colonne), de sorte qu'un élément donné E (i, j) représente le seul élément dans la ligne "i", j colonne ".

La syntaxe pour les tableaux déclarant se présente comme suit:

  / / Dimensions tableaux à une ou des vecteurs
 10 ] ; // 10 elementos, do 0 ao 9 int Ivet [10] / / 10 éléments, de 0 à 9
 23 ] ; // 23 elementos, do 0 ao 22 Cvet char [23] / / 23 éléments, de 0 à 22

 / / Deux dimensions des tableaux
 2 ] [ 3 ] ; // 2 linhas (0 a 1) e 3 colunas (0 a 2) int IMAT [2] [3] / / 2 lignes (0-1) et trois colonnes (0-2)
 10 ] [ 2 ] ; // 10 linhas (0 a 9) e 2 colunas (0 a 1) DMAT double [10], [2] / / 10 lignes (0-9) et deux colonnes (0-1) 

Chaque élément du tableau est indépendant des autres et peut être consulté que la syntaxe suivante:

  10 ] ; Ivet int [10];
 3 ] [ 4 ] ; int IMAT [3] [4];

 / / Modification de la quatrième élément de la Ivet vecteur.
 / / N'oubliez pas que vous commencez à compter à partir de l'élément zéro
 ] = 13 ; Ivet [3] = 13;

 / / Lecture du deuxième élément du vecteur Ivet
 ivet [ 1 ] ; num = int Ivet [1];

 / / Changement de l'élément de la première rangée, deuxième colonne IMAT
 ] [ 1 ] = 42 ; IMAT [0] [1] = 42;

 / / Lecture de l'élément dans la troisième rangée, quatrième colonne du IMAT
 imat [ 2 ] [ 3 ] ; int foo = IMAT [2] [3]; 

A ce poste, nous ne discuterons pas le fond de matrice, nous avons seulement étudier la relation entre les tableaux et les pointeurs dans un assez intuitif.

Tailles des tableaux

Depuis un tableau est une abstraction qui contient plusieurs valeurs du même type, quelle est son importance? Quel est l'espace qu'il occupe dans la mémoire?

Considérons le code ci-dessous:

  17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
  10 ] ; int Ivet [10];
 13 ] ; Cvet char [13];
 20 ] ; DVET double [20];

 3 ] [ 4 ] ; CMAT char [3] [4];
 5 ] [ 4 ] ; int IMAT [5], [4];

 "sizeof(int)     = " << sizeof ( int ) << endl ; Cout <<"sizeof (int) =" <<sizeof (int) <endl <;
 "sizeof(char)    = " << sizeof ( char ) << endl ; Cout <<"sizeof (char) =" <<sizeof (char) <endl <;
 "sizeof(double)  = " << sizeof ( double ) << endl ; Cout <<"sizeof (double) =" <<sizeof (double) <endl <;

 "sizeof(ivet)    = " << sizeof ( ivet ) << endl ; Cout <<"sizeof (FPI) =" <<sizeof (FPI) <<endl;
 "sizeof(cvet)    = " << sizeof ( cvet ) << endl ; Cout <<"sizeof (FPC) =" <<sizeof (FPC) <<endl;
 "sizeof(dvet)    = " << sizeof ( dvet ) << endl ; Cout <<"sizeof (DVET) =" <<sizeof (DVET) <endl <;
 "sizeof(cmat)    = " << sizeof ( cmat ) << endl ; Cout <<"sizeof (CMAT) =" <<sizeof (CMAT) <<endl;
 "sizeof(imat)    = " << sizeof ( imat ) << endl ; Cout <<"sizeof (IMAT) =" <<sizeof (IMAT) <<endl; 

Le résultat est tout à fait raisonnable. L'espace occupé par une matrice est égal au nombre d'éléments, multiplié par la taille du type d'élément (lignes x colonnes x sizeof (type)). Toutefois, si chaque élément est indépendant, il est supposé que chacun y occupe une place à part dans la mémoire, sinon on va écraser un autre élément. Alors, est-ce que chaque élément a son adresse propre mémoire?

Les tableaux et les adresses de la mémoire

Pour plus de commodité, nous analyserons ensuite les adresses possibles un tableau de caractères, dont la taille est seulement donné un octet celui-ci:

  15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
  5 ; const int max = 5;
 max ] = { 'A' , 'B' , 'C' , 'D' , 'E' } ; Cvet [] = max char ('A', 'B', 'C', 'D', 'E');

 / / Afficher le contenu, les valeurs des données et des adresses.
 "Índice \t Valor \t Endereço do elemento \n " ) ; printf ("Index t \ Rapport \ adresse élément t \ n");
 int i = 0 ; i < max ; i ++ ) { for (int i = 0; i <max; i + +) (
     "%d \t %c \t %p \n " , i, cvet [ i ] , & cvet [ i ] ) ; printf ("% d \ t% c \ t% p \ n", i, Cvet [i], et Cvet [i]);
 )

 / / Affichage de l'adresse du tableau
 "Endereço da matriz: %p \n " , & cvet ) ; printf ("Adresse du tableau: \ n% p", et Cvet);

 / / Affiche l'adresse du tableau à nouveau
 "Endereço da matriz: %p \n " , cvet ) ; printf ("Adresse du tableau: \ n% p", Cvet); 

Les adresses des éléments sont séquentielles, c'est à dire, chaque élément est entreposé le long de l'ancienne. En outre, il existe deux autres faits intéressants:

  1. L'adresse de l'array (& Cvet), inscrit à la ligne 25, est le même que le premier élément du tableau;
  2. Le très variable Cvet peut être interprété comme un pointeur, comme le montre la ligne 28;

En C + +, un tableau commun est un bloc contigu de mémoire dont le nom peut être interprété (CAST) comme un pointeur qui pointe vers son premier élément. De plus, il est valable de faire un point pointeur vers un tableau où le pointeur de la destination est du même type que le type des éléments du tableau. Au cours de atrubuição d'un tableau à un pointeur, le compilateur fait une conversion de type implicite. Le pointeur de destination doit être interprété comme un pointeur vers la zone de mémoire occupée par le tableau.

Une des conséquences pas si évident, c'est que lors de la conversion implicite est perdue de l'information que la région était une matrice mémoire. C'est ainsi perdu les informations sur la taille du tableau. Du point de vue de l'aiguille, elle pointe vers le début d'un bloc arbitraire de la mémoire, une taille trop arbitraire. Sortir et aller dans un tableau à un pointeur, c'est aller à partir d'une abstraction plus restrictive et plus abstraction de haut niveau à une moins restrictive et de niveau inférieur.

D'autre part essayez d'attribuer un pointeur vers un tableau génère une erreur de compilation pour les types incompatibles. Un tableau est un bloc de mémoire de données (n octets), car un pointeur a seulement une donnée, une adresse. Le compilateur ne peut pas savoir à l'avance si un pointeur vers une zone de 1, 2 ou 200 octets.

  15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
  300 ; const int max = 300;
 max ] ; Cvet char] [max;
 pc = 0 ; char * pc = 0;

 " \n Antes da atribuição \n " ) ; printf ("\ n Avant la n \ cession");
 "cvet = %p \n " , cvet ) ; printf ("% p = Cvet \ n", Cvet);
 "pc   = %p \n " , pc ) ; printf ("% pc \ p = n", PC);
 "sizeof(cvet) = %lu \n " , sizeof ( cvet ) ) ; printf ("sizeof (FPC) =% lu \ n", sizeof (FPC));
 "sizeof(pc)   = %lu \n " , sizeof ( pc ) ) ; printf ("sizeof (PC) =% lu \ n", sizeof (PC));

 pc = Cvet;

 " \n Depois da atribuição \n " ) ; printf ("\ n Après la n \ cession");
 "cvet = %p \n " , cvet ) ; printf ("% p = Cvet \ n", Cvet);
 "pc   = %p \n " , pc ) ; printf ("% pc \ p = n", PC);
 "sizeof(cvet) = %lu \n " , sizeof ( cvet ) ) ; printf ("sizeof (FPC) =% lu \ n", sizeof (FPC));
 "sizeof(pc)   = %lu \n " , sizeof ( pc ) ) ; printf ("sizeof (PC) =% lu \ n", sizeof (PC)); 

Notez que préalablement à la cession (ligne 25), le pointeur pc. est nulle, car elle a été démarré alors. Déjà tailles indiquent que le tableau a 300 octets et le pointeur seuls 8 (ma machine est un AMD 64). Après la cession à la fois passer le «point» de la même zone de la mémoire, mais la taille ne change pas. Il ya eu une conversion implicite de char [300] de char * et le pointeur dans ce jeu PC ne peut pas connaître la taille de la zone de mémoire sur lequel il pointe. Mais le tableau Cvet toujours savoir exactement ce qu'il est sans crise existentielle.

arithmétique des pointeurs - vulgo, et alors?

Pourquoi, si je sais que les données dans un tableau sont disposées côte à côte, je peux utiliser un pointeur qui saute à l'adresse suivante et d'accéder à l'élément suivant. Le nom de cette arithmétique est pointeurs.

  15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
  6 ; const int max = 6; 
 max ] = { 'B' , 'L' , 'A' , 'B' , 'O' , 'S' } ; Cvet [] = max char ('B', 'L', 'A', 'B', 'O', 'S');

 pc = cvet ; char * pc = Cvet;

 int i = 0 ; i < max ; i ++ ) { for (int i = 0; i <max; i + +) (
     "%c" , * ( pc + i ) ) ; printf ("% c", * (pc + i));
 )
 " \n " ) ; printf ("\ n");

 int i = 0 ; i < max ; i ++ ) { for (int i = 0; i <max; i + +) (
     "%c" , * pc ++ ) ; printf ("% c", * pc + +);
 )
 " \n " ) ; printf ("\ n");


 / / Maintenant, avec des nombres entiers
 max ] = { 1 , 2 , 3 , 4 , 5 , 6 } ; Ivet int [max] = (1, 2, 3, 4, 5, 6);
 pi = ivet ; int * pi = Ivet;

 int i = 0 ; i < max ; i ++ ) { for (int i = 0; i <max; i + +) (
     "%p = %d \n " , pi, * pi ++ ) ; printf ("% p =% d \ n", pi pi * + +);
 )
 " \n " ) ; printf ("\ n");

 / / En deux dimensions
 2 ] [ 3 ] = { { 'B' , 'L' , 'A' } , { 'B' , 'O' , 'S' } } ; CMAT char [2] [3] = (('B', 'L', 'A'), ('B', 'O', 'S'));
 ppc ; Char * PPC;

 char * ) cmat ; ppc = (char *) CMAT;

 int i = 0 ; i < 2 ; i ++ ) { for (int i = 0; i <2; i + +) (
     int j = 0 ; j < 3 ; j ++ ) { for (int j = 0, j <3 j + +) (
         "%c" , * ( ppc + 3 * i + j ) ) ; printf ("% c", * (PPC + 3 * i + j));
     )
     " \n " ) ; printf ("\ n");
 )
 " \n " ) ; printf ("\ n"); 

Dans la ligne 18 pc pointeur est maintenant le point de la Cvet tableau, et par conséquent de son premier élément, le caractère 'B'. Dans la ligne 21, le contenu du PC, qui est l'adresse où il était stocké le caractère 'B', soit on augmente alors déréférencé. Dans le premier passage de la valeur de i est nul, il est donc le déréférencement de la valeur de 'B'. Dans les étapes suivantes, l'adresse suivante est de renvoyer à d'autres caractères stockés dans le tableau original. Il est plus ou moins ce que le compilateur ne l'interne lorsque vous utilisez la syntaxe Cvet [i]. L'abstraction du tableau vous donne un moyen plus convivial de traiter les zones contiguës de mémoire * (pc + i).

Mais si l'abstraction du tableau est simple à utiliser que l'arithmétique des pointeurs?

Une des réponses est la ligne 26. Elle fait la même chose que la ligne 21, mais un peu plus vite. Dans la syntaxe de la ligne 21, ou même, la syntaxe de tableau, l'accès à toute donnée peut être à peu près bien résumé dans les commandes:

  1. Prendre l'adresse de base du tableau;
  2. Ajouter à l'adresse de la valeur de l'indice;
  3. De-référencement de la nouvelle adresse;

Déjà avec l'arithmétique pointeur ressemble à ceci:

  1. De-référence à cette adresse;

L'accroissement de commande (ou le compte rendu avec des adresses) ne sera pas le dire parce que cela fait partie de la boucle, même si i + +; est plus rapide que a + b = c;. Maintenant, imaginez ce petit gain de 66% appliquée à une zone de données de 1 MB. Il n'y aura plus de 2 millions de commandes de moins!

La technique consistant à utiliser un pointeur pour gérer un espace arbitraire de la mémoire est généralement utilisé dans la programmation de bas niveau (proche de la machine), la manipulation des tampons et les cordes, entre autres, de sales tours. Dans les entrailles des ordinateurs, des opérations que les zones de balayage avec beaucoup de mémoire, sont souvent réalisées avec l'arithmétique des pointeurs. A ce niveau, Darwin règne en maître et que les prêts les plus survivre. De là, la langue commence à donner un pouvoir que seuls les cœurs purs peuvent comprendre.

Une remarque importante est celle entre les lignes 31 et 38 de l'expérience est répétée avec des nombres entiers. Notez que les entiers sont 4 octets, les augmentations sont automatiquement mises à 4 en 4 octets, pas 1 sur 1, à savoir l'augmentation est calculé automatiquement pour sizeof (type). Incrémenter un pointeur de moyens d'accéder à la zone suivante de la mémoire similaire à données réelles, et non seulement l'adresse suivante. Comme la taille d'un char occupe un octet, lorsque incremetamos un pointeur vers un char, déplacer un seul octet. Si vous incrémenter un pointeur sur un double, nous allons passer de 8 octets, et ainsi de suite.

Une autre observation est que le tableau à deux dimensions peut être «linéarisés» comme indiqué dans les lignes 40-52. Ceci est utile, le cas échéant, de faire un meilleur usage de cache du processeur, par exemple.

void *, le pansexuelle des pointeurs

Plus tôt, j'ai dit que c'était seulement possible d'assigner un tableau à un pointeur qui a été pour le même type que les données du tableau. J'ai carrément couché! La raison en est que, pour quelqu'un qui a démissionné du poste avant ce sujet, il est plus sûr de croire qu'il ne peut pas :) !

Il existe deux exceptions à la règle. Le premier, c'est quand il ya une conversion de type explicite et pointeur ciblé "pense" qui pointe vers le bon type. Un exemple est la ligne 44 du code précédent.

Le second est le cas des pointeurs vers nulle. Un pointeur vers le vide est un pointeur qui ne demande rien sur le type de données qui se trouve dans la zone de mémoire sur lequel il pointe. Il est un pointeur vers une zone générique de la mémoire, quelque chose de très bas niveau.

Pour utiliser un ensemble de données pointé par un pointeur nul avant de le référencer-, nous devons faire une conversion explicite d'un type valide, car si un int * est déréférencé à un int et char * est de renvoyer aux char, devinez quoi, il est déréférencé un void *?

  15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
  6 ; const int max = 6; 
 max ] = { 'B' , 'L' , 'A' , 'B' , 'O' , 'S' } ; Cvet [] = max char ('B', 'L', 'A', 'B', 'O', 'S');

 pv = cvet ; void * PV = Cvet;

 / / Erreur de compilation.
 / PV * /
 / / Quel est le sizeof (void)?

 int i = 0 ; i < max ; i ++ ) { for (int i = 0; i <max; i + +) (
     "%c" , * ( ( ( char * ) pv ) + i ) ) ; printf ("% c", * (((char *) pv) + i));
 )
 " \n " ) ; printf ("\ n"); 

Pointeur vers nulle sont utilisés lorsque l'on doit pointer vers un domaine général de la mémoire sans avoir le contrôle / la connaissance du type de données qui contient ce domaine, ou des fonctions qui ne peuvent pas faire des hypothèses sur les types de ses paramètres, tels que le API lib pthreads (lien arbitraire).

Clôture

Plus nous plonger dans les sujets sur les pointeurs, nous sommes plus près de la machine. Une grande partie de la puissance de C et C + + qui en dérivent, et beaucoup de trop de problèmes. La complexité est croissante et les risques aussi. Pour beaucoup c'est là que réside le plaisir!

Liens

Commentaires

    commentaires de blog actionnés par Disqus