Visual C++ 中的重大更改
當你升級到 Visual C++ 編譯器的新版本后,可能會在之前編譯並正常運行的代碼中遇到編譯和/或運行時錯誤。 新版本中會引起這類問題的更改稱為重大更改,通常,修改 C++ 語言標准、函數簽名或內存中的對象布局時需要進行這種更改。
若要避免難以檢測和診斷的運行時錯誤,我們建議你永遠不靜態鏈接到使用不同編譯器版本編譯的二進制文件。 此外,當你升級 EXE 或 DLL 項目時,請確保升級它所鏈接的庫。 如果使用 CRT(C 運行時庫)或 STL(標准模板庫)類型,請勿在使用不同編譯器版本編譯的二進制文件(包括 DLL)之間傳遞這些類型。 有關詳細信息,請參閱 跨 DLL 邊界傳遞 CRT 對象時可能的錯誤。
我們進一步建議,你在編寫代碼時永遠不依賴除 COM 接口或 POD 對象以外的特定對象布局。 如果確實要編寫此類代碼,則必須在升級后確保其正常運行。 有關詳細信息,請參閱 ABI 邊界處的可移植性(現代 C++)。
本文的其余部分介紹了 Visual Studio 2015 中的 Visual C++ 中具體的重大更改,並且在本文中,術語“新行為”或“現在”均指該版本。 術語“舊行為”和“之前”指 Visual Studio 2013 和早期版本。
-
編譯器的重大更改
-
b38385a9-a483-4de9-99a6-797488bc5110#BK_CRT
-
b38385a9-a483-4de9-99a6-797488bc5110#BK_STL
-
b38385a9-a483-4de9-99a6-797488bc5110#BK_MFC
-
/Zc:forScope- 選項
編譯器選項 /Zc:forScope- 已棄用,並且將在將來版本中刪除。
Command line warning D9035: option 'Zc:forScope-' has been deprecated and will be removed in a future release
以前會經常用到此選項,以便允許非標准代碼在點的位置之后使用循環變量,根據標准規范,這些變量本應該在范圍之外。 僅當使用 /Za 選項進行編譯時才需要,因為沒有 /Za,將始終允許在循環結束后使用 for 循環變量。 如果你不關心標准一致性(例如,如果你的代碼不是為了移植到其他編譯器),你可以關閉 /Za 選項(或將“禁用語言擴展”屬性設置為“否”)。 如果你確實關心編寫可移植且符合標准的代碼,則應重寫代碼,以便通過將此類變量的聲明移到循環以外的點使其符合標准。
// zc_forScope.cpp // compile with: /Zc:forScope- /Za // C2065 expected int main() { // Uncomment the following line to resolve. // int i; for (int i =0; i < 1; i++) ; i = 20; // i has already gone out of scope under /Za }
-
/Zg 編譯器選項
/Zg 編譯器選項(生成函數原型)不再可用。 此此編譯器選項已被棄用。
-
你無法再使用 mstest.exe 從命令行運行 C++/CLI 單元測試。 請改用 vstest.console.exe。 請參閱 VSTest.Console.exe 命令行選項。
-
可變關鍵字
在之前其正確編譯的位置,不再允許存在 mutable 存儲類說明符。 現在,編譯器報告錯誤 C2071(非法存儲類)。 根據標准,可變說明符僅可應用於類數據成員的名稱,不能應用於聲明為 const 或 static 的名稱,也不能應用於引用成員。
例如,考慮以下代碼:
struct S { mutable int &r; };
早期版本的 Visual C++ 編譯器接受此代碼,但現在編譯器則報告以下錯誤:
錯誤 C2071: 'S::r':非法存儲類
若要修復此錯誤,只需刪除冗余的可變關鍵字。
-
char_16_t 和 char32_t
你不能再使用 char16_t 或 char32_t 作為 typedef 中的別名,因為這些類型現在被視為內置。 用戶和庫作者通常會將 char16_t 和 char32_t 分別定義為 uint16_t 和 uint32_t 的別名。
#include <cstdint> typedef uint16_t char16_t; //C2628 typedef uint32_t char32_t; //C2628 int main(int argc, char* argv[]) { uint16_t x = 1; uint32_t y = 2; char16_t a = x; char32_t b = y; return 0; }
若要更新你的代碼,請刪除 typedef 聲明,並重命名與這些名稱發生沖突的任何其他標識符。
-
非類型模板參數
現在會在提供顯式模板參數時准確檢查包含非類型模板參數的某些代碼的類型符合性。 例如,在早期版本的 Visual C++ 中正確編譯的以下代碼。
struct S1 { void f(int); void f(int, int); }; struct S2 { template <class C, void (C::*Function)(int) const> void f() {} }; void f() { S2 s2; s2.f<S1, &S1::f>(); }
當前編譯器可以准確報告錯誤,因為模板參數類型不匹配模板參數(該參數是指向 const 成員的指針,但函數為非 const):
錯誤 C2893:未能特殊化函數模板“void S2::f(void)” 備注:使用以下模板參數: 備注:“C=S1” 備注:“Function=S1::f”
若要在代碼中修復此錯誤,請確保你使用的模板參數類型匹配模板參數聲明的類型。
-
__declspec(align)
編譯器不再接受函數上的 __declspec(align)。 以前會始終忽略此項,但現在會產生編譯器錯誤。
error C3323: 'alignas' and '__declspec(align)' are not allowed on function declarations
若要解決此問題,請從函數聲明中刪除 __declspec(align)。 因為它不起作用,將其刪除不會更改任何內容。
-
異常處理
有幾個對異常處理的更改。 首先,異常對象必須可復制或可移動。 在 Visual Studio 2013 中的 Visual C++ 中編譯的以下代碼卻不能在 Visual Studio 2015 中的 Visual C++ 中進行編譯:
struct S { public: S(); private: S(const S &); }; int main() { throw S(); // error }
問題在於,復制構造函數是私有的,因此對象無法像處理異常的標准過程那樣進行復制。 當復制構造函數為聲明的 explicit 時,這同樣適用。
struct S { S(); explicit S(const S &); }; int main() { throw S(); // error }
若要更新你的代碼,請確保異常對象的復制構造函數是公用的且未標記為 explicit。
通過值捕獲異常還要求異常對象可復制。 在 Visual Studio 2013 中的 Visual C++ 中編譯的以下代碼卻不能在 Visual Studio 2015 中的 Visual C++ 中進行編譯:
struct B { public: B(); private: B(const B &); }; struct D : public B { }; int main() { try { } catch (D d) // error { } }
可以通過將 catch 的參數類型更改為引用來解決此問題。
catch(D& d) { }
-
后面是宏的字符串文本
編譯器現在支持用戶定義的文本。 因此,宏之前沒有任何干預空格的字符串文本被視為用戶定義的文本,這可能會產生錯誤或意外結果。 例如,在早期的編譯器中,成功編譯了以下代碼:
#define _x "there" char* func() { return "hello"_x; } int main() { char * p = func(); return 0; }
編譯器將此視為后面是宏的字符串文本“hello”,該宏是展開的“there”,然后兩個字符串串聯成一個。 在 Visual Studio 2015 中的 Visual C++ 中,編譯器將此視為用戶定義的文字,但由於沒有定義匹配的用戶定義的 _x 文本,它將報告錯誤。
error C3688: invalid literal suffix '_x'; literal operator or literal operator template 'operator ""_x' not found note: Did you forget a space between the string literal and the prefix of the following string literal?
若要解決此問題,請在字符串文本和宏之間添加一個空格。
-
相鄰字符串文本
與上文類似,由於字符串分析中的相關變化,沒有任何空格的相鄰字符串文本(或寬或窄的字符字符串文本)被視為 Visaul C++ 早期版本中的單個串聯字符串。 在 Visual Studio 2015 中的 Visual C++ 中,現在必須在兩個字符串之間添加空格。 例如,必須更改以下代碼:
char * str = "abc""def";
只需在兩個字符串之間添加空間。
char * str = "abc" "def";
-
placement new 和 placement delete
對 delete 運算符做出更改以使其符合 C++14 標准。 標准更改的詳細信息位於 C++ 調整了大小的釋放。 這些更改將添加采用大小參數的全局 delete 運算符的形式。 重大更改為,如果你之前使用的是具有相同簽名的運算符 delete(以與 placement new 運算符對應),你將收到編譯器錯誤(C2956,在使用 placement new 的點位置出現,因為在代碼中的該位置,編譯器會嘗試標識適當匹配的 delete 運算符)。
函數void operator delete(void *, size_t)是與 C++11 中的 placement new 函數“void * operator new(size_t, size_t)”對應的 placement delete 運算符。 使用 C++14 調整了大小的釋放,此 delete 函數現在是常用釋放函數(全局 delete 運算符)。 標准要求為,如果使用 placement new 查找相應的 delete 函數和常用釋放函數,則程序會出現格式錯誤。
例如,假設你的代碼同時定義了 placement new 和 placement delete:
void * operator new(std::size_t, std::size_t); void operator delete(void*, std::size_t) noexcept;
由於定義的 placement delete 運算符和新的全局調整大小的 delete 運算符之間的函數簽名匹配,因此就會出現問題。 考慮是否可以使用任何 placement new 和 placement delete 運算符的其他類型(size_t 除外)。 請注意,size_t typedef 的類型取決於編譯器;在 Visual C++ 中,它是一個無符號整型的 typedef。 較好的解決辦法就是使用如下的枚舉類型:
enum class my_type : size_t {};
然后,更改你對 placement new 和 placement delete 的定義,以使用此類型作為第二個參數(而不是 size_t)。 你還需要更新對 placement new 的調用以傳遞新類型(例如,通過使用static_cast<my_type>從整數值轉換)並更新 new 和 delete 的定義以強制轉換回整數類型。 你無需為此使用枚舉;具有 size_t 成員的類類型也將起作用。
你還可以將 placement new 全部消除作為備選解決方案。 如果你的代碼使用 placement new 實現內存池,其中位置參數是分配或刪除的對象的大小,則調整了大小的釋放功能可能適合替換你自定義的內存池代碼,且你可以去掉位置函數,僅使用自己兩個參數的 delete 運算符(而不是位置參數)。
如果你不想立即更新代碼,可以通過使用編譯器選項 /Zc:sizedDealloc- 恢復到舊行為。 如果使用此選項,則不存在兩個參數的 delete 函數,並且也不會導致與 placement delete 運算符發生沖突。
-
聯合數據成員
聯合數據成員不再具有引用類型。 以下代碼在 Visual Studio 2013 中的 Visual C++中成功編譯,但在 Visual Studio 2015 中的 Visual C++ 中產生錯誤。
union U1 { const int i; }; union U2 { int &i; }; union U3 { struct {int &i;}; };
前面的代碼產生以下錯誤:
test.cpp(67):錯誤 C2625:U2::i:非法的聯合成員;類型“int &”為引用類型 test.cpp(70):錯誤 C2625:U3::i:非法的聯合成員;類型“int &”為引用類型
若要解決此問題,請將引用類型更改為指針或值。 更改指針類型需要對使用聯合字段的代碼進行更改。 將代碼更改為值將更改存儲在聯合中的數據,這會影響其他字段,因為聯合類型中的字段共享相同的內存。 根據值的大小,它還可能更改聯合的大小。
-
匿名聯合現在更符合標准。 早期版本的編譯器生成了匿名聯合的顯式構造函數和析構函數。 這些在 Visual Studio 2015 中的 Visual C++ 中已刪除。
struct S { S(); }; union { struct { S s; }; } u; // C2280
前面的代碼在 Visual Studio 2015 中的 Visual C++ 中生成以下錯誤:
error C2280: '<unnamed-type-u>::<unnamed-type-u>(void)': attempting to reference a deleted function note: compiler has generated '<unnamed-type-u>::<unnamed-type-u>' here
若要解決此問題,請提供你對構造函數和/或析構函數的定義。
struct S { // Provide a default constructor by adding an empty function body. S() {} }; union { struct { S s; }; } u;
-
具有匿名結構的聯合
為了符合標准,已正對聯合中的匿名結構的成員更改了運行時行為。 創建此類聯合時,將不再隱式調用聯合中的匿名結構成員的構造函數。 此外,聯合超出范圍時,不再隱式調用聯合中的匿名結構成員的析構函數。 請考慮以下代碼,其中聯合 U 包含一個匿名結構,此匿名結構包含的成員是一個具有析構函數的命名結構 S。
#include <stdio.h> struct S { S() { printf("Creating S\n"); } ~S(){ printf("Destroying S\n"); } }; union U { struct { S s; }; U() {} ~U(){} }; void f() { U u; // Destructor implicitly called here. } int main() { f(); char s[1024]; printf("Press any key.\n"); gets_s(s); return 0; }
在 Visual Studio 2013 中的 Visual C++ 中,創建聯合時會調用 S 的構造函數,清理函數 f 的堆棧時會調用 S 的析構函數。 但在 Visual Studio 2015 中的 Visual C++ 中,不會調用構造函數和析構函數。 編譯器會對關於此行為的更改發出警告。
警告 C4587:U::s:行為更改:不再隱式調用構造函數 警告 C4588:U::s:行為更改:不再隱式調用析構函數
若要還原原始行為,請賦予匿名結構一個名稱。 無論編譯器版本為何,非匿名結構的運行時行為都是相同的。
#include <stdio.h> struct S { S() { printf("Creating S.\n"); } ~S() { printf("Destroying S\n"); } }; union U { struct { S s; } namedStruct; U() {} ~U() {} }; void f() { U u; } int main() { f(); char s[1024]; printf("Press any key.\n"); gets_s(s); return 0; }
或者,嘗試將構造函數和析構函數代碼移到新的函數中,並從聯合的構造函數和析構函數添加對這些函數的調用。
#include <stdio.h> struct S { void Create() { printf("Creating S.\n"); } void Destroy() { printf("Destroying S\n"); } }; union U { struct { S s; }; U() { s.Create(); } ~U() { s.Destroy(); } }; void f() { U u; } int main() { f(); char s[1024]; printf("Press any key.\n"); gets_s(s); return 0; }
-
模板解析
對模板的名稱解析進行了更改。 在 C++ 中,考慮名稱解析的候選對象時,可能會出現作為潛在匹配項考慮的一個或多個名稱生成無效的模板實例化的情況。 這些無效的實例化通常不會導致編譯器錯誤,這被稱為 SFINAE(替換失敗不是錯誤)原則。
現在,如果 SFINAE 要求編譯器將類模板專用化進行實例化,則在此過程中發生的任何錯誤都是編譯器錯誤。 在早期版本中,編譯器會忽略此類錯誤。 例如,考慮以下代碼:
#include <type_traits> template<typename T> struct S { S() = default; S(const S&); S(S&&); template<typename U, typename = typename std::enable_if<std::is_base_of<T, U>::value>::type> S(S<U>&&); }; struct D; void f1() { S<D> s1; S<D> s2(s1); } struct B { }; struct D : public B { }; void f2() { S<D> s1; S<D> s2(s1); }
如果使用當前編譯器進行編譯,將得到以下錯誤:
type_traits(1110):錯誤 C2139:“D”:未定義的類不允許作為編譯器內部類型特征“__is_base_of”的參數 ..\t331.cpp(14):備注:請參閱“D”的聲明 ..\t331.cpp(10):備注:請參閱對正在編譯的類模板實例化“std::is_base_of<T,U>”的引用 替換為 [ T=D, U=D ]
這是因為在第一次調用 is_base_of 時,尚未定義類“D”。
在這種情況下,解決方法是在定義類之前,不使用此類類型特征。 如果將 D 和 B 的定義移到代碼文件的開頭,錯誤將得到解決。 如果定義位於標頭文件中,請檢查標頭文件的 include 語句的順序,以確保在使用有問題的模板之前,對任何類定義進行了編譯。
-
復制構造函數
在 Visual Studio 2013 和 Visual Studio 2015 RC 中,如果該類具有用戶定義的移動構造函數,但沒有用戶定義的復制構造函數,則編譯器生成類的復制構造函數。 在 Dev14 中,此隱式生成的復制構造函數也標記為“= delete”。
-
重構的二進制文件
CRT 庫被重構為兩個不同的二進制文件、一個通用 CRT (ucrtbase)(其中包含大多數標准功能)和一個 VC 運行時庫 (vcruntime140)(其中包含與編譯器相關的功能,如異常處理和內部函數)。 如果你使用的是默認項目設置,則此更改不會對你產生影響,因為鏈接器將自動使用新的默認庫。 如果將項目的“鏈接器”屬性“忽略所有默認庫”設置為“是”,或你使用的是命令行上的 /NODEFAULTLIB 鏈接器選項,則必須更新庫的列表(位於“附加依賴項”屬性)以包括新的重構庫。 將舊的 CRT 庫(libcmt.lib、libcmtd.lib、msvcrt.lib、msvcrtd.lib)替換為等效的重構庫。 對於兩個中的每個重構庫,都存在靜態 (.lib) 和動態 (.dll) 版本,發行(無后綴)和調試版本(使用“d”后綴)。 動態版本具有與之鏈接的導入庫。 兩個重構庫是通用的 CRT(特別是 ucrtbase.dll 或 .lib、ucrtbased.dll 或 .lib)和 VC 運行時庫(libvcruntime.lib、libvcruntime.dll、libvcruntimed.lib 和 libvcruntimed.dll)。 請參閱 CRT 庫功能。
-
localeconv
啟用 每個線程區域設置后,locale.h 中聲明的 Localeconv 函數現在正常工作。 在早期版本的庫中,此函數將返回全局區域設置(而不是線程的區域設置)的 lconv 數據。
如果使用每個線程區域設置,應該檢查 localeconv 的使用以查看你的代碼是否假定返回的 lconv 數據代表全局區域設置,並相應地對其進行修改。
-
數學庫函數的 C++ 重載
在早期版本中,<math.h> 定義了部分(而不是全部)數學庫函數的 C++ 重載。 <cmath> 定義了其余的重載,因此為了獲取所有重載,其中一個需要包括 <cmath> 標頭。 這就會導致只包括 <math.h> 的代碼中的函數重載解析出現問題。 現在,已從 <math.h> 中刪除了所有 C++ 重載,現在僅包含在 <cmath> 中。
若要解決錯誤,包括 <cmath> 以獲取已從 <math.h> 中刪除的函數的聲明。 下表列出了移動的函數。
移動的函數:
-
雙精度型 abs(double) 和浮點型 abs(float)
-
雙精度型 pow(double, int)、浮點型 pow(float, float)、浮點型 pow(float, int)、長雙精度型 pow(long double, long double)、長雙精度型 pow(long double, int)
-
浮點型和長雙精度型版本的浮點函數 acos、acosh、asin、asinh、atan、atanh、atan2、cbrt、ceil、copysign、cos、cosh、erf、erfc、exp、exp2、expm1、fabs、fdim、floor、fma、fmax、fmin、fmod、frexp、hypot、ilogb、ldexp、lgamma、llrint、llround、log、log10、log1p、log2、lrint、lround、modf、nearbyint、nextafter、nexttoward、remainder、remquo、rint、round、scalbln、scalbn、sin、sinh、sqrt、tan、tanh、tgamma、trunc
如果你的代碼使用具有僅包含 math.h 標頭的浮點型的 abs,則浮點版本將不再可用,因此調用(即使具有浮點參數)現在已解析為 abs(int)。 這將產生錯誤:
警告 C4244:“參數”:從“float”轉換為“int”,可能丟失數據
此警告的解決方法是將對 abs 的調用替換為浮點版本的 abs(例如雙精度型參數的 fabs 或浮點型參數的 fabsf)或包含 cmath 標頭並繼續使用 abs。
-
-
浮點一致性
對數學庫所做的許多更改都用以使特例輸入(如 NaN 和無窮大)更符合 IEEE-754 和 C11 附錄 F 規范。 例如,在早期版本的庫中通常被視為錯誤的 quiet NaN 輸入已不再被視為錯誤。 請參閱 IEEE 754 標准和 C11 標准的附錄 F。
這些更改不會導致編譯時錯誤,但可能會根據標准使程序以不同的方式更准確地運行。
-
FLT_ROUNDS
在 Visual Studio 2013 中,FLT_ROUNDS 宏擴展為常量表達式,這是錯誤的,因為舍入模式在運行時是可配置的,例如,通過調用 fesetround。 FLT_ROUNDS 宏現在是動態的,並正確反映當前的舍入模式。
-
new 和 delete
在早期版本的庫中,實現定義的運算符 new 和 delete 函數已從運行時庫 DLL(例如,msvcr120.dll)中導出。 這些運算符函數現在始終以靜態方式鏈接到二進制文件,即使是使用運行時庫 DLL 時也是如此。
這對於本機或混合代碼 (/clr) 而言不是一項重大更改,但是對於編譯為 /clr:pure 的代碼而言,這可能會導致代碼無法進行編譯。 如果將代碼編譯為 /clr:pure,可能需要添加 #include <new> 或 #include <new.h> 以解決由於此更改導致的生成錯誤。 請注意,/clr:pure 在Visual Studio 2015 RC 中已被棄用,並且可能在未來版本中刪除。
-
_beginthread 和 _beginthreadex
現在, _Beginthread 和 _beginthreadex 函數保存對模塊的引用,在該模塊中,已針對線程持續時間定義了線程過程。 這有助於確保線程在完成運行之后才卸載模塊。
-
va_start 和引用類型
編譯 C++ 代碼時, va_start 現在會在編譯時驗證傳遞給它的參數是否為引用類型。 C++ 標准禁止引用類型的參數。
-
Printf 和 scanf 系列函數現在采用內聯方式進行定義。
所有 printf 和 scanf 函數的定義已以內聯方式移動到 <stdio.h>、<conio.h> 和其他 CRT 標頭中。 這項重大更改會導致本地聲明這些函數(沒有適當的 CRT 標頭)的任何程序發生鏈接器錯誤(LNK2019、無法解析的外部符號)。 如果可能,應更新代碼以包括 CRT 標頭(即,添加 #include <stdio.h>)和內聯函數,但如果不想修改代碼以包括這些標頭文件,則可以選擇將其他庫添加到鏈接器輸入 (legacy_stdio_definitions.lib)。
若要將此庫添加到 IDE 中的鏈接器輸入,請打開項目節點的上下文菜單,選擇“屬性”,然后在“項目屬性”對話框中選擇“鏈接器”,編輯“鏈接器輸入”以將 legacy_stdio_definitions.lib 添加到用分號隔開的列表。
如果項目鏈接的靜態庫是使用早於 2015 的 Visual C++ 版本編譯的,則鏈接器可能會報告無法解析的外部符號。 這些錯誤可能會引用 _imp_ * 窗體中某些 stdio 函數的 _iob、_iob_func 或相關導入的內部 stdio 定義。 Microsoft 建議在升級項目時使用最新版本的 Visual C++ 編譯器和庫編譯所有靜態庫。 如果庫是第三方庫並且第三方庫的源不可用,則應請求來自第三方更新后的二進制文件,或者將你對此庫的用法封裝到單獨的 DLL(使用舊版 Visual C++ 或庫編譯的)。
警告
如果你鏈接的是 Windows SDK 8.1 或更早版本,可能會遇到這些無法解析的外部符號錯誤。 在這種情況下,應通過將 legacy_stdio_definitions.lib 添加到鏈接器輸入(如上文所述)來解決該錯誤。
若要解決無法解析的符號錯誤,可以嘗試使用 dumpbin.exe 來檢查二進制文件中定義的符號。 請嘗試使用下面的命令行來查看在庫中定義的符號。
dumpbin.exe /LINKERMEMBER somelibrary.lib
-
gets 和 _getws
已刪除 gets 和 _getws 函數。 已從 C11 中的 C 標准庫刪除 gets 函數,因為其不能安全使用。 _getws 函數是與 gets 等效(但可用於寬字符串)的 Microsoft 擴展。 作為這些函數的替代,請考慮使用 fgets、 fgetws、 gets_s 和 _getws_s。
-
_cgets 和 _cgetws
已刪除 _Cgets 和 _cgetws 函數。 作為這些函數替代,請考慮使用 _cgets_s 和 _cgetws_s。
-
無窮大和非數字 (NaN) 格式設置
在早期版本中,可以使用 Visual C++ 特定的 sentinel 字符串集進行無窮大和 NaN 格式設置。
-
無窮大:1.#INF
-
靜默 NaN:1.#QNAN
-
信號 NaN:1.#SNAN
-
不定 NaN:1.#IND
這些字符串的任何一種都可能已采用符號作為前綴並且格式設置也可能略有不同,具體取決於字段寬度和精度(有時會起到不尋常的作用,例如 printf("%.2f\n", INFINITY) 可以打印 1.#J,因為 #INF 會“四舍五入”為 2 位數的精度)。 C99 引入了有關如何設置無窮大和 NaN 格式的新要求。 現在,Visual C++ 實現符合這些要求。 新字符串如下所示:
-
無窮大:inf
-
靜默 NaN:nan
-
信號 NaN:nan(snan)
-
不定 NaN:nan(ind)
可能以符號作為其中任何一種字符串的前綴。 如果使用了大寫格式說明符(%F 而不是 %f),則字符串將按要求以大寫字母形式(INF 而不是 inf)打印。
已修改 scanf 函數以便分析這些新的字符串,因此這些字符串會通過 printf 和 scanf 往返。
-
-
浮點格式設置和分析
引入了新浮點格式設置和分析算法以提高正確性。 此更改會影響 printf 和 scanf 系列函數,以及像 strtod 這樣的函數。
舊的格式設置算法將僅生成有限數量的數字,然后將用零填充其余的小數位數。 這是通常足以生成將往返回原始浮點值的字符串,但如果你想要精確值(或最接近十進制的表示),則不夠完美。 新的格式設置算法會盡可能多地生成數字來表示值(或填充指定的精度)。 作為改進的一個例子;打印兩個中指數較大的一個時,請考慮結果:
printf("%.0f\n", pow(2.0, 80))
舊版本:1208925819614629200000000 新版本:1208925819614629174706176
舊版本分析算法考慮的輸入字符串中有效位數僅達 17,並將丟棄其余數位。 這不足以生成由字符串表示的近似值,結果通常是非常接近正確舍入的結果。 新版本的實現會考慮所有存在的數字,並生成所有輸入(長度多達 768 位)的正確舍入的結果。 此外,這些函數現在遵循舍入模式(可通過 fesetround 控制)。 這可能是重大的行為更改,因為這些函數可能會輸出不同的結果。 新版本的結果始終比舊版本的結果更准備。
-
十六進制和無窮大/NaN 浮點分析
浮點分析算法現在將分析十六進制浮點字符串(例如,那些由 %a 和 %A printf 格式說明符生成的字符串)和由 printf 函數生成的所有無窮大和 NaN 字符串(如上文所述)。
-
%A 和 %a 零填充
%a 和 %A 格式說明符將浮點數轉化為十六進制的尾數和二進制指數。 在早期版本中,printf 函數可能會錯誤地用零填充字符串。 例如,printf ("%07.0a\n", 1.0) 可能會打印 00x1p+0,而它本應打印 0x01p+0。 已解決此問題。
-
%A 和 %a 精度
在早期版本的庫中,%A 和 %a 格式說明符的默認精度是 6。 為了符合 C 標准,現在默認精度為 13。
這是使用帶 %A 或 %a 的格式字符串的任一函數輸出中的運行時行為更改。 在舊版本行為中,使用 %A 說明符的輸出可能是“1.1A2B3Cp+111”。 現在相同值的輸出是“1.1A2B3C4D5E6F7p+111”。 若要獲取舊版本行為,則可以指定精度(例如,%.6A)。 請參閱 精度規范。
-
%F 說明符
現在支持 %F 格式/轉換說明符。 它在功能上等效於 %f 格式說明符,但使用大寫字母形式進行格式設置的無窮大和 Nan 除外。
在早期版本中,實現過去通常將 F 和 N 分析為長度修飾符。 此行為追溯到分段地址空間的時代:這些長度修飾符分別用於指示或近或遠的指針(如 %Fp 或 %Ns 中所示)。 此行為已被刪除。 如果遇到 %F,現在則將其視為 %F 格式說明符;如果遇到 %N,現在則將其視為無效的參數。
-
指數格式設置
%e 和 %E 格式說明符將浮點數轉化為十進制的尾數和指數。 %g 和 %G 格式說明符在某些情況下也以此形式設置格式位數。 在早期版本中,CRT 會始終生成具有三個數字指數的字符串。 例如,printf ("%e\n", 1.0) 可能會打印 1.000000e+000。 這是錯誤的:根據 C 要求,如果可使用一個或兩個數字表示指數,則僅打印兩個數字。
Visual Studio 2005 中添加了全局一致性切換: _set_output_format。 程序可以調用參數為 _TWO_DIGIT_EXPONENT 的此函數,以啟用符合標准的指數打印。 已將默認行為更改為符合標准的指數打印模式。
-
格式字符串驗證
在早期版本中,printf 和 scanf 函數以靜默方式接受許多無效格式字符串,有時會起到不尋常的作用。 例如,%hlhlhld 將被視為 %d。 現在所有無效格式字符串都被視為無效的參數。
-
fopen 模式字符串驗證
在早期版本中,fopen 系列函數以靜默方式接受某些無效的模式字符串(例如 r+b+)。 現在可檢測無效的模式字符串並將其視為無效的參數。
-
_O_U8TEXT 模式
_Setmode 函數現在可以准確報告在 in_O_U8TEXT 模式中打開的流模式。 在早期版本的庫中,它將報告正在 _O_WTEXT 中打開的此類流。
如果你的代碼解釋其中編碼為 UTF-8 的流的 _O_WTEXT 模式,這則是一項重大更改。 如果你的應用程序不支持 UTF_8,請考慮為此越來越常見的編碼添加支持。
-
snprintf 和 vsnprintf
現在已實現 Snprintf 和 vsnprintf 函數。 較舊的代碼通常為宏版本的這些函數提供定義,因為它們未由 CRT 庫實現,但在較新版本中則不再需要這些。 如果將 snprintf 或vsnprintf 在包括 <stdio.h> 之前定義為宏,則現在編譯將因出錯而失敗,該錯誤指示定義了宏的位置。
通常情況下,解決此問題的方法是刪除用戶代碼中 snprintf 或 vsnprintf 的任何聲明。
-
tmpnam 生成可用文件名
在早期版本中,tmpnam 和 tmpnam_s 函數在驅動器根目錄(如 \sd3c)中生成文件名。 這些函數現在在臨時目錄中生成可用的文件名路徑。
-
文件封裝
在早期版本中,完全在 <stdio.h> 中定義文件類型,因此用戶代碼可以進入文件並修改其內部結構。 已對 stdio 庫進行了更改以隱藏實現細節。 作為此操作的一部分,<stdio.h> 中所定義的文件現在是不透明類型且無法從 CRT 自身外部訪問其成員。
-
_outp 和 _inp
-
strtof 和 wcstof
當值不是以浮點形式表示時,Strtof 和 wcstof 函數無法將 errno 設置為 ERANGE。 已解決此問題。 (請注意此錯誤只特定於這兩個函數;strtod、wcstod、strtold 和 wcstold 函數不受影響。) 這是運行時重大更改。
-
對齊的分配函數
在早期版本中,對齊的分配函數(_aligned_malloc、_aligned_offset_malloc 等)以靜默方式接受帶 0 的對齊方式的塊的請求。 請求的對齊方式冪必須是 2(而不是零)。 已解決此問題,且請求的 0 的對齊方式現在被視為無效的參數。 這是運行時重大更改。
-
堆函數
刪除了 _Heapadd、_heapset 和 _heapused 函數。 這些函數已不起作用,因為 CRT 已更新為使用 Windows 堆。
-
smallheap
刪除了 Smalheap 鏈接選項。 請參閱 鏈接選項。
-
wcstok
更改了 wcstok 函數的簽名,以便匹配 C 標准所要求的內容。 在早期版本的庫中,此函數的簽名為:
wchar_t* wcstok(wchar_t*, wchar_t const*)
它使用內部的每個線程上下文來跟蹤跨狀態調用(就像為 strtok 所進行的操作一樣)。 該函數現在具有簽名 wchar_t* wcstok(wchar_t*、wchar_t const*、wchar_t**),並要求調用方將上下文作為第三個參數傳遞給函數。
添加了新的 _wcstok 函數,並具有舊簽名以便進行遷移。 編譯 C++ 代碼時,還存在具有舊簽名的 wcstok 的內聯重載。 已聲明棄用此重載。 在 C 代碼中,你可能會定義 _CRT_NON_CONFORMING_WCSTOK 以使 _wcstok 用於替換 wcstok。
-
clock
在早期版本中,已使用 Windows API GetSystemTimeAsFileTime 實現了 clock 函數。 使用此實現,clock 函數對系統時間比較敏感,因此不一定是單一的。 已根據QueryPerformanceCounter 重新實現了 clock 函數,現在它是單一的。
-
fstat 和 _utime
在早期版本中, _stat、 fstat 和 _utime 函數對夏時制的處理方式不正確。 在 Visual Studio 2013 之前的版本中,所有這些函數錯誤調整標准時時間,就像處於夏時制時間內一樣。
在 Visual Studio 2013 中,解決了 _stat 系列函數中的此問題,但未解決 fstat 和 _utime 系列函數中的類似問題。 這就導致了由於問題函數之間的不一致引起的問題。 現在已修復 fstat 和 _utime 系列函數,因此所有這些函數現在可正確且一致地處理夏時制。
-
asctime
在早期版本中, asctime 函數會以前導零填充單位數的日期(例如:Fri Jun 06 08:00:00 2014)。 規范要求此類日期應以前導空格填充,例如 Fri Jun 6 08:00:00 2014。 已解決此問題。
-
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 生成的形式相同。 在早期版本中,使用 MM/DD/YY HH:MM:SS 表示形式,%c 格式說明符設置的時間格式不正確。 已解決此問題。
-
timespec 和 TIME_UTC
現在,<time.h> 標頭根據 C11 標准定義 timespec 類型和 timespec_get 函數。 此外,現在可定義與 timespec_get 函數連用的 TIME_UTC 宏。 這對於在任一這些方面具有沖突定義的代碼而言,是一項重大更改。
-
CLOCKS_PER_SEC
現在,CLOCKS_PER_SEC 宏根據 C 語言要求擴展為整數類型 clock_t。
為了實現新的優化和調試檢查,C++ 標准庫的 Visual Studio 實現特意破壞了連續兩個版本之間的二進制兼容性。 因此,在使用 C++ 標准庫時,使用不同版本編譯的對象文件和靜態庫不能混合在同一二進制文件(EXE 或 DLL)中,並且不能在使用不同版本編譯的二進制文件之間傳遞 C++ 標准庫對象。 這樣混合會發出關於 _MSC_VER 不匹配的鏈接器錯誤。(_MSC_VER 是包含編譯器主版本的宏,例如,Visual Studio 2013 的 1800。) 此檢查無法檢測 DLL 混合,也無法檢測涉及 Visual C++ 2008 或早期版本的混合。
-
STL 包含文件
對 STL 標頭中的 include 結構進行了一些更改。 允許 STL 標頭以未指定的方式相互包含。 一般情況下,應編寫你的代碼,以便其根據 C++ 標准謹慎包括其需要的所有標頭,且不依賴於哪些 STL 標頭包含哪些其他 STL 標頭。 這使得代碼可跨版本和平台進行移植。 至少更改 Visual Studio 2015 RC 的兩個標頭才會影響用戶代碼。 首先,<string> 不再包括 <iterator>。 第二,<tuple> 現在用於聲明 std::array 但不包括所有 <array>,這可能中斷代碼通過以下代碼構造的組合:代碼具有名為“數組”的變量,你具有 using 指令“using namespace std;” 和你包括含有 <tuple> 的 STL 標頭(如 <functional>),其現在用於聲明 std::array。
-
steady_clock
已更改 steady_clock 的 <chrono> 實現,以便滿足 C++ 標准對穩定性和單一性的要求。 steady_clock 現在以 QueryPerformanceCounter 為基礎,而 high_resolution_clock 現在是 steady_clock 的 typedef。 因此,在 Visual C++ 中,steady_clock::time_point 現在是 chrono::time_point<steady_clock> 的 typedef;但是,其他實現不一定是這種情況。
-
分配器和 const
現在,我們要求分配器進行相等/不等比較,以接受兩端上的 const 參數。 如果你的分配器如下定義這些運算符:
bool operator==(const MyAlloc& other)
你應更新這些以將它們聲明為 const 成員。
bool operator==(const MyAlloc& other) const
-
const 元素
C + + 標准始終禁止 const 元素(如 vector<const T> 或 set<const T>)的容器。 Visual C++ 2013 及更早版本接受此類容器。 在當前版本中,此類容器無法編譯。
-
std::allocator::deallocate
在 Visual C++ 2013 和早期版本中,std::allocator::deallocate(p, n) 忽略了傳入用於 n 的參數。 C + + 標准始終要求 n 應等於作為第一個參數傳遞給調用分配(返回 p)的值。但是,在當前版本中將檢查 n 的值。 在運行時,為 n 傳遞不同於標准要求的參數的代碼可能會崩潰。
-
hash_map 和 hash_set
非標准標頭文件 hash_map 和 hash_set 在 Visual Studio 2015 RC 中已被棄用,並且將在未來版本中移除。 請改用 unordered_map 和 unordered_set。
-
比較運算符和 operator()
關聯容器(<map> 系列)現在要求其比較運算符具有可調用 const 的函數調用運算符。 現在比較運算符類聲明中的以下代碼無法進行編譯:
bool operator()(const X& a, const X& b)
若要解決此錯誤,請將函數聲明更改為:
bool operator()(const X& a, const X& b) const
-
類型特征
早期版本的 C++ 草稿標准中刪除了類型特征的舊名稱。 C++11 中已對這些進行了更改,並且已更新為 Visual Studio 2015 RC 中的 C++11 值。 下表顯示了舊名稱和新名稱。
舊名稱
新名稱
add_reference
add_lvalue_reference
has_default_constructor
is_default_constructible
has_copy_constructor
is_copy_constructible
has_move_constructor
is_move_constructible
has_nothrow_constructor
is_nothrow_default_constructible
has_nothrow_default_constructor
is_nothrow_default_constructible
has_nothrow_copy
is_nothrow_copy_constructible
has_nothrow_copy_constructor
is_nothrow_copy_constructible
has_nothrow_move_constructor
is_nothrow_move_constructible
has_nothrow_assign
is_nothrow_copy_assignable
has_nothrow_copy_assign
is_nothrow_copy_assignable
has_nothrow_move_assign
is_nothrow_move_assignable
has_trivial_constructor
is_trivially_default_constructible
has_trivial_default_constructor
is_trivially_default_constructible
has_trivial_copy
is_trivially_copy_constructible
has_trivial_move_constructor
is_trivially_move_constructible
has_trivial_assign
is_trivially_copy_assignable
has_trivial_move_assign
is_trivially_move_assignable
has_trivial_destructor
is_trivially_destructible
-
launch::any 和 launch::sync 策略
已刪除非標准的 launch::any 和 launch::sync 策略。 相反,對於 launch::any,請使用 launch:async | launch:deferred。 對於 launch::sync,請使用 launch::deferred。 請參閱launch 枚舉。
-
Microsoft 基礎類 (MFC) 由於其尺寸大不再包含在 Visual Studio 的“典型”安裝中。 若要安裝 MFC,請在 Visual Studio 2015 安裝程序中選擇自定義安裝選項。 如果你已安裝 Visual Studio 2015,可以通過重新運行 Visual Studio 安裝程序,選擇自定義安裝選項,並選擇 Microsoft 基礎類來安裝 MFC。 可從控制面板、程序和功能,或從安裝媒體重新運行 Visual Studio 安裝程序。
Visual C++ 可再發行組件包仍包含此庫。
-
與 concurrency::Context::Yield 沖突的 Windows.h 中的 Yield 宏
並發運行時之前使用 #undef 來取消定義 Yield 宏,以避免 Windows.h h 中定義的 Yield 宏和 concurrency::Context::Yield 函數之間的沖突。 已刪除此 #undef,並添加了新的非沖突等效 API 調用 concurrency::Context::YieldExecution。 若要解決與 Yield 的沖突,可以改為更新代碼以調用 YieldExecution 函數,或在調用站點用括號將 Yield 函數名括起來,如下例所示:
(concurrency::Context::Yield)();