C++

Write Faster Code with the Modern Language Features of Visual C++ 2005

Stephen Toub

This article was based on a pre-release version of Microsoft Visual Studio 2005, formerly code-named "Whidbey." All information contained herein is subject to change.

This article discusses:

  • .NET C++/CLI syntax
  • Interop technologies
  • Profile Guided Optimization
  • MSIL optimization
  • OpenMP support
  • Enhanced buffer security checks
This article uses the following technologies:
Visual C++ .NET 2003, Visual C++ 2005

Contents

C++/CLI—The New Syntax
Interop Options
It Just Works
Optimizations
Security
Conclusion

The introduction of the Visual Studio® .NET 2003 C++ compiler was a mouthwatering experience for enthusiasts of the C++ language. With 98 percent conformance to the ISO C++ standard, Visual C++® .NET 2003 was truer to these standards than any previous version and incorporated language support for features such as partial template specialization. It also included enhanced buffer security checks and improved compiler diagnostics. C++ developers joined the ranks of developers using C# and Visual Basic® .NET who are able to use the drag and drop forms designer to build robust Windows® Forms applications. The compiler also included optimizations targeting the Intel Pentium 4 and AMD Athlon processors.

If you were excited about Visual C++ .NET 2003, you'll be in a frenzy over the next version, Visual C++ 2005. Visual C++ 2005 has a new syntax for development in .NET that is both elegant and powerful. It has new optimization technology that has improved the speed of Microsoft products up to 30 percent. It has new compilation modes that ensure Common Language Infrastructure (CLI) compliance and verifiability for the Microsoft® .NET Framework, and it has new models for interop that provide a seamless merging of the native and managed worlds as well as complete control over when these boundaries are crossed. The compiler includes an enhanced version of the buffer security check option present in the previous two versions, and it includes new security-focused versions of libraries in prevalent use by C++ applications. It has support for the OpenMP standard as well as for 64-bit platforms, including the Intel Itanium and AMD64 chips. It fixes the mixed DLL loading issue and provides automatic runtime elimination of the Double P/Invoke performance problem. The list of enhancements and improvements goes on and on. As one of the architects on the C++ team told me, "C++ is where it's at, man!"

C++/CLI—The New Syntax

How many of us found working with the Managed Extensions syntax of the previous two versions of C++ cumbersome and fraught with error? How many of us felt that Visual C++ wasn't being treated as a first-class .NET-based language? Apparently, most of us did (including the development group itself, if you read their blogs). The Visual C++ team heard our cries, and starting with Visual C++ 2005, the Managed Extensions for C++ syntax that was introduced with Visual Studio .NET 2002 is going the way of the dinosaurs as a revised language definition is introduced that brings with it an attractive new syntax.

The design team had a few major goals for this release in regard to the language design. First (and probably most important to those of us who think code is art), they wanted to make sure that writing C++ felt natural and that it provided an elegant syntax with pure extensions to the ISO C++ standard. They wanted to make it easy to write verifiable code in C++ in order to enable partial-trust scenarios such as ClickOnce deployment, Form design support and managed-code hosting in SQL Server™ 2005. They wanted to leave no room for a language "lower" than C++. They wanted to bring the complete power of .NET to C++, and at the same time bring the power of C++ to .NET. They've succeeded brilliantly in all aspects.

The specification for the new extensions is called C++/CLI and is now being standardized. To get a taste of the new language extensions, see the candidate base document that was posted online on November 21, 2003, and which is available for download at C++/CLI Language Specification.

Most noticeably for anyone reading code in the new syntax, the common double-underscore keywords prevalent in Managed Extensions for defining garbage-collected classes, properties, and so on, are a thing of the past. While a few of these keywords remain and a few more are being introduced, they are infrequently used and won't muddy up the readability of the code. These double-underscore keywords are being replaced with two new types of keywords: context-sensitive and spaced. Context-sensitive keywords are only keywords when used in certain contexts, and spaced keywords are only keywords when used in combination with other keywords. For example, the __property keyword from Managed Extensions is replaced with the property keyword. (Not only that, but the entire syntax for defining a property and its accessors has been dramatically refined, making the declaration look very similar to what you might write in C#. See Figure 1 for an example.) This doesn't prevent you from using "property" as the name of a variable in your code. A token parsed as "property" is only treated as a keyword when in the context of declaring a property on a type.

