c - votre - template web django




Comment puis-je comprendre les déclarations de fonctions compliquées? (8)

x: la fonction retourne le pointeur vers le tableau [] du pointeur vers la fonction retournant char "- hein?

Vous avez une fonction

Cette fonction renvoie un pointeur.

Ce pointeur pointe vers un tableau.

Ce tableau est un tableau de pointeurs de fonctions (ou de pointeurs vers des fonctions)

Ces fonctions retournent char *.

 what's the use case for a pointer to a pointer?

La première consiste à faciliter le retour des valeurs via des arguments.

Disons que vous avez

int function(int *p)
  *p = 123;
   return 0; //success !
}

Vous l'appelez comme

int x;
function(&x);

Comme vous pouvez le voir, pour que la function puisse modifier notre x nous devons lui passer un pointeur sur notre x.

Et si x n'était pas un int, mais un char * ? Eh bien, c'est toujours la même chose, nous devons passer un pointeur à cela. Un pointeur sur un pointeur:

int function(char **p)
  *p = "Hello";
   return 0; //success !
}

Vous l'appelez comme

char *x;
function(&x); 

Comment puis-je comprendre les déclarations compliquées?

char (*(*f())[])();

char (*(*X[3])())[5];

void (*f)(int,void (*)()); 

char far *far *ptr;

typedef void (*pfun)(int,float);

int **(*f)(int**,int**(*)(int **,int **));

Cela ressemble à un travail pour l'outil cdecl:

cdecl> explain char (*(*f())[])();
declare f as function returning pointer to array of pointer to function returning char

J'ai cherché une page d'accueil officielle de l'outil, mais je n'ai pas pu en trouver une qui semblait authentique. Sous Linux, vous pouvez généralement vous attendre à ce que votre distribution inclue l'outil, alors je viens de l'installer pour générer l'exemple ci-dessus.


Il semble que votre question soit la suivante:

Quel est le cas d'utilisation d'un pointeur sur un pointeur?

Un pointeur sur un pointeur a tendance à apparaître lorsque vous avez un tableau de type T et que T est lui-même un pointeur sur autre chose. Par exemple,

  • Qu'est-ce qu'une chaîne en C? En règle générale, c'est un char * .
  • Souhaitez-vous un tableau de chaînes de temps en temps? Sûr.
  • Comment pourriez-vous en déclarer un? char *x[10] : x est un tableau de 10 pointeurs vers char , alias 10 chaînes.

À ce stade, vous pouvez vous demander où char ** entre en jeu. Il entre dans l'image à partir de la relation très étroite entre l'arithmétique des pointeurs et les tableaux en C. Un nom de tableau, x est (presque) toujours converti en un premier élément .

  • Quel est le premier élément? Un char * .
  • Qu'est-ce qu'un pointeur sur le premier élément? Un char ** .

En C, le tableau E1[E2] est défini comme étant équivalent à *(E1 + E2) . Habituellement, E1 est le nom du tableau, disons x , qui est automatiquement converti en un caractère char ** , et E2 est un index, disons 3. (Cette règle explique également pourquoi 3[x] et x[3] sont la même chose. )

Les pointeurs vers les pointeurs apparaissent également lorsque vous voulez un tableau de type T alloué dynamiquement, qui est lui-même un pointeur . Pour commencer, supposons que nous ne savons pas quel type T est.

  • Si nous voulons un vecteur de T alloué dynamiquement, de quel type avons-nous besoin? T *vec .
  • Pourquoi? Comme nous pouvons effectuer une arithmétique de pointeur en C, tout T * peut servir de base à une séquence contiguë de T en mémoire.
  • Comment allouer ce vecteur, disons de n éléments? vec = malloc(n * sizeof(T)) ;

Cette histoire est vraie pour absolument tout type T , et donc c'est vrai pour char * .

  • Quel est le type de vec si T est char * ? char **vec .

