web-dev-qa-db-fra.com

Arithmétique des pointeurs

Quelqu'un at-il de bons articles ou explications (blogs, exemples) pour l'arithmétique des pointeurs? Figure que le public est un groupe de Java programmeurs apprenant le C et le C++.

58
leora

Tout d'abord, la vidéo binky peut vous aider. C'est une belle vidéo sur les pointeurs. Pour l'arithmétique, voici un exemple:

int * pa = NULL;
int * pb = NULL;
pa += 1; // pa++. behind the scenes, add sizeof(int) bytes
assert((pa - pb) == 1);

print_out(pa); // possibly outputs 0x4
print_out(pb); // possibly outputs 0x0 (if NULL is actually bit-wise 0x0)

(Notez que l'incrémentation d'un pointeur qui contient une valeur de pointeur null strictement est un comportement non défini. Nous avons utilisé NULL parce que nous n'étions intéressés que par la valeur du pointeur. Normalement, n'utilisez l'incrémentation/décrémentation que pour pointer vers les éléments d'un tableau).

Ce qui suit montre deux concepts importants

  • l'ajout/la soustraction d'un entier à un pointeur signifie déplacer le pointeur vers l'avant/vers l'arrière de N éléments. Donc, si un entier fait 4 octets, pa pourrait contenir 0x4 sur notre plateforme après avoir incrémenté de 1.
  • la soustraction d'un pointeur par un autre pointeur signifie obtenir leur distance, mesurée par les éléments. Donc, soustraire pb de pa donnera 1, car ils ont une distance d'un élément.

Sur un exemple pratique. Supposons que vous écriviez une fonction et que les gens vous fournissent un pointeur de début et de fin (chose très courante en C++):

void mutate_them(int *begin, int *end) {
    // get the amount of elements
    ptrdiff_t n = end - begin;
    // allocate space for n elements to do something...
    // then iterate. increment begin until it hits end
    while(begin != end) {
        // do something
        begin++;
    }
}

ptrdiff_t est le type de (fin - début). Il peut être synonyme de "int" pour certains compilateurs, mais peut être un autre type pour un autre. On ne peut pas savoir, donc on choisit le typedef générique ptrdiff_t.

51

Voici où j'ai appris les pointeurs: http://www.cplusplus.com/doc/tutorial/pointers.html

Une fois que vous comprenez les pointeurs, l'arithmétique des pointeurs est facile. La seule différence entre elle et l'arithmétique régulière est que le nombre que vous ajoutez au pointeur sera multiplié par la taille du type vers lequel le pointeur pointe. Par exemple, si vous avez un pointeur sur un int et que la taille d'un int est de 4 octets, (pointer_to_int + 4) sera évalué à une adresse mémoire de 16 octets (4 pouces) à l'avance.

Alors quand tu écris

(a_pointer + a_number)

en arithmétique des pointeurs, ce qui se passe vraiment est

(a_pointer + (a_number * sizeof(*a_pointer)))

en arithmétique régulière.

57
Jeremy Ruten

en appliquant la PNL, appelez-la arithmétique d'adresse. les "pointeurs" sont craints et mal compris principalement parce qu'ils sont enseignés par les mauvaises personnes et/ou au mauvais stade avec de mauvais exemples de la mauvaise manière. Il n'est pas étonnant que personne ne "l'obtienne".

lors de l'enseignement des pointeurs, la faculté continue sur "p est un pointeur vers a, la valeur de p est l'adresse d'un" et ainsi de suite. ça ne marchera pas. voici la matière première avec laquelle vous pouvez construire. pratiquez-le et vos élèves l'obtiendront.

'int a', a est un entier, il stocke des valeurs de type entier. 'int * p', p est une 'int star', il stocke les valeurs de type 'int star'.

