Compiler Switch Changes in Visual C++ 2005


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


Comments (10)

  1. Anonymous says:

    Regarding Zc:wchar_t – I have some code that’s breaking becuase of the change in defaults with this switch.

    Code that pre-VC 2005 relied on wchar_t to be an unsigned short to get overloads to resolve no longer compiles.

    That’s reasonably easy enough to fix (so far)by adding a new explicit override for wchar_t. However, after doing that the code breaks when compiled with the compiler since there are now 2 overrides for unsigned short.

    Is there a good way to conditionally compile the wchar_t overloads? I tried using _WCHAR_T_DEFINED and _NATIVE_WCHAR_T_DEFINED but they’re both defined in both compiler cases.

    I’m currently using #if (_MSC_VER >= 1400) which seems to be working, but it doesn’t feel like the best test.

  2. Anonymous says:

    On a current product using VS2003, I’ve been using the /Fx switch to show me the code injected by attributes. I looked at the VS2005 Beta2 to see if this has been improved, but it has not. I’m sure it’s too late in the dev cycle, so please add this suggestion to the next cycle: Let me specify an output directory for the generated files. Perhaps, /Fx:objRelease, could be the syntax.

  3. Anonymous says:

    If nothing else has come out of this article, then it’s me learning something about the different compiler switch options. I never knew that the /G switches didn’t affect the ability to run the code on older platform, just the optimisations done for them.

  4. Anonymous says:

    Originally, we deprecated the J switch. The

    > switch changes the meaning of char to mean

    > unsigned char instead of signed char.

    I hope you were just writing too tersely, but in case you weren’t, and for the benefit of anyone else reading, the meaning of char never means unsigned char or signed char. Plain char always remains a separate type. The range of values of plain char matches either the range of values of signed char or the range of values of unsigned char, and it’s OK for a switch to alter the meaning of plain char as to which range of values it will get, but the type still has to remain a separate type.

    At a former employer, I spent a few weeks fixing a program to use unsigned char where it needed unsigned char, and to make its remaining uses of plain char work properly regardless of which range of values plain char would get in any implementation. A colleague tried to persuade me to use a compiler switch instead. In a sense my colleague was right: when the customer says what the customer wants, the customer is right about what the customer wants. My result worked equally well and was safer for the code but wasn’t safe for my career.