引言
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
复制代码
评论