Как посчитать количество многобайтовых символов?

Я хотел бы получить 5 вместо 10 для следующей программы. Кто-нибудь знает, как исправить код для подсчета количества многобайтовых символов? Благодарю.

/* vim: set noexpandtab tabstop=2 shiftwidth=2 softtabstop=-1 fileencoding=utf-8: */
#include <stdlib.h>
#include <string.h>
#include <wchar.h>
#include <locale.h>

size_t nchars(const char *s) {   
    size_t charlen, chars;
    mbstate_t mbs;

    chars = 0;
    memset(&mbs, 0, sizeof(mbs));
    while (
            (charlen = mbrlen(s, MB_CUR_MAX, &mbs)) != 0
            && charlen != (size_t)-1
            && charlen != (size_t)-2
            ) {
        s += charlen;
        chars++;
    }   

    return (chars);
}   

int main() {
    setlocale(LC_CTYPE, "en_US.utf8");
    char * text = "öçşğü";

    printf("%zu\n", nchars (text));

    return 0;
}
$ ./main.exe 
10

1 ответ

Вторичная проблема: вы должны инициализировать объект типа mbstate_t через mbsinit функция, а не memcpy, Все байты-ноль mbsinit не гарантируется представление начального состояния сдвига или даже любого допустимого состояния сдвига.

Основная проблема с вашим кодом заключается в том, что он анализирует строковый литерал, представление которого определяется во время компиляции на основе фактической кодировки этих символов в исходном файле, их представления в исходном наборе символов компилятора и набор символов выполнения, выбранный компилятором. Вы не можете выбрать LC_CTYPE произвольно - оно должно соответствовать данным, чтобы функции преобразования в mb работали как задумано.

C не определяет механизм для программы, чтобы определить язык, чей LC_TYPE соответствует набору символов выполнения и даже не требует наличия такой локали. Документация вашего компилятора должна описывать сопоставление между исходными символами и исполняющими символами, однако, возможно, с точки зрения локали или хорошо известной кодировки, и она может даже описывать способ указать это. В документации вашего компилятора также может быть указан способ указать кодировку, которую он должен использовать для исходных файлов.

Кроме того, у вас есть дополнительная потенциальная проблема с Юникодом, которая может не совпадать между тем, что вы, человек, считаете "персонажем", и символами Юникода, с которыми он представлен. Как правило, это касается символов с диакритическими знаками, таких как акценты. Многие из наиболее часто используемых из них имеют односимвольное "составное" представление, но также могут быть представлены в виде последовательности базового символа плюс один или несколько комбинирующих символов.

mbrlen() вряд ли будет различать базовые и комбинируемые символы, поэтому даже без какой-либо путаницы в кодировании ваш наблюдаемый результат может возникнуть из-за того, что символы представлены в разложенном виде в исходных файлах или преобразованы в эту форму компилятором.

Суть в том, что ваша программа зависит от характеристик среды и реализации, которые не указаны в стандарте, поэтому она может вести себя по-разному в разных реализациях, что, как кажется, и есть наблюдение. Ваше конкретное наблюдение может возникнуть, например, из исходного файла, кодируемого в UTF-8, компилятор предполагает, что он будет закодирован в однобайтовой кодировке, такой как ISO-8859-1, а компилятор использует UTF-8. для его набора символов исполнения.

Ваш подход может работать без изменений, если вы убедитесь, что компилятор интерпретирует исходный файл в соответствии с фактической кодировкой этого файла и использует UTF-8 в качестве своего набора символов выполнения. В качестве альтернативы, в C11 или более поздней версии вы можете убедиться, что кодированием этой конкретной строки является UTF-8, используя литерал UTF-8, например, так:

char * text = u8"öçşğü";

Это заботится только о кодировании на стороне выполнения, как бы то ни было. Вам все еще нужно сопоставить кодировку исходного файла с фактической кодировкой, ожидаемой компилятором, и вы все равно можете испытывать различия между предварительно составленными и разложенными символами.

Другие вопросы по тегам