Question

Je travaille sur un format de fichier qui doit être lu et écrit dans plusieurs systèmes d'exploitation différents et des ordinateurs. Certains de ces ordinateurs devraient être des machines x86, x86-64 autres. D'autres processeurs peuvent exister, mais je ne suis pas préoccupé par les encore .

Ce format de fichier doit contenir plusieurs numéros qui seraient lus comme ceci:

struct LongAsChars{
    char c1, c2, c3, c4;
};

long readLong(FILE* file){
    int b1 = fgetc(file);
    int b2 = fgetc(file);
    int b3 = fgetc(file);
    int b4 = fgetc(file);
    if(b1<0||b2<0||b3<0||b4<0){
        //throwError
    }

    LongAsChars lng;
    lng.c1 = (char) b1;
    lng.c2 = (char) b2;
    lng.c3 = (char) b3;
    lng.c4 = (char) b4;

    long* value = (long*) &lng;

    return *value;
}

et écrit:

void writeLong(long x, FILE* f){
    long* xptr = &x;
    LongAsChars* lng = (LongAsChars*) xptr;
    fputc(lng->c1, f);
    fputc(lng->c2, f);
    fputc(lng->c3, f);
    fputc(lng->c4, f);
}

Bien que cela semble fonctionner sur mon ordinateur, je crains que cela ne peut pas dans d'autres ou que le format de fichier peut finir par être différent sur les ordinateurs (32 bits vs 64 bits des ordinateurs, par exemple). Est-ce que je fais quelque chose de mal? Comment dois-je mettre en œuvre mon code à utiliser un nombre constant d'octets par nombre?

Dois-je utiliser juste fread (qui peut rendre le code plus rapide aussi) à la place?

Était-ce utile?

La solution

Utilisez les types de stdint.h pour vous assurer d'obtenir le même nombre d'octets et de sortie.

Ensuite, vous êtes juste à gauche de traiter des questions de endianness, que vous code ne probablement pas vraiment gérer.

sérialisation long avec un crénelage char * vous laisse avec différents ordres d'octets dans le fichier écrit pour les plates-formes avec différentes endianess.

Vous devez décomposer les octets quelque chose comme ceci:

char c1 = (val >>  0) & 0xff;
char c2 = (val >>  8) & 0xff;
char c3 = (val >> 16) & 0xff;
char c4 = (val >> 24) & 0xff;

Et puis recadrez en utilisant quelque chose comme:

val = (c4 << 24) |
      (c3 << 16) |
      (c2 <<  8) |
      (c1 <<  0);

Autres conseils

Vous pouvez également rencontrer des problèmes avec boutisme . Pourquoi ne pas simplement utiliser quelque chose comme NetCDF ou HDF , qui prennent en charge tous les problèmes de portabilité qui peuvent survenir?

Au lieu d'utiliser des structures avec des personnages en eux, envisager une approche plus mathématique:

long l  = fgetc() << 24;
     l |= fgetc() << 16;
     l |= fgetc() <<  8;
     l |= fgetc() <<  0;

Ceci est un peu plus direct et clair sur ce que vous essayez d'accomplir. Il peut également être mis en œuvre dans une boucle pour traiter un plus grand nombre.

Vous ne voulez pas utiliser long int. Cela peut être de tailles différentes sur différentes plates-formes, donc est un non-starter pour un format indépendant de la plateforme. Vous devez décider quelle gamme de valeurs doivent être stockées dans le fichier. 32 bits est probablement plus facile.

Vous dites que vous n'êtes pas inquiet pour d'autres plates-formes encore . Je vais prendre cela pour vous dire souhaitez conserver la possibilité de les soutenir, dans ce cas, vous devez définir l'ordre des octets de format de fichier. x86 est petit-boutiste, vous pourriez penser que est le meilleur. Mais big-endian est l'ordre d'échange « standard » si quelque chose est, car il est utilisé dans les réseaux.

Si vous optez pour big-endian ( "l'ordre des octets du réseau"):

// can't be bothered to support really crazy platforms: it is in
// any case difficult even to exchange files with 9-bit machines,
// so we'll cross that bridge if we come to it.
assert(CHAR_BIT == 8);
assert(sizeof(uint32_t) == 4);

{
    // write value
    uint32_t value = 23;
    const uint32_t networkOrderValue = htonl(value);
    fwrite(&networkOrderValue, sizeof(uint32_t), 1, file);
}

{
    // read value
    uint32_t networkOrderValue;
    fread(&networkOrderValue, sizeof(uint32_t), 1, file);
    uint32_t value = ntohl(networkOrderValue);
}

En fait, vous ne devez même pas déclarer deux variables, il est juste un peu déroutant pour remplacer « valeur » à son ordre de réseau équivalent dans la même variable.

Cela fonctionne parce que « l'ordre des octets du réseau » est défini comme étant n'importe quel agencement des bits de résultats dans un ordre interchangeable (grand-boutiste) dans la mémoire. Pas besoin de jouer avec les syndicats parce que tout objet stocké dans C peut être traitée comme une séquence de charbon. Pas besoin de cas particulier pour boutisme parce que ce ntohl / htonl sont pour.

Si cela est trop lent, vous pouvez commencer à penser à byte-swapping spécifique à la plateforme diaboliquement optimisée, avec SIMD ou autre chose. Ou en utilisant peu endian, sur l'hypothèse que la plupart de vos plates-formes seront peu endian et il est donc plus rapide « en moyenne » à travers eux. Dans ce cas, vous aurez besoin d'écrire ou de trouver « hôte little-endian » et « little-endian pour accueillir » fonctions, bien sûr, sur x86 simplement ne rien faire.

Je crois que l'approche de l'architecture la plus croix est d'utiliser les types de uintXX_t, tels que définis dans stdint.h. Voir la page de manuel ici. Par exemple, un int32_t vous donnera un entier de 32 bits sur x86 et x86-64. Je les utiliser par défaut maintenant dans tout mon code et ai eu aucun problème, car ils sont assez standard sur tous les * NIX.

En supposant sizeof(uint32_t) == 4, il y a 4!=24 commandes d'octets possibles, dont peu endian et big-endian sont les exemples les plus importants, mais d'autres ont également été utilisés (par exemple PDP-endian).

Voici les fonctions de lecture et d'écriture des entiers non signés de 32 bits à partir d'un flux, tenant compte d'un ordre d'octet arbitraire qui est spécifiée par le nombre entier dont la représentation est la séquence d'octets 0,1,2,3: endian.h , endian.c

L'en-tête définit ces prototypes

_Bool read_uint32(uint32_t * value, FILE * file, uint32_t order);
_Bool write_uint32(uint32_t value, FILE * file, uint32_t order);

et ces constantes

LITTLE_ENDIAN
BIG_ENDIAN
PDP_ENDIAN
HOST_ORDER
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top