visualstudio14ctp1中的C运行时(CRT)特性、修复和突破性更改

(这是有关VisualStudio“14”CTP中C运行时(CRT)更改的两篇文章中的第二篇。第一篇文章, 伟大的C运行时(CRT)重构 ,涵盖了CRT的主要架构变化;第二篇文章列举了新特性、错误修复和突破性更改。)

null

此列表涵盖了在Visual Studio 2013 RTM之后对CRT所做的所有主要更改,这些更改出现在Visual Studio“14”CTP中。有关覆盖C++标准库更改的类似列表,请参见6月6日的Stephan文章, Visual Studio 2014中C++ 14的STL特性、修复和破坏更改 . 这些更改按与之相关联的主CRT标头分组,其中一个较大的更改是 printf scanf 首先介绍功能。

在VisualStudio“14”CTP中,我们完全实现了C99标准库,除了依赖于Visual C++编译器尚未支持的编译器特征的任何库特征(特别是 <tgmath.h> 未实现)。毫无疑问,仍然存在一些一致性问题——我们知道其中一些问题,包括 _Exit 不见了 wcstok 有错误的签名,我们正在努力修复这些。如果您发现一个错误或一个丢失的功能,请报告它 微软连接 . 如果你现在报告错误,有一个非常好的机会,我们将能够在RTM之前修复它们。

请注意,MSDN上的文档还没有更新到包含这些博客文章中所涉及的任何更改。

修复宽字符串格式和转换说明符

2015年4月7日更新: 此功能已在Visual Studio 2015 CTP6中恢复;它不会出现在Visual Studio 2015中。  我们有许多客户对此变化表示关注,并且在使用静态库时发现了几个新问题。

visualstudio“14”CTP中对CRT最大的“突破性改变”是对宽字符串格式的I/O功能的改变(例如。, wprintf wscanf )处理 %c , %s ,和 %[] (扫描集)格式和转换说明符。

在20世纪90年代初,Visual C++ CRT首先实现了宽字符串格式的I/O函数。它们的实施使得 %c , %s ,和 %[] 映射到宽字符或字符串参数的说明符。例如,这是行为(并且通过Visual C++ 2013保持行为):

 printf("Hello, %s!", "World"); // Lowercase s: narrow string printf("Hello, %S!", L"World"); // Capital S: wide string wprintf(L"Hello, %s!", L"World"); // Lowercase s: wide string wprintf(L"Hello, %S!", "World"); // Capital S: narrow string 

这种设计的优点是 %c , %s ,和 %[] 说明符总是映射到函数调用的“自然”宽度的参数。如果您正在调用窄字符串格式的I/O函数,它们将映射到窄字符或字符串参数;如果要调用宽字符串格式的I/O函数,它们将映射到宽字符或字符串参数。除此之外,通过中的宏,这种设计使得从使用窄字符串到使用宽字符串更加容易 <tchar.h> .

这些功能后来在C99中被标准化,不幸的是,标准化的行为是不同的。在C99规范中 %c , %s ,和 %[] 说明符总是映射到窄字符或字符串参数。这个 l (小写L)长度修饰符必须用于格式化宽字符或字符串参数。因此,根据C99规范,以下调用是正确的:

 printf("Hello, %s!", "World"); // s: narrow string printf("Hello, %ls!", L"World"); // ls: wide string wprintf(L"Hello, %ls!", L"World"); // ls: wide string wprintf(L"Hello, %s!", "World"); // s: narrow string 

这种设计的优点是,无论调用哪个函数,说明符总是具有相同的含义。它有一个缺点,它与Visual C++ CRT中先前实现的不匹配,而且它与宏的工作不匹配。 <tchar.h> .

