One noticeable change in the coming release of the Visual C++ compiler is the changes to compiler switches. If you use the project system, when the IDE upgrades your project files, many of these changes will be taken care of automatically. However, if you use another build system like nmake, you might have to fix some build issues.

Why did we remove and deprecate some compiler switches?
The Visual C++ compiler has been around a long time. Long enough that many features that were once very useful are no longer needed (optimizations for the 486 probably don't get used that much these days). In some cases, new switches have been added that mean exactly the same thing as an existing switch. Furthermore, some switches existed that are essentially evil and easily led to the program errors.

Much of the investigation here began when we started thinking harder about security issues during the development of Visual C++ 2003. In particular, as soon as potential security issues became a higher priority than backwards compatibility, we had to justify more of the feature set Visual C++ exposed and of course we had to question how much testing we actually did on all the configurations the compiler could exercise. The first change was modest. Visual C++ 2003 deprecated the Gf switch. In that compiler, you would have seen the following warning:

c1xx : warning C4349: /Gf is deprecated and will not be supported in future versions of Visual C++; remove /Gf or use /GF instead

For everyone who does not immediately recall what the Gf switch does, it pooled all the string literals into a writable page of memory. Just the description screams of security issues if not simple reliability issues. The alternative GF switch pools string literals into a read-only page of memory, which will break C programs that mutate string literals. Of course, pooling is a great way to reduce the size of a binary.

Goals behind the switch reduction
The changes to the switch model for Visual C++ 2005 came to be known as switch reduction. It was designed along with another improvement, warning families, that did not make it into this release of Visual C++. Hopefully, warning families will make its way into the next release. But I'll delay discussion of that since we're considering switches right now.

When I started to write the specification for the changes in this area, I listed the following four requirements:

  1. Removing the switch would benefit testing, by reducing the test matrix for the compiler.
  2. Removing the switch would lead to a better user experience.
  3. Changing switches would make multiple versions and platforms have consistent switches.
  4. After considering the above, removing or changing the switch does not place undue burden on users.

I also noted in the specification that changes to switches needed to happen swiftly to avoid prolonged impact on users. Unfortunately, making changes to compiler switches turned out to be harder than I ever imagined. Fixing all of the files in the test harness took the majority of our effort. As a result, all of the work called for in the switch reduction specification wasn't completed.

When it came to improved user experience, the focus was on making it easy for programmers to select the correct defaults for their program or at least to easily understand the interactions switches have with each other. A good example of this are the Os and Ot switches, which mean "favor code space" and "favor code speed" respectively. Neither of these switches do anything unless the Og switch is also given to the compiler. This issue frequently comes up. We can either fix it by warning the user that he probably did something wrong, or we can make the switch model intuitive. Given the overall complexity of just getting a standard C++ program to run correctly, I favor making the compiler more intuitive and making it work by default.

The third requirement addresses the problem with the G3, G4, G5, G6, and G7 compiler switches. In a good attempt to allow application writers to make use of the latest and greatest hardware, nearly every compiler introduced a new better switch to make your code even faster. Unfortunately, compiler switches end up in make files that rarely get revised. It wasn't uncommon to see a make file specify a G4 or G5 switch even though 80486 and Pentium have long been out of mainstream production. The G-series of switches do not prevent programs from running on older hardware, which was a common misconception. Eventually, Visual C++ just ignored the G3, G4, and G5 switches and the program compiled as if G6 had been given to the compiler. I'll say more on what changed with these switches below.

Choosing what to do and getting it done
Throughout this process, I acquired a reputation for not liking switches. A running joke in the Visual C++ product team was to suggest adding a switch to me. To be honest, the reputation wasn't quite deserved. I just challenged the notion of adding a switch with its necessity. To many times, switches were added to the compiler to cover over other problems. In fact, that's par for the industry. Most compilers offer dozens of switches to disable particular optimizations because they break code. That's lazy – the programmer using the compiler has to understand the impact of an optimization and even if he does, he's likely to overlook something. The Visual C++ team has a much higher aspiration to make the compiler useful to every application and systems programmer on Windows, not just geniuses with intimate knowledge of every version of the compiler.

All that said, I started evaluating changes by doing none else than reading the source code for the compiler driver. Through that I came across obsolete, outdated, bizarre, undocumented, and useless switches. I looked at each one asking whether it was necessary for the compiler in the long term and evaluating each switch against the requirements listed above. I actually spent most of my time trying to figure out what each switch did. Even asking developers who work on the compiler, I'd sometimes get several different answers. In a few cases, no one knew what the switch did. If our own team couldn't recall a switch's purpose, it's not hard to believe nearly every programmer using Visual C++ will have the same problem.

Out of this process came the first list of changes to the switch model. Team members were given opportunity to comment along with the Visual C++ MVPs. Through much discussion, the list was narrowed to what we originally implemented. Early usage of the compiler in alphas and the first beta yielded feedback that made us reconsider some of the changes. In several cases we reverted changes due to direct feedback.

Before I go into more detail about some of the specific changes, a primary concern was ease of migration. The most noticeable impact of a change like this is breaking a build. Fortunately, additional warnings about compiler switches usually come from the compiler driver cl.exe instead of the compiler itself (one of c1.dll, c1xx.dll, or c2.dll). This is usually noticeable by the message number, Dxxxx instead of Cxxxx. As a result, the WX switch does not cause a build failure when the driver warns about an unrecognized or deprecated switch.

Now let's look at some of the specific changes...

Optimization switches
As I mentioned earlier, the G-series of switches offered two problem points. First, many customers confused the meaning of these switches, thinking that it was necessary to throw G4 to make the compiler produce code that could still run on a 486 computer. Second, the switches tended to stay in make files well past the intended period of time. In recent versions of Visual C++, the G3, G4, and G5 switches were just ignored.

Since G6 and G7 were the only two that made a difference, the natural question was what the difference between these two were. It turns out there was very little difference, and there were ways to generate code that resulted in great performance on both Pentium III and and all the new processors coming from both Intel and AMD. The switches weren't really necessary. So, we removed them and reduced the testing matrix at the same time.

So far I've taken an x86 focused view of the world. On IA64, there are significant differences between first generation processors and the following generations. IA64 compilers continue to have the G1 and G2 switch because the usability problems haven't shown up on IA64, partly because the market of developers is relatively tiny compared to x86.

On x64 platforms though, we tried at first to avoid generating different code between compilers. In the end, we were compelled to support specific processors. To avoid any confusion, rather than continue with the G-series of switches, the x64 compiler has the favor:AMD64, favor:Pentium4, favor:EM64T, and favor:blend switches. It should now be clear that throwing one of these switches still allows the program to run on other processors, but likely with weaker performance.

Certainly, I would have preferred to see the same switch set on all compilers. That hasn't happened yet, and it hasn't been necessary to drive that idea to the top of my priorities.

Regarding other optimization switches, I did a cognitive walkthrough of the optimization settings shown in the cl /? results. These were some of my thoughts:

  • Ox and O2 are almost identical. They differ only in the fact that O2 also throws GF and Gy. There is almost no reason to avoid throwing these two switches.
  • It is difficult to know that Os and Ot are ineffective.
  • Using Og means that Oy Ob2 and Oi are not thrown, which is usually only useful for debugging purposes – it is not the starting point for a release build, and it's better to use pragmas for this level of control rather than changing the build system.

At the time, I wanted to converge the optimization settings so that multiple switches resulted in the same behavior. This would allow build scripts to continue working without changes, but would both reduce the test matrix and improve default compiler switch selection. This is what I had proposed converging to:

  • Os: now turns on optimization, favoring size (also throws Oy Ob2 GF)
  • Ot: now turns on optimization, favoring speed (also throws Oi Oy Ob2 GF)
  • O1: has exactly the same meaning as Os
  • O2: has exactly the same meaning as Ot
  • Od: disables optimization (also throws Oi- Oy- Ob0 GF-)
  • Ox: has exactly the same meaning as Ot
  • Remove the Og switch

If you're using Beta 2 of Visual C++ 2005, you'll see that this convergence of optimization switches did not happen. That's mostly because we didn't have enough time in the schedule to do so. Despite known usability issues with optimization switches, we don't currently have plans to complete this part of the switch reduction specification in the next release.

Buggy features
It may come as a shock, but some of the features we've released in Visual C++ haven't had the level of quality that we wish it would have. The most notable of all is automatic precompiled header (PCH) files. The YX switch was used to tell the compiler to automatically select and create a PCH file. Developers would use this to speed up a build, but in practice it slowed the build down. Only in a few test cases did it benefit the build time. Given that information, we decided to remove the switch. If you do want to use a PCH, the Yc and Yu switch still exist and, when used correctly, they will dramatically improve build times.

There were a few other undocumented switches left over from experiments that never showed results. We took the opportunity to at least deprecate these switches.

I also listed the Wp64 switch to be deprecated and turned on by default, which doesn't appear to have happened. Overall, the Wp64 switch is no longer necessary. Visual Studio 2005 now includes 64-bit compilers. Compiling code with a 64-bit compiler yields accurate warnings and errors, whereas the Wp64 option only yielded approximations and in many cases had false positives and false negatives.

CLR switches
Visual C++ 2005 introduces at least three new switches for CLR modes. All told, we have the following CLR modes:

  • clr-: This corresponds to not using CLR functionality at all. It is the default.
  • clr: This tells the compiler to enable CLR funcationality, using the new syntax, and to produce a mixed executable image (one that can contain both machine code and MSIL). Object files generated from this mode can be linked with object files compiled with the clr- mode.
  • clr:oldSyntax: This tells the compiler to enable CLR functionality, using the old managed syntax, and to produce a mixed executable image.
  • clr:pure: This tells the compiler to enable CLR functionality and to produce a pure executable image (one that contains only MSIL).
  • clr:safe: This tells the compiler to enable CLR functionality, to produce a pure executable image, and to only allow verifiable source code through the compiler.

I'd say selecting the names for these switches was the hardest part of the process. I heard concerns that "old syntax" could be insulting to geriatric constituents. That was perhaps the highlight of the conversation. The "safe" switch had equally heated debate as concerns would be raised that Standard C++ isn't safe. The choice of "clr:safe" was for consistency with other .NET languages, which typically have an "unsafe" switch. "pure" has similar subjective concerns. Of course, it's too late to change the names now... we'll all have to live with the ones listed above.

Each of these switches override each other, so it's not possible to mix these modes (for example, old syntax and verifiable code cannot be mixed). In previous releases, there was also the clr:noAssembly switch. In reality, that should have been a linker switch since the compiler generates object files, not assemblies. Thus, in Visual C++ 2005, the LN switch replaces clr:noAssembly.

The other CLR switch, clr:initialAppDomain, was originally scheduled to be removed from Visual C++ 2005 since it was there for compatibility with CLR 1.0. It turns out that there are actually useful things one can do with this switch with CLR 2.0. We discovered that late in the feedback cycle, so the switch was left alone. Ideally, it would have been replaced with a switch starting with G since it affects code generation.

The introduction of three new modes does add testing burden as the matrix of test combinations now quadruples. In reality, we needed to focus testing on all the CLR modes to bring it up to the same level of quality as unmanaged code. One of the first things we noticed was that C code compiled with any CLR option made very little sense. C doesn't have namespaces which makes it obviously unable to access any of the .NET Frameworks. Compiling C code with the clr switch was nothing more than an interesting experiment... an experiment that unnecessarily expanded the test matrix. Thus, we made Tc and TC conflict with all of the CLR modes.

Conformance switches and default behavior
After the work in Visual C++ 2003 to support more Standard C++ features, a common complaint was that standard conforming code that should compile and run did not. It almost always came down to a particular switch was not thrown. To that effect, there were semantic differences between strict compliance mode Za and the extensions mode Ze. I'm a strong believer that different modes in a compiler should at most add and remove features from the language – they should never change the semantics of the language. Here are examples of where that principle is violated in Visual C++ 2003:

  • Compiling with and without Zc:wchar_t changes the meaning of the wchar_t type, which impacts overload resolution, name decoration, and binary compatibility.
  • Compiling with and without Zc:forScope changes the meaning of the iterator variable in a for loop.
  • Compiling with GX- verses EHs determines whether stack allocated C++ objects will have their destructors called in the event of an exception.
  • Compiling with and without J changes the meaning of char.
  • Compiling with vd0 instead of vd1 breaks binary compatibility and changes the capabilities of virtual inheritance.

Even when we were almost doing the right thing, as in the case of GR (enable dynamic type info), Visual C++ had the wrong default. In the past, the compiler defaulted to no dynamic type info. The result of all of this is that Visual C++ wasn't standards conformant out of the box. You had to set a handful of compiler switches to get top notch conformance with the C++ standard. Unfortunately, whenever you set all these switches, many libraries no longer compiled.

So, while Visual C++ 2005 hasn't been on the bleeding edge of adding dozens of new standard features, we have been getting more and more libraries to compile cleanly with a mostly conformant mode. Visual C++ 2005 now makes the following switches on by default: GR, Zc:wchar_t, Zc:forScope, fp:precise, and GS. The one switch I wish we had made a defult but we didn't get to was EHsc (enable conformant exception handling). Because some new defaults certainly break code, new switches were added to override them, including GS-, Zc:wchar_t-, and Zc:forScope-. Some of these new switches are deprecated off the bat since you are better off fixing code than just ignoring problems.

At the same time, I have problems with the Za switch. At first, it makes some sense, but very few people actually write the entire program in the restrictive subset that is Standard C++. More often, they want parts of the program like the core engine to be standard compliant. Just as using Wp64 was a bad solution to diagnose portability of code between 32-bit and 64-bit, using Za to diagnose portability between Visual C++ and other compilers is a truly bad model. If you care about portability, you'll compile your code with multiple compilers on a regular basis. The Ze switch turns out to be completely unnecessary because it is the default mode of the compiler and it is not possible to override the Za switch. Thus, the Ze switch is deprecated in Visual C++ 2005. In the long term, I hope that the default mode will be able to compile any code that Za can with exactly the same behavior, thus making Za unnecessary. For ease of diagnosing non-standard extensions, the warning families feature I spoke about earlier is a far better solution than the restrictive Za mode.

Truly evil switches
As I mentioned in the introduction, the Gf switch is particularly bad since it pools string literals into a writable section of memory. C programs are allowed to mutate string literals, so a program that pools two unrelated string literals can have unexpected results when a string is mutated. Visual C++ 2003 deprecated this option, and it is now removed from Visual C++ 2005. C++ isn't affected by this change to the same degree that C is because C++ specifies that string literals shall not be mutated, and thus the compiler always allocates them in read-only memory.

Another switch that just should never be used is H. This switch placed a maximum length on external names, which was achieved by just truncating the names. The result is that multiple entities could have the same name. Even more, undecorating the names in the debugger was impossible. Really, nothing good could come out of using the H compiler switch, so it was deprecated.

Yet another example are the Oa and Ow switches. These switches gave the compiler the freedom to make assumptions about memory aliasing that were often untrue. As a result, the optimizer made an optimization that broke programs in subtle ways. Very few programs could actually work with these options, so they were both removed from Visual C++ 2005.

Switches that had customer feedback
Originally, we deprecated the J switch. The switch changes the meaning of char to mean unsigned char instead of signed char. A program that always specifies signed or unsigned in front of char would never be affected by this compiler switch; however, nearly no program does. System headers should be vigilant and always specify whether char is signed or unsigned in source code. That again is where warning families can provide a solution. Anyways, we deprecated the switch understanding that is probably could never be removed (it can be useful in migrating source code from other operating systems). After a while, the feedback on this one compiler switch became so overwhelming that we're leaving J alone.

Another set of switches that had similar, if not thundering feedback, are the vd switches. These control the binary layout for objects to enable virtual inheritance. I personally find these switches quite distasteful because it prevents code from interoperating, but we had feedback that a particular virtual inheritance scenario didn't work. The only solution given the binary format we had was to introduce the vd2 compiler switch. So, in my opinion, the situation got even worse, but I don't see a better solution. C'est la vie.

Switches that had replacements
In several cases, switches have been replaced by a group of switches. A good example is the GX switch which ended up being replaced by EHsc when all the EH switches were added. GX stuck around for a while, but it is finally deprecated in Visual C++ 2005.

Another case of this happening are the floating-point switches. In the past, Visual C++ used the Op switch to limit the freedom the optimizer had with floating-point code generation. Now that Visual C++ 2005 has much more granular control over floating-point with the fp:precise, fp:strict, and fp:fast switches, Op was no longer necessary. Thus it was removed from Visual C++ 2005.

Switches that were obsolete
Some switches have just been around so long that they cannot possibly be useful anymore. One such switch is the G3 switch, as favoring 386 processors with the latest version of Visual C++ isn't going to happen. Another example is the QIfdiv switch... processors that don't have the Pentium division bug have long been dominant in the market. Not to mention, the GM switch is no longer necessary, since enabling MMX instructions in the compiler is no longer useful these days. A long forgotten switch from the Windows perspective was GD which enabled specific optimizations for DLLs. It turns out those optimizations weren't limited to DLLs, and the switch has been unnecessary for years.

Bizarre switches
Sometimes problems are over-designed and under-implemented. One example of this is the nologo- switch. It's meant to force the product banner to show. Different versions of the product ignore it though.

Some other bizarre switches are the TO and To switches, which tell the compiler to consider unknown files as object files. It would be better to pass these files to the linker rather than the compiler, especially if compiling with the c switch. These are deprecated in Visual C++ 2005.

Another switch that is both bizarre and wrong is one that we should have gotten rid of years ago. The switch remained undocumented for a reason – only "bozos" would use it. In fact the name of the switch meant bozo alignment, which kept the alignment of local variables the same as if it had been declared in a class; basically preventing the optimizer from reordering local variables. Fortunately, we were able to correct all the code that used bozo alignment and removed the switch from Visual C++ 2005.

The single-threaded CRT
Reducing the test matrix is really important to Visual C++. In the past, it has taken more than six weeks to complete a full test pass. In Visual C++ 2005, we haven't really made huge cuts to functionality (i.e. the test matrix hasn't gotten any smaller), but we've made a few attempts. One such attempt is removing the single-threaded CRT. As a result, the ML and MLd switches needed to be removed.