Les pointeurs vers les pointeurs apparaissent également lorsque vous avez une fonction devant modifier un argument de type T, lui-même un pointeur .

  • Regardez la déclaration de strtol : long strtol(char *s, char **endp, int b) .
  • Qu'est-ce que tout cela signifie? strtol convertit une chaîne de base b en un entier. Il veut vous dire jusqu'où il est arrivé dans la chaîne. Il pourrait peut-être retourner une structure contenant à la fois un long et un char * , mais ce n'est pas comme cela que la déclaration est faite.
  • Au lieu de cela, il retourne son deuxième résultat en transmettant l' adresse d'une chaîne qu'il modifie avant de retourner.
  • Qu'est-ce qu'une chaîne à nouveau? Oh oui, char * .
  • Alors, quelle est l'adresse d'une chaîne? char ** .

Si vous parcourez ce chemin suffisamment longtemps, vous pouvez également utiliser des types T *** , bien que vous puissiez presque toujours restructurer le code pour les éviter.

Enfin, des pointeurs vers des pointeurs apparaissent dans certaines implémentations délicates de listes liées . Considérons la déclaration standard d'une liste à double lien dans C.

struct node {
    struct node *next;
    struct node *prev;
    /* ... */
} *head;

Cela fonctionne bien, bien que je ne reproduise pas les fonctions d'insertion / suppression ici, mais cela pose un petit problème. Tout nœud peut être retiré de la liste (ou avoir un nouveau nœud inséré avant) sans référence à la tête de la liste. Eh bien, pas tout à fait un nœud. Ce n'est pas le cas du premier élément de la liste, où prev sera nul. Cela peut être modérément agaçant dans certains types de code C où vous travaillez plus avec les nœuds eux-mêmes qu'avec la liste en tant que concept. Ceci est une occurrence assez courante dans le code de systèmes de bas niveau.

Et si on réécrivait le node comme ceci:

struct node {
    struct node *next;
    struct node **prevp;
    /* ... */
} *head;

Dans chaque nœud, les points de prevp ne sont pas au nœud précédent, mais au pointeur next des nœuds précédents. Qu'en est-il du premier nœud? C'est des points de head à la head . Si vous dessinez une liste comme celle-ci (et vous devez la dessiner pour comprendre comment cela fonctionne), vous verrez que vous pouvez supprimer le premier élément ou insérer un nouveau nœud avant le premier élément sans référencer explicitement head by name.


La réponse de Remo.D aux fonctions de lecture est une bonne suggestion. Voici quelques réponses aux autres.

Un cas d'utilisation d'un pointeur sur un pointeur est lorsque vous souhaitez le transmettre à une fonction qui modifiera le pointeur. Par exemple:

void foo(char **str, int len)
{
   *str = malloc(len);
}

En outre, cela pourrait être un tableau de chaînes:

void bar(char **strarray, int num)
{
   int i;
   for (i = 0; i < num; i++)
     printf("%s\n", strarray[i]);
}

En règle générale, il ne faut pas utiliser des déclarations aussi compliquées, bien que vous ayez parfois besoin de types assez compliqués pour des choses comme les pointeurs de fonctions. Dans ces cas, il est beaucoup plus lisible d'utiliser des typedefs pour les types intermédiaires; par exemple:

typedef void foofun(char**, int);
foofun *foofunptr;

Ou, pour votre premier exemple de "fonction renvoyant le pointeur vers le tableau [] du pointeur vers la fonction retournant char", vous pouvez faire:

typedef char fun_returning_char();
typedef fun_returning_char *ptr_to_fun;
typedef ptr_to_fun array_of_ptrs_to_fun[];
typedef array_of_ptrs_to_fun *ptr_to_array;
ptr_to_array myfun() { /*...*/ }

En pratique, si vous écrivez quelque chose de sain, beaucoup de ces choses auront des noms significatifs; Par exemple, il peut s'agir de fonctions renvoyant des noms (de quelque sorte), de sorte que fun_returning_char pourrait être name_generator_type et array_of_ptrs_to_fun pourrait être name_generator_list . Vous pouvez donc le réduire de quelques lignes et définir uniquement ces deux types de caractères - qui seront probablement utiles ailleurs dans tous les cas.


Nous devons évaluer toutes les déclarations de déclaration de pointeur de gauche à droite, en commençant par l'endroit où le nom du pointeur ou le nom de la déclaration est déclaré dans l'instruction.

Lors de l'évaluation de la déclaration, nous devons partir de la parenthèse la plus intérieure.

