描述语言环境的’struct lconv’中的值的forms和实际约束是什么?

背景

C99标准的第7.11节描述了标头及其内容。 特别是,它定义了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_precedesp_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. 没有。

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

怎么说你?

forms约束

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

  struct lconv *localeconv(void); 

因此,由于实现名义上是struct lconv值的唯一来源,因此无论实现方式如何,在实现方面都必须正常。 总而言之,它有点像一个尚未出生的特征; 它提供了没有直接使用的function。 但是,在幕后,支持部分信息(想想printf()scanf()等,对于初学者来说)。 任何标准Cfunction都不使用货币信息。 它们( 头文件和localeconv()setlocale()函数)由委员会添加到C89,部分是为了确保C的单一ISO标准与ANSI相同C标准

Plauger的书“ 标准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类型的内容locale_t ,尽管它确实提供了以下函数来以有限的方式操作它们:

  • locale_t duplocale(locale_t)
  • void freelocale(locale_t)
  • locale_t newlocale(int, const char *, locale_t)
  • locale_t uselocale (locale_t)

但是,这些方法提供了一种操作区域设置的最小和不干涉的方法,并且绝对不会详细介绍struct lconv可能接受或不接受的内容。 还有nl_langinfo()函数:

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

这些允许您一次找到一个项目,区域设置的部分值,使用ABDAY_1等名称查找第1天的缩写名称,即英语语言环境中的“Sun”。 有大约55个这样的名字。 有趣的是,这个集合并不完整; 你不能用这种方式找到国际货币符号。

实际约束

鉴于两个主要相关标准对struct lconv内容的约束没有任何说明,我们只能尝试确定实际约束。

旁白 :鉴于C99标准中国家和国际格式信息的对称性,在某些方面遗憾的是,结构不用于对信息进行编码;它使得繁琐的代码选择正确的比特和部分进入generics函数。某些字段( cs_precedessep_by_space )也可能是布尔值,但不在C89中。)

重述问题:

我的前两个问题是关于四个三元组( cs_precedessep_by_spacesign_posn ),以及关于什么构成有效语言环境的其他更一般的问题:

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

原始的大纲答案是:

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

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

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

 /* 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])断言是明智的。 严格检查将validation国际货币符号是ISO 4217中列出的符号之一; 但是编码有点棘手!