写点什么

C++ 学习 ------cfenv 头文件的作用与源码分析

作者:桑榆
  • 2022 年 8 月 14 日
    广东
  • 本文字数:6348 字

    阅读完需:约 21 分钟

C++学习------cfenv头文件的作用与源码分析

引言

cfenv 是 C++对 C 语言头文件 fenv.h 的封装,该头文件定义了一系列与浮点数运算环境相关的函数和宏定义,以及一些相关的结构体定义。它的作用主要是控制程序运行过程中浮点数运算的状态 flag 和控制模式,接下来我们来看看这个头文件的具体作用与实现原理。

注一:下面的源码参考 android-12.0.0_r3 中的源码,具体代码路径为:

http://www.aospxref.com/android-12.0.0_r3/xref/

注二:贴近底层的代码实现通常都与具体的计算机体系结构相关,如:aarch64、arm、i386、x86_64 等,文中以笔者的运行环境为主,主要讲述 x86_64 架构相关的代码实现与分析。

C++的封装逻辑

参考对应文件http://www.aospxref.com/android-12.0.0_r3/xref/external/libcxx/include/cfenv中的代码片段如下:

56 #include <__config>57 #include <fenv.h>58 59 #if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)60 #pragma GCC system_header61 #endif62 63 _LIBCPP_BEGIN_NAMESPACE_STD64 65 using ::fenv_t;66 using ::fexcept_t;67 68 using ::feclearexcept;69 using ::fegetexceptflag;70 using ::feraiseexcept;71 using ::fesetexceptflag;72 using ::fetestexcept;73 using ::fegetround;74 using ::fesetround;75 using ::fegetenv;76 using ::feholdexcept;77 using ::fesetenv;78 using ::feupdateenv;79 80 _LIBCPP_END_NAMESPACE_STD81 82 #endif  // _LIBCPP_CFENV
复制代码

实际上这里做了两件事情,一是 include 了 C 语言的头文件“fenv.h”,二是,将头文件中全局的结构体、对应的函数都使用 using 语句修饰,保证在对应 std 命名空间中都可以正常访问到。这便是 C++对 C 头文件的封装,具体实现还是 C 语言头文件中的。

fenv.h 的结构体定义与宏定义

我们先来看看定义部分:

//http://www.aospxref.com/android-12.0.0_r3/xref/bionic/libc/include/fenv.h34  #if defined(__aarch64__) || defined(__arm__)35  #include <bits/fenv_arm.h>36  #elif defined(__i386__)37  #include <bits/fenv_x86.h>38  #elif defined(__x86_64__)39  #include <bits/fenv_x86_64.h>40  #endif...42  __BEGIN_DECLS//上面三个平台的函数定义78  __END_DECLS...80  #if defined(__arm__)81  #include <android/legacy_fenv_inlines_arm.h>82  #endif
复制代码

这里通过不同的平台宏进行区分,最后选择了不同的平台头文件,我们以__x86_64__进行分析,其它平台类似(注意,__arm__平台有些区别,其它三个平台的头文件只定义了结构体或者变量,所以需要在该文件中定义函数,但是__arm__平台在其头文件中同时定义了结构体变量和函数,就不需重复定义了)

FE_xxx 异常宏

定义了 float 运算的相关类别,按位进行标识。

  • FE_INVALID:非法参数异常

  • FE_DENORMAL:非法操作异常

  • FE_DIVBYZERO:除 0 异常

  • FE_OVERFLOW:上越界溢出异常

  • FE_UNDERFLOW:下越界溢出异常

  • FE_INEXACT:结果不精确异常

  • FE_ALL_EXCEPT:上面所有的异常,按位表示为 0x3f

