Подтвердить что ты не робот

Каковы формальные и практические ограничения на значения в "struct lconv", описывающие локаль?

Фон

Стандарт C99, раздел 7.11, описывает заголовок <locale.h> и его содержимое. В частности, он определяет struct lconv и говорит, что:

[...] В локали "C" члены должны иметь значения, указанные в комментариях.

char *decimal_point;     // "."
char *thousands_sep;     // ""
char *grouping;          // ""
char *mon_decimal_point; // ""
char *mon_thousands_sep; // ""
char *mon_grouping;      // ""
char *positive_sign;     // ""
char *negative_sign;     // ""
char *currency_symbol;   // ""
char frac_digits;        // CHAR_MAX
char p_cs_precedes;      // CHAR_MAX
char n_cs_precedes;      // CHAR_MAX
char p_sep_by_space;     // CHAR_MAX
char n_sep_by_space;     // CHAR_MAX
char p_sign_posn;        // CHAR_MAX
char n_sign_posn;        // CHAR_MAX
char *int_curr_symbol;   // ""
char int_frac_digits;    // CHAR_MAX
char int_p_cs_precedes;  // CHAR_MAX
char int_n_cs_precedes;  // CHAR_MAX
char int_p_sep_by_space; // CHAR_MAX
char int_n_sep_by_space; // CHAR_MAX
char int_p_sign_posn;    // CHAR_MAX
char int_n_sign_posn;    // CHAR_MAX

Раздел 7.11.2.1 "Функция localeconv()" продолжает:

Элементы структуры с типом char * являются указателями на строки, любые из которых (кроме decimal_point) может указывать на "", чтобы указать, что это значение недоступно в текущая локаль или имеет нулевую длину. [...] Элементы с типом char являются неотрицательные числа, любые из которых могут быть CHAR_MAX, чтобы указать, что это значение не доступный в текущем регионе.

Далее обсуждается каждый из членов. Вы можете видеть 4 группы из 3 членов, одна представительская группа - p_cs_precedes, p_sep_by_space и p_sign_posn.

char p_cs_precedes
Установите значение 1 или 0, если значению currency_symbol предшествует или преуспевает значение для неотрицательной локально отформатированной денежной величины.

char p_sep_by_space
Установите значение, указывающее на разделение currency_symbol, знаковая строка, а также значение для неотрицательного формата, количество.

char p_sign_posnУстановите значение, указывающее позиционирование позитивного_сигнала для неотрицательное локально отформатированное денежное количество.

Даны детали интерпретации p_sign_posn; они не являются существенными для этого вопроса.

В стандарте также приводятся некоторые примеры того, как интерпретировать эти типы.

Если вы обнаружите, что исходный стандарт C99 (ISO/IEC 9899: 1999) должен знать, что как TC1 (международный стандарт ISO/IEC 9899: 1999 Technical Corrigendum 1, опубликованный 2001-09-01), так и TC2 (международный стандарт ISO/IEC 9899: 1999 Техническое исправление 2, опубликованное в 2004-11-15 гг.) Вносит изменения в п. 7.11.2.1 (но TC3 - нет). Однако изменения не затрагивают и не влияют на ответы на вопросы, которые я собираюсь задать.


Вопросы

Мои первые два вопроса касаются четырех троек (cs_precedes, sep_by_space и sign_posn), а остальные более общие вопросы о том, что представляет собой действующий язык:

  • Возможно ли иметь один или два члена тройки с обозначением CHAR_MAX, в то время как другие члены имеют значения в нормальном диапазоне (0-1, 0-1, 0-4)?
  • Если разумно, как следует интерпретировать комбинации?

    Определены две комбинации (все значения, установленные на CHAR_MAX, как в локали "C" и все установленные значения); это другие 6 гибридных настроек, которые мне интересны.

  • Правильно ли сформирована локаль, если определены трижды, но соответствующий символ валюты не является?

  • Правильно ли сформирован языковой стандарт, если денежная десятичная точка не определена, но обозначен символ валюты.
  • Если позиция знака не равна 0 (указывает на то, что значение окружено круглыми скобками), правильно ли сформирована локаль, если символ валюты установлен, но как положительные, так и отрицательные знаковые строки пусты?
  • Имеет ли смысл определить положительную тройку, когда отрицательная тройка не является?

Моя склонность отвечать:

  • Нет; либо все или ни один из членов тройки должен быть установлен в CHAR_MAX.
  • Не применимо, учитывая ответ на (1).
  • Нет.
  • Нет (но есть пограничный случай для старой итальянской валюты (лиры), где не было фракций, и поэтому не требовалось десятичной точки, с которой можно было бы справиться с условием, что денежная десятичная точка нужна только в том случае, если frac_digits или int_frac_digits больше нуля).
  • Нет.
  • Нет.