The output of cl /?
Mostly command-line users would be impacted by the results of cl /?, but they're more likely to use that to guide switch selection. Many of the changes I wanted to see happen in the help results didn't actually happen. It's too bad, but I've been busy driving the language design effort. First, I wanted to make the results shorter and leave a more complete listing for the official documentation. That would at least leave the really necessary switches (and better defaults) for command-line users.

Second, I wanted to fix some of the text. If you read the description of the GA switch, it says "optimize for Windows Application". Who wouldn't want to do that? After all, Visual C++ only targets Windows. Well, it actually impacts thread-local storage and the switch is safe for EXEs and not DLLs. I would have replaced the description with something like "generate TLS accesses for EXEs".

In the long term, many people have suggested having more in depth help available at the command-line. Now that Visual C++ has support for localizable text on the command-line, that's a more likely reality. Much will depend on the feedback customers gives us over the coming years.

Conclusion
I started writing this because the first comment I got on my last post asked about the G5, G6, and G7 switches. I certainly ended up writing much more, and the sad part is that I could write way more than I already have. The experience of investigating compiler modes and implementing the changes that actually did happen was a huge learning experience for me. I'm still learning about customer experiences due to changes like this. As I learn more and the rest of the Visual C++ team learns more, we'll make even better design decisions in upcoming releases. So, if you do have feedback, send it my way. J