33  /*34   * Each symbol representing a floating point exception expands to an integer35   * constant expression with values, such that bitwise-inclusive ORs of _all36   * combinations_ of the constants result in distinct values.37   *38   * We use such values that allow direct bitwise operations on FPU/SSE registers.39   */40  #define FE_INVALID    0x0141  #define FE_DENORMAL   0x0242  #define FE_DIVBYZERO  0x0443  #define FE_OVERFLOW   0x0844  #define FE_UNDERFLOW  0x1045  #define FE_INEXACT    0x2046  47  /*48   * The following symbol is simply the bitwise-inclusive OR of all floating-point49   * exception constants defined above.50   */51  #define FE_ALL_EXCEPT   (FE_INVALID | FE_DENORMAL | FE_DIVBYZERO | \52                           FE_OVERFLOW | FE_UNDERFLOW | FE_INEXACT)
复制代码

FE_xxx 浮点数舍入方向宏

定义了浮点数进行舍入时的方向,按位进行标识(因为浮点数有精度限制,现有的编码方式并不能表示所有的数,需要考虑进行舍入,即使用一个可以的编码来近似表示该浮点数)。

  • FE_TONEAREST:将 x 舍入为最接近 x 的值,有一半的 case 舍入为 0

  • FE_DOWNWARD:将 x 舍入为不大于 x 的最大值

  • FE_UPWARD:将 x 舍入为不小于 x 的最小值

  • FE_TOWARDZERO:将 x 舍入为绝对值不大于 x 的最接近 x 的值

54  /*55   * Each symbol representing the rounding direction, expands to an integer56   * constant expression whose value is distinct non-negative value.57   *58   * We use such values that allow direct bitwise operations on FPU/SSE registers.59   */60  #define FE_TONEAREST  0x00061  #define FE_DOWNWARD   0x40062  #define FE_UPWARD     0x80063  #define FE_TOWARDZERO 0xc00
复制代码

fenv_t 类型定义

该结构体实际上就是整个浮点数运算环境的定义,包括控制字、状态字、标志字等信息。

65  /*66   * fenv_t represents the entire floating-point environment.67   */68  typedef struct {69    struct {70      __uint32_t __control;   /* Control word register */71      __uint32_t __status;    /* Status word register */72      __uint32_t __tag;       /* Tag word register */73      __uint32_t __others[4]; /* EIP, Pointer Selector, etc */74    } __x87;75    __uint32_t __mxcsr;       /* Control, status register */76  } fenv_t;
复制代码

fexcept_t 类型定义

异常状态类型信息,具体到函数使用时说明。

78  /*79   * fexcept_t represents the floating-point status flags collectively, including80   * any status the implementation associates with the flags.81   *82   * A floating-point status flag is a system variable whose value is set (but83   * never cleared) when a floating-point exception is raised, which occurs as a84   * side effect of exceptional floating-point arithmetic to provide auxiliary85   * information.86   *87   * A floating-point control mode is a system variable whose value may be set by88   * the user to affect the subsequent behavior of floating-point arithmetic.89   */90  typedef __uint32_t fexcept_t;
复制代码

fenv.h 的变量定义

定义了一个全局只读变量__fe_dfl_env,即我们上面说的 fenv_t 结构体,保存浮点数运算的相关环境设置信息;同时定义了一个获取该变量地址的宏 FE_DFL_ENV,使用取地址符,返回变量地址。

67  /*68   * The following constant represents the default floating-point environment69   * (that is, the one installed at program startup) and has type pointer to70   * const-qualified fenv_t.71   *72   * It can be used as an argument to the functions that manage the floating-point73   * environment, namely fesetenv() and feupdateenv().74   */75  extern const fenv_t __fe_dfl_env;76  #define FE_DFL_ENV (&__fe_dfl_env)
复制代码

fenv.h 的函数定义

可以明显地看出,函数定义分为四类:

  • 管理异常 flag 位的

  • 管理浮点数舍入方向的

  • 管理整体环境变量的

  • 管理异常整体控制的

