web-dev-qa-db-fra.com

Peut-on inclure #include dans le fichier source pour la maintenabilité du code C incorporé?

Je ne suis pas un expert en programmation C et je sais que, y compris .c le fichier source d'un autre est considéré comme une mauvaise pratique, mais je pense que cela pourrait aider la maintenabilité.

J'ai une grande structure avec beaucoup d'éléments et j'utilise #define pour conserver les index.

#define TOTO_IND 0 
#define TITI_IND 1 
…
#define TATA_IND 50

static const MyElements elems [] = {
    {"TOTO", 18, "French"},
    {"TITI", 27, "English"},
    ...,
    {"TATA", 45, "Spanish"}
}

Comme je dois accéder à la structure depuis index, je dois garder le #define et la déclaration de structure synchronisée. Cela signifie que je dois insérer de nouveaux éléments au bon endroit et mettre à jour le #define en conséquence.

Il est sujet aux erreurs et je ne l’aime pas vraiment (mais pour des raisons de performance, je n’ai pas trouvé de meilleure solution).

Quoi qu'il en soit, ce fichier contient également de nombreuses fonctions pour gérer cette structure. Je veux aussi garder la séparation du code et éviter les variables globales.

Pour rendre les choses "plus faciles", je pensais déplacer cette "définition sujette aux erreurs" à un seul .c fichier source qui ne contiendrait que cette structure. Ce fichier serait “le fichier dangereux” et serait inclus dans mon fichier “fonctionnel normal”.

Qu'est-ce que tu en penses? Est-ce une situation valable pour inclure .c fichier source? Existe-t-il une autre meilleure façon de gérer ma structure?

29
ncenerar

Vous devez utiliser les initialiseurs désignés, comme indiqué dans la réponse de Ian Abbot.

De plus, si les index du tableau sont adjacents, comme cela semble être le cas ici, vous pouvez utiliser une énumération à la place:

toto.h

typedef enum
{
  TOTO_IND,
  TITI_IND,
  ...
  TATA_IND,
  TOTO_N    // this is not a data item but the number of items in the enum
} toto_t;

toto.c

const MyElements elems [] = {
  [TITI_IND] = {"TITI", 27, "English"},
  [TATA_IND] = {"TATA", 45, "Spanish"},
  [TOTO_IND] = {"TOTO", 18, "French"},
};

Et maintenant, vous pouvez vérifier l’intégrité des données du tableau dans son ensemble avec une assertion statique:

_Static_assert(sizeof elems/sizeof *elems == TOTO_N, 
               "Mismatch between toto_t and elems is causing rain in Africa");
_Static_assert(sizeof elems/sizeof *elems == TOTO_N, ERR_MSG);

ERR_MSG est défini comme

#define STR(x) STR2(x)
#define STR2(x) #x
#define ERR_MSG "Mismatching toto_t. Holding on line " STR(__LINE__)
33
Lundin

Vous pouvez utiliser des initialiseurs désignés pour initialiser les éléments de elems[] sans avoir à connaître la valeur explicite de chaque identifiant d'index (ou macro).

const MyElements elems[] = {
    [TOTO_IND] = {"TOTO", 18, "French"},
    [TITI_IND] = {"TITI", 27, "English"},
    [TATA_IND] = {"TATA", 45, "Spanish"},
};

Les éléments du tableau seront initialisés de la même manière, même si vous modifiez leur ordre d'apparition dans le code source:

const MyElements elems[] = {
    [TITI_IND] = {"TITI", 27, "English"},
    [TATA_IND] = {"TATA", 45, "Spanish"},
    [TOTO_IND] = {"TOTO", 18, "French"},
};

Si la longueur du tableau est définie automatiquement à partir de l’initialiseur comme indiqué ci-dessus (c’est-à-dire en utilisant [] plutôt que [NUM_ELEMS]), la longueur sera alors supérieure à l’indice maximal de l’élément.

Cela vous permet de conserver les valeurs d'index et la déclaration externe du tableau elems dans un fichier .h et de définir le contenu du tableau elems dans un fichier .c séparé.

39
Ian Abbott

D'autres réponses ont déjà été plus claires, mais par souci de complétude, voici une approche x-macros, si vous êtes prêt à emprunter cette voie et à risquer la rage de votre collègue.