在visualstudio“14”CTP中,我们翻转了 %c , %s ,和 %[] 宽格式I/O函数的说明符,以便它们的行为符合C标准的要求。 大写字母说明符等同物的含义( %C %S )为了保持一致性,也进行了更改。为了便于继续使用 <tchar.h> 我们还添加了一个新的长度修饰符, T ,这意味着参数具有“自然”宽度,实际上给出了遗留行为。因此,例如,以下所有调用都是正确的:

 printf("Hello, %s!", "World"); // narrow string printf("Hello, %S!", L"World"); // wide string printf("Hello, %ls!", L"World"); // wide string printf("Hello, %Ts!", "World"); // natural width (narrow) wprintf(L"Hello, %s!", "World"); // narrow string wprintf(L"Hello, %S!", L"World"); // wide string wprintf(L"Hello, %ls!", L"World"); // wide string wprintf(L"Hello, %Ts!", L"World"); // natural width (wide) 

这个相当小的更改对现有代码有很大的影响。有数百万行代码需要旧的行为,我们认识到我们不能无条件地破坏所有这些代码。虽然我们鼓励您迁移代码以使用一致格式字符串模式,但我们还提供了一个编译时开关,使您能够将行为恢复到传统模式。因此,有两种模式:

  • C99一致性模式 :在此模式下,调用宽字符串格式的I/O函数将获得C99所需的正确行为。默认情况下启用此模式。

  • 传统模式 :在此模式下,对宽字符串格式I/O函数的调用将获得这三个格式说明符的旧行为,因为它们是在Visual Studio 2013和早期版本中实现的。要启用此模式,请预定义 _CRT_STDIO_LEGACY_WIDE_SPECIFIERS 宏生成程序时。

这种模式是可配置的每个可执行模块,所以每个DLL或EXE可以独立指定它需要的模式。此模式只能在编译时配置,不能动态更改。因为模式是每个可执行模块的,所以链接到单个可执行模块的所有源文件都必须为同一模式编译(即,有或没有) _CRT_STDIO_LEGACY_WIDE_SPECIFIERS 定义。如果尝试在链接时混合和匹配对象,其中一些对象需要传统模式,而另一些对象需要一致性模式,则会出现链接时不匹配错误。