44  // fenv was always available on x86.45  #if __ANDROID_API__ >= 21 || defined(__i386__)46  int feclearexcept(int __exceptions) __INTRODUCED_IN_ARM(21) __INTRODUCED_IN_X86(9);47  int fegetexceptflag(fexcept_t* __flag_ptr, int __exceptions) __INTRODUCED_IN_ARM(21) __INTRODUCED_IN_X86(9);48  int feraiseexcept(int __exceptions) __INTRODUCED_IN_ARM(21) __INTRODUCED_IN_X86(9);49  int fesetexceptflag(const fexcept_t* __flag_ptr, int __exceptions) __INTRODUCED_IN_ARM(21) __INTRODUCED_IN_X86(9);50  int fetestexcept(int __exceptions) __INTRODUCED_IN_ARM(21) __INTRODUCED_IN_X86(9);51  52  int fegetround(void) __INTRODUCED_IN_ARM(21) __INTRODUCED_IN_X86(9);53  int fesetround(int __rounding_mode) __INTRODUCED_IN_ARM(21) __INTRODUCED_IN_X86(9);54  55  int fegetenv(fenv_t* __env) __INTRODUCED_IN_ARM(21) __INTRODUCED_IN_X86(9);56  int feholdexcept(fenv_t* __env) __INTRODUCED_IN_ARM(21) __INTRODUCED_IN_X86(9);57  int fesetenv(const fenv_t* __env) __INTRODUCED_IN_ARM(21) __INTRODUCED_IN_X86(9);58  int feupdateenv(const fenv_t* __env) __INTRODUCED_IN_ARM(21) __INTRODUCED_IN_X86(9);59  60  int feenableexcept(int __exceptions) __INTRODUCED_IN_ARM(21) __INTRODUCED_IN_X86(9);61  int fedisableexcept(int __exceptions) __INTRODUCED_IN_ARM(21) __INTRODUCED_IN_X86(9);62  int fegetexcept(void) __INTRODUCED_IN_ARM(21) __INTRODUCED_IN_X86(9);63  #else64  /* Defined as inlines for pre-21 ARM. */65  #endif
复制代码

查看其对应的实现.c 文件

只发现了 amd64,arm64,i387 的实现,具体该选择哪一个呢?我们来看看具体的编译情况。

http://www.aospxref.com/android-12.0.0_r3/xref/bionic/libm/Android.bp