Figure 1 Syntax Comparison

Managed Extensions Syntax

public __gc __sealed class Student { private: double m_grade; String* m_name; public: __property double get_Grade() { return m_grade; } __property void set_Grade(double newGrade) { m_grade = newGrade; } __property String* get_Name() { return m_name; } __property void set_Name(String* newName) { m_name = newName; } }

C++/CLI Syntax

public ref class Student sealed { private: double m_grade; public: // standard property syntax property double Grade { double get() { return m_grade; } void set(double newGrade) { m_grade = newGrade; } } // trivial property // compiler can generate accessors and backing store property String^ Name; }

Types in the new syntax are declared with the form "adjective class", where the adjective describes what type of class you're creating, as shown in the following:

class N { /*...*/ }; // native type ref class R { /*...*/ }; // CLR reference type value class V { /*...*/ }; // CLR value type interface class I { /*...*/ }; // CLR interface type enum class E { /*...*/ }; // CLR enumeration type

In previous versions of the language, the way a type was declared controlled where and how it lived. Only native classes or structs and managed value types could be created on the stack. Managed ref classes always lived on the managed heap. In Visual C++ 2005, all types, be they native or managed, can be created on the stack, with stack-based deterministic cleanup semantics.

To instantiate an object of type T to live on the native heap, use "new T". This returns a pointer to the location of the object on the native heap (a concept referred to as a __nogc pointer in Visual Studio .NET 2002 and Visual Studio .NET 2003). To instantiate an object of type T that should live on the managed heap, Visual C++ 2005 introduces the gcnew keyword to be used in the same manner as the new keyword. Calling "gcnew T" returns a handle to a whole object on the managed heap. A handle is a new construct introduced in Visual C++ 2005 that resembles the __gc pointer of Managed Extensions. To instantiate a type T on the stack, the standard "T t;" declaration suffices.

To be fair, I'm taking some liberties in how I define instantiation. Managed reference classes always live on the managed heap, and native types always live on the stack or on the native heap. When a managed reference type is declared to live on the stack, the compiler is in fact instantiating it on the managed heap, as shown in Figure 2.

Figure 2 Managed Ref Type on the Stack

Figure 2** Managed Ref Type on the Stack **

This begs some questions. What happens when my instance on the stack goes out of scope? How is it cleaned up? Many C# developers have complained about the C# language's lack of deterministic cleanup. The C# language provides the using keyword to make it easy to dispose of an IDisposable object, but this requires extra code and is clunky compared to the destructor pattern familiar to C++ developers. In C#, safe cleanup is off by default and requires explicit coding. For example, consider the first C# code snippet in Figure 3. The StreamReader object is declared on the managed heap. When this method finishes execution, no more references to the StreamReader instance remain. However, the object won't be finalized until the garbage collector runs. Until that time, the file will not be closed and the application will continue to hold onto its open file handle. To add deterministic cleanup, you must use the IDisposable interface implemented by classes that use unmanaged resources.

Figure 3 Deterministic Cleanup

Implemenation Code
C# without deterministic cleanup

 

string ReadFirstLineFromFile(string path){ StreamReader reader = new StreamReader(path); return reader.ReadLine();})

C# with deterministic cleanup

 

string ReadFirstLineFromFile(string path){ using (StreamReader reader = new StreamReader(path)) { return reader.ReadLine(); }}

Visual Basic .NET with deterministic cleanup

 

Function ReadFirstLineFromFile( _ ByVal path As String) As String Dim reader As StreamReader Try reader = New StreamReader(path) ReadFirstLineFromFile = reader.ReadLine() Finally If Not reader Is Nothing Then _ CType(reader, IDisposable).Dispose() End TryEnd Function

