Littéraux Unicode dans Visual C++
-
26-12-2019 - |
Question
Considérez le code suivant :
#include <string>
#include <fstream>
#include <iomanip>
int main() {
std::string s = "\xe2\x82\xac\u20ac";
std::ofstream out("test.txt");
out << s.length() << ":" << s << std::endl;
out << std::endl;
out.close();
}
Sous GCC 4.8 sous Linux (Ubuntu 14.04), le fichier test.txt
contient ceci :
6:€€
Sous Visual C++ 2013 sous Windows, il contient ceci :
4:€\x80
(Par '\x80', j'entends le caractère unique de 8 bits 0x80).
J'ai été complètement incapable de faire en sorte que l'un ou l'autre des compilateurs génère un €
personnage utilisant std::wstring
.
Deux questions:
- Que pense exactement le compilateur Microsoft avec le
char*
littéral?Il fait évidemment quelque chose pour l'encoder, mais ce n'est pas clair. - Quelle est la bonne façon de réécrire le code ci-dessus en utilisant
std::wstring
etstd::wofstream
pour qu'il produise deux€
personnages?
La solution
C'est parce que vous utilisez \u20ac
qui est un caractère littéral Unicode dans une chaîne ASCII.
Encodages MSVC "\xe2\x82\xac\u20ac"
comme 0xe2, 0x82, 0xac, 0x80,
qui est de 4 caractères étroits.Il code essentiellement \u20ac
comme 0x80 car il mappait le caractère euro au standard 1252 page de code
GCC convertit le littéral Unicode /u20ac
à la séquence UTF-8 de 3 octets 0xe2, 0x82, 0xac
donc la chaîne résultante se termine par 0xe2, 0x82, 0xac, 0xe2, 0x82, 0xac
.
Si tu utilises std::wstring = L"\xe2\x82\xac\u20ac"
il est codé par MSVC comme 0xe2, 0x00, 0x82, 0x00, 0xac, 0x00, 0xac, 0x20
qui contient 4 caractères larges, mais comme vous mélangez un UTF-8 créé à la main avec un UTF-16, la chaîne résultante n'a pas beaucoup de sens.Si vous utilisez un std::wstring = L"\u20ac\u20ac"
vous obtenez 2 caractères Unicode dans une chaîne large comme vous vous en doutez.
Le problème suivant est que les ofstream et wofstream de MSVC écrivent toujours en ANSI/ASCII.Pour le faire écrire en UTF-8, vous devez utiliser <codecvt>
(VS 2010 ou version ultérieure) :
#include <string>
#include <fstream>
#include <iomanip>
#include <codecvt>
int main()
{
std::wstring s = L"\u20ac\u20ac";
std::wofstream out("test.txt");
std::locale loc(std::locale::classic(), new std::codecvt_utf8<wchar_t>);
out.imbue(loc);
out << s.length() << L":" << s << std::endl;
out << std::endl;
out.close();
}
et pour écrire UTF-16 (ou plus précisément UTF-16LE) :
#include <string>
#include <fstream>
#include <iomanip>
#include <codecvt>
int main()
{
std::wstring s = L"\u20ac\u20ac";
std::wofstream out("test.txt", std::ios::binary );
std::locale loc(std::locale::classic(), new std::codecvt_utf16<wchar_t, 0x10ffff, std::little_endian>);
out.imbue(loc);
out << s.length() << L":" << s << L"\r\n";
out << L"\r\n";
out.close();
}
Note:Avec UTF-16, vous devez utiliser un mode binaire plutôt que le mode texte pour éviter toute corruption, nous ne pouvons donc pas utiliser std::endl
et je dois utiliser L"\r\n"
pour obtenir le comportement correct du fichier texte de fin de ligne.