Lecture de fichier dans une structure (C ++)

J’essaie de lire les données d’un fichier binary et de les mettre dans une structure. Les premiers octets de data.bin sont:

 03 56 04 FF FF FF ... 

Et mon implémentation est:

 #include  #include  int main() { struct header { unsigned char type; unsigned short size; } fileHeader; std::ifstream file ("data.bin", std::ios::binary); file.read ((char*) &fileHeader, sizeof header); std::cout << "type: " << (int)fileHeader.type; std::cout << ", size: " << fileHeader.size << std::endl; } 

La sortie que j’attendais est de type: 3, size: 1110 , mais pour une raison quelconque, il s’agit de type: 3, size: 65284 , donc le deuxième octet du fichier est ignoré. Qu’est-ce qu’il se passe ici?

En réalité, le comportement est défini par l’implémentation. Ce qui se passe réellement dans votre cas est probablement, il y a un remplissage d’un octet, après le membre de type de la structure, puis après cela suit la size du deuxième membre. J’ai fondé cet argument après avoir vu la sortie.

Voici vos octets d’entrée:

 03 56 04 FF FF FF 

le premier octet 03 va au premier octet de la structure, qui est le type , et vous voyez ceci 3 en sortie. Ensuite, l’octet suivant 56 passe au deuxième octet qui est le remplissage, donc ignoré, puis les deux octets suivants 04 FF passent aux deux octets suivants de la structure qui est size (qui est de taille 2 octets). Sur la machine little-endian, 04 FF est interprété comme 0xFF04 qui n’est autre que 66284 que vous obtenez en sortie.

Et vous avez besoin d’une structure compacte pour compresser le rembourrage. Utilisez #pragma pack. Mais une telle structure serait lente comparée à la structure normale. Une meilleure option consiste à remplir la structure manuellement comme suit:

 char bytes[3]; std::ifstream file ("data.bin", std::ios::binary); file.read (bytes, sizeof bytes); //read first 3 bytes //then manually fill the header fileHeader.type = bytes[0]; fileHeader.size = ((unsigned short) bytes[2] << 8) | bytes[1]; 

Une autre façon d'écrire la dernière ligne est la suivante:

 fileHeader.size = *reinterpret_cast(bytes+1); 

Mais ceci est défini par l’implémentation, car cela dépend de l’endurance de la machine. Sur une machine little-endian, cela fonctionnerait très probablement.

Une approche conviviale serait la suivante (définie par la mise en œuvre):

 std::ifstream file ("data.bin", std::ios::binary); file.read (&fileHeader.type, sizeof fileHeader.type); file.read (reinterpret_cast(&fileHeader.size), sizeof fileHeader.size); 

Mais encore une fois, la dernière ligne dépend de l’endurance de la machine.

Eh bien, il pourrait s’agir de struct padding. Pour que les structures fonctionnent rapidement sur les architectures modernes, certains compilateurs y appendont du padding pour les maintenir alignées sur des limites de 4 ou 8 octets.

Vous pouvez remplacer cela par un pragma ou un paramètre du compilateur. par exemple. Studio visuel son / Zp

Si cela se produisait alors vous verriez la valeur 56 dans le premier caractère, alors il lirait les n octets suivants dans le remplissage, puis lirait les 2 suivants dans le short. Si le 2e octet a été perdu en tant que remplissage, les 2 octets suivants sont lus dans le short. Et comme le court contient maintenant les données ’04 FF ‘, ceci (en petit boutien) équivaut à 0xff04 qui est 65284.

Vous pouvez utiliser une directive de compilation #pragma pack pour remplacer le problème de remplissage:

 #pragma pack(push) #pragma pack(1) struct header { unsigned char type; unsigned short size; } fileHeader; #pragma pack(pop) 

Les compilateurs relient les structures à des octets multiples de 2 ou 4 pour faciliter leur implémentation dans le code machine. Je n’utiliserais pas #pragma pack sauf si cela était vraiment nécessaire, et cela ne s’applique généralement que lorsque vous travaillez à un niveau très bas (comme le niveau du microprogramme). Article de Wikipedia à ce sujet.

Cela se produit parce que les microprocesseurs ont des opérations spécifiques pour accéder à la mémoire sur des adresses multiples de quatre ou deux, ce qui facilite la création du code source, utilise la mémoire de manière plus efficace et, parfois, le code est un peu plus rapide. Il existe des moyens de mettre fin à ce comportement, comme la directive pragma pack, mais ils dépendent de la compilation. Mais remplacer les valeurs par défaut du compilateur est généralement une mauvaise idée, les gars du compilateur avaient une très bonne raison de le faire se comporter de cette façon.

Une meilleure solution, pour moi, serait de résoudre ce problème avec du C pur, ce qui est très, très simple et suivrait une bonne pratique de programmation, à savoir: ne vous fiez jamais à ce que le compilateur fait avec vos données à bas niveau.

Je sais que faire #pragma pack (1) est sexy, simple et donne à tous le sentiment que nous agissons et que nous comprenons directement ce qui se passe dans les entrailles de l’ordinateur, et que cela allume tout vrai programmeur, mais la meilleure solution est toujours celle qui est implémentée avec le langage que vous utilisez. C’est plus facile à comprendre et donc plus facile à maintenir; C’est le comportement par défaut, donc ça devrait marcher partout, et dans ce cas précis, la solution C est vraiment simple et directe: il suffit de lire votre atsortingbut struct par atsortingbut, comme ceci:

 void readStruct(header &h, std::ifstream file) { file.read((char*) &h.type, sizeof(char)); file.read((char *) &h.size, sizeof(short)); } 

(cela fonctionnera si vous définissez la structure globalement, bien sûr)

Mieux encore, lorsque vous travaillez avec C ++, vous pouvez définir une méthode membre pour effectuer cette lecture à votre place et, plus tard, appeler simplement myObject.readData(file) . Pouvez-vous voir la beauté et la simplicité?

Plus facile à lire, à maintenir, à comstackr, conduit à un code plus rapide et optimisé, c’est le code par défaut.

D’habitude, je n’aime pas jouer avec les directives #pragma à moins d’être assez sûr de ce que je fais. Les implications peuvent être surprenantes.