web-dev-qa-db-fra.com

Double pointeur vs tableau de pointeurs (** tableau vs * tableau [])

Je ne sais pas vraiment quelle est la différence entre ces 2. Mon professeur a écrit que ** array est identique à * array [] et on nous a présenté un exemple où il a utilisé ** array (donc après les cours, j'ai essayé d'échanger cela avec * array [ ] et cela n'a pas fonctionné), est-ce que quelqu'un pourrait me dire si ces 2 sont en fait les mêmes que ceux qu'il a écrits ?? Quoi qu'il en soit, la classe portait sur l'allocation dynamique de la mémoire

@ Dès que j'ai changé le double pointeur, cette ligne a commencé à générer une erreur

    lines = malloc(sizeof(char*));

et quelques autres où la mémoire est réaffectée

@ 2 Hell ouais, voici tout le code

Et pour ces commentaires ci-dessous, non, il n'y a rien à l'intérieur [] parce que sa déclaration était

    **array = *array[]

GRANDE MISE À JOUR

Je suis vraiment désolé pour tout inconvénient, j'étais tout simplement trop fatigué au moment de la rédaction de cet article, voici le code complet sans modifications

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>

    char **lines;     // global text buffer, organized as an array of lines

    // --------------------------------------------------------------------------------
    // initialize global buffer
    void initialize()
    {
      lines = malloc(sizeof(char*));
      lines[0] = NULL;
    }

    // --------------------------------------------------------------------------------
    // return number of lines in buffer
    int countLines()
    {
      int count = 0;
      while(lines[count++]) ;
      return count-1;
    }

    // --------------------------------------------------------------------------------
    // print one line
    void printLine(int line)
    {
      printf("Line %d: %p %p %s\n",line, &lines[line], lines[line], lines[line]);
    }

    // --------------------------------------------------------------------------------
    // print all lines
    void printAll()
    {
      int num_lines = countLines();
      int line = 0;
      printf("----- %d line(s) ----\n",num_lines);
      while (line < num_lines)
        printLine(line++);
      printf("---------------------\n");
    }

    // --------------------------------------------------------------------------------
    // free whole buffer
    void freeAll()
    {
      int line = countLines();
      while (line >= 0)
        free(lines[line--]);
      free(lines);
    }

    // --------------------------------------------------------------------------------
    // insert a line before the line specified
    void insertLine(int line, char *str)
    {
      int num_lines = countLines();

      // increase lines size by one line pointer:
        lines = realloc(lines, (num_lines+2) * sizeof(char*));

      // move line pointers backwards:
      memmove(&lines[line+1], &lines[line], (num_lines-line+1)*sizeof(char*));

      // insert the new line:
      lines[line] = malloc(strlen(str)+1);
      strcpy(lines[line],str);
    }

    // --------------------------------------------------------------------------------
    // remove the specified line
    void removeLine(int line)
    {
      int num_lines = countLines();

      // free the memory used by this line:
      free(lines[line]);

      // move line pointers forward:
      memmove(&lines[line], &lines[line+1], (num_lines-line+1)*sizeof(char*));

      // decrease lines size by one line pointer:
        lines = realloc(lines, num_lines * sizeof(char*));
    }

    // --------------------------------------------------------------------------------
    // insert a string into specified line at specified column
    void insertString(int line, int col, char *str)
    {
      // make room for the new string:
      lines[line] = realloc(lines[line], strlen(lines[line])+strlen(str)+1);

      // move characters after col to the end:
      memmove(lines[line]+col+strlen(str), lines[line]+col, strlen(lines[line])-col);

      // insert string (without terminating 0-byte):
      memmove(lines[line]+col, str, strlen(str));
    }

    // --------------------------------------------------------------------------------
    // MAIN program
    int main()
    {
      initialize();

      printAll();
      insertLine(0,"Das ist");
      printAll();
      insertLine(1,"Text");
      printAll();
      insertLine(1,"ein");
      printAll();
      insertLine(2,"kurzer");
      printAll();
      printf("lines[2][4] = %c\n",lines[2][4]);
      insertString(2,0,"ziemlich ");
      printAll();
      removeLine(2);
      printAll();

      freeAll();
      return 0;
    }
12
Maxitj

Si le code auquel vous faites référence dans votre question vous a été donné par votre professeur comme exemple d'utilisation de tableaux de pointeurs de pointeurs vers des pointeurs, je ne sais pas à quel point cette classe fera vraiment du bien. Je soupçonne que cela a été fourni comme un exercice de débogage ou que cela pourrait être votre tentative de solution. Quoi qu'il en soit, si vous compilez simplement avec Avertissements activé, vous trouverez un certain nombre de problèmes qui nécessitent une attention avant de passer au débogage de votre code.

Concernant le code que vous référencez, alors que vous êtes libre d'utiliser un tampon de texte global, vous êtes bien mieux servi en n'utilisant pas de tampon global et en passant un pointeur vers vos données comme requis. Il existe certaines instances, diverses fonctions de rappel, etc. qui nécessitent des données globales, mais en règle générale, ce sont l'exception et non la règle.

Votre question se résume essentiellement à "Comment utiliser correctement un tableau de pointeurs et de pointeurs doubles (pointeur-à-pointeur-à-type). Il n'y a aucun moyen que le sujet puisse être complètement couvert dans une réponse car il y a beaucoup trop de situations et de contextes où l'un ou l'autre peut être (ou devrait être) utilisé et pourquoi. Cependant, quelques exemples vous aideront, espérons-le, à comprendre les différences fondamentales.

En commençant par tableau de pointeurs à taper (par exemple char *array[]). Il est généralement considéré sous cette forme comme un argument de fonction. Lorsqu'elle est déclarée comme variable, elle est suivie d'une initialisation. par exemple.:

char *array[] = { "The quick",
                  "brown fox",
                  "jumps over",
                  "the lazy dog." };

char *array[]; En tant que déclaration de variable n'est pas valide en raison de la taille de tableau manquante entre [..]. Lorsqu'il est utilisé globalement, comme dans votre exemple, le compilateur accepte la déclaration, mais warn la déclaration est supposée avoir n élément.

Les éléments de array déclarés ci-dessus sont des pointeurs vers le type char. Plus précisément, les éléments sont des pointeurs vers les string-literals créés par la déclaration. Chacune des chaînes est accessible par le pointeur associé dans array sous la forme array[0], ... array[3].

Un pointeur vers pointeur pour taper (double-pointeur), c'est exactement ce que son nom l'indique. C'est un pointeur, qui contient n pointeur comme valeur. En termes de base, c'est un pointeur qui pointe vers un autre pointeur. Il peut être utilisé pour accéder aux membres du tableau ci-dessus en attribuant l'adresse de array comme:

char **p = array;

p[1] Ou *(p + 1) pointe vers "brown fox", Etc.

Alternativement, un certain nombre de pointeurs à pointeurs à taper peuvent être alloués dynamiquement et utilisés pour créer un tableau de pointeurs à taper, qui peuvent ensuite être alloués et réaffectés pour gérer l'accès ou le stockage d'un nombre inconnu d'éléments. Par exemple, un bref exemple pour lire un nombre inconnu de lignes de stdin, vous pouvez voir:

#define MAXL 128
#define MAXC 512
...
char **lines = NULL;
char buf[MAXC] = {0};
lines = malloc (MAXL * sizeof *lines);
size_t index = 0;
...
while (fgets (buf, MAXC, stdin)) {
    lines[index++] = strdup (buf);
    if (index == MAXL)
        /* reallocate lines */
}

Ci-dessus, vous avez lines, un pointeur vers pointeur vers caractère, initialement NULL, qui est utilisé pour allouer MAXL (128) pointeurs vers caractère. Les lignes sont ensuite lues de stdin dans buf, après chaque lecture réussie, la mémoire est allouée pour contenir le contenu de buf et l'adresse de début résultante pour chaque bloc de mémoire est attribuée à chaque pointeur line[index]index est 0-127, et après incrément de index à 128, index est réalloué pour fournir des pointeurs supplémentaires et la lecture continue.

Ce qui rend le sujet plus grand que ce qui peut être traité dans n'importe quelle réponse, c'est qu'un tableau de pointeurs ou pointeur vers pointeur vers type peut être vers n'importe quel type. (int, struct, ou en tant que membre d'une structure de type différent, ou function, etc ...) Ils peuvent être utilisés listes liées, dans le retour des listes de répertoires (par exemple opendir), ou de plusieurs façons supplémentaires. Ils peuvent être initialisés statiquement, alloués dynamiquement, passés en tant que paramètres de fonction, etc. Il y a juste trop de contextes différents pour les couvrir tous. Mais dans tous les cas, ils suivront les règles générales vues ici et dans l'autre réponse ici et dans 1000 réponses de plus ici sur StackOverflow.

Je terminerai par un court exemple que vous pouvez utiliser pour examiner les différentes utilisations de base du tableau et du double pointeur. J'ai fourni des commentaires supplémentaires dans la source. Cela fournit juste une poignée d'utilisations de base différentes et de déclaration statique et d'allocation dynamique:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main (void) {

    /* array is a static array of 4 pointers to char, initialized to the 
       4 string-literals that a part of the declaration */
    char *array[] = { "The quick",
                    "brown fox",
                    "jumps over",
                    "the lazy dog." };
    /* p is a pointer-to-pointer-to-char assigned the address of array */
    char **p = array;
    /* lines is a pointer-to-pointer-to-char initialized to NULL, used
       below to allocate 8 pointers and storage to hold 2 copes of array */
    char **lines = NULL;
    size_t narray = sizeof array/sizeof *array;
    size_t i;

    printf ("\nprinting each string-literal at the address stored by\n"
            "each pointer in the array of ponters named 'array':\n\n");
    for (i = 0; i < narray; i++)
        printf (" %s\n", array[i]);

    printf ("\nprinting each string using a pointer to pointer to char 'p':\n\n");
    for (i = 0; i < narray; i++, p++)
        printf (" %s\n", *p);

    p = array;
    printf ("\nprinting each line using a pointer to pointer"
            " to char 'p' with array notation:\n\n");
    for (i = 0; i < narray; i++)
        printf (" %s\n", p[i]);

    /* allocate 8 pointers to char */
    lines = malloc (2 * narray * sizeof *lines);

    /* allocate memory and copy 1st 4-strings to lines (long way) */
    for (i = 0; i < narray; i++) {
        size_t len = strlen (array[i]);
        lines[i] = malloc (len * sizeof **lines + 1);
        strncpy (lines[i], array[i], len);
        lines[i][len] = 0;
    }

    /* allocate memory and copy 1st 4-strings to lines 
       (using strdup - short way) */
    // for (i = 0; i < narray; i++)
    //     lines[i] = strdup (array[i]);

    /* allocate memory and copy again as last 4-strings in lines */
    p = array;
    for (i = 0; i < narray; i++, p++)
        lines[i+4] = strdup (*p);

    p = lines; /* p now points to lines instead of array */
    printf ("\nprinting each allocated line in 'lines' using pointer 'p':\n\n");
    for (i = 0; i < 2 * narray; i++)
        printf (" %s\n", p[i]);

    /* free allocated memory */
    for (i = 0; i < 2 * narray; i++)
        free (lines[i]);
    free (lines);

    return 0;
}

Faites moi savoir si vous avez des questions. C'est un grand sujet avec un ensemble relativement petit de règles qui peuvent être appliquées de nombreuses manières différentes et dans différents contextes.

12
David C. Rankin

Mon professeur a écrit que **array est identique à *array[]

Cela est vrai dans certains contextes et pas vrai dans d'autres contextes.

Si utilisé dans une fonction comme argument,

void foo(int **array) {}

est le même que

void foo(int *array[]) {}

Lorsqu'il est déclaré en tant que variable,

int **array;

n'est pas la même chose que

int *array[];

Disclaimer: Il ne s'agit en aucun cas d'une liste exhaustive où ils sont identiques et où ils sont différents.

8
R Sahu