描述语言环境的'struct lconv'中对值的形式和实际约束是什么?

Jon*_*ler 13 c locale

背景

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
Run Code Online (Sandbox Code Playgroud)

第7.11.2.1节"localeconv()函数"继续说:

具有类型的结构的成员char *是指向字符串的指针,其中任何一个(除decimal_point)可以指向"",以指示该值在当前语言环境中不可用或者长度为零.[...] char类型的成员是非负数,其中任何一个都可以CHAR_MAX表示该值在当前语言环境中不可用.

它继续讨论每个成员.你可以看到4组3个成员,一个代表组p_cs_precedes,p_sep_by_spacep_sign_posn.

char p_cs_precedes
如果currency_symbol分别位于非负本地格式货币数量的值之前或之后,则设置为1或0.

char p_sep_by_space
设置为一个值,指示currency_symbol,符号字符串和非负局部格式货币数量的值的分隔.

char p_sign_posn 设置为一个值,指示positive_sign对非负局部格式货币数量的定位.

p_sign_posn给出了解释的细节; 它们对这个问题不重要.

该标准还给出了一些如何解释这些类型的例子.

如果您发现原始C99标准(ISO/IEC 9899:1999),请注意TC1(国际标准ISO/IEC 9899:1999技术勘误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),以及关于什么构成有效语言环境的其他更一般的问题:

  1. 拥有一个或两个具有CHAR_MAX名称的三元组成员是否可行或明智,而其他成员的值在正常范围内(0-1,0-1,0-4)?
  2. 如果它是明智的,那么组合应该如何解释?

    定义了两种组合(所有值设置为CHAR_MAX,如在"C"语言环境中,以及所有有效设置的值); 这是我很好奇的其他6种混合设置.

  3. 如果定义了三元组但是相关的货币符号不是正确形成的语言环境?

  4. 如果未定义货币小数点但定义了货币符号,则是否正确形成了区域设置.
  5. 如果符号位置不为0(表示值是括号括起来的),如果设置了货币符号但是正号和负号都是空的,是否正确形成了语言环境?
  6. 当负三联不是时,是否有必要定义正三联?

我倾向于回答:

  1. 没有; 三元组的全部或全部成员都不应设置为CHAR_MAX.
  2. 鉴于(1)的答案,不适用.
  3. 没有.
  4. 否(但是旧的意大利货币(里拉)存在边界情况,其中没有分数,因此不需要小数点;这可以通过以下条件来处理:仅当frac_digitsint_frac_digits大于零时才需要货币小数点).
  5. 没有.
  6. 没有.

然后,实现可以强制执行这些规则,但可以想象另一个实现将以不同的方式解释规则并得出不同的结论.

怎么说你?

Jon*_*ler 0

形式约束

据我所知,标准 C 和 POSIX 都没有规定任何关于struct lconv. 一个可能的原因是,标准 C 或 POSIX 中没有函数将 astruct lconv作为参数。只有localeconv()函数返回结构:

 struct lconv *localeconv(void);
Run Code Online (Sandbox Code Playgroud)

因此,由于实现名义上是struct lconv值的唯一来源,因此无论实现做什么,在实现的眼中都必须是OK的。总而言之,这在某种程度上是一个胎死腹中的特征;它提供了没有任何东西可以直接使用的功能。不过,在幕后,这些信息的部分内容得到了支持(对于初学者来说,请思考printf()scanf()等人)。任何标准 C 函数都不使用货币信息。它们(<locale.h>头文件和localeconv()函数setlocale())由委员会添加到 C89 中,部分是为了确保 C 可以有一个与 ANSI C 标准相同的单一 ISO 标准。

Plauger 的书“标准 C 库”(实现了 C89 标准库)提供了一个名为 的函数,_Fmtval()该函数可用于使用当前语言环境的约定来格式化国际货币、国家(本地)货币和数字,但再一次,使用的结构由实现定义,而不是由用户提供。

POSIX 确实提供了一对函数strfmon()strfmon_l(),后者将 alocale_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, ...);
Run Code Online (Sandbox Code Playgroud)

然而,POSIX 对 type 的内容只字未提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);
Run Code Online (Sandbox Code Playgroud)

这些允许您一次找出区域设置部分的值,例如使用名称ABDAY_1找出第一天的缩写名称,在英语区域设置中为“Sun”。中大约有 55 个这样的名字<langinfo.h>。有趣的是,该套装并不完整;您无法通过这种方式找到国际货币符号。

实际限制

鉴于两个主要的相关标准没有提及对内容的限制struct lconv,我们只能尝试确定实际的限制。

旁白:考虑到 C99 标准中国家和国际格式化信息的对称性,在某些方面遗憾的是没有使用结构来编码信息;这使得繁琐的代码将正确的位和片段挑选出来泛型函数。某些字段 ( cs_precedes, sep_by_space) 也可以是布尔值,但<stdbool.h>C89 中没有。)

重述问题:

我的前两个问题是关于四个三元组(cs_precedessep_by_spacesign_posn),其他更一般的问题是关于什么构成有效区域设置:

  1. 让三元组中的一个或两个成员具有 CHAR_MAX 名称,而其他成员具有正常范围(0-1、0-1、0-4)内的值是否可行或明智?
  2. 如果合理的话,应该如何解释这些组合?
  3. 如果定义了三元组但未定义相关货币符号,则区域设置是否正确形成?
  4. 如果未定义货币小数点但定义了货币符号,则语言环境是否正确形成。
  5. 如果符号位置不为 0(表示值被括号括起来),并且设置了货币符号但正号和负号字符串均为空,那么区域设置是否正确形成?
  6. 当负三元组没有定义时,定义正三元组是否有意义?

最初的概要答案是:

  1. 不; 三元组的所有成员都应设置为 CHAR_MAX,或者不设置为 CHAR_MAX。
  2. 考虑到 (1) 的答案,不适用。
  3. 不。
  4. 否(但旧意大利货币(里拉)有一种边界情况,其中没有分数,因此不需要小数点;可以通过以下条件处理:仅当frac_digitsint_frac_digits大于零时才需要货币小数点)。
  5. 不。
  6. 不。

花了一些时间实现代码来处理这样的格式,在我看来,我原来的答案基本上是正确的。

我最终实现的用于验证语言环境的代码是:

/* 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;
}
Run Code Online (Sandbox Code Playgroud)

该标准规定,loc->int_curr_symbol[3]在格式化国际货币时,将其用作“空格”字符,并且允许使用字母字符以及 ISO 4217 国际货币代码(基本字母表中的三个大写字母)没有什么意义。如果符号也是分开的,那么允许数字存在可能会导致混乱,所以我认为这个!isalnum(loc->int_curr_symbol[3])断言是明智的。严格检查将验证国际货币符号是否为 ISO 4217 中列出的符号之一;不过,编码有点棘手!