写点什么

C++ 学习 ------clocale 头文件的源码学习

作者:桑榆
  • 2022 年 9 月 07 日
    广东
  • 本文字数:6482 字

    阅读完需:约 21 分钟

引言

clocale 是 C++对 locale.h 头文件的封装,该文件是本地化的特殊设置,比如特殊的时间日期打印格式,货币符号等。我们来看看它的具体实现。

locale.h

参考代码: http://www.aospxref.com/android-12.0.0_r3/xref/bionic/libc/include/locale.h

locale.h 中设置的影响

上面说到,locale.h 实际上是与本地化相关的设置,所以使用这个头文件,会对其它标准 C 函数有一些影响:

  • string.h 里面的 strcoll 和 strxfrm 会被符号转换规则影响;

  • ctypes.h 里面除了 isdigit 和 isxdigit 其余的函数都会被该文化特有的扩展符号影响;

  • stdio.h 里面的输入输出过程中会被符号转换规则和特殊设定的小数点符号影响;

  • time.h 里面的 strftime 会被时间格式设置影响。

宏定义

按照 local 的相关类型,如时间,金钱符号等,设置了宏定义及对应的 mask,以便后续使用。

40  #define LC_CTYPE           041  #define LC_NUMERIC         142  #define LC_TIME            243  #define LC_COLLATE         344  #define LC_MONETARY        445  #define LC_MESSAGES        546  #define LC_ALL             647  #define LC_PAPER           748  #define LC_NAME            849  #define LC_ADDRESS         950  #define LC_TELEPHONE      1051  #define LC_MEASUREMENT    1152  #define LC_IDENTIFICATION 1253  54  #define LC_CTYPE_MASK          (1 << LC_CTYPE)55  #define LC_NUMERIC_MASK        (1 << LC_NUMERIC)56  #define LC_TIME_MASK           (1 << LC_TIME)57  #define LC_COLLATE_MASK        (1 << LC_COLLATE)58  #define LC_MONETARY_MASK       (1 << LC_MONETARY)59  #define LC_MESSAGES_MASK       (1 << LC_MESSAGES)60  #define LC_PAPER_MASK          (1 << LC_PAPER)61  #define LC_NAME_MASK           (1 << LC_NAME)62  #define LC_ADDRESS_MASK        (1 << LC_ADDRESS)63  #define LC_TELEPHONE_MASK      (1 << LC_TELEPHONE)64  #define LC_MEASUREMENT_MASK    (1 << LC_MEASUREMENT)65  #define LC_IDENTIFICATION_MASK (1 << LC_IDENTIFICATION)66  67  #define LC_ALL_MASK (LC_CTYPE_MASK | LC_NUMERIC_MASK | LC_TIME_MASK | LC_COLLATE_MASK | \68                       LC_MONETARY_MASK | LC_MESSAGES_MASK | LC_PAPER_MASK | LC_NAME_MASK | \69                       LC_ADDRESS_MASK | LC_TELEPHONE_MASK | LC_MEASUREMENT_MASK | \70                       LC_IDENTIFICATION_MASK)
复制代码

类型定义---lconv

