(这是有关VisualStudio“14”CTP中C运行时(CRT)更改的两篇文章中的第二篇。第一篇文章, 伟大的C运行时(CRT)重构 ,涵盖了CRT的主要架构变化;第二篇文章列举了新特性、错误修复和突破性更改。)
此列表涵盖了在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一致性模式或传统模式的模块中,则可以执行以下操作:
-
确保静态库中的代码不使用或以其他方式处理(例如通过传递)两种模式之间行为不同的格式字符串,以及
-
预定义
_CRT_STDIO_ARBITRARY_WIDE_SPECIFIERS
宏编译静态库的源文件时。这不是另一种模式;它只允许使用一致性或遗留模式将这些文件链接到模块中。
- 在以前的版本中,当 编辑并继续(/ZI) 已启用,使用
assert
宏可能导致虚假的C4365有符号/无符号警告。这个问题已经解决了( 接#792554 ).
和
-
_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++重载 :在以前的版本中,
<math.h>
为数学库函数定义了一些但不是全部的C++重载。<cmath>
定义了剩余的重载,因此要获取所有重载,需要包含<cmath>
标题。这是引起各种烦恼的原因;例如,如果只包含源文件<math.h>
然后试图打电话sin
对于整数类型的参数,由于重载解析期间的歧义,源文件将无法编译。要解决此问题,所有的C++重载都已从<math.h>
现在只出现在<cmath>
( 接#807080 ). -
新C99数学库函数的正确性修复 :在VisualStudio2013中,我们添加了对新的C99数学库函数的支持(请参见 帕特·布伦纳去年的博客文章 添加内容的列表)。我们修复了这些函数中的几个错误,包括:
-
FP_ILOGB0
和FP_ILOGBNAN
:的FP_ILOGB0
和FP_ILOGBNAN
宏现在在中定义<math.h>
; 它们以前用前导下划线定义不正确( 接#806666 ).
和
-
一致宽格式说明符 :请参阅本文的第一节,了解对
%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
现已实施 :C99snprintf
和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>
现在定义C11timespec
类型和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 ).