引言
cinttypes 是 C++对 inttypes.h 头文件的封装,里面封装了一系列宏定义,用于 C 语言 printf 和 scanf 函数的 format 打印,封装了一些函数,用于 str 类型转换为 xxmax_t 类型。我们来一起看看具体的实现。
inttypes.h
代码参考:http://www.aospxref.com/android-12.0.0_r3/xref/bionic/libc/include/inttypes.h
宏定义
基础前缀定义
这里根据平台(32/64)分别定义了 64 位打印和指针打印的前缀,与前一篇文章中一致,64 位机器用 long,32 位机器需要 long long,值得注意的是,32 位下面的指针前缀为空
25 #ifdef __LP64__
26 #define __PRI_64_prefix "l"
27 #define __PRI_PTR_prefix "l"
28 #else
29 #define __PRI_64_prefix "ll"
30 #define __PRI_PTR_prefix
31 #endif
32 #define __PRI_FAST_prefix __PRI_PTR_prefix
复制代码
有符号整数输出定义
通常有符号整数用'%d'、'%i'打印,其中'%jd','%ji'分别表示 intmax_t,代码如下:
50 /* fprintf macros for signed integers */
51 #define PRId8 "d" /* int8_t */
52 #define PRId16 "d" /* int16_t */
53 #define PRId32 "d" /* int32_t */
54 #define PRId64 __PRI_64_prefix"d" /* int64_t */
55
56 #define PRIdLEAST8 "d" /* int_least8_t */
57 #define PRIdLEAST16 "d" /* int_least16_t */
58 #define PRIdLEAST32 "d" /* int_least32_t */
59 #define PRIdLEAST64 __PRI_64_prefix"d" /* int_least64_t */
60
61 #define PRIdFAST8 "d" /* int_fast8_t */
62 #define PRIdFAST16 __PRI_FAST_prefix"d" /* int_fast16_t */
63 #define PRIdFAST32 __PRI_FAST_prefix"d" /* int_fast32_t */
64 #define PRIdFAST64 __PRI_64_prefix"d" /* int_fast64_t */
65
66 #define PRIdMAX "jd" /* intmax_t */
67 #define PRIdPTR __PRI_PTR_prefix"d" /* intptr_t */
68
69 #define PRIi8 "i" /* int8_t */
70 #define PRIi16 "i" /* int16_t */
71 #define PRIi32 "i" /* int32_t */
72 #define PRIi64 __PRI_64_prefix"i" /* int64_t */
73
74 #define PRIiLEAST8 "i" /* int_least8_t */
75 #define PRIiLEAST16 "i" /* int_least16_t */
76 #define PRIiLEAST32 "i" /* int_least32_t */
77 #define PRIiLEAST64 __PRI_64_prefix"i" /* int_least64_t */
78
79 #define PRIiFAST8 "i" /* int_fast8_t */
80 #define PRIiFAST16 __PRI_FAST_prefix"i" /* int_fast16_t */
81 #define PRIiFAST32 __PRI_FAST_prefix"i" /* int_fast32_t */
82 #define PRIiFAST64 __PRI_64_prefix"i" /* int_fast64_t */
83
84 #define PRIiMAX "ji" /* intmax_t */
85 #define PRIiPTR __PRI_PTR_prefix"i" /* intptr_t */
复制代码
无符号整数输出定义
因为无符号整数可以被'%o'(8 进制),'%u'(10 进制),'%x'(16 进制小写字母),'%X'(16 进制大写字母)修饰,再加上最大值修饰'j',所以定义的宏如下(展示 %o 的,其余类似):
87 /* fprintf macros for unsigned integers */
88 #define PRIo8 "o" /* int8_t */
89 #define PRIo16 "o" /* int16_t */
90 #define PRIo32 "o" /* int32_t */
91 #define PRIo64 __PRI_64_prefix"o" /* int64_t */
92
93 #define PRIoLEAST8 "o" /* int_least8_t */
94 #define PRIoLEAST16 "o" /* int_least16_t */
95 #define PRIoLEAST32 "o" /* int_least32_t */
96 #define PRIoLEAST64 __PRI_64_prefix"o" /* int_least64_t */
97
98 #define PRIoFAST8 "o" /* int_fast8_t */
99 #define PRIoFAST16 __PRI_FAST_prefix"o" /* int_fast16_t */
100 #define PRIoFAST32 __PRI_FAST_prefix"o" /* int_fast32_t */
101 #define PRIoFAST64 __PRI_64_prefix"o" /* int_fast64_t */
102
103 #define PRIoMAX "jo" /* intmax_t */
104 #define PRIoPTR __PRI_PTR_prefix"o" /* intptr_t */
复制代码
有符号整数输入定义
有输出符号就有输入符号,对应输出的处理基本相同,使用'%d'或'%i',不同的是,输出时,int8_t,int16_t,int32_t 都是使用'%d'输出,但是输入时,我们必须要增加修饰符:
160 /* fscanf macros for signed integers */
161 #define SCNd8 "hhd" /* int8_t */
162 #define SCNd16 "hd" /* int16_t */
163 #define SCNd32 "d" /* int32_t */
164 #define SCNd64 __PRI_64_prefix"d" /* int64_t */
165
166 #define SCNdLEAST8 "hhd" /* int_least8_t */
167 #define SCNdLEAST16 "hd" /* int_least16_t */
168 #define SCNdLEAST32 "d" /* int_least32_t */
169 #define SCNdLEAST64 __PRI_64_prefix"d" /* int_least64_t */
170
171 #define SCNdFAST8 "hhd" /* int_fast8_t */
172 #define SCNdFAST16 __PRI_FAST_prefix"d" /* int_fast16_t */
173 #define SCNdFAST32 __PRI_FAST_prefix"d" /* int_fast32_t */
174 #define SCNdFAST64 __PRI_64_prefix"d" /* int_fast64_t */
175
176 #define SCNdMAX "jd" /* intmax_t */
177 #define SCNdPTR __PRI_PTR_prefix"d" /* intptr_t */
复制代码
无符号整数输入定义
这里的输入定义也与有符号相同,需要用'h','hh'修饰,但是不同的是输入并不区分 16 进制大写或者小写,只定义了'x'的小写输入如下,其它的类似:
234 #define SCNx8 "hhx" /* uint8_t */
235 #define SCNx16 "hx" /* uint16_t */
236 #define SCNx32 "x" /* uint32_t */
237 #define SCNx64 __PRI_64_prefix"x" /* uint64_t */
238
239 #define SCNxLEAST8 "hhx" /* uint_least8_t */
240 #define SCNxLEAST16 "hx" /* uint_least16_t */
241 #define SCNxLEAST32 "x" /* uint_least32_t */
242 #define SCNxLEAST64 __PRI_64_prefix"x" /* uint_least64_t */
243
244 #define SCNxFAST8 "hhx" /* uint_fast8_t */
245 #define SCNxFAST16 __PRI_FAST_prefix"x" /* uint_fast16_t */
246 #define SCNxFAST32 __PRI_FAST_prefix"x" /* uint_fast32_t */
247 #define SCNxFAST64 __PRI_64_prefix"x" /* uint_fast64_t */
248
249 #define SCNxMAX "jx" /* uintmax_t */
250 #define SCNxPTR __PRI_PTR_prefix"x" /* uintptr_t */
复制代码
结构体定义
定义了一个除法的结构体,里面保存了商值和余数值,用来做后面的函数处理
252 typedef struct {
253 intmax_t quot; /* quotient */
254 intmax_t rem; /* remainder */
255 } imaxdiv_t;
复制代码
函数定义
257 __BEGIN_DECLS
258 intmax_t imaxabs(intmax_t __i) __attribute_const__ __INTRODUCED_IN(19);
259 imaxdiv_t imaxdiv(intmax_t __numerator, intmax_t __denominator) __attribute_const__ __INTRODUCED_IN(19);
260 intmax_t strtoimax(const char* __s, char** __end_ptr, int __base);
261 uintmax_t strtoumax(const char* __s, char** __end_ptr, int __base);
262 intmax_t wcstoimax(const wchar_t* __s, wchar_t** __end_ptr, int __base) __INTRODUCED_IN(21);
263 uintmax_t wcstoumax(const wchar_t* __s, wchar_t** __end_ptr, int __base) __INTRODUCED_IN(21);
264 __END_DECLS
复制代码
imaxabs---返回绝对值
实现方法非常简单,判断是否大于 0,大于 0 返回原值,小于 0 返回相反数。
//http://www.aospxref.com/android-12.0.0_r3/xref/bionic/libc/upstream-openbsd/lib/libc/stdlib/imaxabs.c
34 intmax_t
35 imaxabs(intmax_t j)
36 {
37 return (j < 0 ? -j : j);
38 }
复制代码
imaxdiv---返回除法结果(有商有余数)
实现方法:使用整数除法'/'求商,模运算'%'求余数注意:因为这里是有符号整数的除法,所以要考虑出现负数的情况,如果被除数大于等于 0,计算后的余数小于 0,那么我们要将商值加 1,从余数中减去除数,即最后要保证输出的余数是正数。最后返回对应的结构体,如下:
//http://www.aospxref.com/android-12.0.0_r3/xref/bionic/libc/upstream-openbsd/lib/libc/stdlib/imaxdiv.c
36 imaxdiv_t
37 imaxdiv(intmax_t num, intmax_t denom)
38 {
39 imaxdiv_t r;
40
41 /* see div.c for comments */
42
43 r.quot = num / denom;
44 r.rem = num % denom;
45 if (num >= 0 && r.rem < 0) {
46 r.quot++;
47 r.rem -= denom;
48 }
49 return (r);
50 }
复制代码
strtoimax---输入 str 返回对应的 int 值,等价于 intmax_t 的 strtol 函数
这里调用了一个模板函数来实现,模板参数为类型,该类型的最小值,最大值
//http://www.aospxref.com/android-12.0.0_r3/xref/bionic/libc/bionic/strtol.cpp
174 intmax_t strtoimax(const char* s, char** end, int base) {
175 return StrToI<intmax_t, INTMAX_MIN, INTMAX_MAX>(s, end, base);
176 }
复制代码
详细分析一下模板函数
36 template <typename T, T Min, T Max> T StrToI(const char* nptr, char** endptr, int base) {
复制代码
入参:const char* nptr 为待转换的 str,char** endptr 为最后转换结束指向的字符指针,int base 为进制
37 // Ensure that base is between 2 and 36 inclusive, or the special value of 0.
38 if (base < 0 || base == 1 || base > 36) {
39 if (endptr != nullptr) *endptr = const_cast<char*>(nptr);
40 errno = EINVAL;
41 return 0;
42 }
复制代码
检查输入的进制是否有效,如果小于 0 或者等于 1 或者大于 36,视为异常的进制,此时将 endptr 赋值为输入 nptr 首字符(如果外部输入了不为空的指针记录这个值的情况下);
44 // Skip white space and pick up leading +/- sign if any.
45 // If base is 0, allow 0x for hex and 0 for octal, else
46 // assume decimal; if base is already 16, allow 0x.
47 const char* s = nptr;
48 int c;
49 do {
50 c = *s++;
51 } while (isspace(c));
复制代码
跳过前面的空格符号,直到遇到非空格符;
52 int neg;
53 if (c == '-') {
54 neg = 1;
55 c = *s++;
56 } else {
57 neg = 0;
58 if (c == '+') c = *s++;
59 }
复制代码
开始检测符号,检测到'-'标记 neg 值然后记录指针 s 调到下一个,检测到'+'也同理;
60 if ((base == 0 || base == 16) && c == '0' && (*s == 'x' || *s == 'X') && isxdigit(s[1])) {
61 c = s[1];
62 s += 2;
63 base = 16;
64 }
复制代码
开始检测 16 进制,如果是 0 进制(默认值)或者 16 进制,而且当前字符(c 指向的)是'0'和下一个(s 指向的),是'x'或'X',而且再下一个字符(s[1])是 16 进制里面的字符,那么就跳过两个检测(即字符'0x'),标识为 16 进制;
65 if (base == 0) base = (c == '0') ? 8 : 10;
复制代码
如果上一部检测 16 进制不是,那么如果当前的字符为'0',那就设定为 8 进制,否则,一律认为是 10 进制;
67 // We always work in the negative space because the most negative value has a
68 // larger magnitude than the most positive value.
69 T cutoff = Min / base;
70 int cutlim = -(Min % base);
复制代码
因为负数表示比整数多一个数,如-128 到 127,我们在负数域上面进行计算,使用最小值除以进制数,得到商值 cutoff 和余数的负数 cutlim
71 // Non-zero if any digits consumed; negative to indicate overflow/underflow.
72 int any = 0;
73 T acc = 0;
74 for (; ; c = *s++) {
75 if (isdigit(c)) {
76 c -= '0';
77 } else if (isalpha(c)) {
78 c -= isupper(c) ? 'A' - 10 : 'a' - 10;
79 } else {
80 break;
81 }
82 if (c >= base) break;
83 if (any < 0) continue;
84 if (acc < cutoff || (acc == cutoff && c > cutlim)) {
85 any = -1;
86 acc = Min;
87 errno = ERANGE;
88 } else {
89 any = 1;
90 acc *= base;
91 acc -= c;
92 }
93 }
复制代码
使用 any 记录,acc 记录最终的结果,开始遍历这一串字符(已经除去了正负号,进制表示);如果是数字那么 c 就减去字符'0'得到具体的数值,如果是字母,那就判断是大写还是小写,相应的减去('A'-10)或('a'-10)得到了具体的数值,其它符号的话就停止循环遍历,识别结束。
每次循环拿到的数值 c 其实就是数字最高位上面的表示,如果 c>=base,说明这个字符表示超过了可以表示的进制,也直接结束;
如果 any 值小于 0,说明在上一次的判断中,acc 值已经小于了 cutoff,即乘以对应的进制之后一定会突破最小值,或者(acc == cutoff && c > cutlim),本次操作完之后也会出现突破最小值的情况,所以直接将 acc 置为最小值,置错位标志为超出限制。
如果 any 值大于 0,说明上一次乘法可以继续累乘。如 128 十进制,acc 的处理如下:
acc = 0;
检测 c = 1; acc = acc*10 = 0; acc = acc - c = 0 - 1 = -1;
检测 c = 2; acc = acc*10 = -10; acc = acc - c = -12;
检测 c = 8; acc = acc10 = -120; acc = acc - c = -128;不同于我们正常的(((110)+2)10)+8 = 128,这里使用的是(((-110)-2)*10)-8 = -128
94 if (endptr != nullptr) *endptr = const_cast<char*>(any ? s - 1 : nptr);
95 if (!neg) {
96 if (acc == Min) {
97 errno = ERANGE;
98 acc = Max;
99 } else {
100 acc = -acc;
101 }
102 }
103 return acc;
104 }
复制代码
最后就是检测对应的结果:如果需要保存最后的指针,先 check any 值(是否越界),越界的话直接返回 nptr 指针,否则返回 s 的前一个(因为 s 是被移到下一个字符之后检测了才发现失效的,所以最后一个字符是上一个);然后检测符号,如果是负数,那么就要处理好越界的情况,置为 Max,然后赋值 errno,对应的取反即可,最后返回对应值就好。
strtoumax---等价于 uintmax_t 的 strtoul
使用无符号的模板函数进行处理,流程基本与 strtoimax 一致,只是这里使用的是(((1*10)+2)*10)+8 = 128 这样的方式,而且没有最小值,只有最大值,可以参照代码进行相应的分析。注意:这个函数你买了也是区分了+/-符号的,也就是说输入字符串‘-128’最后在模板函数里面最后的 acc 取反也是可以得到 acc 等于-128 的,但是因为返回参数为 uintmax_t,那就会将-128(补码表示 1000 0000)解释为 unsigned 类型的数据也即是 128(源码表示 1000 0000)
197 uintmax_t strtoumax(const char* s, char** end, int base) {
198 return StrToU<uintmax_t, UINTMAX_MAX>(s, end, base);
199 }
复制代码
模板函数
106 template <typename T, T Max> T StrToU(const char* nptr, char** endptr, int base) {
107 if (base < 0 || base == 1 || base > 36) {
108 if (endptr != nullptr) *endptr = const_cast<char*>(nptr);
109 errno = EINVAL;
110 return 0;
111 }
112
113 const char* s = nptr;
114 int c;
115 do {
116 c = *s++;
117 } while (isspace(c));
118 int neg;
119 if (c == '-') {
120 neg = 1;
121 c = *s++;
122 } else {
123 neg = 0;
124 if (c == '+') c = *s++;
125 }
126 if ((base == 0 || base == 16) && c == '0' && (*s == 'x' || *s == 'X') && isxdigit(s[1])) {
127 c = s[1];
128 s += 2;
129 base = 16;
130 }
131 if (base == 0) base = (c == '0') ? 8 : 10;
132
133 T cutoff = Max / static_cast<T>(base);
134 int cutlim = Max % static_cast<T>(base);
135 T acc = 0;
136 int any = 0;
137 for (; ; c = *s++) {
138 if (isdigit(c)) {
139 c -= '0';
140 } else if (isalpha(c)) {
141 c -= isupper(c) ? 'A' - 10 : 'a' - 10;
142 } else {
143 break;
144 }
145 if (c >= base) break;
146 if (any < 0) continue;
147 if (acc > cutoff || (acc == cutoff && c > cutlim)) {
148 any = -1;
149 acc = Max;
150 errno = ERANGE;
151 } else {
152 any = 1;
153 acc *= base;
154 acc += c;
155 }
156 }
157 if (neg && any > 0) acc = -acc;
158 if (endptr != nullptr) *endptr = const_cast<char*>(any ? s - 1 : nptr);
159 return acc;
160 }
复制代码
wcstoimax---等价于 intmax_t 的 wcstol
与上面的类似,只是输入为 w_char,实现方案类似,不再赘述
wcstoumax---等价于 uintmax_t 的 wcstoul
与上面的类似,只是输入为 w_char,实现方案类似,不再赘述
评论