该类型定义,包含了所有特殊设置项:

  • decimal_point---用于非货币量的小数点分隔符;

  • thousands_sep---用于分隔非货币量小数点左侧的数字组的分隔符;

  • grouping---指定形成每个组的位数,对于非货币量,这些数字将由千分位分隔符分隔。举个例子,比如 thousand_sep = ',',当我们表示 1000000 时:grouping = '\3',那就表示为'1,000,000';grouping = '\1\2\3',那就表示为'1,000,00,0';grouping = '\3\1',那就表示为'1,0,0,0,000'。

  • int_curr_symbol---国际货币符号;

  • currency_symbol---本地货币符号,如“$”;

  • mon_decimal_point---用于货币量的小数点分隔符;

  • mon_thousands_sep---分隔符,用于分隔货币量小数点左侧的数字组;

  • mon_grouping---指定形成每个组的数字数量,这些数字由货币量的 mmon_thousands_sep 分隔符分隔;

  • positive_sign---用于非负(正或零)货币量的符号;

  • negative_sign---用于负货币量的符号;

  • int_frac_digits---国际格式货币量小数点右侧的小数位数

  • frac_digits---本地格式货币量小数点右侧的小数位数

  • p_cs_precedes---货币符号是否应在非负(正或零)货币量之前。如果该值为 1,则货币符号应在前;如果为 0,则应跟随;

  • p_sep_by_space---货币符号和非负(正或零)货币量之间是否应出现空格。如果该值为 1,则应显示一个空格;如果为 0,则无空格;

  • n_cs_precedes---货币符号是否应在负货币量之前。如果该值为 1,则货币符号应在前;如果为 0,则应跟随;

  • n_sep_by_space---货币符号和负货币量之间是否应出现空格。如果该值为 1,则应显示一个空格;如果为 0,则无空格;

  • p_sign_posn---非负(正或零)货币量的符号位置:0:用括号括起来的货币符号和数量;1:在数量和货币符号前签名;2:在数量和货币符号后签名;3:在货币符号前签名;4:在货币符号后面签名。

  • n_sign_posn---负货币量符号的位置;

  • int_p_cs_precedes---同 p_cs_precedes,国际格式;

  • int_p_sep_by_space---同 p_sep_by_space,国际格式;

  • int_n_cs_precedes---同 n_cs_precedes,国际格式;

  • int_n_sep_by_space---同 n_sep_by_space,国际格式;

  • int_p_sign_posn---同 p_sign_posn,国际格式;

  • int_n_sign_posn---同 n_sign_posn,国际格式;

72  struct lconv {73    char* decimal_point;74    char* thousands_sep;75    char* grouping;76    char* int_curr_symbol;77    char* currency_symbol;78    char* mon_decimal_point;79    char* mon_thousands_sep;80    char* mon_grouping;81    char* positive_sign;82    char* negative_sign;83    char int_frac_digits;84    char frac_digits;85    char p_cs_precedes;86    char p_sep_by_space;87    char n_cs_precedes;88    char n_sep_by_space;89    char p_sign_posn;90    char n_sign_posn;91    char int_p_cs_precedes;92    char int_p_sep_by_space;93    char int_n_cs_precedes;94    char int_n_sep_by_space;95    char int_p_sign_posn;96    char int_n_sign_posn;97  };
复制代码

函数定义

参考代码:http://www.aospxref.com/android-12.0.0_r3/xref/bionic/libc/bionic/locale.cpp

其实逻辑也比较清晰,主要是通过 localeconv 函数返回当前的配置结构体 lconv,通过 setlocale 设置对应的 locale。

99  struct lconv* localeconv(void) __INTRODUCED_IN_NO_GUARD_FOR_NDK(21);100  101  locale_t duplocale(locale_t __l) __INTRODUCED_IN(21);102  void freelocale(locale_t __l) __INTRODUCED_IN(21);103  locale_t newlocale(int __category_mask, const char* __locale_name, locale_t __base) __INTRODUCED_IN(21);104  char* setlocale(int __category, const char* __locale_name);105  locale_t uselocale(locale_t __l) __INTRODUCED_IN(21);106  107  #define LC_GLOBAL_LOCALE __BIONIC_CAST(reinterpret_cast, locale_t, -1L)108  109  __END_DECLS
复制代码

接下来我们具体看看每个函数的具体实现:

localeconv---获取当前配置

注意到,这里使用了 pthread_once,该线程的作用正如其名,在多线程环境下,__locale_init函数只会被执行一次,然后对全局静态变量 g_locale 进行了初始化。

130  lconv* localeconv() {131    pthread_once(&g_locale_once, __locale_init);132    return &g_locale;133  }
复制代码

许多符号的默认值都是空,小数点默认为'.',与位置有关的数值默认值都为 CHAR_MAX(255);