Затем реализация может принудительно применять эти правила, но вполне возможно, что другая реализация будет интерпретировать правила по-разному и прийти к другому выводу.

Что ты говоришь?

4b9b3361

Ответ 1

Формальные ограничения

Насколько я могу судить, ни Standard C, ни POSIX не устанавливает никаких правил о том, что есть и недействительно в struct lconv. Одна из правдоподобных причин этого заключается в том, что никакая функция в стандарте C или POSIX не принимает struct lconv в качестве аргумента; функция localeconv() возвращает структуру:

 struct lconv *localeconv(void);

Следовательно, поскольку реализация номинально является единственным источником значений struct lconv, независимо от того, какая реализация должна быть в порядке в реализации. В общем, это несколько неподвижная функция; он обеспечивает функциональность, которую ничего не использует напрямую. Однако за кулисами есть поддержка частей этой информации (подумайте printf() и scanf() et al, для начала). Денежная информация не используется никакими стандартными функциями C. Они (заголовок <locale.h> и функции localeconv() и setlocale()) были добавлены в C89 комитетом, частично для обеспечения того, что может быть один стандарт ISO для C, который будет таким же, как стандарт ANSI для С.

Plauger book 'Стандартная библиотека C > (которая реализует стандартную библиотеку C89) предоставляет функцию под названием _Fmtval(), которая может использоваться для (местная) валюта и номера, использующие соглашения текущего региона, но еще раз, используемая структура определяется реализацией и не предоставляется пользователем.

POSIX предоставляет пару функций strfmon() и strfmon_l(), последний из которых принимает locale_t как один из аргументов.

ssize_t strfmon(char *restrict s, size_t maxsize, const char *restrict format, ...);
ssize_t strfmon_l(char *restrict s, size_t maxsize, locale_t locale,
                  const char *restrict format, ...);

Однако POSIX ничего не говорит о содержимом типа locale_t, хотя он предоставляет следующие функции для управления ими ограниченным образом:

Тем не менее, они обеспечивают минимальный подход к манипулированию локалями и не требуют подробного описания того, что может или не может быть приемлемым в struct lconv. Существуют также функции nl_langinfo():

#include <langinfo.h>

char *nl_langinfo(nl_item item);
char *nl_langinfo_l(nl_item item, locale_t locale);

Это позволяет вам узнать, по одному элементу за раз, значения частей локали, используя имена типа ABDAY_1, чтобы узнать сокращенное название дня 1, которое является "Солнцем" на английском языке, говорящих мест. В <langinfo.h> имеется около 55 таких имен. Интересно, что набор не завершен; вы не можете найти символ международной валюты таким образом.

Практические ограничения

Учитывая, что два основных соответствующих стандарта ничего не говорят о ограничениях на содержимое struct lconv, нам остается попытаться определить практические ограничения.

(Помимо этого: учитывая симметрию национальной и международной информации форматирования в стандарте C99, в некоторых отношениях жаль, что структура не использовалась для кодирования информации, она делает для произвольного кода выбор правильных битов и некоторые из полей (cs_precedes, sep_by_space) также могут быть булевыми, но <stdbool.h> не был в C89.)

Повторение вопросов:

Мои первые два вопроса касаются четырех троек (cs_precedes, sep_by_space и sign_posn), а остальные более общие вопросы о том, что представляет собой действительный язык:

  • Возможно ли иметь один или два члена тройки с обозначением CHAR_MAX, в то время как другие члены имеют значения в нормальном диапазоне (0-1, 0-1, 0-4)?
  • Если разумно, как следует интерпретировать комбинации?
  • Правильно ли сформирован языковой стандарт, если трижды определены, но соответствующий символ валюты не является?
  • Правильно ли сформирован языковой стандарт, если денежная десятичная точка не определена, но обозначен символ валюты.
  • Если позиция знака не равна 0 (указывает на то, что значение окружено круглыми скобками), правильно ли сформирована локаль, если символ валюты установлен, но как положительные, так и отрицательные знаковые строки пусты?
  • Имеет ли смысл определить положительную тройку, когда отрицательная тройка не является?

Исходными, набросками были:

  • Нет; либо все или ни один из членов тройки должен быть установлен в CHAR_MAX.
  • Не применимо, учитывая ответ на (1).
  • Нет.
  • Нет (но есть пограничный случай для старой итальянской валюты (лиры), где не было фракций, и поэтому не требовалось десятичной точки, которое можно было бы обработать с условием, что денежная десятичная точка нужна только в том случае, если frac_digits или int_frac_digits больше нуля).
  • Нет.
  • Нет.

Проведя некоторое время, внедряя код для обработки форматирования, как это, мои исходные ответы в основном верны, на мой взгляд.

Код, который я завершил для проверки локалей, был:

/* Locale validation */
#define VALUE_IN_RANGE(v, mn, mx) ((v) >= (mn) && (v) <= (mx))
#define ASSERT(condition)           do { assert(condition); \
                                         if (!(condition)) \
                                             return false; \
                                       } while (0)