C++ with deterministic cleanup

 

String^ ReadFirstLineFromFile(String^ path){ StreamReader reader(path); return reader.ReadLine();}

The second code sample in Figure 3 shows what the new code in C# looks like. Not horrible, and still readable, but as you start introducing more objects that require cleanup, your code becomes harder and harder to read. Also, any objects you forget to dispose will put additional pressure on the finalizer thread when the garbage collector eventually does run. In the meantime, you could possibly lock up valuable resources. This becomes even uglier when viewing the equivalent implementation in Visual Basic .NET, also shown in Figure 3 (although Visual Basic 2005 gains a Using statement similar to that in C#).

Visual C++ 2005 now provides the ability to have destructors and/or finalizers on any type, regardless of whether it is managed or native. In the case where the type is managed, the compiler maps the destructor to the IDisposable::Dispose method. This means that you can write the same method in C++ using the fourth snippet in Figure 3, where reader's destructor/Dispose method will automatically be called just as if you were using the "using" statement in C#. When a type is created on the stack, its destructor is invoked when it goes out of scope.

One of the biggest problems with Managed Extensions was working with pointers. Pointers were used for a variety of tasks and in a variety of situations, and as such were very difficult to understand. Deciphering which kind of pointer you were dealing with in any given piece of code required intelligence somewhere on the level of genius. That complexity is removed in this next version. In Visual C++ 2005, pointers are plain old C++ pointers. They point to an object that is stable, and you can perform arithmetic with them. Pointers refer to objects whose lifetime must be explicitly managed by the developer. When dealing with a pointer, it is not the runtime's responsibility to clean up after it.

Now let's see how the designers of Visual C++ 2005 have pulled this off. Unlike the new operator which in both Visual Studio .NET 2003 and Visual Studio 2005 returns a pointer, the gcnew operator returns a "handle," a new construct represented in the syntax by a caret (^). Handles refer to whole objects on the managed heap. That is, they cannot be used to point to the interior of types, and the compiler has a number of restrictions on their usage that enforce this behavior and that help the developer to use them correctly and safely. Handles do not allow pointer arithmetic, nor can they be cast to a void pointer or to any integral type. The asterisk and arrow operators are still used to dereference it, however.

This doesn't mean you can no longer acquire a pointer to something on the garbage collected heap. Similar to a combination of the & operator and the fixed keyword in C#, the pin_ptr abstraction in Visual C++ 2005 allows you to retrieve a pinning pointer to an object on the managed heap. As long as this pointer is in existence, the object on the managed heap will be pinned, preventing the garbage collector from moving it around during a collection. Visual C++ 2005 also introduces the tracking reference operator, notated with a percent sign (%). When introduced to the native & reference operator in C++, most developers learn to understand it as a pointer to an object that is automatically dereferenced by the compiler upon usage. In most respects, % is to ^ as & is to *.

In the managed world, having native references to managed objects is just as dangerous as having native pointers to managed objects. The fundamental reasoning behind pointers and references is that the referenced objects don't move around. The tracking reference is similar to the native reference except that it references objects on the managed heap and "tracks" them even if they are moved by the garbage collector. The percent sign operator is also used to "take the address of" managed objects, so just as the & operator when applied to a native type returns a pointer to that object, the % operator when applied to a managed reference type returns a handle to that object.

In general, C++ developers are at peace knowing that standards control their language. For this reason, to promote adoption by third parties, and to ensure the stability of the language going forward, this new syntax has been pulled together into a proposed standard known as C++/CLI. In October of 2003, ECMA voted to create a special task force, called TG5, dedicated to the analysis and adoption of this standard, just as WG21 serves as the governing body for ISO C++. In fact, key players in WG21 are also serving in TG5. The plan is to have this standardized by the end of 2004.

Interop Options

Visual C++ 7.1 provided the best interop functionality of any of the .NET Framework-based languages in Visual Studio .NET 2003. It has the necessary power to implement real-world interop scenarios, as exemplified by the port of Quake II to the .NET Framework, available at https://www.vertigo.com/Quake2.htm. Visual C++ 2005 extends this functionality further.