85  static pthread_once_t g_locale_once = PTHREAD_ONCE_INIT;86  static lconv g_locale;87  88  static void __locale_init() {89    g_locale.decimal_point = const_cast<char*>(".");90  91    char* not_available = const_cast<char*>("");92    g_locale.thousands_sep = not_available;93    g_locale.grouping = not_available;94    g_locale.int_curr_symbol = not_available;95    g_locale.currency_symbol = not_available;96    g_locale.mon_decimal_point = not_available;97    g_locale.mon_thousands_sep = not_available;98    g_locale.mon_grouping = not_available;99    g_locale.positive_sign = not_available;100    g_locale.negative_sign = not_available;101  102    g_locale.int_frac_digits = CHAR_MAX;103    g_locale.frac_digits = CHAR_MAX;104    g_locale.p_cs_precedes = CHAR_MAX;105    g_locale.p_sep_by_space = CHAR_MAX;106    g_locale.n_cs_precedes = CHAR_MAX;107    g_locale.n_sep_by_space = CHAR_MAX;108    g_locale.p_sign_posn = CHAR_MAX;109    g_locale.n_sign_posn = CHAR_MAX;110    g_locale.int_p_cs_precedes = CHAR_MAX;111    g_locale.int_p_sep_by_space = CHAR_MAX;112    g_locale.int_n_cs_precedes = CHAR_MAX;113    g_locale.int_n_sep_by_space = CHAR_MAX;114    g_locale.int_p_sign_posn = CHAR_MAX;115    g_locale.int_n_sign_posn = CHAR_MAX;116  }
复制代码

duplocale---复制一个当前的配置结构体并返回

将传入的 locale_t 作为构造函数入参,new 一个返回

135  locale_t duplocale(locale_t l) {136    return new __locale_t(l);137  }
复制代码

对应 locale_t 被定义为__locale_t*

参考代码 http://www.aospxref.com/android-12.0.0_r3/xref/bionic/libc/include/xlocale.h

43  /* If we just use void* in the typedef, the compiler exposes that in error messages. */44  struct __locale_t;45  46  /**47   * The `locale_t` type that represents a locale.48   */49  typedef struct __locale_t* locale_t;
复制代码

我们来看看__locale_t 的定义,里面的特定构造函数实际上就是对这种传入 locale_t 的处理,如果该指针等于 LC_GLOBAL_LOCALE,即 reinterpret_cast<locale_t>(-1L),那么说明没有初始化,需要根据标志位进行初始化;由于这里面默认是使用 utf8 的 locale 配置,所以 mb_cur_max 就被设置为 4,否则就是传入参数的值拷贝


53  static bool __bionic_current_locale_is_utf8 = true;
55  struct __locale_t {56   size_t mb_cur_max;57  58   explicit __locale_t(size_t mb_cur_max) : mb_cur_max(mb_cur_max) {59   }60  61   explicit __locale_t(const __locale_t* other) {62   if (other == LC_GLOBAL_LOCALE) {63   mb_cur_max = __bionic_current_locale_is_utf8 ? 4 : 1;64   } else {65   mb_cur_max = other->mb_cur_max;66   }67   }68  69   BIONIC_DISALLOW_IMPLICIT_CONSTRUCTORS(__locale_t);70  };
复制代码

上面的BIONIC_DISALLOW_IMPLICIT_CONSTRUCTORS(__locale_t);其实是禁止隐式构造的宏,如下:将类的默认构造函数,拷贝构造函数,赋值重载都隐藏起来

#define DISALLOW_COPY_AND_ASSIGN(TypeName) \private:                     \TypeName(const TypeName&);               \TypeName& operator=(const TypeName&)
#define DISALLOW_IMPLICIT_CONSTRUCTORS(TypeName) \private:                     \TypeName();                                    \DISALLOW_COPY_AND_ASSIGN(TypeName)
复制代码

freelocale---释放传入的配置结构体

释放对应的指针即可

139  void freelocale(locale_t l) {140    delete l;141  }
复制代码

newlocale---创建一个新的配置结构体

判断传入参数是否合法:是否是已知的 mask,locale_name 不可以为空;

143  locale_t newlocale(int category_mask, const char* locale_name, locale_t /*base*/) {144    // Are 'category_mask' and 'locale_name' valid?145    if ((category_mask & ~LC_ALL_MASK) != 0 || locale_name == nullptr) {146      errno = EINVAL;147      return nullptr;148    }149  
复制代码

传入的 locale_name 是否支持

150    if (!__is_supported_locale(locale_name)) {151      errno = ENOENT;152      return nullptr;153    }
复制代码

通过函数__is_supported_locale判断,默认的字符串只能是如下几个:"","C","C.UTF-8","en_US.UTF-8","POSIX"

118  static bool __is_supported_locale(const char* locale_name) {119    return (strcmp(locale_name, "") == 0 ||120            strcmp(locale_name, "C") == 0 ||121            strcmp(locale_name, "C.UTF-8") == 0 ||122            strcmp(locale_name, "en_US.UTF-8") == 0 ||123            strcmp(locale_name, "POSIX") == 0);124  }
复制代码

最后返回构造的__locale_t,这里事先决定其中的参数值,传入进行初始化

154  155    return new __locale_t(__is_utf8_locale(locale_name) ? 4 : 1);156  }
复制代码

