web-dev-qa-db-fra.com

Comment inclure un tableau dynamique à l'intérieur d'une structure en C?

J'ai regardé autour de moi mais je n'ai pas pu trouver de solution à ce qui doit être une question bien posée. Voici le code que j'ai:

 #include <stdlib.h>

struct my_struct {
    int n;
    char s[]
};

int main()
{
    struct my_struct ms;
    ms.s = malloc(sizeof(char*)*50);
}

et voici l'erreur que gcc me donne: erreur: utilisation invalide d'un membre de tableau flexible

Je peux le faire compiler si je déclare que la déclaration de s à l'intérieur de la structure est

char* s

et c'est probablement une implémentation supérieure (l'arithmétique des pointeurs est plus rapide que les tableaux, oui?) mais je pensais dans c une déclaration de

char s[]

est le même que

char* s
42
Tom

La façon dont vous l'avez écrit maintenant, s'appelait le "struct hack", jusqu'à ce que C99 le bénisse en tant que "membre de tableau flexible". La raison pour laquelle vous obtenez une erreur (probablement de toute façon) est qu'elle doit être suivie d'un point-virgule:

#include <stdlib.h>

struct my_struct {
    int n;
    char s[];
};

Lorsque vous allouez de l'espace pour cela, vous souhaitez allouer la taille de la structure plus la quantité d'espace que vous voulez pour le tableau:

struct my_struct *s = malloc(sizeof(struct my_struct) + 50);

Dans ce cas, le membre du tableau flexible est un tableau de char et sizeof (char) == 1, vous n'avez donc pas besoin de multiplier par sa taille, mais comme tout autre malloc dont vous auriez besoin s'il s'agissait d'un tableau d'un autre type:

struct dyn_array { 
    int size;
    int data[];
};

struct dyn_array* my_array = malloc(sizeof(struct dyn_array) + 100 * sizeof(int));

Modifier: cela donne un résultat différent de la modification du membre en pointeur. Dans ce cas, vous avez (normalement) besoin de deux allocations distinctes, une pour la structure elle-même et une pour les données "supplémentaires" à pointer vers le pointeur. En utilisant un membre de tableau flexible, vous pouvez allouer toutes les données dans un seul bloc.

67
Jerry Coffin

Vous devez d'abord décider ce que vous essayez de faire.


Si vous voulez avoir une structure avec un pointeur vers un tableau [indépendant] à l'intérieur, vous devez le déclarer comme

struct my_struct { 
  int n; 
  char *s;
}; 

Dans ce cas, vous pouvez créer l'objet struct réel comme bon vous semble (comme une variable automatique, par exemple)

struct my_struct ms;

puis allouer la mémoire pour le tableau indépendamment

ms.s = malloc(50 * sizeof *ms.s);  

En fait, il n'est généralement pas nécessaire d'allouer dynamiquement la mémoire du tableau

struct my_struct ms;
char s[50];

ms.s = s;

Tout dépend du type de vie dont vous avez besoin pour ces objets. Si votre structure est automatique, dans la plupart des cas, le tableau sera également automatique. Si l'objet struct possède la mémoire du tableau, il est tout simplement inutile de faire autrement. Si la structure elle-même est dynamique, le tableau doit également normalement être dynamique.

Notez que dans ce cas, vous avez deux blocs de mémoire indépendants: la structure et le tableau.


Une approche complètement différente consisterait à utiliser l'idiome "struct hack". Dans ce cas, le tableau devient partie intégrante de la structure. Les deux résident dans un seul bloc de mémoire. En C99, la structure serait déclarée comme

struct my_struct { 
  int n; 
  char s[];
}; 

et pour créer un objet, vous devez allouer le tout dynamiquement

struct my_struct *ms = malloc(sizeof *ms + 50 * sizeof *ms->s);

Dans ce cas, la taille du bloc de mémoire est calculée pour tenir compte des membres de structure et du tableau de fin de taille d'exécution.

