Maintenant, nous sommes tous doivent parfois travailler avec des données binaires. En C++, nous travaillons avec des séquences d'octets, et depuis le début char
a été la notre bloc de construction. Défini pour avoir sizeof
de 1, c'est l'octet. Et toutes bibliothèque de fonctions d'e/S d'utilisation char
par défaut. Tout est bon mais il y avait toujours un peu d'inquiétude, un peu de bizarrerie qui énerve certaines personnes - le nombre de bits dans un octet est mise en œuvre définies.
Donc en C99, il a été décidé d'introduire plusieurs typedefs les développeurs peuvent facilement s'exprimer, la largeur fixe les types d'entiers. En option, bien sûr, puisque nous ne voulons pas blesser la portabilité. Parmi eux, uint8_t
, migré dans C++11 std::uint8_t
, une largeur fixe de 8 bits type entier non signé, a été le choix parfait pour les gens qui voulaient vraiment travailler avec 8 bits octets.
Et donc, les développeurs ont adopté la nouvelle outils et a commencé la construction de bibliothèques que de manière expressive de l'état qu'ils acceptent de 8 bits de l'octet de séquences, comme std::uint8_t*
, std::vector<std::uint8_t>
ou autrement.
Mais, peut-être avec un très profond de la pensée, le comité de normalisation a décidé de ne pas exiger la mise en œuvre de l' std::char_traits<std::uint8_t>
donc interdire aux développeurs de facilement et de façon portable de l'instanciation, disons, std::basic_fstream<std::uint8_t>
et facilement la lecture de std::uint8_t
s en tant que données binaires. Ou peut-être, certains d'entre nous ne se soucient pas le nombre de bits dans un octet, et sont heureux avec elle.
Mais malheureusement, deux mondes s'affrontent et parfois, vous avez à prendre des données en char*
et passer à une bibliothèque qui attend std::uint8_t*
. Mais attendez, vous dites, n'est-ce pas char
variable bit et std::uint8_t
est fixé à 8? Il va entraîner une perte de données?
Ainsi, il est intéressant Standardese sur cette. L' char
défini pour contenir exactement un byte et octet est le plus bas adressable partie de la mémoire, donc il ne peut pas être un type avec peu de largeur moindre que celle de l' char
. Ensuite, il est défini pour être en mesure de tenir UTF-8 unités de code. Cela nous donne le minimum de 8 bits. Alors maintenant, nous avons une définition de type qui est nécessaire pour être 8 bits de large et un type qui est au moins 8 bits de large. Mais y at-il des alternatives? Oui, unsigned char
. Rappelez-vous que ce paramètre de char
est mise en œuvre définies. Tout autre type? Heureusement, pas de. Tous les autres types intégraux ont besoin des plages qui sont à l'extérieur de 8 bits.
Enfin, std::uint8_t
est facultative, ce qui signifie que la bibliothèque qui utilise ce type ne compilera pas si elle n'est pas définie. Mais si ça compile? Je peux dire avec un grand degré de confiance que cela signifie que nous sommes sur une plate-forme avec 8 bits, octets et CHAR_BIT == 8
.
Une fois que nous avons cette connaissance, que nous avons octets de 8 bits, qui std::uint8_t
est mis en application comme char
ou unsigned char
, peut-on supposer que nous pouvons faire, reinterpret_cast
de char*
de std::uint8_t*
, et vice-versa? Est-il portable?
C'est là que mon Standardese les compétences en lecture à me manquer. J'ai lu en toute sécurité provenant des pointeurs ([basic.stc.dynamic.safety]
) et, comme je le comprends, le suivant:
std::uint8_t* buffer = /* ... */ ;
char* buffer2 = reinterpret_cast<char*>(buffer);
std::uint8_t buffer3 = reinterpret_cast<std::uint8_t*>(buffer2);
est sûr, si nous n'avons pas touch buffer2
. Corrigez-moi si je me trompe.
Donc, étant donné les conditions suivantes:
CHAR_BIT == 8
-
std::uint8_t
est défini.
Est-il portable et coffre-fort de jeter char*
et std::uint8_t
d'avant en arrière, en supposant que nous travaillons avec des données binaires et l'absence de signe d' char
n'a pas d'importance?
Je vous serais reconnaissant de références à la Norme avec des explications.
EDIT: Merci, Jerry Cercueil. Je vais ajouter la citation de la Norme ([base.lval], §3.10/10):
Si un programme tente d'accéder à la valeur d'un objet par l'intermédiaire d'un glvalue d'autre que l'une des types suivants le comportement est indéfini:
...
- un char ou unsigned char type.
EDIT2: Ok, d'aller plus loin. std::uint8_t
n'est pas garanti d'être un typedef d' unsigned char
. Il peut être mis en œuvre prolongée de type entier non signé et étendu entier non signé types ne sont pas inclus dans le §3.10/10. Que faire maintenant?
EDIT3: Supprimé.
EDIT4: je crois que je vais garder cette question ouverte, parce que je ne suis pas sûr à 100% et il soulève un débat intéressant.
EDIT5: Ok, faire reinterpret_cast
n'est pas dangereux en soi. C'est quand vous essayez de déférence pointeur résultant de la que les problèmes commencent à surgir. Donc nous avons besoin d'observer les cas possibles d'utilisation des données binaires en tant que char*
et std::uint8_t*
. Une erreur de vérification a été omis par souci de concision.
Cas 1:
#include <cassert>
#include <fstream>
int main()
{
int value1 = 1234;
{
std::ofstream outputstream("test.bin", std::ios_base::binary);
outputstream.write(reinterpret_cast<char*>(&value1), sizeof(value1));
}
int value2 = 1234;
{
std::ifstream inputstream("test.bin", std::ios_base::binary);
inputstream.read(reinterpret_cast<char*>(&value2), sizeof(value2));
}
assert(value1 == value2);
}
Cas 2:
#include <cstdint>
#include <cassert>
#include <fstream>
class uint8_char_traits
{
/* ... */
};
int main()
{
int value1 = 1234;
{
std::basic_ofstream<std::uint8_t, uint8_char_traits> outputstream("test.bin", std::ios_base::binary);
outputstream.write(reinterpret_cast<std::uint8_t*>(&value1), sizeof(value1));
}
int value2 = 1234;
{
std::basic_ifstream<std::uint8_t, uint8_char_traits> inputstream("test.bin", std::ios_base::binary);
inputstream.read(reinterpret_cast<std::uint8_t*>(&value2), sizeof(value2));
}
assert(value1 == value2);
}
Cas 3:
#include <cstdint>
#include <cassert>
#include <fstream>
#include <memory>
#include <algorithm>
void library_function1(char* buffer, std::size_t size);
void library_function2(std::uint8_t* buffer, std::size_t size);
int main()
{
const std::size_t size = sizeof(int);
std::unique_ptr<char[]> data(new char[size]);
{
std::ifstream inputstream("test.bin", std::ios_base::binary);
inputstream.read(data.get(), size);
}
library_function1(data.get(), size);
library_function2(reinterpret_cast<std::uint8_t*>(data.get()), size);
}
void library_function1(char* buffer, std::size_t size)
{
//Subcase 1:
int value1 = *reinterpret_cast<int*>(buffer);
//Subcase 2:
int value2 = 0;
std::memcpy(&value2, buffer, sizeof(int));
//Subcase 3:
int value3 = 0;
char* ptr = reinterpret_cast<char*>(&value3);
std::copy(buffer, buffer + sizeof(int), ptr);
assert((value1 == value2) && (value2 == value3));
}
void library_function2(std::uint8_t* buffer, std::size_t size)
{
//Subcase 4:
int value1 = *reinterpret_cast<int*>(buffer);
//Subcase 5:
int value2 = 0;
std::memcpy(&value2, buffer, sizeof(int));
//Subcase 6:
int value3 = 0;
std::uint8_t* ptr = reinterpret_cast<std::uint8_t*>(&value3);
std::copy(buffer, buffer + sizeof(int), ptr);
assert((value1 == value2) && (value2 == value3));
}
Ces cas d'utilisation devrait permettre de donner une plus claire intention de ma question.