4
votes

Comment exposer la taille de la structure C sans exposer son type?

J'ai ce qui suit en C (pas en C ++!):

module.c
    struct Private {...};
    void foo(void* private, int param) {...}

module.h
    #define PRIVATE_SIZE ???;
    void foo(void* private, int param);

main.c
    char m1[PRIVATE_SIZE];
    char m2[PRIVATE_SIZE];

    int main()
    {
        foo(m1, 10);
        foo(m2, 20);
    }

Comment puis-je exposer sizeof (Private) au moment de la compilation pour que l'application puisse statiquement em> allouer son stockage sans exposer le type Private?

Remarque, il s'agit d'un système embarqué très limité et l'allocation dynamique n'est pas disponible.

c

13 commentaires

Pourquoi en avez-vous besoin pour une allocation statique? Si vous avez quand même besoin d'une fonction init, pourquoi ne pas allouer dynamiquement la mémoire à cette fonction également?


On dirait que vous ne pouvez pas: pour déterminer la taille, vous devez connaître tous ses membres; masquer ses membres signifie que vous ne pouvez pas déterminer la taille.


Cela ressemble à un modèle de dissimulation d'informations. Regardez cet exemple de code, peut-être qu'il peut vous aider: github.com/adamtornhill/PatternsInC/ tree / master / 1_FirstClass‌ ADT


Vous pouvez définir une variable (const) size_t dans module.c qui est initialisée avec sizeof (Private) (qui y est connue) et l'exposer à un usage public via une déclaration dans module.h. [En fait, la variable serait probablement une variable statique dans une fonction qui renvoie sa valeur; cela évite les problèmes d'ordre d'initialisation statique.] En passant, je me demande également si vous pourriez déclarer en avant Private et avoir une meilleure vérification de type, par exemple avec le paramètre pour Init () .


Vous ne devriez pas négliger les exigences d'alignement, car vous ne devez exposer que la taille.


@ PeterA.Schneider Il n'y a pas de problèmes d'ordre d'initialisation statique en C. De plus, le déplacement de variables statiques dans des fonctions n'affecte pas l'ordre d'initialisation.


@Gerhardh Je tourne sur un système embarqué avec des dizaines de kilo-octets de mémoire


@ A.R.C Malheureusement, dans l'exemple, l'objet s'alloue dynamiquement, je ne peux utiliser que l'allocation statique, c'est pourquoi mon module doit s'appuyer sur l'application pour allouer un ou plusieurs objets privés.


Vous pouvez remplacer l'allocation dynamique dans l'exemple par un objet statique dans le fichier .c. Les informations sur les types d'informations stockées à l'intérieur de la structure sont toujours cachées à l'extérieur.


@melpomene Êtes-vous en train de dire qu'il existe un ordre strict de l'initialisation des variables statiques dans différentes unités de traduction en C? Ainsi, le problème existant en C ++ n'existe pas en C?


@ PeterA.Schneider Je dis qu'il n'y a pas de dépendances entre les variables statiques, donc l'ordre n'a pas d'importance.


"Le système embarqué limité et l'allocation dynamique ne sont pas disponibles." -> quel système / compilateur est-ce?


Quel problème essayez-vous de résoudre en cachant des informations? Qu'espérez-vous y gagner? Si ce n'est pas beaucoup, déplacez simplement votre définition de type vers l'en-tête.


5 Réponses :


4
votes

Vous ne devriez pas exposer la taille de la structure à l'appelant, car cela brise tout l'objectif d'avoir une encapsulation privée en premier lieu. L'attribution de vos données privées n'est pas l'affaire de l'appelant. Aussi, évitez d'utiliser void * car ils manquent complètement de sécurité de type.

