By: Advanced Technology Group
Updated: April 28, 2017
Visual C++ implements C++11 and C++14 language standard features, as well as some C++17 draft features. Xbox One titles can take advantage of the performance and usability of these features in both the language and the C++ Standard Library.
Table 1. Comparison of C++11/C++14 language features in Visual Studio.
| C++11/C++14 language feature | VS 2012 | VS 2015 | VS 2017 |
|---|---|---|---|
| nullptr | ✓ | ✓ | ✓ |
| static_assert | ✓ | ✓ | ✓ |
| override / final* | ✓ | ✓ | ✓ |
| Lambda expressions | ✓ | ✓ | ✓ |
| Rvalue references | ✓ | ✓ | ✓ |
| decltype | ✓ | ✓ | ✓ |
| auto | ✓ | ✓ | ✓ |
| Strongly typed enumerations | ✓ | ✓ | ✓ |
| Forward declared enumerations | ✓ | ✓ | ✓ |
| Ranged-based for loops | ✓ | ✓ | ✓ |
| Variadic templates | ✓ | ✓ | |
| Uniform initialization and initializer lists | ✓ | ✓ | |
| Delegating constructors | ✓ | ✓ | |
| Raw string literals | ✓ | ✓ | |
| Explicit conversion operators | ✓ | ✓ | |
| Default template arguments for function templates | ✓ | ✓ | |
| Alias templates | ✓ | ✓ | |
| Defaulted functions | ✓ | ✓ | |
| Deleted functions | ✓ | ✓ | |
| Non-static data member initializers (NSDMIs) | ✓ | ✓ | |
| Attributes, constexpr, ref-qualifiers, inheriting constructors, char16_t, char32_t, Unicode string literals, user-defined literals (UDLs), extended sizeof, inline namespaces, unrestricted unions, noexcept, thread_local, magic statics, universal character names in literals | ✓ | ✓ | |
| C99: func, long long | ✓ | ✓ | |
| Expression SFINAE | Update 1 Update 3 |
✓ | |
| C++14: Sized deallocation | ✓ | ✓ | |
| C++14: New rules for auto with braced-init-lists, attributes for namespaces and enumerators, typename in template template-parameters; removing trigraphs, u8 character literals | Update 2 | ✓ | |
| C++14: Extended constexpr | ✓ | ||
| C++14: Non-Static Data Member Initilizer (NSDMI) for aggregates | ✓ |
Note: For more on the VS 2017 related C++14 changes, see the Visual C++ team blog.
Table 2. Comparison of C++11/C++14 headers in Visual Studio.
| C++11/C++14 header | VS 2012 | VS 2015 | VS 2017 |
|---|---|---|---|
| <array>, <memory>, <random>, <regex>, <tuple>, <type_traits>, <unordered_map>, <unordered_set> | ✓ | ✓ | ✓ |
| <stdint.h>, cstdint | ✓ | ✓ | ✓ |
| unique_ptr |
✓ | ✓ | ✓ |
| cbegin(), cend(), crbegin(), crend() | ✓ | ✓ | ✓ |
| <forward_list> | ✓ | ✓ | ✓ |
| <algorithm> and <exception> updates find_if_not, copy_if, is_sorted,etc. exception_ptr | ✓ | ✓ | ✓ |
| <allocators> | ✓ | ✓ | ✓ |
| <codecvt> | ✓ | ✓ | ✓ |
| <system_error> | ✓ | ✓ | ✓ |
| emplace(), emplace_front(), emplace_back(), etc. | ✓ | ✓ | ✓ |
| <chrono>* | ✓ | ✓ | ✓ |
| <ratio> | ✓ | ✓ | ✓ |
| <scoped_allocator> | ✓ | ✓ | ✓ |
| <atomic>, <condition_variable>, <future>, <mutex>, <thread> | ✓ | ✓ | ✓ |
| <intializer_list> | ✓ | ✓ | |
| C99: <stdbool.h>, <complex.h> / <ccomplex>, <fenv.h> / <cfenv>, <inttypes.h> / <cinttypes>, <ctgmath> | ✓ | ✓ | |
| C99: <uchar.h> / <cuchar> | ✓ | ✓ | |
| C99: <tgmath.h>, some printf format specifiers | ✓ | ✓ | |
| C++14: insert_or_assign/try_emplace for map | ✓ | ✓ | |
| C++14: make_unique |
✓ | ✓ | |
| C++14: non-member size(), empty(), data() | ✓ | ✓ | |
| C++14: shared_mutex. shared_mutex_timed | ✓ | ✓ | |
| C++14: UDLs usage in Standard C++ Library | ✓ | ✓ | |
| C++14: constexpr use in Standard C++ Library | Update 1 | ✓ |
For more information, see the following resources on MSDN:
In Visual Studio 2015 Update 3 and Visual Studio 2017, the compiler now offers a new standards version switch to control access to newer C++ draft features. By default, the compiler will only support C++11/C++14 (/std:c++14). You can also access C++17 draft features (/std:c++latest). For more information, see the Visual C++ Team Blog.
The x64 native programming model exclusively uses SSE/SSE2 instructions for floating-point math computations. The compiler never generates the older x87 FPU instructions (as used for Windows x86) which are deprecated for x64 native code. This difference has a number of implications:
Double-precision has improved support. On the Xbox 360, scalar double-precision is supported but VMX128 only provides single-precision floating-point and some limited integer operations. For Xbox One, you should still prefer to use single-precision where possible for a variety of reasons, but you can use double-precision Single Instruction Multiple Data (SIMD) processing where required. Be sure to be explicit in your declaration of constants (that is, use 1.0f instead of 1.0) to ensure expressions are using single-precision throughout when desired, and watch out for truncation warnings, such as warning C4305 (on MSDN).
Converting between integer and floating-point does not result in a load-hit-store. On the PowerPC–based Xbox 360 platform, converting from integer to floating-point required writing the integer to memory—typically on the stack in L1 cache—and then loading it back as a float, and vice-versa. The mov instruction can directly move an integer between the int / SIMD Processor Register File. Ping-ponging data between float<->int throughout a computation is not recommended for performance, but there’s no need to go to great lengths to eliminate these conversions.
Floating-point computations on Xbox One always use the XMM0L-XMM15L registers. There is no distinct ‘scalar’ floating-point unit used generally for Xbox One or x64 native code generation. All floating-point computations are done using the SIMD unit. The compiler can more effectively schedule with a flat register file rather than with the “stack” model of the older x87. This also means that “mixing” scalar and SIMD floating-point operations will not result in any parallelization advantages that might have been present in Windows x86, that is, trying to use both x87 and Streaming SIMD Extension (SSE) registers at the same time. Scalar single-precision operations take place in the x element of the XMM registers (the remaining 3 elements are unused). Scalar double-precision operations take place in the lower-half of the XMM register (the upper half is unused).
There is no ‘global’ precision setting for floating-point operations. With the legacy x87 FPU instructions, the precision of the computation was controlled by the x87 control word and could be set to double-precision, single-precision, or a 80-bit extended precision (which was set by default to double-precision by the x86 CRT). This precision was independent of the size of the data type (float vs. double). With x64 native programming, the precision of the computation is tied to the precision of the data type (that is, computations with the float type will always use single-precision computations). Note that this also means there is no 80-bit extended precision mode available for x64 and there is no 80-bit extended precision data type supported by the VC++ compiler in any case (that is, long double is still just 64-bits).
SSE/SSE2 makes use of a control word similar to the x87 FPU control word which is manipulated by the controlfp_s function. Here are some recommendations for using the control word:
Disable denormal (_MCW_DN) usage: Denormal handling can be a serious source of performance degradation and is typically not required behavior for game computations. Setting denormals to flush-to-zero (_DN_FLUSH) can avoid this impact. Note that for Xbox One titles, denormals are set to flush-to-zero by default.
Use extreme caution when generating exceptions ( _MCW_EM) or changing the rounding mode (_MCW_RC): By default the control word is set to generate floating-point (FP) specials (that is, NaN, INF, and so on) rather than generate exceptions, round-to-nearest. Some developers like to enable FP exceptions during development. It is important that exceptions not be globally enabled when calling code out of direct control of the developer (that is, system calls or third-party libraries), or changing the default rounding mode globally. Most floating-point code is written to assume specials are generated instead of exceptions, and that rounding is round-to-nearest, including the system APIs. Saving and restoring the control word around specific code paths is therefore recommended.
Don’t use precision-control (_MCW_PC): As noted above, precision control does not apply to x64 native programs.
The SSE control word is a per-thread setting, and modifying the control word is a potentially low-performance operation so such changes should be minimized. There is an SSE4.1 intrinsic (_mm_round_ps) that can perform various rounding operations explicitly without needing to modify the control word. SSE2 intrinsics (_mm_cvttsd_si32, _mm_cvttss_si32) can convert to an integer with explicit truncation without changing the control word as well.
The Visual C++ compiler includes an auto-vectorizer that can improve the utilization of the SIMD instruction set and registers automatically for scalar integer and floating-point code. Explicit use of SIMD intrinsics or DirectXMath (see below) can be a more efficient approach, but the auto-vectorizer can provide a speed-up for existing code or scalar algorithms that are difficult to explicitly vectorize. Auto-vectorization is enabled with /O2 or /Ox. You can disable it for specific loops using #pragma(loop(no_vector)). /Qvec-report:2 can be used to generate a report on which loops were vectorized and why other loops were not.
There is a distinct auto-parallelization feature available as well by way of the /Qpar compiler switch and #pragma(loop(hint_parallel(n))). This feature will parallelize loops using multiple threads to scale across multiple cores. You can use /Qpar-report:2 to generate a report.
For more information, see Auto-Parallelization and Auto-Vectorization and Auto-Vectorizer in Visual Studio 11 on MSDN.
With the Xbox One out-of-order execution CPU and full-featured branch-predictor, the use of virtual methods is no longer a major performance challenge. It is still helpful to not over-generalize your object design (that is, using a base class, with virtual methods, which only ever has a single instantiated derived type for the life of the game), but you no longer need to go to great lengths to avoid virtual methods as was suggested for Xbox 360. This guidance also extends to the Pimpl idiom, which has less of a performance impact on Xbox One than it did on Xbox 360. It is still important to minimize or eliminate calls to essentially random branch targets, so some care when using polymorphism is justified.
Note Use of the new C++11 keywords override and final make using virtual methods in C++ less error-prone.
Visual Studio 2015 Update 2: Be sure to read up on the __declspec(empty_bases) optimization on the Visual C++ Team Blog.
Game developers have a long history of avoiding the use of exception handling (EH), both Structured Exception Handling and C++ Exception Handling. For the Xbox 360 console, EH was officially unsupported. For Xbox One, however, EH is an assumed aspect of the platform. The C++ Standard Library (also known as the Standard Template Library / STL) and the Windows Runtime (WinRT) APIs both rely on exception handling for their error reporting and handling.
For x64 native code, EH does not have any direct code impacts on the function epilogue or prologue. While Windows x86 used a stack-based scheme that involved additional code injected into the modules, x64 native code generation uses a table-based scheme to ensure the stack can be properly unwound during exception processing. The table-based scheme used by x64 native also has the advantage of being more secure because it is not subject to stack buffer overrun attacks. This design has a number of implications:
EH does not impact the number of instructions in the generated code. While stack-based schemes like used those for Windows x86 can induce a 5-10% code penalty, x64 native exception handling only generates table-based data associated with the executable and not more instructions. Even explicitly disabling exception handling for x64 native (which is not recommended) only saves a small amount of auxiliary data for C++ handling and does not disable the generation of stack unwinding data.
Try/catch blocks are inexpensive to use. A try/catch block in the stack-based scheme adds some additional code for tracking, while on x64 this only adds some more entries in the unwinding table.
Exception processing (that is, the actual throwing of an exception) requires some additional table lookups. The tradeoff made is that while stack-based schemes can throw exceptions more quickly, it does so with a penalty in all code paths. The table-based scheme is slower for ‘throwing’ but does not impact other code paths. Since exceptions should not be used as normal flow-control and should in fact only be triggered in exceptional circumstances, this additional performance cost for processing is negligible compared to the overall application performance.
Developers should not attempt to disable EH, which as noted above cannot be completely eliminated for x64 native code in any case, and should use ‘exception-safe’ coding patterns. Primarily this impacts resource allocation (memory, file handles, locks, and so forth.). The C++11 Standard Library provides a plethora of ready-made tools for ensuring code is exception-safe including std::shared_ptr, std::unique_ptr, std::lock_guard, and various C++ Standard Library containers (array, vector, and so forth). Your code should never have to explicitly call delete to properly clean up memory resources. This technique is known as Resource Acquisition Is Initialization (RAII).
For cases that are not directly supported by the C++ Standard Library, simple customizations can solve the problems just as effectively.
Table 3. Traditional C++ vs. Exception-safe C++.
| Traditional C++ | Exception-safe C++ |
|---|---|
| MyObject *obj = new MyObject; | std::unique_ptr obj(new MyObject); -or- std::shared_ptr obj( make_shared |
| BYTE* buffer = new BYTE[ 2048 ]; | std::array<uin8_t, 2048> buffer; -or- std::unique_ptr<uint8_t[]> buffer( new uint8_t[2048]; ) |
| float* buffer = _aligned_malloc( 2048, 16 ); | struct aligned_deleter { void operator()(void* p) { _aligned_free(p); } }; std::unique_ptr<float, aligned_deleter> buffer( _aligned_malloc(2048,16) ) ; |
| HANDLE h = CreateFile(…); if ( h == INVALID_HANDLE) // error |
struct handle_closer { void operator()(HANDLE h) { assert(h != INVALID_HANDLE_VALUE); if (h) CloseHandle(h); } }; inline HANDLE safe_handle( HANDLE h ) { return (h==INVALID_HANDLE_VALUE) ? 0:h; } std::unique_ptr<void, handle_closer> hFile( safe_handle( CreateFile2(…) ) ); if ( !hFile ) // error |
| CRITICAL_SECTION cs; InitializeCriticalSection (&cs); EnterCriticalSection(&cs); … LeaveCriticalSection(&cs); |
std::mutex m; { std::lock_guard /* lock on m held until end of scope */ } |
| ID3D11InputLayout* inputLayout = NULL; device->CreateInputLayout( …, &inputLayout ); SAFE_RELEASE(inputLayout); |
#include <wrl/client.h> Microsoft::WRL::ComPtr device->CreateInputLayout(…, &inputLayout ) -or- device->CreateInputLayout(…, inputLayout.ReleaseAndGetAddressOf() ) |
Note When passing these objects to other functions, you can pass raw pointers and use .get on the memory control object on each call, or pass the smart pointer object. When using smart pointer objects as parameters, pass them by constant reference, similar to other C++ Standard Library containers, in order to avoid additional temporary copies and to avoid excessive reference count increment and decrement cycles.
See Exception Handling (x64) on MSDN.
Compile using C++ Exceptions (/EHsc). This is the reverse of the advice given for Xbox 360. WinRT and C++ Standard Library are both written assuming C++ exceptions are enabled. This mode is much more efficient than the ‘asynchronous / structured’ model used by the .NET CLR (enabled by /EHa) as the compiler does not need to assume that every individual instruction has the potential to throw an exception, and also means that extern “C” functions are assumed not to throw C++ exceptions. Only functions containing throw statements are assumed to potentially generate C++ exceptions.
Do not use ‘nothrownew.obj’. This is the reverse of the advice given for Xbox 360. As a workaround for the C++Standard Library defining operator new as throwing a C++ exception to report out-of-memory conditions, the nothrownew.obj module included with VC++ disables this by making it return nullptr instead. For Xbox 360 we recommended using this workaround, but warned that the C++ Standard Library would essentially have no error-condition checking in place for running out of memory. For Xbox One, this workaround is discouraged. When writing code that uses HRESULT values or other error codes to report out-of-memory conditions, you can make use of the operator new (std::nothrow) and an explicit check for a nullptr result.
Throw exceptions only in exceptional situations. As stated above, processing a throw can be a low-performance scenario. Using traditional error codes for performance scenarios can be a useful pattern; for example, the Direct3D 11 API itself uses HRESULTS rather than throwing exceptions. But the use of traditional error codes does suffer from the usual problem of remembering to check for error conditions, which is not an issue for C++ exceptions.
Write all code using ‘exception-safe’ patterns, and prefer C++ constructor/destructors over explicit try/catch(…) cleanup. Even if a particular code path never directly throws an exception, it is still possible that an exception will be thrown further down in the call graph. This primarily impacts resource allocation and cleanup. Doing this with C++ constructors and destructors makes proper cleanup in the presence of C++ exceptions automatic, and is a more portable solution and less error-prone than trying to explicitly catch all clean-up cases.
Be careful when using catch(…). Writing code that “eats exceptions” is a risky coding practice. It can be useful to write a try / catch(…){ … throw; } block that rethrows the exception after some local processing, but in general don’t catch exceptions you don’t know how to handle. Remember also that you should “catch” exceptions by reference and not by value. In general, you want to minimize the use of explicit try / catch(…) blocks as these can inhibit the optimizer.
Avoid using exception specifications. While intended as a usability and optimization improvement, in practice using C++ exception specifications is problematic and error-prone. It can also potentially prevent a function from being inlined.
See Exception Handling (Debugging) on MSDN and A Pragmatic Look at Exception Specifications on GotW.ca.
The Visual C++ compiler supports a wide variety of intrinsics sets. Considering that x64 native programming does not support inline assembly, intrinsics are the primary method for utilizing specific CPU instructions in code.
Table 4. Intrinsics headers and descriptions.
| Header | Description |
|---|---|
| intrin.h | General intrinsics, notably __cpuid and various intrinsics forms of the CRT routines. |
| ammintrin.h | FMA4 and XOP intrinsics (formerly known as “SSE5”); BMI, LWP, and TBM intrinsics Note that FMA4 and XOP are not supported by the Xbox One console. |
| xmmintrin.h | SSE intrinsics and the __m128 type (single-precision float SIMD) |
| emmintrin.h | SSE2 intrinsics and the __m128i/__m128d types (double-precision float and integer SIMD) |
| pmmintrin.h | SSE3 intrinsics (horizontal adds and subtracts float/double operations, specific ‘dup’ operations) |
| tmmintrin.h | SSSE3 intrinsics (more horizontal ops, integer abs, ‘byte’ shuffle to augment SSE2) |
| smmintrin.h | SSE4.1 intrinsics (dot-product, rounding, augmented min/max support for SSE2) |
| nmmintrin.h | SSE4.2 intrinsics |
| immintrin.h | AVX, FMA3, F16C/CVT16, and AVX2 intrinsics Note that FMA3 and AVX2 are not supported by the Xbox One console. |
| wmmintrin.h | AES intrinsics |
Note mmintrin.h is for Intel MMX™ intrinsics (including the __m64 type), which are not supported for x64 native applications.
mm3dnow.h is for AMD’s 3DNow!™ intrinsics which are not supported for x64 native applications. The exception to this is _m_prefetch / _m_prefetchw which is supported for x64 native code generation. These intrinsics are also defined in intrin.h.
The DirectXMath library is the best starting place for utilizing SIMD intrinsics in your title. It supports Windows running on systems based on x86, on x64, and on ARM; it also supports Windows Store apps and the Xbox One console. It is the successor to the xboxmath/XNAMath library on Xbox 360. On Xbox One, DirectXMath takes advantage of SSE, SSE2, SSE3, SSE4.1, AVX, and F16C instructions.
Note The Xbox One XDK (March 2017) and the Windows 10 Creators Update SDK include DirectXMath 3.10. It is also available on GitHub.
See the following resources:
Native code generation for x64 uses a simple and standard __fastcall calling convention that can pass the first four parameters in-register.
If the first parameter is an integer or pointer equivalent, it is passed in RCX. If it is a float or double, it is passed in XMM0L. Otherwise it is passed by the stack.
If the second parameter is an integer or pointer equivalent, it is passed in RDX. If it is a float or double, it is passed in XMM1L. Otherwise it is passed by the stack.
If the third parameter is an integer or pointer equivalent, it is passed in R8. If it is a float or double, it is passed in XMM2L. Otherwise it is passed by the stack.
If the fourth parameter is an integer or pointer equivalent, it is passed in R9. If it is a float or double, it is passed in XMM3L. Otherwise it is passed by the stack.
All other parameters are passed by the stack.
A return value that fits in 64-bits is returned in RAX. XMM0 is used to return float, double, __m128, __m128i, and __m128d values. Otherwise, the caller passed in a pointer to the memory as a 0th argument in RCX and RAX must be returned with the same pointer.
These points mean that shorter function signatures are better performing, and passing pointers (or references) to objects is faster than passing by value. The calling convention will automatically pass by reference objects larger than 8 bytes.
SIMD values are never passed in-register with the __fastcall scheme.
With the new Visual Studio compiler in the Xbox One XDK, there is an optional calling convention available known as __vectorcall for x64 native code generation.
If the first parameter is an integer or pointer equivalent, it is passed in RCX. If it is a float, double, __m128, __m128i, or __m128d, the parameter is passed in XMM0. If it is a __m256, __m256i, or __m256d, it is passed in YMM0. Otherwise, the first parameter is passed by the stack.
If the second parameter is an integer or pointer equivalent, it is passed in RDX. If it is a float, double, __m128, __m128i, __m128d, __m256, __m256i, or __m256d, the parameter is passed in XMM1/YMM1. Otherwise, the second parameter is passed by the stack.
If the third parameter is an integer or pointer equivalent, it is passed in R8. If it is a float, double, __m128, __m128i, __m128d, __m256, __m256i, or __m256d, the parameter is passed in XMM2/YMM2. Otherwise, the third parameter is passed by the stack.
If the fourth parameter is an integer or pointer equivalent, it is passed in R9. If it is a float, double, __m128, __m128i, __m128d, __m256, __m256i, or __m256d, the parameter is passed in XMM3/YMM3. Otherwise, the fourth parameter is passed by the stack.
If the fifth parameter is a float, double, __m128, __m128i, __m128d, __m256, __m256i, or __m256d, the parameter is passed in XMM4/YMM4. Otherwise, the fifth parameter is passed by the stack.
If the sixth parameter is a float, double, __m128, __m128i, __m128d, __m256, __m256i, or __m256d, the parameter is passed in XMM5/YMM5. Otherwise, the sixth parameter is passed by the stack.
All other parameters are passed by the stack.
A return value that fits in 64-bits is returned in RAX. XMM0 is used to return float, double, __m128, __m128i, and __m128d values. YMM0 is used to return __m256, __m256i, or __m256d values. Otherwise, the caller is passed in a pointer to the memory as a 0th argument in RCX, and RAX must be returned with the same pointer.
The __vectorcall convention can also handle homogeneous vector aggregate (HVA) types, which consist of one or more of the same kind of vector or floating-point types—up to four members (struct XMMATRIX { __m128 v[4]; } is an HVA).
As a by value parameter, an HVA will be passed in register if sufficient space is available in XMM0/YMM0 – XMM5/YMM5 after allocating other parameters. As a return value, an HVA is returned via XMM0/YMM0 – XMM3/YMM3.
Note __vectorcall does not support vararg functions, __vectorcall is not supported in Managed C++ (/clr) contexts, and __vectorcall cannot be applied to WinRT APIs.
DirectXMath makes use of inlining, but the latest version (3.05 and later) is also annotated to use __vectorcall in cases where the compiler does not make use of inlining.
For code that makes use of the DirectXMath calling convention types (for example, FXMVECTOR, GXMVECTOR, and CXMMATRIX), consider adopting the new calling-convention macro and changes:
XMMATRIX MyFunction (FXMVECTOR v1, FXMVECTOR v2, FXMVECTOR v3,GXMVECTOR v4, CXMVECTOR v5, CXMVECTOR v6, CXMVECTOR v7 );
->
XMMATRIX XM_CALLCONV MyFunction (FXMVECTOR v1, FXMVECTOR v2, FXMVECTOR v3,GXMVECTOR v4, HXMVECTOR v5, HXMVECTOR v6, CXMVECTOR v7 );
XMVECTOR MyFunction2( CXMMATRIX M1, CXMMATRIX M2);
->
XMVECTOR XM_CALLCONV MyFunction2( FXMMATRIX M1, CXMMATRIX M2);
See Overview of x64 Calling Conventions on MSDN and the Visual C++ Team Blog.
Static code analysis (/analyze) is supported in all editions of Visual C++ (including “Express”) and is recommended for use by all developers. The Visual Studio IDE provides easy methods for running static code analysis, as well as making it easier to navigate the warnings and suppress any noise.
Visual C++ implements a new release of the Standard Annotation Language (SAL2) and this annotation is present in all the platform headers provided in the Xbox One XDK. Developers should consider making use of SAL2 annotation in their own code, particularly shared libraries used across the project and with other teams. Existing code bases that are making use of Windows-style SAL (__in) should be updated to the new SAL form. Converting VS-style SAL (_In_), introduced with Visual Studio 2008, to SAL2 is fairly straightforward. Here are some equivalents for various forms of SAL.
Table 5. Windows-style SAL vs Visual Studio-style SAL vs SAL2 (Standard Annotation Language).
| Windows-style SAL | Visual Studio-style | SAL SAL2 |
|---|---|---|
| _in _In | In | |
| __in_opt | In_opt | In_opt |
| __out | Out | Out |
| __out_opt | Out_opt | Out_opt |
| __inout | Inout | Inout |
| _inout_opt | Inout_opt | Inout_opt |
| __in_ecount(count) | In_count(count) | In_reads(count) |
| __in_ecount(constexpr) | In_count_c(constexpr) | In_reads(constexpr) |
| __in_bcount(count) | In_bytecount(count) | In_read_bytes(count) |
| __in_xcount(count) | In_count_x(count) | In_reads(Inexpressible(count)) |
| __in_ecount_opt(count) | In_opt_count(count) | In_reads_opt(count) |
| __out_ecount(count) | Out_cap(count) | Out_writes(count) |
| __out_ecount(constexpr) | Out_cap_c(constexpr) | Out_writes(constexpr) |
| __out_bcount(count) | Out_bytecap(count) | Out_writes_bytes(count) |
| __out_xcount(count) | Out_cap_x(count) | Out_writes(Inexpressible(count)) |
| __inout_ecount(count) | Inout_cap(count) | Inout_updates(count) |
| __inout_ecount(cexpr) | Inout_cap_c(cexpr) | Inout_updates(cexpr) |
| __inout_bcount(count) | Inout_bytecap(count) | Inout_updates_bytes(count) |
| __deref_out | Deref_out | Outptr |
| __deref_opt_out | Deref_opt_out | Outptr_opt |
SAL2 can handle more complex expressions for the count than older versions of SAL, so you can often successfully remove the _Inexpressible_ modifier. Another improvement with SAL2 is the introduction of the _Use_decl_annotations_ macro, which you can put on the body of a function to ensure it uses the same SAL2 annotation present on the declaration prototype rather than having to duplicate the annotation.
Note The annotations _Check_return_ and _Must_inspect_result_ can help mitigate some of the risks of using traditional error codes where developers forget to check important result codes.
See Understanding SAL, and What’s New in Code Analysis for Visual Studio 11 on MSDN.
Code security is important for any gaming platform, and taking basic precautions can help to ensure that an Xbox One title itself won’t be subject to tampering or other exploits. Regular use of static code analysis can help prevent bugs with buffer overruns and other correctness issues. In addition, there is a new /sdl switch in the compiler that enables a number of useful features for ensuring good quality code security. Use of this switch is encouraged, which is labeled as SDL Checks in the Visual Studio property dialogs. This setting is particularly useful when compiling older codebases or third-party code. For newer code that is cleaned up well using /analyze, just enabling the warnings (ideally /Wall) and enabling standard buffer checks (/GS) is likely sufficient.
The Visual C++ compiler implements the existing stack cookie security check when using /GS, and performs a new range check in some specific cases that have shown up in security bugs. The range check is added when using a fixed known size array of element size 1 or 2, and a value of 0 is being written (typically a nul marker for an ASCII or Unicode string). The Visual C++ optimizer also has improved logic for removing unnecessary security checks to minimize potential performance impact from using /GS.
For more information, see the following MSDN blog posts:
Visual C++ includes support for the Safer C Runtime, which includes more secure forms of the various standard C routines. Use of these safer versions is strongly encouraged, and is also recommended over the older <strsafe.h> header. Use of the _CRT_SECURE_NO_WARNINGS compilation defined in Xbox One projects is discouraged. Instead, you should clean up the various warnings in code to ensure the title is not using the insecure versions of C Runtime (CRT) routines.
See Security Features in the CRT on MSDN.
Many Xbox One developers will have code bases written for Xbox 360, Windows x86, or other platforms. Developers who have already ported to the Windows x64 or other 64-bit platform will have addressed these issues including pointer truncation, legacy APIs, removal of assembly code, binary file structure mismatch, and reliance on 32-bit only libraries. Here are some recommendations for writing x64 native portable code:
Use portable types. Proper use of size_t and ptrdiff_t is critical to avoiding pointer truncation and other problems. When creating size-specific types, make use of types in <stdint.h> / <cstdint> including int8_t, uint8_t, int16_t, uint16_t, int32_t, uint32_t, int64_t, uint64_t, intptr_t, and uintptr_t. Older recommendations suggested using Windows specific types (on MSDN) that are also available, but these are more portable.
Group pointers in structures and classes. Most data types do not change size when moving to x64 native, but pointers become 8 bytes (known as the “LLP64” data model). The default pack setting for x64 is 16 rather than 8 to ensure structures are padded to a natural alignment including pointers. Mixing pointers with other data types in structures results in more padding than would happen if the pointers were grouped together. For example, on 32-bit platforms, both of the following structures are 16 bytes in size. For x64 native, however, the first one is 32 bytes while the second is 24 bytes.
struct MyObject {
uint32_t count;
void* ptr;
uint32_t flags;
void* next;
};
struct MyObject {
void* ptr;
void* next;
uint32_t count;
uint32_t flags;
};
Prefer C++ style casts. Use of const_cast<>, static_cast<>, reinterpret_cast<>, and dynamic_cast<> rather than C-style casts can help highlight potential pointer-truncation issues more easily. If a cast is only supposed to remove the const, for example, but also changes the data type size, a C-style cast will silently do both while a const_cast<> will generate a compile-time warning.
Use intrinsics instead of inline assembly. Use of assembly requires a standalone Microsoft macro assembler (MASM) module, but intrinsics can eliminate the need to write directly in assembly. Intrinsics allow the compiler the freedom to schedule instructions and manage the register file more optimally in the context of surrounding code. Avoid using intrinsics that operate on the __m64 data type as these are deprecated MMX™ or 3DNow!™ instructions. Converting existing Xbox 360 intrinsics to use XNAMath and then porting those to DirectXMath on Xbox One is a good strategy for porting VM128 intrinsics code.
Use maximum warnings (/Wall). A number of warnings that tend to highlight 64-bit portability issues include C4302 and C4826 and are off by default. You can disable specific warnings to reduce ‘noise’ as they are identified by #pragma warning or /wd.
Use /analyze. Static code analysis will highlight a number of issues, particularly using the incorrect printf format specifications.
For more information, see the following MSDN resources:
While many source-level and algorithm-level optimization techniques remain the same for x64 native programming for Xbox One, it is important not to assume that hotspots will remain in the same place across platforms. Profiling of x64 native code is the only way to be sure that a given optimization effort is likely to be fruitful. This section provides some general guidance, but is no substitute for real-world measurements.
Note: VS 2015 Update 3 and VS 2017 include a new C++ code optimizer based on Static Single Assignment (SSA). For more information, see the Visual C++ blog.
The x64 native platform will automatically handle misaligned reads similarly to x86, but there is still a performance penalty. Using natural alignment for data structures will help prevent excessive misalignment penalties.
The default memory allocation routines (new and malloc) return 16-byte aligned memory for the x64 native platform. The __declspec(align()) directive is useful for structures allocated on the stack as local variables or in the global data segment, but does not affect the alignment of such structures when allocated from the heap. The __aligned_malloc function is used to ensure any other alignment for heap allocations than the default of 16 bytes. Windows x86 heap allocation functions use 8-byte alignment, while Xbox 360 also uses 16 bytes.
Use of C++ Standard Library containers, make_shared<>, conversions to Windows Runtime types, and/or derivation from other classes can impact the final alignment of objects in memory.
While read and write alignment can both be useful for best performance, it is generally more important to write to aligned memory than it is to read from aligned memory. Therefore, if having to trade off one for the other, ensure that memory writes are aligned.
This keyword has been supported for providing a hint to the compiler about pointer aliasing since Visual Studio 2005. Similar to Xbox 360, it is important to make good use of this keyword to improve the utilization of the register file of the x64 native code-generation model.
For Xbox 360, we recommended always using optimize for time due to the in-order execution of the processor. For the out-of-order execution CPU on Xbox One, fitting a code segment into the instruction cache is usually more important than the absolutely fastest version of the code, so optimize for space is preferred. Reserve the use of optimize for time only for specific modules or functions. Intrinsics generation is worth the additional code for games as it improves floating-point performance. You can also enable optimize for time on a specific function or set of functions by surrounding it with #pragma optimize( “t”, on) / #pragma optimize( “”, on ). Remember that optimize for time (/O2 or /Ox) can result in a drastic increase in program size due to aggressive loop unrolling.
Note The SIMD auto-vectorizer optimizations are only enabled for /O2 or /Ox. Profile-Guided Optimization (PGO) can make better choices about where exactly to apply optimize for time vs. optimize for space.
The /fp flag introduced with Visual Studio 2005 controls the various floating-point models used by the compiler (fast, precise, strict, except). The fast floating-point model uses a number of optimizations that are not part of the core “C” rules. The precise floating-point model disables these extra optimizations and is the default. Using /fp:fast is recommended for games.
See /fp.
The implied default for x64 native platforms is /arch:SSE2. For the Xbox One console you should make use of /arch:AVX. The primary benefit is that this causes the compiler to generate VEX prefix versions of SSE instructions that use a non-destructive 3-op encoding instead of the standard 2-op encoding where one of the source operands is overwritten by the result. This affects both scalar floating-point for x64 native code using SSE instructions and explicit use of SSE intrinsics. This helps reduce register scheduling pressure and can eliminate some movaps instructions.
The /favor switch controls the x64 compiler optimization choices for AMD64 versus Intel 64 processors. For Xbox One, use /favor:AMD64.
While traditional compiler optimization techniques can optimize functions and modules, Whole Program Optimization, also known as Link Time Code Generation (LTCG), performs optimizations across modules. This includes improved register allocation and scheduling with better knowledge of side effects, interprocedural const/range propagation, partial inlining, and de-virtualizing function calls on objects where possible.
See /GL, /Gw, and /LTCG on MSDN.
Profile-Guided Optimization (PGO) takes the LTCG process one step further where the whole program optimization is controlled by performance traces gathered in prior runs of the application. This improves the quality of the existing optimizations as well as optimizations for function layout, block layout, and code separation for improved working-set and branch prediction.
See Profile-Guided Optimizations on MSDN.
Note Large EXEs can exhaust the virtual memory available to the 32-bit x64 cross compiler toolset. You will likely have more success using the 64-bit native x64 compiler toolset. By default, the Visual Studio IDE uses the 32-bit x64 cross compiler. You can cause the IDE to utilize the 64-bit native x64 compiler by setting the environment variable PreferredToolArchitecture to x64 (was _IsNativeEnvironment to true for Visual Studio 2012) before launching the IDE.
Note You cannot use PGO and OpenMP together. If needed, isolate your OpenMP code to a static library while using PGO for the remainder of your application.
These eliminate redundant content in the various object modules when linked into the final executable. Typically, these are disabled for Debug builds, but should be present for Release builds.
The use of the buffer check in release configurations is recommended for all code. For cases where there are specific buffers that are safe from security threats, but performance seems impacted by the check, use __declspec(safebuffers) on the function rather than globally disabling /GS. For higher-risk code, use #pragma strict_gs_check(on).
See /GS on MSDN.
The use of this switch in optimized builds can greatly increase the debug-ability of release builds of your application. If used for production builds, it can simplify the process of analyzing and debugging crash dumps. This adds a small amount of overhead for storing additional parameters on the stack, but in most cases this does not have a noticeable impact on performance.
See /homeparams on MSDN.
This applies the more strict C++11 rules for inline function declaration, which requires that the body of all inline functions be present in the same compilation unit as the declaration and their use. Using this flag can improve build times, and can reduce the size of .obj files significantly. If your inline functions fail to be C++11 conforming, you will get LNK2019 errors at link time.
The C++ Standard and the Standard C++ Library templates require that operator new will throw an exception rather than return nullptr on a failed allocation—you can use std::nothrow with new to force the return nullptr behavior. The Visual C++ compiler has long generated a check for null after each call to new, which is unnecessary in the presence of exception handling. By using this switch, the extra checks are eliminated. For more details, see the Visual C++ blog.
The keyword volatile has a number of Microsoft-specific extended behaviors related to its behavior that are not really portable even between generations of Visual C++. You might consider turning on this switch and then ensuring that you aren’t misusing volatile in your code.
Visual Studio 2017 has introduced a new standard conformance switch to replace the outdated /Za switch. This switch causes the compiler to emit errors and warnings when using Microsoft-specific extension behavior that could cause problems when porting to other compilers. For more information, see the Visual C++ blog.
These two switches are implicit when using x64 native targets. The no-execute (NX) protection is required for Xbox One titles.
See /NXCOMPAT and /SAFESEH on MSDN.
This switch enables the use of address space layout randomization (ASLR) and is required for Xbox One titles.
See /DYNAMICBASE on MSDN.
For local builds, enabling this linker switch can greatly improve build iteration times. For more information, see the Visual C++ Team Blog.
Visual C++ Porting and Upgrading Guide (VS 2015)
Visual C++ Porting and Upgrading Guide (VS 2017)
Troubleshooting for the Microsoft Visual C++ Compiler or the Visual C++ Linker, Microsoft Knowledge Base article 974229
Optimization Best Practices, MSDN Visual C++ Documentation
Scott Meyers. More Effective C++. Addison-Wesley, 1996. Print.
Scott Meyers. Effective Modern C++: 42 Specific Ways to Improve Your Use of C++11 and C++14. O’Reilly, 2014. Print.
Software Optimization Guide for AMD Family 16h Processors, AMD
Intel 64 and IA-32 Architectures Optimization Reference Manual, Intel
BUILD 2013: *The Future of C++ *
C++ and Beyond 2012: atomic<> Weapons
C9::GoingNative: VC11 Auto-Vectorizer, C++ NOW, Lang.NEXT
GoingNative 2013: C++ and the Windows Runtime
GoingNative 2013: An Effective C++11/14 Sampler
GoingNative 2012: C+11, VC++11 and Beyond
GoingNative 2012: Threads and Shared Variables in C++11
GoingNative 2012: STL11: Magic && Secrets
Gamefest 2011: Modern C++(0x) Programming with Visual Studio (US / UK)
Gamefest 2010: Unleashing the Power of C++0x (US)
Gamefest 2008: 64-bit Gaming Is Here Today: Where Are You? (US)
Xfest 2017: Visual Studio 2017 (GDN)
Xfest 2015: Visual Studio 2015 for Xbox One (GDN)
Xfest 2014: C++/Cx Performance Gotchas (GDN)
Xfest 2013: Windows Runtime Programming for High-Performance Games (GDN)
Xfest 2012: Coding Best Practices for Xbox One (GDN)
Visual C++ - Microsoft Pushes C++ into the Future (April 2016)
Windows with C++ - Coroutines in Visual C++ 2015 (October 2015)
Windows with C++ - Adding Compile-Time Type Checking to Printf (May 2015)
Visual C++ 2015 Brings Modern C++ to Legacy Code (April 2015)
C++ : Visual C++ 2015 Brings Modern C++ to the Windows API (Special 2014)
Windows with C++ : Using Regular Expressions with Modern C++ (January 2014)
The Windows Runtime Application Model (August 2013)
A Tour of C++/CX (April 2013)
The Pursuit of Efficient and Composable Asynchronous Systems (September 2012)
C++: Functional-Style Programming in C++ (August 2012)
C++: New Standard Concurrency Features in Visual C++ 11 (March 2012)
X64 Primer: Everything You Need To Know To Start Programming 64-Bit Windows Systems (May 2006)
OpenMP and C++: Reap the Benefits of Multithreading without All the Work (October 2005)
Safe! Repel Attacks on Your Code with the Visual Studio 2005 Safe C and C++ Libraries (May 2005)
Under the Hood: Link-time Code Generation (May 2002)
| Date | Updates |
|---|---|
| April 2017 | Updates for Visual Studio 2017 |
| June 2016 | Updates for Visual Studio 2015 Updates 1, 2, and 3 |
| September 2015 | Updates for Visual Studio 2015; more resources |
| September 2013 | Additional intrinsics notes; more resources |
| August 2013 | Refreshed some links; more resources |
| May 2013 | Updated for new Visual Studio compiler in the Xbox One XDK 2013 releases |
| August 2012 | Updated for Visual Studio 2012 RTM |
| June 2012 | Updated for the Visual Studio 2012 Release Candidate |
| April 2012 | Original release using Visual Studio 11 Beta |