22 cc_library {23     name: "libm",24     defaults: ["linux_bionic_supported"],25     ramdisk_available: true,26     vendor_ramdisk_available: true,27     recovery_available: true,28     static_ndk_lib: true,29 30     whole_static_libs: ["libarm-optimized-routines-math"],31 ...285     // arch-specific settings286     arch: {287         arm: {288             srcs: [289                 "arm/fenv.c",290             ],...314         arm64: {315             srcs: [316                 "arm64/fenv.c",317                 "arm64/lrint.S",318                 "arm64/sqrt.S",319             ],347         x86: {348             srcs: [349                 "i387/fenv.c",...416         x86_64: {417             srcs: [418                 "amd64/fenv.c",
复制代码

可以知道,x86_64 使用的是 amd64/fenv.c 的实现,接下我我们就分析该文件中的具体实现。

http://www.aospxref.com/android-12.0.0_r3/xref/bionic/libm/amd64/fenv.c

管理异常 flag 位

feclearexcept

作用是清空输入指定的异常,不进行监控。

77  /*78   * The feclearexcept() function clears the supported floating-point exceptions79   * represented by `excepts'.80   */81  int82  feclearexcept(int excepts)83  {84    fenv_t fenv;85    unsigned int mxcsr;86  87    excepts &= FE_ALL_EXCEPT;88  89    /* Store the current x87 floating-point environment */90    __asm__ __volatile__ ("fnstenv %0" : "=m" (fenv));91  92    /* Clear the requested floating-point exceptions */93    fenv.__x87.__status &= ~excepts;94  95    /* Load the x87 floating-point environent */96    __asm__ __volatile__ ("fldenv %0" : : "m" (fenv));97  98    /* Same for SSE environment */99    __asm__ __volatile__ ("stmxcsr %0" : "=m" (mxcsr));100    mxcsr &= ~excepts;101    __asm__ __volatile__ ("ldmxcsr %0" : : "m" (mxcsr));102  103    return (0);104  }
复制代码

通过代码,可以看到,首先将输入值与 FE_ALL_EXCEPT 做按位与,截断操作数据,保证后续操作是针对有效的异常位;通过__asm__汇编嵌入语句,fnstenv 获取当前的环境变量,然后使用取反再按位与的操作,将想要清除的异常位置为 0,保留其它位,变量为 fenv.__x87.__status;然后 fldenv 加载修改后的环境变量;相同的操作对 SSE(Streaming SIMD Extensions) 环境变量进行处理,主要是对 MXCSR 寄存器进行操作,该寄存器也是管理浮点数运算相关的。

feraiseexcept

使用输入值更新异常检测环境信息,主要的逻辑还是在 fesetexceptflag 函数中

131  /*132   * The feraiseexcept() function raises the supported floating-point exceptions133   * represented by the argument `excepts'.134   *135   * The standard explicitly allows us to execute an instruction that has the136   * exception as a side effect, but we choose to manipulate the status register137   * directly.138   *139   * The validation of input is being deferred to fesetexceptflag().140   */141  int142  feraiseexcept(int excepts)143  {144    excepts &= FE_ALL_EXCEPT;145  146    fesetexceptflag((fexcept_t *)&excepts, excepts);147    __asm__ __volatile__ ("fwait");148  149    return (0);150  }
复制代码

fesetexceptflag

相关的汇编操作与 feclearexcept 类似,这次 set 操作的两个输入值其实是同一个值,一个只读,一个可改写,对位运算进行两次校验,保证修改正确。

152  /*153   * This function sets the floating-point status flags indicated by the argument154   * `excepts' to the states stored in the object pointed to by `flagp'. It does155   * NOT raise any floating-point exceptions, but only sets the state of the flags.156   */157  int158  fesetexceptflag(const fexcept_t *flagp, int excepts)159  {160    fenv_t fenv;161    unsigned int mxcsr;162  163    excepts &= FE_ALL_EXCEPT;164  165    /* Store the current x87 floating-point environment */166    __asm__ __volatile__ ("fnstenv %0" : "=m" (fenv));167  168    /* Set the requested status flags */169    fenv.__x87.__status &= ~excepts;170    fenv.__x87.__status |= *flagp & excepts;171  172    /* Load the x87 floating-point environent */173    __asm__ __volatile__ ("fldenv %0" : : "m" (fenv));174  175    /* Same for SSE environment */176    __asm__ __volatile__ ("stmxcsr %0" : "=m" (mxcsr));177    mxcsr &= ~excepts;178    mxcsr |= *flagp & excepts;179    __asm__ __volatile__ ("ldmxcsr %0" : : "m" (mxcsr));180  181    return (0);182  }
复制代码

fegetexceptflag

获取对应的异常信息,注意,这里从两个地方获取之后做了与操作

106  /*107   * The fegetexceptflag() function stores an implementation-defined108   * representation of the states of the floating-point status flags indicated by109   * the argument excepts in the object pointed to by the argument flagp.110   */111  int112  fegetexceptflag(fexcept_t *flagp, int excepts)113  {114    unsigned short status;115    unsigned int mxcsr;116  117    excepts &= FE_ALL_EXCEPT;118  119    /* Store the current x87 status register */120    __asm__ __volatile__ ("fnstsw %0" : "=am" (status));121  122    /* Store the MXCSR register */123    __asm__ __volatile__ ("stmxcsr %0" : "=m" (mxcsr));124  125    /* Store the results in flagp */126    *flagp = (status | mxcsr) & excepts;127  128    return (0);129  }
复制代码


管理浮点数舍入方向

管理整体环境变量

管理异常整体控制

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

桑榆

关注

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

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

评论

发布
暂无评论
C++学习------cfenv头文件的作用与源码分析_签约计划第三季_桑榆_InfoQ写作社区