Печать строк UTF-8 с помощью printf — широкие и многобайтовые строковые литералы

В подобных утверждениях, когда оба вводятся в исходный код с одинаковой кодировкой (UTF-8) и локаль настроена правильно, есть ли между ними какая-либо практическая разница?

printf("ο Δικαιοπολις εν αγρω εστιν\n");
printf("%ls", L"ο Δικαιοπολις εν αγρω εστιν\n");

И, следовательно, есть ли причина предпочесть один другому при выводе? Я предполагаю, что второй работает немного хуже, но есть ли у него какие-либо преимущества (или недостатки) по сравнению с многобайтовым литералом?

РЕДАКТИРОВАТЬ: нет проблем с печатью этих строк. Но я не использую широкие строковые функции, потому что хочу также использовать printf и т.д. Итак, вопрос в том, отличаются ли эти способы печати (учитывая описанную выше ситуацию), и если да, то есть ли у второго какое-либо преимущество?

EDIT2: после комментариев ниже я знаю, что эта программа работает, что, как мне казалось, было невозможно:

int main()
{
    setlocale(LC_ALL, "");
    wprintf(L"ο Δικαιοπολις εν αγρω εστιν\n");  // wide output
    freopen(NULL, "w", stdout);                 // lets me switch
    printf("ο Δικαιοπολις εν αγρω εστιν\n");    // byte output
}

EDIT3: я провел дополнительное исследование, изучив, что происходит с этими двумя типами. Возьмите более простую строку:

wchar_t *wides = L"£100 π";
char *mbs = "£100 π";

Компилятор генерирует другой код. Широкая строка:

.string "\243"
.string ""
.string ""
.string "1"
.string ""
.string ""
.string "0"
.string ""
.string ""
.string "0"
.string ""
.string ""
.string " "
.string ""
.string ""
.string "\300\003"
.string ""
.string ""
.string ""
.string ""
.string ""

В то время как второй:

.string "\302\243100 \317\200"

И, глядя на кодировки Unicode, вторая — это простая UTF-8. Широкое представление символов — UTF-32. Я понимаю, что это будет зависеть от реализации.

Так что, возможно, широкосимвольное представление литералов более переносимо? Моя система не будет печатать кодировки UTF-16/UTF-32 напрямую, поэтому она автоматически преобразуется в UTF-8 для вывода.


person teppic    schedule 20.03.2013    source источник
comment
Вы сказали, что оба примера вводятся с UTF-8. Во второй строке примера, если этот текст на самом деле является кодировкой UTF-8, а не широкой кодировкой, то вам, вероятно, не следует использовать префикс L, и поэтому вы просто используете %s, а не %ls. Или я все еще неправильно понимаю вопрос.   -  person Adrian McCarthy    schedule 20.03.2013
comment
@AdrianMcCarthy - обе строки в исходном коде имеют кодировку UTF-8, да. Но строковый литерал всегда многобайтовый. Литерал символьной строки — это последовательность из нуля или более многобайтовых символов, заключенная в двойные кавычки, как в xyz. Широкий строковый литерал такой же, за исключением префикса буквы L. из стандарта.   -  person teppic    schedule 20.03.2013
comment
AFAIR, любые символы, не входящие в базовый исходный набор символов (который является подмножеством US-ASCII-7), вызывают поведение, определяемое реализацией, т. е. все, что здесь обсуждается, эффективно зависит от используемого компилятора. Если вы действительно хотите перестраховаться (и переносить), вам придется прибегнуть к \u... и \U...   -  person DevSolar    schedule 20.03.2013
comment
Это вполне может быть в области реализации. Что я пытаюсь сделать, так это постоянно переключаться на широкое представление символов, но придерживаться обычных функций stdio для вывода, чтобы не нарушать совместимость со всеми вещами, которые ожидают, что они будут работать. Мне действительно просто интересно, следует ли мне придерживаться только многобайтовых литералов (как указано выше) или есть причина использовать широкие литералы. Это трудно объяснить, и я не очень хорошо справляюсь!   -  person teppic    schedule 20.03.2013
comment
utf8everywhere.org в значительной степени убеждает в том, что использование L не рекомендуется, особенно на платформе Windows.   -  person Pavel Radzivilovsky    schedule 21.03.2013


Ответы (1)


printf("ο Δικαιοπολις εν αγρω εστιν\n");

выводит строковый литерал (const char*, специальные символы представлены как многобайтовые символы). Хотя вы можете увидеть правильный вывод, есть и другие проблемы, с которыми вы можете столкнуться при работе с такими символами, отличными от ASCII. Например:

char str[] = "αγρω";
printf("%d %d\n", sizeof(str), strlen(str));

выводит 9 8, так как каждый из этих специальных символов представлен 2 chars.

При использовании префикса L у вас есть литерал, состоящий из широких символов (const wchar_t*), а спецификатор формата %ls приводит к преобразованию этих широких символов в многобайтовые символы (UTF-8). Обратите внимание, что в этом случае локаль должна быть установлена ​​соответствующим образом, иначе это преобразование может привести к недопустимому результату:

#include <stdio.h>
#include <wchar.h>
#include <locale.h>

int main(void)
{
    setlocale(LC_ALL, "");
    printf("%ls", L"ο Δικαιοπολις εν αγρω εστιν");
    return 0;
}

