8
votes

Ce piratage est-il valide selon la norme?

Ceci est juste comme la structure hack. Est-il valide selon la norme C?

 // error check omitted!

    typedef struct foo {
       void *data;
       char *comment;
       size_t num_foo;
    }foo;

    foo *new_Foo(size_t num, blah blah)
    {
        foo *f;
        f = malloc(num + sizeof(foo) + MAX_COMMENT_SIZE );
        f->data = f + 1;            // is this OK?
        f->comment = f + 1 + num;
        f->num_foo = num;
        ...
        return f;

}

c

0 commentaires

6 Réponses :


6
votes

Oui, c'est complètement valide. Et je l'encouragerais fortement à ce que cela vous permet d'éviter des allocations supplémentaires inutiles (et la gestion des erreurs et la fragmentation de la mémoire qu'ils entraînent). D'autres peuvent avoir des opinions différentes.

Au fait, si vos données ne sont pas Void * mais quelque chose que vous pouvez accéder directement, il est encore plus facile (et plus efficace car il permet d'économiser de l'espace et d'éviter les autres Indirection) Pour déclarer votre structure comme suit: xxx

et allouer de l'espace pour la quantité de données dont vous avez besoin. La syntaxe [] est valable uniquement en C99, donc pour C89-Compatibilité, vous devez utiliser [1] , mais cela peut perdre quelques octets. < / p>


3 commentaires

Comme vous le savez, mais cela vaut probablement la peine d'être signalé, l'élément de matrice flexible à la fin de la structure ne fonctionne que lorsque vous avez un élément de longueur variable. La question a deux éléments de longueur variable ( data et commentaire ) et ne peut pas utiliser facilement la technique.


Dans ce cas, stockez le comme normal MALLOC () String.


FWIW, j'ai donné cette réponse avant que le champ de commentaire ait été ajouté. Avec des commentaires ajoutés, suivez les conseils de Donal.



1
votes

C'est un ancien jeu, bien que la forme habituelle soit comme xxx

, puis allouez l'espace aussi gros que vous le souhaitez et utilisez la matrice comme si elle avait la taille souhaitée.

It est valide, mais je vous encourageons à trouver un autre moyen si possible: il y a beaucoup de chance de baiser cela.


10 commentaires

Cet ancien hack n'est pas * valide * (strictement) selon la norme.


Non, ce n'est pas le cas, mais travaillera avec une implémentation conforme de MALLOC . :: pense un peu sur les architectures de mémoire pathologiquement segmentées: presque tout MALLOC .


Il est valable comme des conséquences d'autres exigences de la norme, par exemple l'exigence selon laquelle si une structure correspond à la partie initiale d'une autre, l'accès à ces éléments via le 2 est compatible. C99 le rend explicitement pris en charge avec la notation de tableau flexible [] mais l'ancienne [1] méthode fonctionne nécessairement!


@dmckee, tout ce que pathologique ne peut pas être conforme. La norme nécessite que la mémoire renvoyée par MALLOC soit accessible en tant que tableau de non signé Char et qu'elle soit alignée de manière appropriée pour tout type.


@R: Le comité a en réalité discuté qu'à un moment donné, et décidé que cela n'était vraiment pas défini en C89. L'accès au-delà de la taille définie d'une matrice provoque un comportement non défini. Bien que ce soit rare, le compilateur pourrait faire la plage de contrôle et de lancer une sorte d'exception (ou autre) lorsque vous essayez d'accéder à la mémoire après quel que soit [0] .


@Jerry: Ce ne serait pas le compilateur, mais plutôt le temps d'exécution. (Le compilateur ne peut surtout pas le dire, car ce n'est pas et ne devrait pas être aussi intelligent.) Ce type de chose est utile de purifier et de purifier cependant, qui sont efficacement des roulements non standard. Vous ne voulez tout simplement pas que Normalement (puisque le succès de la performance est très douloureux).


@Donal: le compilateur génère le code, qui exécute au moment de l'exécution. Oui, le succès de la performance est assez grave que, dans la pratique, personne ne le fait, mais le comité a décidé que c'était autorisé de toute façon.


@Jerry: Mais dans certains cas, le temps d'exécution modifie le code compilé lorsqu'il est chargé pour ajouter le support pour la vérification des limites. (Vraiment. C'est exactement la façon dont le travail d'accès à la mémoire fonctionne.) Pour mon astuce, discutons de la nature de l'infinité des anges autorisée à danser sur la tête d'une épingle et quelle école de ballet ils sont allés ... ;-)


