C Language
Structs
Recherche…
Introduction
Les structures permettent de regrouper un ensemble de variables connexes de divers types en une seule unité de mémoire. La structure dans son ensemble peut être référencée par un seul nom ou un seul pointeur; les membres de la structure sont également accessibles individuellement. Les structures peuvent être transmises à des fonctions et renvoyées à partir de fonctions. Ils sont définis à l'aide du mot struct
clé struct
.
Structures de données simples
Les types de données de structure sont un moyen utile de regrouper les données associées et de les faire se comporter comme une seule variable.
Déclarer une struct
simple qui contient deux membres int
:
struct point
{
int x;
int y;
};
x
et y
sont appelés les membres (ou champs ) de la structure de point
.
Définir et utiliser des structures:
struct point p; // declare p as a point struct
p.x = 5; // assign p member variables
p.y = 3;
Les structures peuvent être initialisées à la définition. Ce qui précède est équivalent à:
struct point p = {5, 3};
Les structures peuvent également être initialisées en utilisant des initialiseurs désignés .
L'accès aux champs se fait également à l'aide de .
opérateur
printf("point is (x = %d, y = %d)", p.x, p.y);
Typedef Structs
La combinaison de typedef
avec struct
peut rendre le code plus clair. Par exemple:
typedef struct
{
int x, y;
} Point;
par opposition à:
struct Point
{
int x, y;
};
pourrait être déclaré comme:
Point point;
au lieu de:
struct Point point;
Encore mieux est d'utiliser les éléments suivants
typedef struct Point Point;
struct Point
{
int x, y;
};
pour bénéficier des deux définitions possibles du point
. Une telle déclaration est plus pratique si vous avez d'abord appris C ++, où vous pouvez omettre le mot struct
clé struct
si le nom n'est pas ambigu.
typedef
noms typedef
pour les structures peuvent être en conflit avec d'autres identificateurs d'autres parties du programme. Certains considèrent cela comme un inconvénient, mais pour la plupart des gens ayant une struct
et un autre identifiant, cela est très inquiétant. Notoire est par exemple POSIX ' stat
int stat(const char *pathname, struct stat *buf);
où vous voyez une fonction stat
qui a un argument qui est struct stat
.
Les structures typedef
sans nom de tag imposent toujours que toute la déclaration de struct
soit visible pour le code qui l'utilise. La déclaration de struct
entière doit alors être placée dans un fichier d'en-tête.
Considérer:
#include "bar.h"
struct foo
{
bar *aBar;
};
Ainsi, avec une struct
typedef
d sans nom de balise, le fichier bar.h
doit toujours inclure toute la définition de bar
. Si nous utilisons
typedef struct bar bar;
dans bar.h
, les détails de la structure de bar
peuvent être cachés.
Voir Typedef
Pointeurs vers les structures
Lorsque vous avez une variable contenant une struct
, vous pouvez accéder à ses champs à l'aide de l'opérateur point ( .
). Cependant, si vous avez un pointeur sur une struct
, cela ne fonctionnera pas. Vous devez utiliser l'opérateur de flèche ( ->
) pour accéder à ses champs. Voici un exemple d'une implémentation extrêmement simple (certains pourraient dire "terrible et simple") d'une pile qui utilise des pointeurs pour struct
et montre l'opérateur de la flèche.
#include <stdlib.h>
#include <stdio.h>
/* structs */
struct stack
{
struct node *top;
int size;
};
struct node
{
int data;
struct node *next;
};
/* function declarations */
int push(int, struct stack*);
int pop(struct stack*);
void destroy(struct stack*);
int main(void)
{
int result = EXIT_SUCCESS;
size_t i;
/* allocate memory for a struct stack and record its pointer */
struct stack *stack = malloc(sizeof *stack);
if (NULL == stack)
{
perror("malloc() failed");
return EXIT_FAILURE;
}
/* initialize stack */
stack->top = NULL;
stack->size = 0;
/* push 10 ints */
{
int data = 0;
for(i = 0; i < 10; i++)
{
printf("Pushing: %d\n", data);
if (-1 == push(data, stack))
{
perror("push() failed");
result = EXIT_FAILURE;
break;
}
++data;
}
}
if (EXIT_SUCCESS == result)
{
/* pop 5 ints */
for(i = 0; i < 5; i++)
{
printf("Popped: %i\n", pop(stack));
}
}
/* destroy stack */
destroy(stack);
return result;
}
/* Push a value onto the stack. */
/* Returns 0 on success and -1 on failure. */
int push(int data, struct stack *stack)
{
int result = 0;
/* allocate memory for new node */
struct node *new_node = malloc(sizeof *new_node);
if (NULL == new_node)
{
result = -1;
}
else
{
new_node->data = data;
new_node->next = stack->top;
stack->top = new_node;
stack->size++;
}
return result;
}
/* Pop a value off of the stack. */
/* Returns the value popped off the stack */
int pop(struct stack *stack)
{
struct node *top = stack->top;
int data = top->data;
stack->top = top->next;
stack->size--;
free(top);
return data;
}
/* destroy the stack */
void destroy(struct stack *stack)
{
/* free all pointers */
while(stack->top != NULL)
{
pop(stack);
}
}
Membres de tableau flexibles
Déclaration de type
Une structure avec au moins un membre peut en outre contenir un seul élément de tableau de longueur non spécifiée à la fin de la structure. Cela s'appelle un membre de tableau flexible:
struct ex1
{
size_t foo;
int flex[];
};
struct ex2_header
{
int foo;
char bar;
};
struct ex2
{
struct ex2_header hdr;
int flex[];
};
/* Merged ex2_header and ex2 structures. */
struct ex3
{
int foo;
char bar;
int flex[];
};
Effets sur la taille et le rembourrage
Un membre de groupe flexible est considéré comme n'ayant pas de taille lors du calcul de la taille d'une structure, même si le remplissage entre ce membre et le membre précédent de la structure peut toujours exister:
/* Prints "8,8" on my machine, so there is no padding. */
printf("%zu,%zu\n", sizeof(size_t), sizeof(struct ex1));
/* Also prints "8,8" on my machine, so there is no padding in the ex2 structure itself. */
printf("%zu,%zu\n", sizeof(struct ex2_header), sizeof(struct ex2));
/* Prints "5,8" on my machine, so there are 3 bytes of padding. */
printf("%zu,%zu\n", sizeof(int) + sizeof(char), sizeof(struct ex3));
Le membre de tableau flexible est considéré comme ayant un type de tableau incomplet, sa taille ne peut donc pas être calculée à l'aide de sizeof
.
Usage
Vous pouvez déclarer et initialiser un objet avec un type de structure contenant un membre de tableau flexible, mais vous ne devez pas tenter d'initialiser le membre du groupe flexible car il est traité comme s'il n'existait pas. Il est interdit d'essayer de faire cela, et des erreurs de compilation en résulteront.
De même, vous ne devez pas tenter d'attribuer une valeur à un élément d'un membre de groupe flexible lors de la déclaration d'une structure de cette manière, car il peut ne pas y avoir suffisamment de remplissage à la fin de la structure pour autoriser les objets requis. Le compilateur ne vous empêchera pas nécessairement de le faire, cependant, cela peut conduire à un comportement indéfini.
/* invalid: cannot initialize flexible array member */
struct ex1 e1 = {1, {2, 3}};
/* invalid: hdr={foo=1, bar=2} OK, but cannot initialize flexible array member */
struct ex2 e2 = {{1, 2}, {3}};
/* valid: initialize foo=1, bar=2 members */
struct ex3 e3 = {1, 2};
e1.flex[0] = 3; /* undefined behavior, in my case */
e3.flex[0] = 2; /* undefined behavior again */
e2.flex[0] = e3.flex[0]; /* undefined behavior */
Vous pouvez plutôt choisir d'utiliser malloc
, calloc
ou realloc
pour allouer la structure avec un espace de stockage supplémentaire et la libérer ultérieurement, ce qui vous permet d'utiliser le membre de tableau flexible comme vous le souhaitez:
/* valid: allocate an object of structure type `ex1` along with an array of 2 ints */
struct ex1 *pe1 = malloc(sizeof(*pe1) + 2 * sizeof(pe1->flex[0]));
/* valid: allocate an object of structure type ex2 along with an array of 4 ints */
struct ex2 *pe2 = malloc(sizeof(struct ex2) + sizeof(int[4]));
/* valid: allocate 5 structure type ex3 objects along with an array of 3 ints per object */
struct ex3 *pe3 = malloc(5 * (sizeof(*pe3) + sizeof(int[3])));
pe1->flex[0] = 3; /* valid */
pe3[0]->flex[0] = pe1->flex[0]; /* valid */
Le 'struct hack'
Les membres de tableau flexibles n'existaient pas avant C99 et sont traités comme des erreurs. Une solution commune consiste à déclarer un tableau de longueur 1, une technique appelée «struct hack»:
struct ex1
{
size_t foo;
int flex[1];
};
Cela affectera la taille de la structure, cependant, contrairement à un véritable membre de tableau flexible:
/* Prints "8,4,16" on my machine, signifying that there are 4 bytes of padding. */
printf("%d,%d,%d\n", (int)sizeof(size_t), (int)sizeof(int[1]), (int)sizeof(struct ex1));
Pour utiliser le membre flex
tant que membre de tableau flexible, vous devez l'allouer avec malloc
comme indiqué ci-dessus, sauf que sizeof(*pe1)
(ou la sizeof(struct ex1)
équivalente sizeof(struct ex1)
) sera remplacée par offsetof(struct ex1, flex)
ou la plus longue, expression de type agnostique sizeof(*pe1)-sizeof(pe1->flex)
. Alternativement, vous pouvez soustraire 1 de la longueur souhaitée du tableau "flexible" car il est déjà inclus dans la taille de la structure, en supposant que la longueur souhaitée est supérieure à 0. La même logique peut être appliquée aux autres exemples d'utilisation.
Compatibilité
Si la compatibilité avec les compilateurs ne FLEXMEMB_SIZE
pas en charge les membres de tableau flexibles est souhaitée, vous pouvez utiliser une macro définie comme FLEXMEMB_SIZE
ci-dessous:
#if __STDC_VERSION__ < 199901L
#define FLEXMEMB_SIZE 1
#else
#define FLEXMEMB_SIZE /* nothing */
#endif
struct ex1
{
size_t foo;
int flex[FLEXMEMB_SIZE];
};
Lorsque vous offsetof(struct ex1, flex)
objets, vous devez utiliser la forme offsetof(struct ex1, flex)
pour faire référence à la taille de la structure (à l'exclusion du membre de tableau flexible) car c'est la seule expression qui reste cohérente entre les compilateurs ne pas:
struct ex1 *pe10 = malloc(offsetof(struct ex1, flex) + n * sizeof(pe10->flex[0]));
L'alternative consiste à utiliser le préprocesseur pour soustraire conditionnellement 1 de la longueur spécifiée. En raison du potentiel accru d'incohérence et d'erreur humaine générale sous cette forme, j'ai déplacé la logique dans une fonction distincte:
struct ex1 *ex1_alloc(size_t n)
{
struct ex1 tmp;
#if __STDC_VERSION__ < 199901L
if (n != 0)
n--;
#endif
return malloc(sizeof(tmp) + n * sizeof(tmp.flex[0]));
}
...
/* allocate an ex1 object with "flex" array of length 3 */
struct ex1 *pe1 = ex1_alloc(3);
Passer des structures à des fonctions
En C, tous les arguments sont transmis aux fonctions par valeur, y compris les structures. Pour les petites structures, c'est une bonne chose car cela signifie qu'il n'y a pas de surcharge à accéder aux données via un pointeur. Cependant, il est également très facile de transmettre accidentellement une structure volumineuse entraînant de mauvaises performances, en particulier si le programmeur est habitué à d'autres langages où les arguments sont transmis par référence.
struct coordinates
{
int x;
int y;
int z;
};
// Passing and returning a small struct by value, very fast
struct coordinates move(struct coordinates position, struct coordinates movement)
{
position.x += movement.x;
position.y += movement.y;
position.z += movement.z;
return position;
}
// A very big struct
struct lotsOfData
{
int param1;
char param2[80000];
};
// Passing and returning a large struct by value, very slow!
// Given the large size of the struct this could even cause stack overflow
struct lotsOfData doubleParam1(struct lotsOfData value)
{
value.param1 *= 2;
return value;
}
// Passing the large struct by pointer instead, fairly fast
void doubleParam1ByPtr(struct lotsOfData *value)
{
value->param1 *= 2;
}
Programmation par objets utilisant des structures
Les structures peuvent être utilisées pour implémenter du code de manière orientée objet. Une structure est similaire à une classe, mais manque les fonctions qui font normalement partie d'une classe, nous pouvons les ajouter en tant que variables de membre de pointeur de fonction. Pour rester avec notre exemple de coordonnées:
/* coordinates.h */
typedef struct coordinate_s
{
/* Pointers to method functions */
void (*setx)(coordinate *this, int x);
void (*sety)(coordinate *this, int y);
void (*print)(coordinate *this);
/* Data */
int x;
int y;
} coordinate;
/* Constructor */
coordinate *coordinate_create(void);
/* Destructor */
void coordinate_destroy(coordinate *this);
Et maintenant le fichier C d'implémentation:
/* coordinates.c */
#include "coordinates.h"
#include <stdio.h>
#include <stdlib.h>
/* Constructor */
coordinate *coordinate_create(void)
{
coordinate *c = malloc(sizeof(*c));
if (c != 0)
{
c->setx = &coordinate_setx;
c->sety = &coordinate_sety;
c->print = &coordinate_print;
c->x = 0;
c->y = 0;
}
return c;
}
/* Destructor */
void coordinate_destroy(coordinate *this)
{
if (this != NULL)
{
free(this);
}
}
/* Methods */
static void coordinate_setx(coordinate *this, int x)
{
if (this != NULL)
{
this->x = x;
}
}
static void coordinate_sety(coordinate *this, int y)
{
if (this != NULL)
{
this->y = y;
}
}
static void coordinate_print(coordinate *this)
{
if (this != NULL)
{
printf("Coordinate: (%i, %i)\n", this->x, this->y);
}
else
{
printf("NULL pointer exception!\n");
}
}
Un exemple d'utilisation de notre classe de coordonnées serait:
/* main.c */
#include "coordinates.h"
#include <stddef.h>
int main(void)
{
/* Create and initialize pointers to coordinate objects */
coordinate *c1 = coordinate_create();
coordinate *c2 = coordinate_create();
/* Now we can use our objects using our methods and passing the object as parameter */
c1->setx(c1, 1);
c1->sety(c1, 2);
c2->setx(c2, 3);
c2->sety(c2, 4);
c1->print(c1);
c2->print(c2);
/* After using our objects we destroy them using our "destructor" function */
coordinate_destroy(c1);
c1 = NULL;
coordinate_destroy(c2);
c2 = NULL;
return 0;
}