11

В сети и здесь, на SO, довольно много материала о работе с кодировками и локалями. Но почему-то нет вразумительной информации о кодировке строковых констант (литералов).

const char * text = "Какая ваша кодировка?";

Чем определяется кодировка строковых литералов: кодировкой файла с исходником, опциями компилятора или чем-то еще? Что на эту тему говорит стандарт? Как достоверно узнать кодировку строковых литералов на этапе компиляции (может макросы какие есть)? А в рантайме?

Cerbo
  • 6,863

3 Answers3

7

Данный ответ посвящён практике применения Microsoft Visual Studio.

Хорошего кроссплатформенного решения я, к сожалению, не знаю.

  • Для файлов-исходников в неюникодных кодировках, строка интерпретируется как строка в ANSI-кодировке. Для русскоязычных систем это CP 1251. Это выполняется даже если для файла исходника задекларирована другая кодировка! При компиляции на системе с другой ANSI-кодировкой результат будет отличаться.
  • Для файлов-исходников в юникодных кодировках, кодировка строки также Unicode.
    • Если при этом строка «узкая» (то есть, типа char[]), то строка будет сконвертирована в ANSI-кодировку с теми же последствиями.
    • Если при этом строка «широкая» (то есть, типа wchar_t[]), то строка останется как есть, то есть, правильной.

Это означает, что нужно использовать

  • либо юникодную кодировку исходников + широкие строки,
  • либо узкие строки в ANSI-кодировке с потерей компилируемости под нерусскими системами,
  • либо кодировать литералы числовыми константами.

Как выяснилось в результате длительной дискуссии с @ixSci и @Abyx, Visual Studio 2015 ведёт себя немного по-другому: в случае кодировки файла utf-8 и узкой строки, в строке таки окажется utf-8. Но в случае кодировки файла utf-16 (ucs-2), результат прежний: попытка сконвертировать в ANSI (которая может и провалиться).


Обновление:

Visual Studio 2015 и старше конвертирует строки во внутренний формат. Конвертация определяется набором символов исходного файла (source character set), из которого символы конвертируются во внутренний формат (на текущий момент это utf-8). Набор символов, т. е., по сути, кодировка исходного файла определяется следующим образом

  • Если файл содержит BOM, этим самым однозначно определяется его кодировка.
  • В противном случае, если файл выглядит как файл в utf-16 (Visual Studio производит прикидку этого по первым восьми байтам) big/little endian, то это считается его кодировкой.
  • В противном случае, если при компиляции (или в настройках проекта) указан ключ /source-charset, указанная в этом ключе кодировка и считается кодировкой входного файла.
  • В противном случае, кодировкой входного файла считается системная кодовая страница (т. е., ANSI). Обратите внимание, что это не самый лучший вариант, т. к. одни и те же байты исходников при этом могут по-разному интерпретироваться на разных системах.

Следующая важная вещь — это набор символов времени выполнения (execution character set). Это, по сути, кодировка, в которую будут сконвертированы узкие строковые/символьные литералы (объявленные без префикса) при записи в выполняемый файл, и которые программа «увидит», если просканирует строки по байтам. Если при компиляции указан ключ /execution-charset, это и будет искомым набором символов. Если нет, в качестве набора символов используется текущая кодовая страница.

Обратите внимание, что вы можете указать ключ /utf-8, который установит одним махом оба набора символов в utf-8.

Ещё один набор символов — широкий набор символов времени выполнения (wide execution character set) — используется для конвертации широких символьных/строковых литералов. Он в MS Visual Studio неизменен и совпадает с utf-16.

Документация: Visual C++ Team Blog / New Options for Managing Character Sets in the Microsoft C/C++ Compiler.

VladD
  • 206,799
5

Кодировка такой строки:

const char * text = "Какая ваша кодировка?";

Определяется исключительно кодировкой файла, в котором содержится данное определение. Начиная с C++11 можно явно указать, что хотите видеть строку в unicode, для этого есть следующие префиксы: u(UCS-2), u8(UTF-8) и U(UCS-4). Пример:

const char * text = u8"Какая ваша кодировка?";

Теперь текст гарантировано содержит строку в utf-8, вне зависимости от компилятора(если он поддерживает стандарт, разумеется)

Узнать кодировку макросами или другим «волшебством», насколько я знаю, — нельзя. Обычный совет, это не использовать не-английский текст в исходниках вообще. Так будет меньше головной боли, храните текст в отдельных файлах и читайте их по мере надобности.

