Je m'enseigne moi-même un peu de C et j'ai fait un exercice que je veux m'assurer de bien comprendre. L'exercice me demande de passer un pointeur vers un tableau multidimensionnel d'entiers à une fonction et d'itérer dans le tableau. J'ai donc commencé avec une fonction d'impression avant de passer à une entrée de prise pour peupler le tableau. J'ai essayé toutes sortes de choses mais après avoir trouvé un peu de code sur le casting de type et les pointeurs d'itération, j'ai le programme ci-dessous qui semble fonctionner mais je ne comprends pas très bien ce qui se passe. Dans mes commentaires, j'ai des questions étiquetées 1 à 3, faisant référence aux questions sous le code. J'espérais que quelqu'un de plus intelligent que moi pourrait m'éclairer.
//passing multi-Array to a simple print function
#include <stdio.h>
//simple print function prototype
void printMyArr(int (*pt)[4]);
int main()
{
//declare and initialize a multi-array. I picked 4x4 and some arbitrary
//numbers for simplicity's sake
int myArr[4][4] = { {12, 16, 19, 20},
{5, 99, 102, 200},
{12, 20, 25, 600},
{65, 66, 999, 1000} };
//declare a pointer to an array of integers and an int for a loop counter
int (*pt)[4], counter;
//initialize the pointer
pt = myArr;
//for loop calling printMyArr function to iterate through arrays -- or this is what I understand it to mean
for(counter=0; counter<4; counter++)
printMyArr(pt++); //<-------------Question 1
return 0;
}
//function called to print array elements to the console
void printMyArr(int(*pt)[4])
{
//declare a counter....and apparently another pointer
int counter, *p;
//initialize new pointer to old pointer and type cast array as int
p = (int *)pt; //<-------------Question 2
//for loop to iterate through elements of array -- or this is what I understand it to mean
for(counter=0; counter<4; counter++)
printf("\n\n\n%d", *p++); //<------Question 3
}
Question 1: Ici, j'ai passé le pointeur vers la fonction et je n'arrête pas de penser, "Qu'est-ce que cette boucle itère?". Ai-je raison de penser que j'incrémente le pointeur vers chacun des premiers éléments de chaque tableau ( myArr [0] [0] , myArr [1] [0] , myArr [2] [0] , myArr [3] [0] )? Aussi, ai-je raison de supposer que la syntaxe de cette ligne dit essentiellement: "Exécuter la fonction en passant le pointeur actuel et ALORS quand c'est fait, incrémenter le pointeur."?
Question 2: C'est ce qui m'a le plus confus. Après un peu de fouille, j'ai trouvé ce morceau pour le faire fonctionner correctement et je me rends compte que c'est ainsi que cela fonctionne, mais pourquoi?
Question 3: Ai-je raison de penser que j'incrémente chaque élément ici?
Donc
1: passez le pointeur comme assigné -> myArr [0] [0] puis imprimez les valeurs dans myArr [0] [ 0] , myArr [0] [1] , myArr [0] [2] et myArr [0] [3] code>, puis incrémenter le pointeur myArr[1[1 .[0. )
2: passer le pointeur comme assigné -> myArr [1] [0] puis imprimer les valeurs dans myArr [1] [0] , myArr [1] [1] , myArr [1] [2] et myArr [1] [3] incrémenter le pointeur vers myArr [2] [0]
3: passer le pointeur comme assigné -> myArr [ 2] [0] puis imprimez les valeurs dans myArr [2] [0] , myArr [2] [1] , myArr [2 ] [2] et myArr [2] [3] incrémentent le pointeur vers myArr [3] [0]
4: pass pointeur tel qu'attribué -> myArr [3] [0] puis imprimez les valeurs dans myArr [3] [0] , myArr [3] [1] , myArr [3] [2] et myArr [3] [3] incrémentent le pointeur vers myArr [4] [0] et si tel est le cas ce que je s le pointeur pointant vers puisqu'il ne devrait pas y avoir de myArr [4] [0] ?
3 Réponses :
Question 1: Ici, j'ai passé le pointeur vers la fonction et je n'arrête pas de penser: "Sur quoi cette boucle est-elle itérée?". Ai-je raison de penser que j'incrémente le pointeur vers chacun des premiers éléments de chaque tableau (myArr [0] [0], myArr [1] [0], myArr [2] [0], myArr [3] [ 0])?
plus ou moins.
ptcommence à l'adresse demyArr. Vous savez qu'il y a 4 choses dans le tableau donc vous bouclez 4 fois, en incrémentantptaprès y avoir accédé (lire ci-dessous) à chaque fois pour passer àprintMyArrchacun des 4 éléments du "haut niveau", tableau "externe". Ensuite,printMyArrparcourt les 4 éléments du tableau interne pour afficher chaque nombre.Aussi, ai-je raison de supposer que la syntaxe de cette ligne dit essentiellement: "Exécute la fonction en passant le pointeur actuel et PUIS quand c'est fait, incrémente le pointeur."?
Fonctionnellement, oui. Techniquement, l'ordre des opérations ressemble à ceci:
1) obtenir la valeur de
pt2) incrémenterpt3) fonction d'appel, en passant la valeur précédente deptde l'étape 1
ptest incrémenté dans le cadre dupt ++call, maispt ++évalue à l'ancienne valeur. En tant qu'argument d'une fonction,pt ++doit être évalué avant l'exécution de la fonction à laquelle il est passé. Mais le timing est le même qu'il évalue directement après l'exécution de la fonction pour votre cas.Prenons ensemble les questions 2 et 3 car elles font toutes deux partie de la réponse.
Question 2: C'est ce qui me trouble le plus. Après pas mal de fouilles, j'ai trouvé ce petit bout pour le faire fonctionner correctement et je me rends compte que c'est comme ça que ça marche, mais pourquoi?
Question 3: Ai-je raison de penser que j'incrémente chaque élément ici?
int* p=*pt;
pstocke une adresse d'un entier. Vous le définissez sur l'adresse du premier élément du tableau interne (qui est l'adresse du tableau interne lui-même, pour ainsi dire). Ensuite, sachant que vous avez 4 éléments dans le tableau, vous pouvez faire une boucle 4 fois, en faisant une opération de post-incrémentation du pointeur similaire surpque vous avez faite avecptsur le tableau externe.La première itération,
pest la même quept, l'adresse de la première des 4 valeurs entières en mémoire.* p, en déréférençant le pointeur, obtient le premier entier.* p ++, en raison de l'ordre des opérations (++ étant de la priorité la plus élevée et le déréférencement * étant inférieur), renvoie l'entier àp, laissantp pointant vers l'adresse entière suivante pour la boucle suivante.En général, transtypage des valeurs dans C doit être évité autant que possible . Il vous suffit de faire un déréférencement ici pour pointer
pvers l'ensemble des entiers.ptcontient l'adresse de l'une des 4 adresses (tableau externe) d'ensembles contigus de 4 entiers (tableau interne) en mémoire. La valeur à* ptest l'adresse de l'ensemble "courant" de 4 entiers contigus en mémoire. Vous pouvez donc simplement dire,p = (int *)pt; for(counter=0; counter<4; counter++) printf("\n\n\n%d", *p++);Ce qui ne lance pas arbitrairement un type et est très simple et clair. (Merci @WhozCraig)
... et sur la dernière itération? Le compilateur sait ne pas incrémenter le pointeur pour pointer sur le myArr [4] [0] inexistant ou est-ce inclus dans le Il me semble que, étant donné l'ordre des opérations, le pointeur est incrémenté avant que la boucle ne puisse tester le compteur et l'évaluer à <4
Question n ° 2: La syntaxe appropriée sans le hard cast (évité par les programmeurs chevronnés, presque toujours un signe de malentendu ou de masquage des problèmes de type par les débutants), est d'utiliser int * p = * pt ;
C'est une excellente question. En fait, pt et p sont post-incrémentés à la première adresse "inexistante" lors de la dernière itération de la boucle, mais ils pas déréférencé à nouveau car la condition de la boucle for est violée à la 5ème passe, quand un p ou pt hors limites serait déréférencé. Ainsi, bien que la dernière valeur de pt et p soit invalide, cette valeur n'est jamais réellement accédée.
@danFarrell eh bien ... j'ai beaucoup lu sur le fait de ne rien montrer et comment cela peut vraiment gâcher votre journée, alors j'ai été ultra sensible ... mais merci! J'apprécie vos réponses!
@whozcraig J'ai besoin de digérer et de rechercher ce que vous avez dit. Je ne suis pas sûr de comprendre mais je vais essayer. J'apprécie la contribution!
@Dan - 2 versions de votre prnMyArr qui peuvent offrir un peu plus d'exercice de la problème.
@whozcraig et @danFarrell, donc je pense que je comprends. pas besoin de taper cast. Que faire si je ne me suis pas soucié de la déclaration du pointeur et que je viens de déréférencer dans l'instruction 'printf () ? C'est là que j'avais des problèmes avant et cela me donnait les adresses au lieu des valeurs alors peut-être que je ne l'avais pas fait tout à fait correctement ...
@Dan D'abord, des pointeurs en C pointent vers des trucs (supportez-moi). Vous déréférencer un pointeur pour obtenir ce vers quoi il pointe. Le type sous-jacent de l'objet pointé dicte beaucoup de choses, parmi lesquelles le résultat de l ' arithmétique du pointeur . Si vous avez du int * p , alors * p vous obtient le int pointé vers, et ++ p code> (ou p ++ post-facto) fait avancer le pointeur vers le int suivant. Cela dit, votre argument de fonction est un pointeur vers un tableau [4] de int . C'est important, car cela signifie que ++ p avancera au prochain int [4] (pas ce que vous voulez faire). C'est probablement là que les roues se sont décollées.
Droite @WhozCraig. Je pense que j'ai le truc de la déréférence ... J'ai juste du mal à garder ça droit! Lol
Un tableau multy D est conservé en mémoire comme un tableau continu donc si vous avez un tableau [2] [2], il allouera 4 sizeof (int) continus. La raison pour laquelle vous devez définir array [] [COL] est d'indiquer au compilateur comment l'arithmétique du pointeur doit être effectuée.
donc- pour Q1- vous avez raison, chaque fois que vous incrémentez vous le faites pour (sizeof (int) * COL). donc à chaque fois que vous vous déplacez d'une ligne.
Q2- maintenant, pour chaque ligne, vous voulez imprimer une valeur int à la fois, donc lorsque vous avez int * p- cela signifie que chaque incrément est effectué par sizeof (int).
Q3 - l'incrément est pour le pointeur - pas la valeur - c'est facile à voir par vous-même si vous faites une autre impression dans votre main.
J'espère que cette aide, bonne chance!
La vue en mémoire du tableau 2D myArr ressemblerait à ceci:
p = *pt;
Question 1: Ici, j'ai passé le pointeur vers la fonction et je n'arrête pas de penser, "Qu'est-ce que cette boucle itère?". Ai-je raison de penser que j'incrémente le pointeur vers chacun des premiers éléments de chaque tableau (myArr [0] [0], myArr [1] [0], myArr [2] [0], myArr [3] [ 0])? Aussi, ai-je raison de supposer que la syntaxe de cette ligne dit essentiellement: "Exécutez la fonction en passant le pointeur actuel et PUIS quand c'est fait, incrémentez le pointeur."?
p = &(*pt)[0];
pt est un pointeur vers un tableau d'entiers 4 . Cette instruction
&(*pt)[0] --> &(*((*pt)+(0))) --> ((*p)+(0)) --> *p
fait que le pt pointe vers le premier 1D tableau de myArr Tableau 2D . Il est similaire à:
p = &(*pt)[0];
L'impact des deux déclarations sera comme ceci:
p = (int *)pt; // pt is pointer to an array of `4` integer
Lorsque vous incrémentez un pointeur, il est incrémenté par étapes de la taille d'objet vers laquelle le pointeur peut pointer . Ici
for(counter=0; counter<4; counter++)
printMyArr(&myArr[counter]);
^^^^^^^^^^^^^^^
d'abord la valeur du pointeur pt passée à printMyArr () puis pt sera incrémenté et puisque son type est int (*) [4] cela signifie qu'il peut pointer vers un objet qui est un tableau d'entiers 4 , donc après incrémentation le pt pointera vers l'adresse (qui est adresse pointant vers + taille du tableau de 4 int ) c'est-à-dire vers myArr [1] adresse.
Notez que ceci:
for(counter=0; counter<4; counter++)
printMyArr(pt++);
est identique à ceci:
printMyArr(pt++);
Question 2: C'est ce qui m'a le plus confus. Après pas mal de fouilles, j'ai trouvé ce petit bout pour le faire fonctionner correctement et je me rends compte que c'est comme ça que ça marche, mais pourquoi?
Ici
pt--------|
\|/
---------------------
myArr[0] | 12 | 16 | 19 | 20 |
---------------------
si vous supprimez le (int *) cast, vous constaterez que le compilateur donne un message d'avertissement sur cette instruction. La raison en est que p et pt ne sont pas les types de pointeurs compatibles. Le type de p est int * tandis que le type de pt est int (*) [4] . Cette attribution de pointeur incompatible fonctionne car l'adresse d'un tableau et l'adresse du premier élément d'un tableau sont numériquement identiques bien que leur type soit différent . Au lieu de cela, vous devriez faire
pt = &myArr[0];
À partir des standards C # 6.5.2.1
La définition de l'opérateur indice [] est que E1 [E2] est identique à (* ((E1) + (E2)))
par cette règle
pt = myArr;
L'opérateur & est utilisé pour obtenir l'adresse et l'opérateur * est utilisé pour le déréférencement. Ces opérateurs s'annulent l'un après l'autre lorsqu'ils sont utilisés l'un après l'autre.
Par conséquent, cette instruction
int (*pt)[4]
est la même comme cette déclaration
myArr
---------------------
myArr[0] | 12 | 16 | 19 | 20 |
---------------------
myArr[1] | 5 | 99 |102 |200 |
---------------------
myArr[2] | 12 | 20 | 25 |600 |
---------------------
myArr[3] | 65 | 66 |999 |1000|
---------------------
Question 3: Ai-je raison de penser que j'incrémente chaque élément ici?
Oui.
p est un pointeur pointant vers le premier élément du tableau de 4 entier et son type est int * c'est-à-dire qu'il peut pointer vers un objet qui est un int . Lorsque vous faites * p ++ , cela va d'abord déréférencer p et obtenir la valeur à l'adresse vers laquelle il pointe puis le p sera incrémenté et pointant vers l'élément suivant du tableau.
C'est génial de voir une question aussi bien réfléchie et bien écrite. Bien fait.
Comprenez, lors de l'accès
int myArr [3] [5]le premier niveau d'indirection (par exemple le premier[..]) est converti en un pointeur vers le premier élément de le tableau. Avec un tableau 2D, le premier élément est un pointeur vers la première ligne (tableau). Il est donc converti(* myArr) [5]qui est un pointeur vers un tableau deint [5]. Vous ne pouvez pas simplement utiliser* myArr [5]car[]lie plus étroitement que'*'du point de vue de la priorité, ce qui entraînerait un tableau de pointeurs versint(5 pointeurs). Avec(* myArr) [5]le pointeur avance de 20 octets (5-int) par incrément, avec* myArr [5]le pointeur avance desizeof a_pointer.La référence dans la norme est C11 Standard - 6.3.2.1 Autres opérandes - Lvalues, tableaux et désignateurs de fonction (p3) (notez les exceptions quand il n'est PAS converti en pointeur)