@Jerry: quoi que ce soit [1] est identique à * (& autre [0] +1) , qui est une déréférence de pointeur valide dans l'objet attribué. Je suis désolé de dire que le comité a du mal à lire ses propres documents.


@R: Il est syntaxiquement valide, mais produit toujours un comportement indéfini. Je ne serais pas trop rapide de décider que vous lisez le document plus précisément que le comité.



1
votes

Oui, l'idée générale du piratage est valide, mais au moins comme je l'ai lu, vous ne l'avez pas mis en œuvre assez correctement. Cela vous avez beaucoup fait correctement: xxx

mais c'est faux: xxx

car f est < code> foo * , le f + 1 + num est calculé en termes de Tailleof (foo) - c'est-à-dire que c'est équivalent à dire f [1 + NUM] - Il (tente de) indexer sur le 1 + num th foo dans un tableau. Je suis sûr que c'est pas ce que vous voulez. Lorsque vous allouez les données, vous passez Tailleof (FOO) + num + max_comment_size , donc ce que vous allouez de l'espace pour IS num char s et ce que vous ( Vraisemblablement) Vouloir est de pointer f-> commentaire à un endroit en mémoire qui est num char S après f-> données , ce qui serait plus Comme ceci: xxx

casting f à un char * force les mathématiques à effectuer en termes de Char S au lieu de FOO s.

OTOH, car vous allouez toujours max_comment_size pour Commentaire Je simplifierais probablement des choses (tout à fait) un peu et utilisez quelque chose comme ceci: xxx

puis l'allouate comme: xxx

et cela fonctionnera sans aucune manipulation de pointeur du tout. Si vous avez un compilateur C99, vous pouvez le modifier légèrement: xxx

et allouer: xxx

ceci a le supplément Avantage que la norme bénit réellement, cependant, dans ce cas, l'avantage est assez mineur (je crois que la version avec Data [1] fonctionnera avec chaque compilateur C89 / 90 existant).


2 commentaires

Vous avez repéré cela à peu près au même moment, nous avons composé nos réponses en parallèle.


@Jonathan Leffler: Yup - surtout avec une réponse verbeuse comme la mienne, c'est presque inévitable ...



4
votes

La ligne que vous question est valide - comme d'autres personnes ont dit.

Fait intéressant, la ligne suivante, que vous n'avez pas interrogée, est syntaxiquement, est valide syntaxiquement, mais ne vous donne pas la réponse que vous souhaitez (sauf dans le cas où num == 0 ). xxx

la valeur de F + 1 est un foo * (implicitement forcé dans un vide * par l'affectation).

La valeur de f + 1 + num est aussi un foo * < / code>; Il pointe vers le num + 1 th foo .

Qu'est-ce que vous aviez probablement à l'esprit était: xxx

ou: xxx

Notez que pendant que GCC vous permettra d'ajouter num à un pointeur vide, et il le traitera comme si tailleof (void) == 1 , la norme C ne vous donne pas cette autorisation.


0 commentaires

0
votes

Un autre problème possible pourrait être un alignement.

Si vous savez simplement Malloc votre F-> Data , vous pouvez alors vous pouvez préciser par E.G. Convertissez votre Void * sur double * et utilisez-le pour lire / écrire un double (à condition que NUM soit suffisamment grand). Cependant, dans votre exemple, vous ne pouvez plus faire cela, car les données F-> peuvent ne pas être correctement alignées. Par exemple, pour stocker une doublure en données F->, vous devrez utiliser quelque chose comme MEMCY au lieu d'une simple typast.


1 commentaires

L'utilisation de mon approche (tableau à la fin) résout le problème d'alignement automatiquement. Il peut également être résolu en affectant suffisamment d'espace supplémentaire que vous pouvez arrondir f-> données au prochain multiple de Tailleof (double) ou quel que soit le type dont vous avez besoin.



0
votes

Je préférerais utiliser une fonction pour allouer les données de manière dynamique et libérer correctement.

L'utilisation de cette astuce ne vous permet que d'initialiser la structure de données et peut conduire à de très mauvais problèmes (voir le commentaire de Jerry) .

Je ferais quelque chose comme ceci: xxx

Notez que je n'ai pas fait de vérification sur la validité des données et que mon ALLOC peut être optimisé (remplaçant SHLEN () appels par une valeur de longue durée stockée).

Il me semble que ce comportement est plus sûr ... au prix d'une chunque de données diffusée peut-être.


0 commentaires