Commencez avec le nom du pointeur ou le nom de la fonction, suivi des caractères les plus à droite du parent et suivi des caractères les plus à gauche.

Exemple:

char (*(*f())[])();
         ^

char (*(*f())[])();
         ^^^
In here f is a function name, so we have to start from that.

F()

char (*(*f())[])();
            ^
Here there are no declarations on the righthand side of the current
parenthesis, we do have to move to the lefthand side and take *:

char (*(*f())[])();
      ^
f() *

Nous avons complété les caractères de parenthèse intérieure, et maintenant nous devons revenir à un niveau:

char (*(*f())[])();

   ------

Maintenant, prenez [], car cela se trouve à droite de la parenthèse actuelle.

char (*(*f())[])();
             ^^

f() * []

Maintenant, prenez le * car il n'y a pas de personnage du côté droit.

char (*(*f())[])();
               ^

char (*(*f())[])();
      ^
f() * [] *

char (*(*f())[])();

Ensuite, évaluez la parenthèse externe ouverte et fermée, cela indique une fonction.

f() * [] * ()

char (*(*f())[])();

Maintenant, nous pouvons ajouter un type de données à la fin de l'instruction.

f() * [] * () char.

char (*(*f())[])();

Réponse finale:

    f() * [] * () char.

f est une fonction renvoyant le pointeur au tableau [] de pointeurs pour que la fonction retourne char.


Oubliez environ 1 et 2 - c'est juste théorique.

3: Ceci est utilisé dans la fonction d'entrée de programme int main(int argc, char** argv) . Vous pouvez accéder à une liste de chaînes en utilisant un caractère char** . argv [0] = première chaîne, argv [1] = seconde chaîne, ...


Vous devriez utiliser cdecl tool. Il devrait être disponible sur la plupart des distributions Linux.

Par exemple, pour cette fonction, vous serez renvoyé:

char (*(*f())[])(); - déclarer f comme fonction renvoyant le pointeur sur un tableau de pointeur pour que la fonction retourne char

void (*f)(int,void (*)()); - prototype du pointeur de fonction f. f est une fonction qui prend deux paramètres, le premier est int et le second est un pointeur de fonction pour une fonction qui renvoie void.

char far *far *ptr; - ptr est un pointeur loin vers un pointeur loin (qui pointe vers un caractère / octet).

char (*(*X[3])())[5]; - X est un tableau de 3 pointeurs pour accepter un nombre d'arguments indéterminé et renvoyer un pointeur sur un tableau de 5 caractères.

typedef void (*pfun)(int,float); - déclarer le pointeur de fonction pfun. pfun est un élément qui prend deux paramètres, le premier est int, le second est de type float. la fonction n'a pas de valeur de retour;

par exemple

void f1(int a, float b)
{ //do something with these numbers
};

Au fait, les déclarations compliquées comme les dernières ne sont pas souvent vues. Voici un exemple que je viens de inventer à cette fin.

int **(*f)(int**,int**(*)(int **,int **));

typedef int**(*fptr)(int **,int **);

int** f0(int **a0, int **a1)
{
    printf("Complicated declarations and meaningless example!\n");
    return a0;
}

int ** f1(int ** a2, fptr afptr)
{
    return afptr(a2, 0);
}

int main()
{
    int a3 = 5;
    int * pa3 = &a3;
    f = f1;
    f(&pa3, f0);

    return 0;
}

char far *far *ptr;

Il s'agit d'un formulaire Microsoft obsolète, datant de MS-DOS et des tout premiers jours de Windows. La version SHORT indique qu'il s'agit d'un pointeur éloigné d'un pointeur distant sur un caractère, où un pointeur distant peut pointer n'importe où dans la mémoire, par opposition à un pointeur proche qui ne peut pointer que dans un segment de données de 64 Ko. Vous ne voulez vraiment pas connaître les détails sur les modèles de mémoire Microsoft pour travailler autour de l'architecture de mémoire segmentée Intel 80x86.

typedef void (*pfun)(int,float);

Cela déclare pfun en tant que typedef pour un pointeur sur une procédure qui prend un int et un float. Vous utiliseriez normalement cela dans une déclaration de fonction ou un prototype, à savoir.

float foo_meister(pfun rabbitfun)
{
  rabbitfun(69, 2.47);
}




declaration