写点什么

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

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

    阅读完需:约 23 分钟

引言

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  #else29  #define __PRI_64_prefix "ll"30  #define __PRI_PTR_prefix31  #endif32  #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'输出,但是输入时,我们必须要增加修饰符:

  • 'h':表示 short int 或 unsigned short int 类型

  • 'hh':表示 signed char 或 unsigned char 类型再加上表示 max 值的'j',所以其中'%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_DECLS258  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.c34  intmax_t35  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.c36  imaxdiv_t37  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.cpp174  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    }
复制代码
  1. 检查输入的进制是否有效,如果小于 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, else46    // 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));
复制代码
  1. 跳过前面的空格符号,直到遇到非空格符;

52    int neg;53    if (c == '-') {54      neg = 1;55      c = *s++;56    } else {57      neg = 0;58      if (c == '+') c = *s++;59    }
复制代码
  1. 开始检测符号,检测到'-'标记 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    }
复制代码
  1. 开始检测 16 进制,如果是 0 进制(默认值)或者 16 进制,而且当前字符(c 指向的)是'0'和下一个(s 指向的),是'x'或'X',而且再下一个字符(s[1])是 16 进制里面的字符,那么就跳过两个检测(即字符'0x'),标识为 16 进制;

65    if (base == 0) base = (c == '0') ? 8 : 10;
复制代码
  1. 如果上一部检测 16 进制不是,那么如果当前的字符为'0',那就设定为 8 进制,否则,一律认为是 10 进制;

67    // We always work in the negative space because the most negative value has a68    // larger magnitude than the most positive value.69    T cutoff = Min / base;70    int cutlim = -(Min % base);
复制代码
  1. 因为负数表示比整数多一个数,如-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,实现方案类似,不再赘述

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

桑榆

关注

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

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

评论

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