но в то время как некоторые вещи могут стать более сложными при работе с широкими символами, другие вещи могут стать намного проще и понятнее. Например:

wchar_t str[] = L"αγρω";
printf("%d %d", sizeof(str) / sizeof(wchar_t), wcslen(str));

выведет 5 4, как и следовало ожидать.

Если вы решили работать с широкими строками, wprintf можно использовать для печати широких символов. напрямую. Здесь также стоит отметить, что в случае консоли Windows режим перевода stdout должен быть явно установлен в один из режимов Unicode, вызвав _setmode:

#include <stdio.h>
#include <wchar.h>

#include <io.h>
#include <fcntl.h>
#ifndef _O_U16TEXT
  #define _O_U16TEXT 0x20000
#endif

int main()
{
    _setmode(_fileno(stdout), _O_U16TEXT);
    wprintf(L"%s\n", L"ο Δικαιοπολις εν αγρω εστιν");
    return 0;
}
person LihO    schedule 20.03.2013
comment
Это я :) wprintf тоже конвертирует в мультибайт, но меня интересуют стандартные функции. - person teppic; 20.03.2013
comment
@teppic: Смотрите мой ответ сейчас. Это должно быть, наконец, более удовлетворительным, я думаю :) - person LihO; 20.03.2013
comment
UTF-16 неширокий, и очень жаль, что этот миф все еще существует. Существует более 2^16 символов Unicode, и UTF-16 кодирует их с переменной шириной, состоящей из одной или двух 16-битных единиц кода. Если вы хотите широкий, вы должны прибегнуть к UTF-32. Давайте не будем попадать в ловушку, думая, что n бита должно быть достаточно для всех, снова. - person DevSolar; 20.03.2013
comment
@DevSolar: я удалил сбивающий с толку UTF-16. - person LihO; 20.03.2013
comment
Спасибо. Я профессионально работаю над вещами, тесно связанными с Unicode, и мне очень грустно видеть, сколько полусырых знаний по этому вопросу вокруг. UTF-16 является прекрасным примером: фактически многобайтовая кодировка со встроенными нулевыми байтами. Удивительно, как много программного обеспечения, поддерживающего Unicode, можно заставить отвернуться от древнегреческого языка, расширенного CJK или одного-двух иероглифов. Не говоря уже о комбинировании персонажей и других подобных тонкостях. ;-) - person DevSolar; 20.03.2013
comment
@DevSolar - я впечатлен, что вы признали это древнегреческим (если только это не совпадение) :) - person teppic; 20.03.2013
comment
@LihO - я согласен с тем, что вы сказали. Я столкнулся с проблемами с такими функциями, как strlen некоторое время назад, прежде чем я узнал о широких символах. Для чего-то внутреннего я бы использовал функции широких строк, но в тот момент, когда вы используете функцию вывода широких строк для stdout, вы не можете снова использовать какие-либо обычные - поэтому я не использую wprintf. Я ожидаю, что ответ, по сути, не имеет значения, пока установлена ​​локаль, и вам не нужно каким-либо образом обрабатывать литерал. - person teppic; 20.03.2013
comment
@teppic: Совпадение, признаю. Я только что назвал пару алфавитов за пределами 16-битного диапазона. Что касается stdout, испорченного широким выводом, имейте в виду, что вы можете сбросить широкую ориентацию через fwide( stdout, -1 ). - person DevSolar; 21.03.2013
comment
@DevSolar - fwide можно использовать только для первоначальной установки потока, к сожалению, он не может изменить его после ориентации. - person teppic; 21.03.2013
comment
@teppic: Черт... Сноска 287, пропустил это. Ну, вы все еще можете использовать freopen... хотя это кажется немного деспотичным. - person DevSolar; 21.03.2013
comment
@teppic: Итак, я пропустил сноску 287 стандарта C99, а вы пропустили сноску 232. ;-) Цитирую: функция freopen в основном используется для изменения файла, связанного со стандартным текстовым потоком (stderr, stdin или stdout), поскольку эти идентификаторы не обязательно должны быть модифицируемыми значениями lvalue, в которые возвращается значение, возвращаемое функцией fopen. могут быть назначены. С чем-то вроде freopen( "test", "r", stdin ) вы получаете stdin для чтения из файла, что полезно, например, для тестирование stdin функций чтения. - person DevSolar; 21.03.2013
comment
@DevSolar - это для перенаправления файловых дескрипторов на имя файла? Вы бы назвали это чем-то вроде freopen("/tmp/output", "w", stdout); (я хочу оставить стандартный вывод как стандартный вывод) - person teppic; 21.03.2013
comment
@teppic: если имя файла является нулевым указателем, функция freopen пытается изменить режим потока на режим, указанный в режиме, как если бы было использовано имя файла, связанного в данный момент с потоком. Какие изменения режима разрешены (если есть) и при каких обстоятельствах определяется реализацией. То есть, определяется реализацией, но стоит попробовать. - person DevSolar; 21.03.2013
comment
@DevSolar: я уверен, что пробовал, но сейчас попробую - спасибо. Если это не сработает, я специально опубликую новый вопрос. Я, очевидно, не сделал - это работает! В линуксе то есть. - person teppic; 21.03.2013