setlocale---设置当前配置

前面的检验流程与 newlocale 基本一致,判断是否合法;

158  char* setlocale(int category, const char* locale_name) {159    // Is 'category' valid?160    if (category < LC_CTYPE || category > LC_IDENTIFICATION) {161      errno = EINVAL;162      return nullptr;163    }164  165    // Caller wants to set the locale rather than just query?166    if (locale_name != nullptr) {167      if (!__is_supported_locale(locale_name)) {168        // We don't support this locale.169        errno = ENOENT;170        return nullptr;171      }172      __bionic_current_locale_is_utf8 = __is_utf8_locale(locale_name);173    }174  175    return const_cast<char*>(__bionic_current_locale_is_utf8 ? "C.UTF-8" : "C");176  }
复制代码

然后给__bionic_current_locale_is_utf8 赋值,通过 locale_name 判断,为空或者"UTF-8"置为 true;返回对应的字符串

126  static bool __is_utf8_locale(const char* locale_name) {127    return (*locale_name == '\0' || strstr(locale_name, "UTF-8"));128  }
复制代码

uselocale---使用当前传入的配置

首先通过 get_current_locale_ptr 获取当前的 locale,如果当前为空,说明是第一次调用,那么默认使用 LC_GLOBAL_LOCALE 给 old_locale 赋值;如果新的不为空,则对应赋值即可,返回 old_locale;

186  locale_t uselocale(locale_t new_locale) {187    locale_t old_locale = *get_current_locale_ptr();188  189    // If this is the first call to uselocale(3) on this thread, we return LC_GLOBAL_LOCALE.190    if (old_locale == nullptr) {191      old_locale = LC_GLOBAL_LOCALE;192    }193  194    if (new_locale != nullptr) {195      *get_current_locale_ptr() = new_locale;196    }197  198    return old_locale;199  }
复制代码

其中 get_current_locale_ptr 的逻辑如下:

178  static locale_t* get_current_locale_ptr() {179  #if USE_TLS_SLOT180    return &__get_bionic_tls().locale;181  #else182    return &g_current_locale;183  #endif184  }
复制代码

分两种情况,如果没有定义 USE_TLS_SLOT,那么直接返回 g_current_locale 的地址即可,后面其它模块也会使用这个全局变量的值做相关的判断

81  #if !USE_TLS_SLOT82  static thread_local locale_t g_current_locale;83  #endif
复制代码

如果定义了 USE_TLS_SLOT,那么要通过__get_bionic_tls 获取线程局部存储里面的 locale 信息,具体__get_tls 函数的执行与过程分析可以参考# C++学习------cerrno头文件的作用与源码学习里面关于__get_tls函数的调用过程与原理。

参考链接:http://www.aospxref.com/android-12.0.0_r3/xref/bionic/libc/bionic/pthread_internal.h?fi=__get_bionic_tls#__get_bionic_tls

202  static inline __always_inline bionic_tls& __get_bionic_tls() {203    return *static_cast<bionic_tls*>(__get_tls()[TLS_SLOT_BIONIC_TLS]);204  }
复制代码

LC_GLOBAL_LOCALE

这个宏实际上是 reinterpret_cast<locale_t>(-1L)的意思,实际上就是使用-1L 初始化 locale_t。

57  #if defined(__cplusplus)58  #define __BIONIC_CAST(_k,_t,_v) (_k<_t>(_v))59  #else60  #define __BIONIC_CAST(_k,_t,_v) ((_t) (_v))61  #endif
复制代码


发布于: 刚刚阅读数: 3
用户头像

桑榆

关注

北海虽赊,扶摇可接;东隅已逝,桑榆非晚! 2020.02.29 加入

Android手机厂商-相机软件系统工程师 爬山/徒步/Coding

评论

发布
暂无评论
C++学习------clocale头文件的源码学习_c++_桑榆_InfoQ写作社区