There are four main ways to do interop in .NET between the managed and native worlds. COM interop can be accomplished using Runtime Callable Wrappers (RCW) and COM Callable Wrappers (CCW). The common language runtime (CLR) is responsible for type marshaling (except in the rare scenarios where a custom marshaler is used) and the cost of these calls can be expensive. You need to be careful that the interfaces are not too chatty; otherwise a large performance penalty could result. You also need to ensure that the wrappers are kept up to date with the underlying component. That said, COM interop is very useful for simple interop scenarios where you are attempting to bring in a large amount of native COM code.

The second option for interop is to use P/Invoke. This is accomplished using the DllImport attribute, specifying the attribute on the method declaration for the function you want to import. Marshaling is handled according to how it has been specified in the declaration. However, DllImport is only useful if you have code that exposes the required functions through a DLL export.

When you need to call managed code from native code, CLR hosting is an option. In such a scenario, the native application has to drive all of the execution: setting up the host, binding to the runtime, starting the host, retrieving the appropriate AppDomain, setting up the invocation context, locating the desired assembly and class, and invoking the operation on the desired class. This is definitely one of the most robust solutions in terms of control over what and when things happen, but it is also incredibly tedious and requires a lot of custom code.

The fourth option, and quite possibly the easiest and the most performant, is to use the interop capabilities in C++. By throwing the /clr switch, the compiler generates MSIL instead of native machine code. The only code that is generated as native machine code is code that just can't be compiled to MSIL, including functions with inline asm blocks and operations that use CPU-specific intrinsics such as Streaming SIMD Extensions (SSE). The /clr switch is how the Quake II port to .NET was accomplished. The Vertigo software team spent a day porting the original C code for the game to code that successfully compiled as C++ and then threw the /clr switch. In no time they were up and running on the .NET Framework. Without adding any additional binaries and simply by including the appropriate header files, managed C++ and native C++ can call each other without any additional work on the part of the developer. The compiler handles the creation of the appropriate thunks to go back and forth between the two worlds.

This resulted in some issues for C++ developers. One of these issues was the now infamous mixed DLL loading problem that affected users of Visual Studio .NET 2002 and Visual Studio .NET 2003. If you're running native code inside the loader lock and you reference a managed type in an assembly that hasn't yet been loaded, the CLR will kindly load the assembly for you. It does this by calling LoadLibrary. Of course, LoadLibrary attempts to acquire the loader lock, at which point you're deadlocked. Developers and product managers alike will be happy to hear that this problem has been solved in the upcoming version.

The /clr switch was a huge boon for C++ developers, but it did have some downsides. As mentioned earlier, the images produced with the /clr switch contained both native and managed code, which sometimes caused problems. First, these mixed images are not CLI-compliant (meaning that, for example, they will not run on Rotor). They have native entry points, and there are often significant transition penalties as you continually cross the managed boundaries. But most importantly, the existence of native entry points can wreak havoc on tools that consume the assemblies, including reflection. In order for reflection to inspect an image, it must first load the assembly and execute it. Only once all initialization has taken place is reflection able to inspect the metadata. Unfortunately, reflection cannot correctly load a managed assembly that contains native entry points.

Moreover, Visual Studio .NET 2003 rarely produced verifiable code, and even when it did, the effort it took in nontrivial cases was often significant. While MSIL has first-class support for unverifiable instructions (you can do pointer arithmetic, perform indirect loads, and access the native heap), verifiable code gives you the ability to engage in partial trust scenarios which, in turn, enables the rich set of features available in Visual Studio 2005. ClickOnce deployment relies on partial trust, as does managed code hosting in SQL Server 2005. One of the major goals the C++ development team had for Visual C++ 2005 was to enable the compiler to aid the developer in the production of non-mixed and verifiable images. They've done so with the introduction of two new compiler switches: /clr:pure and /clr:safe. But before I delve into these new switches, I need to examine how C++ interop functions.