Notez que dans ce cas, vous n'avez pas la possibilité de créer des objets struct tels que des objets statiques ou automatiques. Les structures avec des membres de tableau flexibles à la fin ne peuvent être allouées dynamiquement qu'en C.


Votre hypothèse selon laquelle les pointeurs aritmetics sont plus rapides que les tableaux est absolument incorrecte. Les tableaux fonctionnent par définition avec l'arithmétique des pointeurs, ils sont donc fondamentalement les mêmes. De plus, un véritable tableau (non décomposé en pointeur) est généralement un peu plus rapide qu'un objet pointeur. La valeur du pointeur doit être lue dans la mémoire, tandis que l'emplacement du tableau en mémoire est "connu" (ou "calculé") à partir de l'objet du tableau lui-même.

19
AnT

L'utilisation d'un tableau de taille non spécifiée n'est autorisée qu'à la fin d'une structure et ne fonctionne que dans certains compilateurs. Il s'agit d'une extension de compilateur non standard. (Bien que je pense que je me souviens que C++ 0x permettra cela.)

Le tableau ne sera cependant pas une allocation distincte de la structure. Vous devez donc allouer tout my_struct, pas seulement la partie du tableau.

Ce que je fais, c'est simplement de donner au tableau une taille petite mais non nulle. Habituellement, 4 pour les tableaux de caractères et 2 pour wchar_t tableaux pour conserver l'alignement 32 bits.

Ensuite, vous pouvez prendre en compte la taille déclarée du tableau lorsque vous effectuez l'allocation. Souvent, je ne pense pas que la pente soit plus petite que la granularité dans laquelle le gestionnaire de tas fonctionne dans tous les cas.

De plus, je pense que vous ne devriez pas utiliser sizeof (char *) dans votre allocation.

Voilà ce que je ferais.

struct my_struct {
    int nAllocated;
    char s[4]; // waste 32 bits to guarantee alignment and room for a null-terminator
};

int main()
{
    struct my_struct * pms;
    int cb = sizeof(*pms) + sizeof(pms->s[0])*50;
    pms = (struct my_struct*) malloc(cb);
    pms->nAllocated = (cb - sizoef(*pms) + sizeof(pms->s)) / sizeof(pms->s[0]);
}
1
John Knoeller

Je soupçonne que le compilateur ne sait pas combien d'espace il devra allouer pour s [], si vous choisissez de déclarer une variable automatique avec lui.

Je suis d'accord avec ce que Ben a dit, déclarez votre struct

struct my_struct {
    int n;
    char s[1];
};

De plus, pour clarifier son commentaire sur le stockage, déclarer char *s Ne mettra pas la structure sur la pile (car elle est allouée dynamiquement) et allouera s dans le tas, ce qu'elle fera sera d'interpréter les premiers sizeof(char *) octets de votre tableau en tant que pointeur, donc vous n'utiliserez pas les données que vous pensez être, et cela sera probablement fatal.

Il est essentiel de se rappeler que bien que les opérations sur les pointeurs et les tableaux puissent être implémentées de la même manière, elles ne sont pas la même chose.

0
Duncan

Les tableaux se résoudront en pointeurs, et ici vous devez définir s comme char *s. La structure est fondamentalement un conteneur, et doit (IIRC) être de taille fixe, il n'est donc pas possible d'avoir un tableau de taille dynamique à l'intérieur. Étant donné que vous malloc utilisez de toute façon la mémoire, cela ne devrait pas faire de différence dans ce que vous recherchez.

Fondamentalement, vous dites que s indiquera un emplacement de mémoire. Notez que vous pouvez toujours y accéder plus tard en utilisant une notation comme s[0].

0
Mark Elliot

l'arithmétique des pointeurs est plus rapide que les tableaux, oui?

Pas du tout - ils sont en fait les mêmes. les tableaux se traduisent en arithmétiques de pointeurs au moment de la compilation.

char test[100];
test[40] = 12;

// translates to: (test now indicates the starting address of the array)
*(test+40) = 12;
0
Alexander Gessler