Voici comment vous écrivez une encapsulation privée en C:

  • Dans module.h, déclarer avant un type incomplet typedef struct module module; .
  • Dans module.c, placez la définition de structure de cette structure. il ne sera visible que par module.c et non par l'appelant. Ceci est connu sous le nom de types opaques .
  • L'appelant peut uniquement allouer des pointeurs à cette structure, jamais allouer des objets.
  • Le code de l'appelant peut ressembler à:

    result module_copy (module** dst, const module* src);
    
  • Et la fonction module_init agit comme un "constructeur", déclaré dans module.h et défini dans module.c:

    bool module_init (module** obj)
    {
      module* m = malloc(sizeof *m);
      ...
      m->something = ...; // init private variables if applicable
    
      *obj = m;
      return true;
    }
    
  • Si l'appelant a besoin de connaître la taille des objets, ce ne serait qu'à des fins de copie papier, etc. Si cela est nécessaire, fournissez une fonction de copie qui encapsule l'allocation et la copie (" copier le constructeur "), par exemple:

    #include "module.h"
    ...
    module* m;
    result = module_init(&m)
    

Edit:

Veuillez noter que le mode d'allocation est un problème distinct. Vous n'êtes pas obligé d'utiliser l'allocation dynamique pour la conception ci-dessus. Dans les systèmes embarqués par exemple, il est courant d'utiliser à la place des pools de mémoire statique. Voir Allocation statique de types de données opaques


2 commentaires

Veuillez consulter mon commentaire @ A.R.C dans la question


@jackhab A ajouté une note au bas de la réponse à ce sujet. En particulier, voir la réponse de Clifford dans l'article lié.



1
votes

Dans le code C conforme, vous ne pouvez pas créer une instance statique d'un type arbitraire inconnu même si vous connaissez sa taille au moment de la compilation (même si vous connaissez l'alignement).

Disons que vous essayez de le faire quand même. Comment feriez-vous, étant donné la taille d'une macro ou d'une énumération PRIVATE_SIZE?

#include "mod.h" // mod.h defines PRIVATE_SIZE
struct Private { ... };
extern char StAtIcAsSeRt[sizeof(struct Private) == PRIVATE_SIZE];

Et puis vous passeriez (void * ) obj là où c'est nécessaire, non? Eh bien, cela enfreint les règles d'alias. Bien que vous puissiez légalement accéder à n'importe quel caractère / octet individuel dans n'importe quel objet, vous ne pouvez pas le faire l'inverse en disant que ces caractères ne sont pas des caractères, ils sont juste stockés derrière d'autres types. Autrement dit, vous ne pouvez pas légalement avoir un short int superposé au-dessus de, disons, obj [2] et obj [3] via smarty-pants casts (par exemple ((struct Private *) obj) -> my_short = 2; ). Le seul moyen légal de faire quelque chose comme ça serait d'utiliser memcpy () , par exemple memcpy (& temp, obj, sizeof temp); puis de nouveau après la modification. Ou vous auriez besoin de travailler avec des caractères individuels de obj[ .

Il y a deux façons possibles de le faire. L'une est décrite dans une autre réponse, définissez essentiellement l'instance où le type est connu, mais ne laissez que le monde extérieur avoir un pointeur vers elle.

Une autre, très similaire, définissez-la dans le code d'assemblage et, encore une fois, laissez le monde extérieur avoir un pointeur vers lui. La "beauté" de l'assemblage est que vous n'avez vraiment besoin que d'un nom, d'un alignement et d'une taille pour allouer de l'espace à un objet nommé.

Et si vous mettez les instances dans une section de données spéciale (voir l'attribut section de gcc et les scripts de l'éditeur de liens), vous pouvez même avoir toutes les instances au même endroit (think, array) et même découvrir leur taille cumulée et donc leur nombre.

Encore une autre chose à faire sans violer explicitement les règles C, c'est toujours utiliser cette astuce unsigned char obj [PRIVATE_SIZE] , mais la laver en la passant inchangée via une fonction d'assemblage que le compilateur C ne peut pas regarder, par exemple quelque chose comme

// struct Private* launder(unsigned char*);
.text
.globl launder
launder:
    move %first_param_reg, %return_reg
    ret

Mais vous devrez vraiment changer unsigned char obj [PRIVATE_SIZE] en quelque chose qui aurait un alignement correct sur votre architecture, par exemple double obj [PRIVATE_SIZE / sizeof (double)] (ou la même chose avec long long si vous aimez mieux ça).

Comme pour PRIVATE_SIZE , vous pouvez vérifier lors de la compilation qu'il correspond à la taille du type, par exemple

unsigned char obj[PRIVATE_SIZE];


0 commentaires

0
votes

Comment exposer la taille d'une structure C sans exposer son type?