It Just Works

In Visual Studio .NET 2003, the C++ interop technology was known as IJW, or "It Just Works." For the upcoming version, this has been renamed to the more descriptive "Interop Technologies." So how does it "just work?" For every native method used by the application, the compiler creates both a managed and an unmanaged entry point. One of them is the actual method implementation, while the other is a forwarding thunk that creates the appropriate transition and performs any necessary marshaling. The managed entry point is almost always the actual method implementation, except when the code for that method can't be expressed in MSIL or when a developer uses the "#pragma unmanaged" compiler directive to force the implementation of the entry point to be native machine code.

When an IJW forwarding thunk is used (for example, when the native entry point is the forwarding thunk), the compiler provides the implementation for the thunk and the call into the actual implementation through an offset or an Import Address Table (IAT) jump. A reasonable amount of time for an IJW thunk is somewhere on the order of 50 to 300 cycles, though contrived test cases can be developed to get that number down to as little as 10. When the forwarding thunk is MSIL, a managed P/Invoke is used. P/Invokes consist only of a declaration and no actual method implementation; the CLR supplies the functionality for the thunk at run time. These forwarding thunks are often slightly slower than their native equivalents.

As mentioned, a consequence of IJW is two entry points for each function, one managed and one unmanaged. But certain constructs require call sites to these entry points to be populated at compile time (function pointers and vtables, for example). Which entry point should the compiler choose, given that it can't know at compile time the managed state of the call site at run time? In Visual Studio .NET 2003, the unmanaged entry point was always selected by the compiler. This, of course, creates a problem if the caller is indeed managed, an issue known as the Double P/Invoke problem, illustrated in Figure 4. In such a scenario, the managed call transitions to the unmanaged thunk only to transition again right back to managed code, operations resulting in a significant amount of unnecessary overhead.

Figure 4 Double P/Invoke Problem

Figure 4** Double P/Invoke Problem **

Visual C++ 2005 provides a few solutions. The first is the __clrcall keyword which lets you specify whether to emit an unmanaged entry point on a method-by-method basis. Augmenting a function declaration with this keyword will prevent the generation of an unmanaged entry point (a downside to this is that the function can no longer be called directly from native code). The __clrcall keyword can also be placed on a function pointer, causing it to be populated with a managed entry point should the compiler have a choice. The second solution provided by Visual C++ 2005 is the automatic elimination of the Double P/Invoke using runtime checks and a cookie that will help the runtime determine whether the unmanaged thunk can be skipped, forwarding the call directly to the managed entry point. This feature, however, may not make it into the final bits.

The third solution is pure MSIL. The new /clr:pure compiler flag instructs the compiler to generate a pure managed image with no native constructs. Not only does this produce CLI-compliant assemblies that enable partial trust scenarios, it also solves the Double P/Invoke problem by preventing the generation of the unmanaged thunk. As a result, there's only one entry point for every function (the managed entry point), so vtables and function pointers are never populated with an unmanaged entry point.

Just because code is CLI-compliant, however, doesn't mean it's verifiable, which is an important goal for enabling low trust scenarios such as when code is loaded from a file share. To that end, Microsoft has introduced an even stricter compiler flag called /clr:safe. This is the holy grail of verifiability for C++ developers. Throwing this switch causes the compiler to ensure that the generated assembly is completely verifiable; any unverifiable constructs will result in a compile-time error. For example, attempting to compile with an integer pointer as a variable will result in the error "int* = this type is not verifiable" referencing the specific line containing the invalid construct. There are a some scenarios where it's appropriate to go to this extreme. For example, all managed C++ code to be run as a stored procedure in SQL Server 2005 should be compiled with this flag.

Figure 5 Compilation Modes

Figure 5** Compilation Modes **

