Pointeurs et tableaux
Dans le premier billet de cette série, nous avons parlé un peu des pointeurs . Dans le second, on parle de références . Aujourd'hui, nous allons explorer la relation intime (ui!) entre les pointeurs et les tableaux (ou matrices).
Rappelant Arrays
Un tableau ou une matrice, ou un arrangement, il 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 du tableau a pour coordonnées uniques (ligne et colonne), de sorte qu'un élément E étant donné (i, j) représente le seul élément de la "ligne i", "colonne j".
La syntaxe de déclaration de tableau est comme suit:
/ / Un dimensions des matrices ou des vecteurs 10 ] ; // 10 elementos, do 0 ao 9 FPI int [10] / / 10 éléments, 0-9 23 ] ; // 23 elementos, do 0 ao 22 CVET char [23] / / 23 éléments, de 0 à 22 / / Deux dimensions des matrices 2 ] [ 3 ] ; // 2 linhas (0 a 1) e 3 colunas (0 a 2) IMAT int [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 ] ; int imat [ 3 ] [ 4 ] ; // Alterando o quarto elemento do vetor ivet. // Lembre-se que começa-se a contar a partir do elemento zero ivet [ 3 ] = 13 ; // Lendo o segundo elemento do vetor ivet int num = ivet [ 1 ] ; // Alterando o elemento da primeira linha, segunda coluna de imat imat [ 0 ] [ 1 ] = 42 ; // Lendo o elemento na terceira linha, quarta coluna de imat int foo = imat [ 2 ] [ 3 ] ; FPI int [10]; IMAT int [3] [4] / / Changer le quatrième élément du vecteur FPI / / N'oubliez pas que l'on commence à compter à partir de zéro élément FPI [3] = 13 / /. La lecture du deuxième élément du tableau int a = FPI FPI [1] / / Modification de l'élément de première ligne, deuxième colonne IMAT IMAT [0] [1] = 42 / / lecture de l'élément dans la troisième rangée, quatrième colonne IMAT IMAT int foo = [2] [3];
Dans ce post, nous ne discuterons pas les matrices de fond, nous allons seulement enquêter sur la relation entre les tableaux et les pointeurs dans un assez intuitif.
Tailles des matrices
Depuis un tableau est une abstraction qui peut contenir plusieurs valeurs du même type, ce qu'il taille? Combien d'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 ] ; FPI int [10]; 13 ] ; CVET char [13]; 20 ] ; dvet double [20]; 3 ] [ 4 ] ; CMAT char [3] [4]; 5 ] [ 4 ] ; IMAT int [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 (EFPC) =" <<sizeof (EFPC) <<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)). Maintenant, si chaque élément est indépendant, il est supposé que chacun d'eux occupe une place à part dans la mémoire, sinon écraser l'autre d'un élément. Ainsi est ce que chaque élément a son adresse propre mémoire?
Les tableaux et les adresses mémoire
Par commodité, nous analyserons ensuite les adresses possibles un tableau de caractères, dont la taille est seulement donné un octet 1:
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 char [max] = {'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 valeur t \ t élément d'adresse \ 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], & CVET [i]); } / / Affichage de l'adresse du tableau "Endereço da matriz: %p \n " , & cvet ) ; printf ("Adresse du tableau:% p \ n", & EFPC); / / Affichage de l'adresse du tableau à nouveau "Endereço da matriz: %p \n " , cvet ) ; printf ("Adresse du tableau:% p \ n", FPC); |
Les adresses des éléments sont séquentielles, c'est à dire chaque élément est stocké à côté de l'ancienne. De plus, il ya deux faits intéressants:
- L'adresse du tableau (& EFPC), illustré à la ligne 25, est le même que le premier élément du tableau;
- La variable se Cvet peut être interprété comme un pointeur, comme indiqué dans 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. En outre, il est un pointeur valide pour pointer vers un tableau aussi longtemps que le pointeur vers la destination est du même type que le type des éléments du tableau. Pendant atrubuição d'un tableau à un pointeur, le compilateur fait une conversion de type implicite. Le pointeur de la cible doit être interprété comme un pointeur vers la zone mémoire occupée par le tableau.
Une des conséquences pas si évident, c'est que implicite dans la fonte, l'information est perdue que la zone était une vaste mémoire. Ainsi l'information est perdue sur la taille du tableau. Du point de vue du pointeur, il pointe vers le début d'un bloc arbitraire de la mémoire, d'une taille trop arbitraire. Sortez et allez pour un tableau à un pointeur signifie passer d'une plus restrictif et plus abstraites de haut niveau d'abstraction pour un niveau moins restrictif et plus bas.
D'autre part essayez d'affecter 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 n (en octets) comme un pointeur a seulement une donnée, une adresse. Le compilateur n'a aucun moyen de savoir à l'avance si un pointeur pointe vers une zone d'octets 1, 2 ou 200.
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 l'attribution \ n"); "cvet = %p \n " , cvet ) ; printf ("% p = CVET \ n", FPC); "pc = %p \n " , pc ) ; printf ("% p = pc \ n", PC); "sizeof(cvet) = %lu \n " , sizeof ( cvet ) ) ; printf ("sizeof (EFPC) =% lu \ n", sizeof (EFPC)); "sizeof(pc) = %lu \n " , sizeof ( pc ) ) ; printf ("sizeof (cp) =% lu \ n", sizeof (cp)); pc = CVET; " \n Depois da atribuição \n " ) ; printf ("\ n Après l'affectation \ n"); "cvet = %p \n " , cvet ) ; printf ("% p = CVET \ n", FPC); "pc = %p \n " , pc ) ; printf ("% p = pc \ n", PC); "sizeof(cvet) = %lu \n " , sizeof ( cvet ) ) ; printf ("sizeof (EFPC) =% lu \ n", sizeof (EFPC)); "sizeof(pc) = %lu \n " , sizeof ( pc ) ) ; printf ("sizeof (cp) =% lu \ n", sizeof (cp)); |
Notez que avant la cession (ligne 25) le pc pointeur. est nulle, donc il a été initialisé. Depuis les tailles indiquent que le tableau ne compte que 300 octets et le pointeur de la 8 (ma machine est un AMD 64). Après avoir attribué les deux passent le "point" à la même zone mémoire, mais la taille ne change pas. Il y avait une conversion implicite de char [300] pour char *, et dans ce jeu, le pc pointeur ne peut pas connaître la taille de la zone de mémoire sur lequel il pointe. Mais le tableau continue CVET sachant exactement ce qu'il est, sans aucune crise existentielle.
Arithmétique des pointeurs - Vulgaire, et alors?
Pourquoi, si je sais que les données dans une matrice sont disposés côte à côte, je peux utiliser un pointeur qui va sauter à l'adresse suivante et accéder à l'élément suivant. C'est ce qu'on appelle l'arithmétique des 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 | ivet [ max ] = { 1 , 2 , 3 , 4 , 5 , 6 } ; int * pi = ivet ; for ( int i = 0 ; i < max ; i ++ ) { printf ( "%p = %d \n " , pi, * pi ++ ) ; } printf ( " \n " ) ; // Em duas Maintenant avec toute FPI int [max] = {1, 2, 3, 4, 5, 6}; int * pi = FPI; for (int i = 0; i <max; i + +) {p printf ("% =% d \ n ", pi, pi * + +);} printf (" \ n ") / / Dans deux |
Dans la ligne 18 le pc pointeur est maintenant pointer vers le CVET tableau, et par conséquent à son premier élément, le caractère «B». Dans la ligne 21 le contenu du PC, qui est l'adresse où il était stocké 'B' du caractère, c'est à dire est augmenté alors déréférencé. Dans le premier passage de la valeur de i est nul, c'est donc la valeur pour le déréférencement 'B'. Dans les étapes suivantes, la prochaine adresse est déréférencé à d'autres personnages stockés dans le tableau original. Il est plus ou moins ce que le compilateur ne l'interne lorsque vous utilisez la syntaxe de la formation professionnelle continue [i]. L'abstraction du tableau vous donne un moyen convivial de traiter des zones contiguës de mémoire * (pc + i).
Mais si l'abstraction tableau est simple à utiliser que l'arithmétique des pointeurs?
Une réponse 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 similaire, la syntaxe de tableau, l'accès à toute donnée donnée peut se résumer dans les commandes très approximative:
- Prenez l'adresse de base de la matrice;
- Ajouter à l'adresse de la valeur de l'indice;
- De-cette nouvelle adresse de référence;
Déjà l'arithmétique des pointeurs ressemble à ceci:
- De-référence à cette adresse;
L'incrément de commande (ou l'a fait avec les adresses) ne dira pas parce qu'il 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 d'utilisation d'un pointeur de manipuler une zone arbitraire de la mémoire est généralement utilisé dans la programmation bas niveau (proche de la machine), la manipulation des tampons et de chaînes, parmi d'autres sales tours. Dans les entrailles des ordinateurs, des opérations qui balayent de vastes zones de la mémoire, sont souvent réalisées avec l'arithmétique des pointeurs. A ce niveau, Darwin règne en maître et seul le prêt va survivre. De là, commence à donner de la langue d'un pouvoir que seul le cœur pur peut comprendre.
Une observation importante est que, entre les lignes 31 et 38 est répété avec toute l'expérience. Notez que les entiers sont de 4 octets, les incréments sont automatiquement mises à 4 sur 4 octets, et non 1 sur 1, c'est à dire l'incrément est calculé automatiquement à sizeof (type). Incrémenter un pointeur moyen d'accéder à la zone suivante de la mémoire semblable à des données réelles, et pas seulement l'adresse suivante. Comme la taille d'un char occupe un octet, quand 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 la mémoire cache du processeur, par exemple.
void * pansexuelle des pointeurs
J'ai dit plus tôt qu'il était seulement possible d'assigner un tableau à un pointeur qui est allé au même type que les données du tableau. J'ai menti purement et simplement! La raison est que pour quelqu'un qui a abandonné le poste avant que ce sujet, il est plus sûr de croire qu'il ne peut pas
!
Il ya deux exceptions à la règle. Le premier est quand il ya une conversion de type explicite et le pointeur de destination "pense" qui pointe vers le bon type. Un exemple est dans la ligne 44 du code précédent.
Le second est le cas de pointeurs vers nulle. Un pointeur void est un pointeur qui ne fait aucune demande sur le type de données qui sont dans la zone de mémoire sur lequel il pointe. Il s'agit d'un pointeur vers une zone générale de la mémoire, quelque chose de très bas niveau.
Pour utiliser un ensemble de données pointé par un pointeur sur void, avant de référence, vous devez faire une conversion explicite d'un type valide, car il est un int *, int, et faisant référence à un char * est dé-référencé à char, devinez quoi, il est 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 char [max] = {'B', 'L', 'A', 'B', 'O', 'S'}; pv = cvet ; void * PV = CVET; / / Erreur de compilation. / / * Pv / / La vallée de 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 vide 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 que cette zone contient, ou des fonctions qui ne peuvent pas faire d'hypothèses sur les types de ses paramètres, tels que le API lib pthreads (lien arbitraire).
Fin
Plus nous fouillons dans les thèmes de pointeurs, nous sommes plus près de la machine. Une grande partie de la puissance du C et C + + vient de là, et beaucoup de la faute aussi. La complexité est accrue et les risques. Pour beaucoup c'est là que réside le plaisir!
Liens
- arrays.zip (toutes les sources de la poste);
- Pointeurs sur cplusplus.com
- Pointeurs dans blabos.org
- Références dans blabos.org
- Le coupable
Commentaires
- Leandro Melo http://0xc0de.wordpress.com
- Francisco-Bruno-Luisa