S'il est possible de compromettre un peu: (statiquement -> main () local)
avec tableaux de longueur variable (C99), utilisez une fonction d'assistance et placez le tableau dans main().

module.h
  size_t foo_size(void);

main.c
  int main() {
    char m1[foo_size()];
    foo(m1, 10);
  }

Des travaux supplémentaires sont nécessaires pour tenir compte des problèmes d'alignement.

Envisagez d'assouplir votre objectif comme suggéré .


0 commentaires

2
votes

Vous ne pouvez pas allouer de taille pour une structure comme celle-ci car elle n'est pas connue au moment de la compilation. Même si vous connaissiez la taille au moment de l'exécution, vous auriez toujours des problèmes liés à l'alignement.

Il existe une solution possible qui consiste à définir une structure distincte qui a les mêmes exigences de taille et d'alignement que la structure privée. p>

Par exemple:

module.h:

struct Private {
    int a;
    double b;
    float c;
};

struct PrivateAllocator {
    struct Private obj;
    int used;
};

struct PrivateAllocator list[5] = {
    { { 0, 0, 0}, 0 },
    { { 0, 0, 0}, 0 },
    { { 0, 0, 0}, 0 },
    { { 0, 0, 0}, 0 },
    { { 0, 0, 0}, 0 }
};

struct Private *private_init()
{
    int i;
    for (i=0; i<5; i++) {
        if (!list[i].used) {
            list[i].used = 1;
            return &list[i].obj;
        }
    }
    return NULL;
}

void private_free(struct Private *p)
{
    int i;
    for (i=0; i<5; i++) {
        if (&list[i].obj == p) {
            list[i].used = 0;
            return;
        }
    }
}

module.c:

#include <assert.h>
#include <stdalign.h>
#include "module.h"

struct Private {
    int a;
    double b;
    float c;
};

static_assert(sizeof(struct Private)==sizeof(struct Public), "sizes differ");
static_assert(alignof(struct Private)==alignof(struct Public), "alignments differ");

void init(struct Public *p)
{
    struct Private *pr = (struct Private *)p;
    pr->a = 2;
    pr->b = 2.5;
    pr->c = 2.4f;
}

Les structures Public et Private sont garanties d'avoir la même taille, et l'alignement devrait être le même. Il est possible que l'utilisateur écrive les champs "opaques" de la structure publique, auquel cas vous pourriez avoir des problèmes d'alias concernant les types efficaces, mais si l'utilisateur peut être fiable pour faire cela, cela devrait fonctionner.


Une autre option, plus robuste, est si vous avez une idée du nombre maximum d'objets que vous souhaitez prendre en charge. Si tel est le cas, vous pouvez avoir un tableau statique de ces objets dans votre fichier d'implémentation, et la fonction init renverrait un pointeur vers l'un des objets de cette liste. Ensuite, vous auriez une fonction de nettoyage associée qui libérerait l'instance.

Par exemple:

module.c:

#include <inttypes.h>

struct Public {
    uint64_t opaque1;
    uint64_t opaque2;
    uint64_t opaque3;
};

void init(struct Public *p);

p>


3 commentaires

Salut, dbush! Pouvez-vous donner un exemple de problème d'alignement qui peut survenir ici?


@jackhab Dans l'exemple ci-dessus, struct Private aurait généralement une exigence d'alignement de 8. Si vous venez de créer un tableau char pour superposer la structure, ce tableau pourrait pas cet alignement. Sur certains systèmes, un mauvais alignement peut entraîner la génération de code inefficace ou même une erreur de mémoire.


Oui, vous pouvez ajouter des variables de remplissage pour remplir l'espace. Mais de toute façon, cette affirmation statique est très efficace.



0
votes

C99 vous permet d'utiliser un tableau de longueur variable .

private.h:

#include <stdio.h>
#include "private.h"
int main(void) {
        char m1[size]; //variable length array
        printf("Size of m1 = %ld\n", sizeof(m1));
}

private.c:

XXX

main.c:

#include "private.h"
struct Private {
        int x;
        int y;
        int z;
};

const size_t size = sizeof(struct Private);


1 commentaires

Malheureusement, mon compilateur (Microchip XC8) ne prend pas en charge les VLA