Figure 5 diagrams the managed and unmanaged worlds of data and code and shows which of them are targeted with the different compiler flags. Not including any of the /clr flags will result in the generation of a completely native image. Using /clr will result in a mixed image that can contain both managed and unmanaged code and data. Pure MSIL, generated by using the /clr:pure flag, will not contain any unmanaged code, though it is still not guaranteed to be verifiable and can contain native types. Safe MSIL is the ultimate in verifiability, targeting only the .NET Framework. In short, these two new compilation modes will enable a wide variety of scenarios that either weren't possible before or that were very difficult to accomplish.

Optimizations

All good software developers want to ensure that their software performs. Compiler writers are a special kind of developer; not only must their code perform, but the code that their code generates must be as efficient as is possible. For this reason, a good optimizing back end is essential to the success of any compiler. Here, Visual C++ 2005 strikes gold.

Visual Studio .NET 2002 and Visual Studio .NET 2003 saw some terrific optimizations added to the C++ compiler as lots of work was done to improve the performance of native code. Support was added for targeting the Intel Pentium IV chip as well as the SSE and SSE2 architectures. Most notably, Whole Program Optimization (WPO) was added, allowing the linker to optimize the entire program when presented with the .obj files for each of the compiled .cpp files. These object files were different from normal object files because rather than containing native machine code, they contained an intermediate language used to communicate between the front and back ends of the compiler. The linker was then able to optimize all of these files as a large unit, providing for more inlining opportunities, better stack alignment options, and the possibility of using custom calling conventions in various scenarios, among other optimizations. Visual C++ 2005 improves upon WPO with a new feature referred to as Bottom-Up, Top-Down analysis. But the big improvement comes in the form of Profile Guided Optimization (POGO), a brand new feature in the compiler that is going to make some performance waves.

Static analysis of source code leaves many open questions for a compiler. Given an if statement that compares two variables, how often will the first be greater than the second? Which of the cases in a switch statement is targeted the most? Which functions are used the most, and which code is often the coldest? If the compiler could know at compile time how the code was going to be used at run time, it could optimize for common scenarios. That's exactly what the Visual C++ 2005 compiler can do.

Figure 6 Profile Guided Optimization

Figure 6** Profile Guided Optimization **

The process for compiling for POGO is shown in Figure 6. The first steps involve compiling the code and linking it into an instrumented build filled with a set of profiling probes. As with WPO, the object files generated by the compiler and fed to the linker are comprised of an intermediate language rather than native machine code. These probes come in two flavors: value probes and count probes. Value probes are used to construct a histogram of the values that variables hold, while count probes are used to keep track of the number of times you traverse a particular path through the application. When the application is run and put through normal use, data is gathered from all of these probes and written to a profile database. Along with the original .obj files fed to the linker, this profile data can then be fed back into the linker. The linker is able to analyze the profile data, determine additional optimizations that should be applied, and spit out a new non-instrumented build of the application. It is this compiled version and not the instrumented version that should be distributed to customers.

Profile Guided Optimization enables a wide variety of optimizations. Based on the count probes, inlining decisions can be made at each function call site. Value probes enable switch and if-else constructs to be reordered so as to pull out the most frequent values and avoid the extra unnecessary checks before hitting the frequent case. Sections of code can be reordered so that the most frequent paths fall through rather than forcing unnecessary jumps around the code. This avoids costly Translation Lookaside Buffer (TLB) thrashing and paging.

Cold code can be placed in special sections of the module, which also helps to avoid these issues. Virtual call speculation can be performed so that virtual call sites that often result in a call made on a specific type can avoid the vtable lookup in the common cases. Partial inlining can be performed, whereby only the hot sections of functions can be inlined and the decision to do so made on a call site by call site basis. Additionally, certain sections of code can be compiled with certain optimization goals in mind, while other sections are compiled with different goals. For example, hot and/or small functions can be compiled to maximize speed (/O2), while colder and/or bigger functions can be compiled to minimize space (/O1).

If you understand your real-world scenarios and can put your apps through common use cases while instrumented, the performance gains can be tremendous. Recently, SQL Server was recompiled using POGO and obtained a whopping performance boost of up to 30 percent in many common scenarios. Moving forward, you can bet that Microsoft will start compiling many of its products with this technology. Note that it is very important not to attempt complete code coverage when profiling your instrumented build. The whole point of POGO is to determine how the common use cases can be optimized. By attempting complete code coverage, you'll be severely penalizing yourself.