#define ASSERT_RANGE(v, mn, mx)     ASSERT(VALUE_IN_RANGE(v, mn, mx))

static bool check_decpt_thous_group(bool decpt_is_opt, const char *decpt,
                                    const char *thous, const char *group)
{
    /* Decimal point must be defined; monetary decimal point might not be */
    ASSERT(decpt != 0);
    ASSERT(decpt_is_opt || *decpt != '\0');
    /* Thousands separator and grouping must be valid (non-null) pointers */
    ASSERT(thous != 0 && group != 0);
    /* Thousands separator should be set iff grouping is set and vice versa */
    ASSERT((*thous != '\0' && *group != '\0') ||
           (*thous == '\0' && *group == '\0'));
    /* Thousands separator, if set, should be different from decimal point */
    ASSERT(*thous == '\0' || decpt_is_opt ||
          (*decpt != '\0' && strcmp(thous, decpt) != 0));
    return true;
}

static bool currency_valid(const char *currency_symbol, char frac_digits,
                           char p_cs_precedes, char p_sep_by_space, char p_sign_posn,
                           char n_cs_precedes, char n_sep_by_space, char n_sign_posn)
{
    ASSERT(currency_symbol != 0);
    if (*currency_symbol == '\0')
    {
        ASSERT(frac_digits    == CHAR_MAX);
        ASSERT(p_cs_precedes  == CHAR_MAX);
        ASSERT(p_sep_by_space == CHAR_MAX);
        ASSERT(p_sign_posn    == CHAR_MAX);
        ASSERT(n_cs_precedes  == CHAR_MAX);
        ASSERT(n_sep_by_space == CHAR_MAX);
        ASSERT(n_sign_posn    == CHAR_MAX);
    }
    else
    {
        ASSERT_RANGE(frac_digits,    0, 9);     // 9 dp of currency is a lot!
        ASSERT_RANGE(p_cs_precedes,  0, 1);
        ASSERT_RANGE(p_sep_by_space, 0, 2);
        ASSERT_RANGE(p_sign_posn,    0, 4);
        ASSERT_RANGE(n_cs_precedes,  0, 1);
        ASSERT_RANGE(n_sep_by_space, 0, 2);
        ASSERT_RANGE(n_sign_posn,    0, 4);
    }
    return true;
}

static bool locale_is_consistent(const struct lconv *loc)
{
    if (!check_decpt_thous_group(false, loc->decimal_point, loc->thousands_sep, loc->grouping))
        return false;
    if (!check_decpt_thous_group((loc->frac_digits == 0 || loc->frac_digits == CHAR_MAX),
                    loc->mon_decimal_point, loc->mon_thousands_sep, loc->mon_grouping))
        return false;
    /* Signs must be valid (non-null) strings */
    ASSERT(loc->positive_sign != 0 && loc->negative_sign != 0);
    /* Signs must be different or both must be empty string (and probably n_sign_posn == 0) */
    ASSERT(strcmp(loc->positive_sign, loc->negative_sign) != 0 || *loc->negative_sign == '\0');
    if (!currency_valid(loc->currency_symbol, loc->frac_digits,
                        loc->p_cs_precedes, loc->p_sep_by_space, loc->p_sign_posn,
                        loc->n_cs_precedes, loc->n_sep_by_space, loc->n_sign_posn))
        return false;
    if (!currency_valid(loc->int_curr_symbol, loc->int_frac_digits,
                        loc->int_p_cs_precedes, loc->int_p_sep_by_space, loc->int_p_sign_posn,
                        loc->int_n_cs_precedes, loc->int_n_sep_by_space, loc->int_n_sign_posn))
        return false;
    /*
    ** If set, international currency symbol must be 3 (upper-case)
    ** alphabetic characters plus non-alphanum separator
    */
    if (*loc->int_curr_symbol != '\0')
    {
        ASSERT(strlen(loc->int_curr_symbol) == 4);
        ASSERT(isupper(loc->int_curr_symbol[0]));
        ASSERT(isupper(loc->int_curr_symbol[1]));
        ASSERT(isupper(loc->int_curr_symbol[2]));
        ASSERT(!isalnum(loc->int_curr_symbol[3]));
    }
    return true;
}

В стандарте указано, что loc->int_curr_symbol[3] используется как символ "пробела" при форматировании международной валюты, и нет смысла допускать буквенный символ, а также международный код валюты ISO 4217, который представляет собой три строчных буквы из основной алфавит. Разрешение цифры может привести к путанице, если знак тоже раздельный, поэтому я думаю, что утверждение !isalnum(loc->int_curr_symbol[3]) разумно. Строгая проверка подтверждает, что международный символ валюты является одним из тех, которые перечислены в ISO 4217; это немного сложно кодировать, хотя!