如果您有静态库,并且希望将这些静态库链接到使用C99一致性模式或传统模式的模块中,则可以执行以下操作:

  1. 确保静态库中的代码不使用或以其他方式处理(例如通过传递)两种模式之间行为不同的格式字符串,以及

  2. 预定义 _CRT_STDIO_ARBITRARY_WIDE_SPECIFIERS 宏编译静态库的源文件时。这不是另一种模式;它只允许使用一致性或遗留模式将这些文件链接到模块中。

  • _clear87 _clearfp :在Visual Studio 2013中 _clear87 _clearfp 如果CPU支持SSE2,则用于x86的CRT中的函数将无法返回原始浮点单元状态。这个问题已经解决了。

  • fegetenv fesetenv :在Visual Studio 2013中,这些函数在用于x86的CRT中未正确实现。有两个错误:[1]一个呼叫 fegetenv 将导致引发任何挂起的、未屏蔽的x87浮点异常,并且[2] fegetenv 函数将在返回之前屏蔽所有x87浮点异常,从而返回不正确的状态。因为 fesetenv 函数使用相同的底层逻辑,它也受到这些问题的影响。这两个问题都已解决。

  • feholdexcept公司 :在Visual Studio 2013中 feholdexcept 函数未能在返回之前屏蔽所有浮点异常。这个问题已经解决了。

  • FLT_ROUNDS :在Visual Studio 2013中 FLT_ROUNDS 宏扩展为常量表达式,这是不正确的,因为舍入模式在运行时是可配置的,例如通过调用 fesetround . 这个 FLT_ROUNDS 宏现在是动态的,并正确反映当前舍入模式( 接#806669 ).

  • /fp:strict 支持 :在Visual Studio 2013中,如果 <fenv.h> 包含在一个C源文件中,并且该源文件是用 /fp:strict ,源文件将无法编译,因为中内联函数的静态初始值设定项中存在浮点运算 <fenv.h> . 这个问题已经解决了( 接#806624 ).

  • 下列宏已添加到 <float.h> : FLT_DECIMAL_DIG , FLT_HAS_SUBNORM , FLT_TRUE_MIN , DBL_DECIMAL_DIG , DBL_HAS_SUBNORM , DBL_TRUE_MIN , LDBL_DECIMAL_DIG , LDBL_HAS_SUBNORM ,和 LDBL_TRUE_MIN .

  • 格式和转换说明符宏现在可以与宽格式字符串一起使用 :在Visual Studio 2013中,宏中的格式和转换说明符 <inttypes.h> 它们的定义方式使得它们在宽格式字符串中不可用。这个问题已经解决了( 堆垛溢出#21788652 ).

  • 一致宽格式说明符 :请参阅本文的第一节,了解对 %c , %s ,和 %[] (扫描集)格式和转换说明符。

  • 这个 printf scanf 函数现在是内联定义的 :为了支持两种宽字符串格式和转换说明符模式,所有 printf scanf 函数已内联移动到 <stdio.h> , <conio.h> ,以及其他CRT标头。对于在本地声明这些函数而不包含相应CRT头的任何程序来说,这是一个突破性的改变。“修复”是包括适当的CRT头。

  • 格式和转换说明符增强功能 :的 %F 现在支持格式/转换说明符。它在功能上等同于 %f 格式说明符,但无穷大和南是用大写字母格式化的。

    现在支持以下长度修饰符:

    • hh : signed char unsigned char
    • j : intmax_t uintmax_t
    • t : ptrdiff_t
    • z : size_t
    • L : long double

    在以前的版本中,用于解析 F N 作为长度修饰符。这种行为可以追溯到分段地址空间时代:这些长度修饰符分别用于表示远指针和近指针,如 %Fp %Ns . 此行为已被删除。如果 %F 如果遇到,则现在将其视为 %F 格式说明符;如果 %N 遇到时,它现在被视为无效参数。

  • 无穷大与NaN格式 在以前的版本中,无限和NANS将使用一组Visual C++特定的哨兵字符串来格式化:

    • 无穷: 1.#INF
    • 静楠: 1.#QNAN
    • 信号NaN: 1.#SNAN
    • 不确定NaN: 1.#IND

    其中任何一个都可能以符号作为前缀,并且根据字段宽度和精度的不同,格式可能略有不同(有时会产生不寻常的效果,例如。 printf("%.2f", INFINITY) 将打印 1.#J 因为 #INF 将“四舍五入”到2位数的精度)。C99对如何格式化无穷大和NaN提出了新的要求。为了符合这些新的要求,我们已经改变了我们的实施方式。新字符串如下:

    • 无穷: inf
    • 静楠: nan
    • 信号NaN: nan(snan)
    • 不确定NaN: nan(ind)

    其中任何一个都可以用符号作为前缀。如果使用大写格式说明符(例如。 %F 而不是 %f )然后字符串用大写字母打印(例如。 INF 而不是 inf ),按要求( 接#806668 ).

    这个 scanf 函数已被修改以解析这些新字符串,因此这些字符串将在 printf scanf .

  • 指数格式 :的 %e %E 格式说明符将浮点数格式化为十进制尾数和指数。这个 %g %G 在某些情况下,格式说明符也会以此形式格式化数字。在以前的版本中,CRT总是生成具有三位数指数的字符串。例如, printf("%e", 1.0) 将打印 1.000000e+000 . 这是不正确的:C要求如果指数只能用一个或两个数字来表示,那么只能打印两个数字。

    在Visual Studio 2005中,添加了一个全局一致性开关: _set_output_format . 程序可以使用参数调用此函数 _TWO_DIGIT_EXPONENT ,以启用一致性指数打印。此一致性开关已被删除,默认行为已更改为标准一致性指数打印模式。

  • %A %a 零填充 :的 %a %A 格式说明符将浮点数格式化为十六进制尾数和二进制指数。在以前的版本中 printf 函数将错误地将填充字符串归零。例如, printf("%07.0a", 1.0) 将打印 00x1p+0 ,它应该在哪里打印 0x01p+0 . 这个问题已经解决了。

  • 浮点格式和解析正确性 :我们实现了新的浮点格式和解析算法,以提高正确性。这种变化会影响 printf scanf 函数族,以及 strtod .

    旧的格式化算法只会生成有限的数字,然后用零填充剩余的小数位。这通常足以生成返回到原始浮点值的字符串,但如果需要精确的值(或最接近的十进制表示形式),这就不太好了。新的格式化算法生成尽可能多的数字来表示值(或填充指定的精度)。作为改进的例子;当打印两个较大的幂时,请考虑结果:

        printf("%.0f", pow(2.0, 80))    Old:  1208925819614629200000000    New:  1208925819614629174706176

    旧的解析算法只考虑输入字符串中最多17个有效数字,而丢弃其余数字。这足以生成字符串表示的值的非常接近的近似值,并且结果通常非常接近正确舍入的结果。新的实现考虑了所有当前的数字,并为所有输入生成正确的四舍五入结果(长度高达768位)。此外,这些函数现在考虑舍入模式(可通过 fesetround ).

  • 十六进制和无穷大/NaN浮点解析 :浮点解析算法现在将解析十六进制浮点字符串(例如 %a %A printf 格式说明符)以及 printf 功能,如上所述。

  • snprintf vsnprintf 现已实施 :C99 snprintf vsnprintf 功能已经实现。

  • 格式字符串验证 :在以前的版本中 printf scanf 函数会默默地接受许多无效的格式字符串,有时会产生不寻常的效果。例如, %hlhlhld 将被视为 %d . 所有无效格式字符串现在都被视为无效参数。

  • fopen 模式字符串验证 :在以前的版本中 fopen 函数族默默地接受一些无效的模式字符串(例如。 r+b+ ). 现在检测到无效的模式字符串并将其视为无效参数( 接#792703 ).

  • fseek 用于大文件 :在以前的版本中 fseek 函数找不到超过 INT_MAX 文件开头的字节数。这个问题已经解决了,但是请注意,如果您使用的是大文件,那么 应该 使用64位I/O函数,如 _fseeki64 . 这个 fseek 功能仍只能查找到 INT_MAX 字节一次向前,因为其offset参数的类型为 int ( 接#810715 ).

  • tmpnam 生成可用的文件名 :在以前的版本中 tmpnam tmpnam_s 函数在驱动器的根目录中生成文件名(例如。 sd3c. ). 这些函数现在在临时目录中生成可用的文件名路径。

  • FILE 封装 :在以前的版本中 FILE 类型已在中完全定义 <stdio.h> ,因此用户代码有可能进入 FILE 把里面的东西弄脏。我们重构了stdio库,以改进对库实现细节的封装。作为其中的一部分, FILE 定义见 <stdio.h> 现在是一个不透明类型,其成员从CRT本身外部无法访问。

  • WEOF :的 WEOF 宏的括号不正确,因此表达式 WEOF (例如。 sizeof WEOF )无法编译。这个问题已经解决了( 接#806655 ).

  • 不可用的端口I/O功能被删除 :已从CRT中删除六个功能: _inp , _inpw , _inpd , _outp , _outpw ,和 _outpd . 这些函数用于在x86上读写I/O端口;因为它们使用特权指令,所以在基于windowsnt的操作系统上,它们从来没有在用户模式代码中工作过。

  • 标准文件描述符和流初始化: 对于非控制台应用程序,标准文件描述符和流的初始化已经修复。 在非控制台程序中,文件句柄被初始化为-2( 连接#785119 ).

  • strtod 等。 :的 strtod 如果输入字符串开头的数字由2个以上组成,则函数族将通过out参数返回不正确的结束指针 32 -1个字符。这个问题已经解决了。

  • strtof wcstof :的 strtof wcstof 函数设置失败 errno ERANGE 当值不能表示为 float . 这个问题已经解决了(注意这个错误是这两个函数特有的;这个 strtod , wcstod , strtold ,和 wcstold 功能未受影响。)

  • _stat 功能 :在以前的版本中 _stat 函数可能会读取超过路径字符串结尾的一个字符。这个问题已经解决了( 连接#796796 ).

  • 对齐的分配函数 :在以前的版本中,对齐的分配函数( _aligned_malloc , _aligned_offset_malloc ,等)将静默地接受对对齐为的块的请求 0 . 文档要求所请求的对齐是2的幂,而0不是。已修复此问题,并请求 0 现在被视为无效参数( 接#809604 ).

  • 这个 _heapadd , _heapset ,和 _heapused 函数已被删除。自从CRT被更新为使用Windows堆以来,这些函数就一直不起作用。

  • 这个 smalheap公司 链接选项已被删除。

  • clock :在以前的版本中 clock 函数是使用windowsapi实现的 GetSystemTimeAsFileTime . 有了这个实现 clock 函数对系统时间很敏感,因此不一定是单调的。这个 clock 功能已在以下方面重新实现 QueryPerformanceCounter 现在是单调的。

    一些客户已经注意到,按照C的规定 clock 函数应该返回进程使用的“处理器时间”,而不是进程启动后经过的挂钟时间。我们继续执行 clock 由于返回的挂钟时间已过,因为有大量为Windows编写的软件预计会出现这种行为。

  • fstat _utime :在以前的版本中 _stat , fstat ,和 _utime 函数不能正确处理夏令时。在VisualStudio2013之前,所有这些函数都有一个微妙的夏令时错误:在夏令时,它们错误地调整了标准时间,就好像它们在夏令时一样。这似乎多年来都没有被注意到,因为虽然实现是不正确的,但它们始终都是不正确的。

    在VisualStudio2013中 _stat 函数族已修复,但 fstat _utime 函数族不是固定的。这暴露了这些函数中的问题,因为它们处理夏令时的方式与 _stat 功能。这个 fstat _utime 函数族现在已经固定,因此所有这些函数现在都正确且一致地处理夏令时( 接#811534 ).

  • asctime :在以前的版本中 asctime 函数将用前导零填充一位数的天数,例如。 Fri Jun 06 08:00:00 2014 . 规范要求在这些日子里加一个前导空格,例如。 Fri Jun _6 08:00:00 2014 (我用下划线标记填充空间)。这个问题已经解决了。

  • time ftime :的 time ftime 函数现在将使用 GetSystemTimePreciseAsFileTime 当API可用(Windows 8及更高版本)以提高精度时。

  • strftime wcsftime :的 strftime wcsftime 函数现在支持 %C , %D , %e , %F , %g , %G , %h , %n , %r , %R , %t , %T , %u ,和 %V 格式说明符。此外 E O 修饰符已分析但被忽略。

    这个 %c 格式说明符指定为为当前区域设置生成“适当的日期和时间表示”。在C语言环境中,此表示形式必须与 %a %b %e %T %Y . 这种形式与 asctime . 在以前的版本中 %c 格式说明符使用 MM/DD/YY HH:MM:SS 陈述。这个问题已经解决了。

  • C11型 timespec timespec_get : <time.h> 现在定义C11 timespec 类型和 timespec_get 功能。此外 TIME_UTC 宏,用于 timespec_get 函数,现在已定义。

  • CLOCKS_PER_SEC :的 CLOCKS_PER_SEC 宏现在扩展为类型为的整数 clock_t ,按C。

运算符new T[N]

  • 在以前的版本中, operator new T[N] 将无法为数组中的元素调用构造函数,如果 N 大于2 32 -1.已修复( 接#802400 ).
詹姆斯·麦克内利斯(詹姆斯。mcnellis@microsoft.com ) 高级软件开发工程师Visual C++库
© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享