Visual C++ 2005 also adds support for OpenMP, an open specification for building multithreaded programs. It consists of a set of pragmas used to instruct the compiler as to which sections of your code can be parallelized. Code with large loops lacking dependencies on previous iterations is most suited for OpenMP. Take a look at the following simple copy function that adds the values from arrays a and b and stores the results in c:

void copy(int a[], int b[], int c[], int length) { #pragma omp parallel for(int i=0; i<length; i++) { c[i] = a[i] + b[i]; } }

For a multiprocessor machine, the compiler will generate multiple threads to execute the iterations of this loop, and each thread will perform a subset of the copy operations. Note that the compiler cannot verify whether the loop has dependencies and thus will not stop you from using these pragmas in improper scenarios. If there are dependencies, you'll most likely get incorrect results according to what you expect from your code, though they will be correct with regards to the specification.

While the biggest gains with OpenMP often come from parallelizing loops as in the example just shown, straight-line code can also exhibit improvement. The "#pragma omp section" directive can be used to demark non-dependent sections in a piece of code, allowing the developer to specify regions that can be run in parallel. The compiler can then generate multiple threads to execute these sections on different processors.

In an important change for developers using .NET, the Visual C++ 2005 optimizer performs most of the same optimizations when targeting MSIL as it does when targeting the native platform, though it does so with different tuning. While the just-in-time (JIT) compiler today analyzes for optimizations at run time, allowing the C++ compiler to optimize during the initial compilation can still provide significant performance benefits (the C++ compiler has much more time to perform its analysis than does the JIT). The Visual C++ 2005 compiler optimizes managed types for the first time, performing loop optimizations, expression optimizations, and inlining. There are places, though, where the compiler can't optimize .NET-based code. For example, it has problems with strength reduction due to the unverifiability of pointer arithmetic, and certain code can't be inlined due to the strict type and member accessibility requirements of the CLR, though it does do significant analysis for legal inlining opportunities. In addition, optimizing MSIL introduces the need to make trade-offs concerning what is presented to the JIT compiler. For example, you wouldn't want to unroll a loop and expose a plethora of variables to the JIT compiler, whereupon it would have to perform register allocation (an NP-complete problem). The Visual C++ team is working through these issues and will have a very well-tuned optimizing solution by the time the system is released.

Security

The Trustworthy Computing initiative, announced by Bill Gates in 2002, has had a noticeable impact on all products developed by Microsoft. Developers for the Windows operating system spent months in security training and code reviews, which resulted in making Windows Server™ 2003 the most secure operating system the company has ever released. Microsoft Office 2003 includes many security features, such as Information Rights Management (IRM), better macro security, and HTML download blocking in Outlook®. The compiler teams are also taking strides towards making their compilers and the code they generate more secure.

Visual Studio .NET 2002 introduced a buffer security check /GS compiler option. This flag causes the compiler to allocate space on the stack before the return address for functions that the compiler decides are susceptible to buffer overrun attacks. On function entry, a security cookie with a known computed value is placed into this buffer, and on function exit it is checked to ensure that the cookie has not been corrupted. A change in cookie value could signify a potential overwriting of the return address, and as a result an error will be generated and the application terminated.

Of course, this doesn't prevent all buffer overrun attacks. Visual Studio .NET 2003 augmented the /GS feature by sorting local variables on the stack so that arrays were allocated in memory addresses higher than the rest of the local variables, shielding these local variables from overruns. This can deter attacks based on vtable hijacking and other pointer-based hacks.

Figure 7 0ld /GS

Figure 7** 0ld /GS **

Visual C++ 2005 brings another update to this powerful feature. When a function call is made, the function activation record is laid out as shown in Figure 7. If one of the local buffers is overrun, an attacker could potentially overwrite everything above it on the stack, including the exception handler record, the security cookie, the frame pointer, the return address, and the function's arguments. Most of these values are protected by various mechanisms (such as safe exception handling), however it is still possible to exploit a buffer overrun in a function which has function pointer parameters. If a function takes a function pointer (or a struct or class that contains a function pointer) as an argument, an attacker could potentially overwrite the value in this pointer and cause the code to execute any function he or she wants to run. To combat this, the Visual C++ 2005 compiler analyzes all function parameters for this vulnerability and lays out the function activation record as shown in Figure 8. Copies of the vulnerable parameters are placed below the local variables on the stack, and these copies are then used instead of the originals. Should the originals be overwritten by a buffer overrun, they won't be exploitable as the copy being used will still retain the original value.

Figure 8 New /GS

Figure 8** New /GS **

In accordance with the "secure by default" Trustworthy Computing directive, the Visual C++ 2005 compiler now enables the buffer security check option by default. This will help make all products compiled with Visual C++ more secure. In fact, Microsoft is now building all of its products, including Windows, Office, and SQL Server, with this option enabled.

Other strides are being taken in Visual C++ 2005 to ensure that code is developed with security in mind. A large majority of C++ applications have dependencies on the C Runtime libraries (CRT) and on the Standard Template Libraries (STL). When these were first designed, code security was not a high priority, nor were many of the attacks in use today commonplace. As a result, much of the functionality provided by these libraries is often used in an insecure manner, opening up applications to potential attacks. Recent books like Michael Howard's Writing Secure Code (Microsoft Press® 2002) have stressed the importance of certain coding practices not exemplified by these libraries.

With Visual C++ 2005, Microsoft is introducing new versions of these libraries that are rewritten in an effort to identify all functions that could lead to common security issues and to provide alternate versions that are more secure. The long term goal of this effort is to deprecate all "insecure" versions in favor of their more robust counterparts. Over 400 new "safe" functions are being introduced in this new version of the CRT alone, ensuring that all pointer parameters are checked for null values, and all functions that perform memory copy operations take the number of bytes to be copied in addition to the destination and source buffers.

Conclusion

There is so much more available in Visual C++ 2005 that it's hard to stop writing about it all: delayed CLR loading for mixed images, the native AppDomains API, new declspecs for better global variable support with respect to AppDomains and processes, module constructors, linker support for object files and .NET modules, implicit boxing, XML comments using the same syntax C# developers have come to love, a brand new version of STL targeting the .NET Framework, param arrays, alias hints, a new floating point model, operator overloading, and on and on.

A new version of any .NET Framework-based language often causes people to ask "what language should my team use if we're writing an app targeting .NET?" Today, if you're doing a lot of native interop work, it's simple. C++ is the easiest language to use for native interop and is often the most performant. Additionally, if you're bringing an existing C++ app forward to .NET, there's really no better way. In fact, Visual C++ is the path most recommended by Microsoft when converting your existing apps to the .NET Framework.

As for new applications, you might ask why developers uninitiated in the world of .NET choose one .NET-based language over another. There is no straightforward answer as each language has its own strengths, but for pure .NET-based applications, the experience in C#, Visual Basic, and C++ is basically the same. If, as a developer, you are already comfortable in a particular language, there is no significant reason to switch to another one.

You might choose C++ over other languages, however, if you're doing any sort of interop. The experience in C++ is almost certain to be nicer than other languages due to the extensive interop support built directly into the language. In addition, the deterministic cleanup it provides through destructors is invaluable when it comes to eliminating resource leakage and ensuring the correctness of your applications. C++ also has many powerful features that can be used in combination with those provided by the CLR. For example, C++ not only supports both templates and generics, but it supports them in combination. This allows for more expressiveness and power than using the individual features alone. In particular, a useful library-writing technique is to write a template that implements a generic interface. This gives you all of the flexibility and power of templates, such as specialization, while still giving other languages the ability to directly use the objects instantiated from the template through the generic interface. All in all, C++ really is where it's at.

Stephen Toub is the Technical Editor for MSDN Magazine. You can reach him at stoub@microsoft.com.