Немного теории
Кодировка символов в памяти и отображения символов на устройстве, это не одно и тоже, кодировка в памяти может быть разной даже в контексте одной программы, а отображение должно любой контекст интерпретировать правильно, и опять же определить контекст полагается программисту, а не устройству которое будет отображать символ.
Современные программисты также как и учебники программирования, стали забывать что программист должен контролировать все устройства ввода вывода, а не операционная система. в связи с этим начинающие программисты не понимают почему программа которая написана корректно с точки зрения алгоритмов работает не правильно, то есть не отображает символы так как задумал программист.
Немного практики
В современном мире, почти все кодировки и отображение контролирует операционная система, чаще всего в Windows отображаются кракозябры, а в Linux все хорошо, и новичкам советуют ставить Linux и не «париться», это в корне не верный подход, На самом деле все дело в настройках операционной системе на которой вы работаете. В Linux кодировка и отображение кодировки настроено на стандарт unicode utf-8 или unicode utf-32le (смотря какой конкретный дистрибутив) и оба стандарта совместимы, а в Windows исторически сложилось Legacy разработка, настройка кодировки отображения идет в console cp866(Если вы используете русский диструбутив windows) а winapi использует code pages 1251(На русском дистрибутиве) в связи с этим у нас несколько путей решения:
- Сохранять исходные коды в cp866 чтобы компилятор или интерпретатор «вшивал» код символов для conosle windows по умолчанию.
- Подменять шрифты где символы соответствуют кодировке cp866 то есть символ «А» в шрифте графически был на месте символа который выглядит как не «А». Например в нумерации кодировки для символа «А» cp866 это код 0x80, а для символа windows code pages 1251 0xc0 таким образом именно в шрифте нужно перерисовать символы где должны отображаться буква «А».
- Подменять кодировку программно нужно написать небольшую функцию(процедуру) которая перехватывает весь поток вывода и подменяет символы на ту кодировку которая отображает символы по умолчанию это cp866.
- Самый рациональный и простой метод это переключить операционную систему в нужную вам кодировку, для console windows это будет команда в самой консоле chcp 1251 для кодировки windows code pages 1251, а для unicode utf-8 соответственно chcp 65001 (список всех кодировок windows) для языка C++ можно написать команду после main
system("chcp 1251");
Все эти советы актуальны для любой платформы (переключение кодировки самой консоли читайте в документации операционной системы), есть и более хитрые или изящные способы кодирования символов, но в данной статье-заметке кодировки рассмотрены с точки зрения операционных систем.
P. S. По мимо обсуждения статьи в комментариях буду рад, пообщаться в telegram: @almost_human
Комментарии (20)
telhin
09.06.2017 16:51+1Была подобная проблема с выводом системных ошибок на Windows формата «Ошибка: файл не найден» <=> «Error: file not found».
Ошибки форматировались в зависимости от используемой локали, но выбирали cp1251 вместо консольной cp866.
#ifdef _WIN32 string GetLastErrorString() { DWORD err = GetLastError(); char* msg_buf; FormatMessageA( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, err, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (char*)&msg_buf, 0, NULL); string msg = msg_buf; LocalFree(msg_buf); return msg; } void Win32Fatal(const char* function) { Fatal("%s: %s", function, GetLastErrorString().c_str()); } #endif
Решал через запрос «системной» кодировки через
GetACP();
А затем последующей установкой в консоли
SetConsoleCP(); SetConsoleOutputCP();
Вроде как получилось решение не зависящее от локали пользователя и исправляющее баг в русской локализации cp1251 <=> cp866.Ruckus
09.06.2017 16:53-1А японский сможете? Или хотябы немецкий? Может эмодзи?
За решение исключительно для русского, конечно, спасибо, но это не спасает.telhin
09.06.2017 17:01+1Это решение для системных ошибок, которые не верно форматируются из-за различия кодовых страниц у win32 api и консоли. Решение работает для всех локалей, у которых присутствует такой баг (кроме русского я не знаю), для остальных ничего не меняет.
Системные сообщения об ошибках хранятся в так называемых Message Tables и для русского языка они там уже в cp1251. Если хотите унификации и utf8, то начать надо с преобразования этих самых Message Tables, но это вопрос к мелкомягким, а сейчас я представил решение, которое работает на настоящее время.Ruckus
09.06.2017 17:13-1Да я не об ошибках, а о выводе в консоль сообщений Telegram хотябы. Там ники у некоторых на иврите и японском бывают и эмоджи повсюду, так ещё и говорят иногда не на русском. А для отладки часто приходится мониторить реалтайм. Я не могу этого добиться с текущими кодировками, не написав своего GUI?
telhin
09.06.2017 17:22+1Нужно в консоль поставить шрифт поддерживающий эмодзи и прочие приблуды, которые требуются, сказать консоли
system("chcp 65001");
и спокойно работать в Unicode кодировке.
Boctopr
09.06.2017 17:22Ваша задача более специфична чем просто вывод на консоль, консоль не поддерживает языки где письменность происходит справа налево (арабский и тд), или сверху вниз (японский и тд), а также консоль не обрабатывает управляющие коды
Ruckus
09.06.2017 17:26Мне достаточно видеть символы без кракозябр, что я по умолчанию получаю в других ОС. Я могу назвать это лишь очередной недоработкой.
telhin
09.06.2017 17:28-1А можно подробнее про поддержку управляющих кодов unicode. Я думал что это просто начало ASCII таблицы. Есть еще символы управляющие направлением письма?
Boctopr
09.06.2017 17:32Управляющие символы это точно такая же кодировка управлением потока вывода, а не отображения. Почитать можно в разных источниках, на википедию ведет эта ссылка.
tandzan
09.06.2017 16:54+1Искал консольный логгер под windows, способный выводить widechar и перенаправляющий сообщения в stdout и stderr, вышла грустная история. Boost.Log выводил иероглифы, при добавлении кодировки пропадали атрибуты-префиксы сообщений и переводы строк (wtf!?). Автор easylogging++ прямо завил о том, что не поддерживает widechar. И т.п. В результате для моей софтины родится такой вот велосипед. Дополнительное еще для ::GetLastError() приходится вызывать CharToOemBuffA.
Ruckus
У меня только один вопрос. Как вместе с кодировкой консоли изменить вывод всего ПО на utf8 и похоронить эту чёртову легаси из IBM866 и cp1251?
Boctopr
Это эволюционный процесс, кодировки рождаются мутируют и отмирают, кодировка utf-8 далеко не вершина идеала и эволюции, просто в данный момент времени она более удобна и предпочтительна, однажды кодировка utf-8 точно также будет для следующего поколения программистов legacy.
Ruckus
Это не меняет вопроса. Мне нужен весь вывод в одном стандарте, как мне этого добиться?
Вестимо, что UTF международен и в нём я могу выводить как русские и английские символы, так и японские иероглифы с эмоджи и вообще на сегодняшний день это международный стандарт дефакто, а дальше скорей всего будет развиваться, как вы и сказали, в сторону совместимого UTF32 и подобных.
Ruckus
Ох да, спасибо за минус, конечно я неправ, UTF не международный, а в cp1251 я могу вывости японский текст без особых проблем. И, конечно, при этом у меня вывод компилятора в кодировке IBM866 будет так же на русском отображаться без искажений, спасибо что сделали всё это возможным своим минусом.
Boctopr
К сожалению Unicode это всего лишь рекомендация к использованию кодировки и это стандарт дефакто, но это не законодательный стандарт, программисты вольны выбирать кодировку которая подходит под задачи программы например где то рационально использовать 7бит на символ. Отобразить японский в кодировке windows code pages 1251 конечно же невозможно, поэтому многие старые программы переключали кодировки во время выполнения
Ruckus
Как переключить кодировку консоли в момент исполнения программы и что после смены кодировки консоли будет с предыдущим тестом?
telhin
В статье приведен пример
system("chcp 1251");
меняет на горячую, предыдущий вывод не портит, консоль не перезапускает.Boctopr
Дело в том что кодировка Unicode также эволюционирует вместе с программным обеспечением на данный момент Unicode версии 9.0.0, например Unicode версии 1.0.0 не отображал многие естественные языки, многие графические знаки и эмодзи.
Ruckus
Вы меня совсем не слушаете?
Windows вот тоже эволюционирует, когда-то был 3.11 без plug&play и UTF, а сейчас уже 10 без UTF (хоть plug&play как-то работает, о качестве не говорю), думаю через пару лет выйдет ещё несколько наборов обновлений, которые будут всё так же ловить BSOD и не поддерживать UTF. Мне нужна консоль с многоязычностью и поддержкой вывода стандартного софта, что мне делать?
Вместо того, чтобы выдавать философфские фразы не о чём лучше бы сказали если решение и знаете ли вы его.
Boctopr
В статье написано что console windows возможно переключить в любую кодировку в том числе в unicode, в том числе в utf-7, utf-8, utf-16le,utf-16be, utf-32le, utf-32be, но к сожалению отображать абсолютно все символы unicode она не способна поскольку нет системных шрифтов поддерживающих unicode версии 9.0.0