ixSci
  • 23,825
  • "Определяется исключительно кодировкой файла" это требование стандарта или так принято у всех компиляторов? – Cerbo Oct 26 '15 at 09:05
  • @Cerbo, стандарт не предъявляет подобных требований, он отдаёт это на откуп компиляторов(implementation defined). Но все компиляторы делают именно так, т.к. ни один не будет переводить исходник в какую-то внутреннюю кодировку, т.к. он не может знать из какой кодировки ему переводить. Поэтому он просто будет использовать кодировку исходного файла. – ixSci Oct 26 '15 at 09:23
  • @ixSci: К сожалению, это не так для MS Visual Studio. – VladD Oct 26 '15 at 09:42
  • @VladD, что конкретно не так? – ixSci Oct 26 '15 at 09:42
  • @ixSci: Например, если вы указываете Visual Studio, что кодировка файла — CP866, то литералы будут всё равно компилироваться как будто кодировка файла CP1251 (на русскоязычной Windows). Выглядит как баг, конечно. – VladD Oct 26 '15 at 09:45
  • @VladD, кодировка тут будет ANSI, а вот codepage никогда не сохраняется в файле, поэтому когда компилятор «ест» файл он должен выбрать какой-то codepage для исходника, вполне естественно, что он берёт системный CP1251. – ixSci Oct 26 '15 at 09:47
  • Относительно gcc могу подтвердить, что всюду где я его использовал (linux, windows, hp-ux, bsd, sun-os, solaris, aix), он ничего не делал с такими байтам. Что записали в редакторе (cp866, cp1251, iso8859-5, koi-8r, utf-8 (с другими дела вроде бы не имел)), то и оказывается в константах оттранслированного модуля. – avp Oct 26 '15 at 10:18
  • 2
    @Cerbo, не надо править мои ответы. – ixSci Oct 26 '15 at 10:27
  • @ixSci Почему же? Я думал вы имеете в виду gcc. Важно знать к ками компиляторам относится описанное поведение, не правда ли. – Cerbo Oct 26 '15 at 10:32
  • @Cerbo, потому что я написал то, что написал. Я не имел в виду gcc, я имел в виду все компиляторы. – ixSci Oct 26 '15 at 10:33
  • @ixSci В таком случае ваш ответ некорректен как минимум в отношении MSVC и вводит в заблуждение, следовательно его нужно минусовать либо править. Такие у нас на SO правила в роде с утра были. Поэтому лучше указать компиляторы. – Cerbo Oct 26 '15 at 10:40
  • @Cerbo, мой ответ вполне корректен, что в нём не так? – ixSci Oct 26 '15 at 10:42
  • @ixSci В том что он не относится ко все компиляторам! Я сужу с точки зрения начинающего: как имено будет работать его компилятор, на что ему ориентироваться. – Cerbo Oct 26 '15 at 10:46
  • @Cerbo, ещё раз — мой ответ относится ко всем компиляторам, включая MSVC. Если Вы ссылаетесь на то, что указал Vlad, то его ремарка не имеет смысла, о чём я ему написал в чате. Совершенно не имеет смысла говорить о codepage у компилятора, т.к. он не транслирует строки, за исключением тех, что он обязан по стандарту(u8, u, U), поэтому если вы сохраните фалй в KOI8, то строка запишет тот набор байт, который можно будет перевести в человеческий текст имено с codepage KOI8. – ixSci Oct 26 '15 at 10:49
  • @ixSci: У MSVC есть явное указание кодировки файла. – VladD Oct 26 '15 at 11:04
  • @VladD, нет, у MSVC нет явного указания кодировки файла. – ixSci Oct 26 '15 at 11:16
  • @ixSci: http://i.stack.imgur.com/UV9WG.png – VladD Oct 26 '15 at 11:41
  • @VladD, это диалог сохранения файла в IDE. Никаким боком эта информация к компилятору не относится. Т.е. это фактически конвертация исходника из текущей кодировку, в какую-то другую. Не более того. – ixSci Oct 26 '15 at 11:54
  • @ixSci Что вы скажете по поводу этого http://gcc.gnu.org/onlinedocs/gcc/Preprocessor-Options.html#index-fexec-charset-1208 – Cerbo Oct 29 '15 at 07:50
  • @Cerbo, а что я должен сказать по этому поводу? В gcc есть настройки, которые позволяют что-то куда-то транслировать. Вы видите связь этих параметров с вопросом? Я — нет. – ixSci Oct 29 '15 at 09:29
3

GCC имеет опции препроцессора для указания кодировок строковых констант и исходников.

-fexec-charset=charset Устанавливает кодировку строковых и символьных констант исполняемого файла. По-умолчанию используется UTF-8. charset может быть именем кодировки которое поддерживается библиотекой iconv установленой в системе.

-fwide-exec-charset=charset Устанавливает кодировку, "широких" строковых и символьных констант исполняемого файла. По-умолчанию используется UTF-32 или UTF-16 в зависимости от "ширины" типа wchar_t. Как и вслучае -fexec-charset, charset может быть именем кодировки которое поддерживается библиотекой iconv установленой в системе.

-finput-charset=charset Указывает компилятору кодировку входных файлов. Кодировка используется для перекодирования входных файлов в кодировку используемую компилятором GCC. По-умолчанию charset равен системеной кодировке, если системная кодировка не задана или GCC не может ее получить, то в качестве charset используется UTF-8. В настоящий момент опция имеет преимущество в случае возникновения конфликтов. charset может быть именем кодировки которое поддерживается библиотекой iconv установленой в системе.

Cerbo
  • 6,863
  • Это документация вводит в заблуждение. И она противоречит экспериментам: если не задавать input-charset, то никаких преобразований не будет сделано — стройка будет записана как есть. Либо документация GCC врёт, либо написана косноязычно и имеется в виду нечто другое – ixSci Oct 30 '15 at 06:37