Les macros X sont une forme de génération de code utilisant le préprocesseur C intégré. L’objectif est de réduire au minimum les redoublements, avec toutefois quelques inconvénients:

  1. Le fichier source qui génère des énumérations et des structures à l'aide du préprocesseur peut sembler complexe si vous n'y êtes pas habitué.
  2. Comparé à un script de construction externe qui générerait les fichiers source, les macros x ne permettent jamais de voir à quoi ressemble le code généré lors de la compilation, à moins d'utiliser un paramètre de compilateur et de rechercher manuellement le fichier prétraité.
  3. Comme vous ne voyez pas la sortie prétraitée, vous ne pouvez pas utiliser le débogueur pour parcourir le code généré de la même manière que vous le feriez avec du code généré par un script externe.

Vous commencez par créer une liste de macro invocations dans un fichier séparé, par exemple. elements.inc, sans définir ce que la macro fait réellement à ce stade:

// elements.inc

// each row passes a set of parameters to the macro,
// although at this point we haven't defined what the
// macro will output

XMACRO(TOTO, 18, French)
XMACRO(TITI, 27, English)
XMACRO(TATA, 45, Spanish)

Ensuite, vous définissez la macro chaque fois que vous devez inclure cette liste, de sorte que chaque invocation soit rendue dans une seule ligne de la construction que vous voulez créer - et vous la répétez généralement plusieurs fois de suite, c'est-à-dire.

// concatenate id with "_IND" to create enums, ignore code and description
// (notice how you don't need to use all parameters each time)
// e.g. XMACRO(TOTO, 18, French) => TOTO_IND,
#define XMACRO(id, code, description) id ## _IND,
typedef enum
{
#    include "elements.inc"
     ELEMENTS_COUNT
}
Elements;
#undef XMACRO

// create struct entries
// e.g. XMACRO(TOTO, 18, French) => [TOTO_IND] = { "TOTO", 18, "French" },
#define XMACRO(id, code, description) [id ## _IND] = { #id, code, #description },
const MyElements elems[] = {
{
#    include "elements.inc"
};
#undef XMACRO

Ce qui sera prétraité en quelque chose comme:

typedef enum
{
    TOTO_IND,
    TITI_IND,
    TATA_IND,
    ELEMENTS_COUNT
}
Elements;

const MyElements elems[] = {
{
    [TOTO_IND] = { "TOTO", 18, "French" },
    [TITI_IND] = { "TITI", 27, "English" },
    [TATA_IND] = { "TATA", 45, "Spanish" },
};

De toute évidence, la maintenance fréquente de la liste devient plus facile, au détriment de la génération du code qui devient plus compliquée.

18
Groo

Définir le const comme static dans plusieurs fichiers n’est pas une bonne idée car cela crée plusieurs instances de la grande variable MyElements. Cela augmentera la mémoire dans un système intégré. Le qualificatif static doit être supprimé.

Voici une solution suggérée:

dans file.h

#define TOTO_IND 0 
#define TITI_IND 1 
…
#define TATA_IND 50
#define MAX_ELEMS 51

extern const MyElements elems[MAX_ELEMS];

dans fichier.c

#include "file.h"
const MyElements elems [MAX_ELEMS] = {
    {"TOTO", 18, "French"},
    {"TITI", 27, "English"},
    ...,
    {"TATA", 45, "Spanish"}
}

Après modification, placez #include "file.h" dans les fichiers .c requis.

5
Rishikesh Raje

Pour répondre à la question spécifique sur l’utilisation de #include Avec un fichier .c (Les autres réponses suggèrent de meilleures options, en particulier celle de Groo), il n’est généralement pas nécessaire.

Tout le contenu d'un fichier .c Peut être rendu visible et accessible de l'extérieur. Vous pouvez donc le référencer via des prototypes de fonctions et #extern. Ainsi, par exemple, vous pouvez référencer votre table avec #extern const MyElements elems []; Dans votre fichier principal .c.

Vous pouvez également placer les définitions dans un fichier .h Et l'inclure. Cela vous permet de séparer le code comme vous le souhaitez. Gardez à l'esprit que tout #include N'insère que le contenu du fichier inclus dans lequel se trouve l'instruction #include, De sorte qu'il ne nécessite pas d'extension de fichier particulière. .h Est utilisé par convention et la plupart des IDE ajouteront automatiquement des fichiers .c À la liste des fichiers à compiler, mais pour le compilateur, la dénomination est arbitraire.

1
shogged