'a' est la façon dont vous obtenez le 'quoi' entier stocké dans un (essayez de ne pas utiliser la 'valeur d'un') '& a' est la façon dont vous obtenez le 'où' un lui-même est stocké (essayez de dire 'adresse')

'b = a' pour que cela fonctionne, les deux côtés doivent être du même type. si a est int, b doit être capable de stocker un int. (donc ______ b, le blanc est rempli avec 'int')

'p = & a' pour que cela fonctionne, les deux côtés doivent être du même type. si a est un entier, & a est une adresse, p doit être capable de stocker des adresses d'entiers. (donc ______ p, le blanc est rempli avec 'int *')

écrivez maintenant int * p différemment pour faire ressortir les informations de type:

int * | p

qu'est-ce que "p"? ans: c'est 'int *'. donc 'p' est l'adresse d'un entier.

int | * p

qu'est-ce que '* p'? ans: c'est un 'int'. donc '* p' est un entier.

passons maintenant à l'arithmétique des adresses:

int a; a = 1; a = a + 1;

que faisons-nous dans 'a = a + 1'? pensez-y comme "suivant". Parce que a est un nombre, cela revient à dire "numéro suivant". Puisqu'un détient 1, dire "suivant" en fera 2.

// exemple fallacieux. Tu étais prévenu!!! int * p int a; p = & a; p = p + 1;

que faisons-nous dans 'p = p + 1'? il dit toujours "suivant". Cette fois, p n'est pas un nombre mais une adresse. Donc, ce que nous disons est "prochaine adresse". L'adresse suivante dépend du type de données, plus précisément de la taille du type de données.

printf ("% d% d% d", sizeof (char), sizeof (int), sizeof (float));

donc 'suivant' pour une adresse fera avancer la taille de (type de données).

cela a fonctionné pour moi et pour toutes les personnes que j'enseignais.

6
Kinjal Dixit

Je considère un bon exemple d'arithmétique de pointeur comme la fonction de longueur de chaîne suivante:

int length(char *s)
{
   char *str = s;
   while(*str++);
   return str - s;
}
3
arul

Ainsi, la chose clé à retenir est qu'un pointeur est juste une variable de taille Word qui est tapée pour le déréférencement. Cela signifie que que ce soit un void *, int *, long long **, ce n'est toujours qu'une variable de taille Word. La différence entre ces types est ce que le compilateur considère comme le type déréférencé. Juste pour clarifier, la taille d'un mot signifie la largeur d'une adresse virtuelle. Si vous ne savez pas ce que cela signifie, rappelez-vous simplement que sur une machine 64 bits, les pointeurs font 8 octets et sur une machine 32 bits, les pointeurs font 4 octets. Le concept d'adresse est SUPER important pour comprendre les pointeurs. Une adresse est un nombre capable d'identifier de manière unique un certain emplacement en mémoire. Tout en mémoire a une adresse. Pour nos besoins, nous pouvons dire que chaque variable a une adresse. Ce n'est pas nécessairement toujours vrai, mais le compilateur nous laisse supposer cela. L'adresse elle-même est granulaire d'octets, ce qui signifie que 0x0000000 spécifie le début de la mémoire et 0x00000001 est un octet en mémoire. Cela signifie qu'en ajoutant un à un pointeur, nous déplaçons un octet vers l'avant dans la mémoire. Maintenant, prenons des tableaux. Si vous créez un tableau de type quux de 32 éléments, il s'étendra du début de son allocation au début de son allocation plus 32 * sizeof (quux), car chaque cellule du tableau est de sizeof (quux) big. Donc, vraiment quand nous spécifions un élément d'un tableau avec tableau [n], c'est juste du sucre syntaxique (raccourci) pour * (tableau + taille de (quux) * n). L'arithmétique du pointeur ne fait que changer l'adresse à laquelle vous faites référence, c'est pourquoi nous pouvons implémenter strlen avec

while(*n++ != '\0'){
  len++;
}

puisque nous ne faisons que balayer le long, octet par octet jusqu'à ce que nous atteignions un zéro. J'espère que ça t'as aidé!

1
gaze

Il existe plusieurs façons de l'aborder.

L'approche intuitive, à laquelle pensent la plupart des programmeurs C/C++, est que les pointeurs sont des adresses mémoire. l'exemple de litb reprend cette approche. Si vous avez un pointeur nul (qui sur la plupart des machines correspond à l'adresse 0) et que vous ajoutez la taille d'un entier, vous obtenez l'adresse 4. Cela implique que les pointeurs ne sont fondamentalement que des entiers fantaisistes.

Malheureusement, cela pose quelques problèmes. Pour commencer, cela peut ne pas fonctionner. Un pointeur nul n'est pas garanti d'utiliser réellement l'adresse 0. (Bien que l'attribution de la constante 0 à un pointeur donne le pointeur nul).

De plus, vous n'êtes pas autorisé à incrémenter le pointeur nul, ou plus généralement, un pointeur doit toujours pointer vers la mémoire allouée (ou un élément passé), ou la constante spéciale de pointeur nul 0.

Donc, une façon plus correcte de penser est que les pointeurs sont simplement des itérateurs vous permettant d'itérer sur la mémoire allouée. C'est vraiment l'une des idées clés derrière les itérateurs STL. Ils sont modélisés pour se comporter comme des pointeurs et pour fournir des spécialisations qui corrigent des pointeurs bruts pour fonctionner comme des itérateurs appropriés.

Une explication plus élaborée de ceci est donnée ici , par exemple.

Mais cette dernière vue signifie que vous devez vraiment expliquer les itérateurs STL, puis dire simplement que les pointeurs en sont un cas particulier. Vous pouvez incrémenter un pointeur pour pointer vers l'élément suivant dans le tampon, tout comme vous pouvez un std::vector<int>::iterator. Il peut pointer un élément au-delà de la fin d'un tableau, tout comme l'itérateur de fin dans n'importe quel autre conteneur. Vous pouvez soustraire deux pointeurs qui pointent dans le même tampon pour obtenir le nombre d'éléments entre eux, comme vous le pouvez avec les itérateurs, et comme avec les itérateurs , si les pointeurs pointent vers des tampons séparés, vous pouvez pas les comparer de manière significative. (Pour un exemple pratique de pourquoi pas, considérez ce qui se passe dans un espace mémoire segmenté. Quelle est la distance entre deux pointeurs pointant vers des segments séparés?)

Bien sûr, dans la pratique, il existe une corrélation très étroite entre les adresses CPU et les pointeurs C/C++. Mais ce ne sont pas exactement la même chose. Les pointeurs ont quelques limitations qui peuvent ne pas être strictement nécessaires sur votre CPU.

Bien sûr, la plupart des programmeurs C++ se débrouillent à première vue, même si c'est techniquement incorrect. Il est généralement assez proche de la façon dont votre code se comporte pour que les gens pensent qu'ils l'obtiennent et passent à autre chose.

Mais pour quelqu'un qui vient de Java et qui apprend tout simplement les pointeurs à partir de zéro, cette dernière explication peut être tout aussi facile à comprendre, et cela leur réservera moins de surprises plus tard.

0
jalf

Ceci est un assez bon lien ici à propos de l'arithmétique du pointeur

Par exemple:

Pointeur et tableau

Formule pour calculer l'adresse de ptr + i où ptr a le type T *. alors la formule de l'adresse est:

addr (ptr + i) = addr (ptr) + [taille de (T) * i]

Pour le type d'int sur une plate-forme 32 bits, addr (ptr + i) = addr (ptr) + 4 * i;

Soustraction

Nous pouvons également calculer ptr - i. Par exemple, supposons que nous ayons un tableau int appelé arr. int arr [10]; int * p1, * p2;

p1 = arr + 3 ; // p1 == & arr[ 3 ] 
p2 = p1 - 2 ; // p1 == & arr[ 1 ] 
0
Gob00st