The Great C Runtime (CRT) Refactoring

The Great C Runtime (CRT) Refactoring

Rate This
  • Comments 55

(This is the first of two articles on changes to the C Runtime (CRT) in the Visual Studio "14" CTP. This article discusses the major architectural changes to the libraries; the second article will enumerate the new features, bug fixes, and breaking changes.)

For the past seven releases of Visual Studio (2002, 2003, 2005, 2008, 2010, 2012, and 2013), the Visual C++ libraries have been versioned and each versioned set of libraries is independent of other versioned sets of libraries. For example, a C++ program built with Visual C++ 2010 using the DLL runtime libraries will depend on msvcr100.dll and msvcp100.dll, while a C++ program built with Visual C++ 2013 will depend on msvcr120.dll and msvcp120.dll.

On the one hand, this model of introducing differently named and completely independent sets of libraries each release makes it a bit easier for us to add new features and fix bugs. We can make breaking changes, e.g. to fix nonconforming or buggy behavior, at any time without worrying about breaking existing software components that depend on already-released versions of these libraries.

However, we have frequently heard from you, our customers, that this model is burdensome and in some cases makes it difficult to adopt new versions of Visual C++ due to dependencies on modules built with an older version of Visual C++ or the need to support plugins built with a particular version of Visual C++.

This problem has grown especially acute in recent years for two reasons. First, we have accelerated the release schedule of Visual Studio in order to make new features available more frequently. Second, it has become very important to support devices smaller than desktops or laptops, like phones, and accumulating multiple copies of very similar libraries on such devices is less than ideal.

Even for us, this model of introducing new versions of the libraries can be painful at times. It makes it very expensive for us to fix bugs in already-released versions of the libraries because we are no longer actively working in the codebases for those versions, so fixes must be individually backported and tested. The result is that we usually fix only serious security vulnerabilities in old versions of the libraries. Other bugs are generally fixed only for the next major version.

We can't fix the past: the versions of these libraries that have already been released are not going away. But we are going to try to make improvements to this experience for the future. This is a major undertaking and will take some time, but we plan to make gradual process, starting with...

The Refactoring of the CRT

The CRT sits at the bottom of the Visual C++ libraries stack: the rest of the libraries depend on it and practically all native modules depend on it as well. It contains two kinds of stuff: [1] the C Standard Library and various extensions, and [2] runtime functionality required for things like process startup and exception handling. Because the CRT sits at the bottom of the stack, it is the logical place to start the process of stabilizing the libraries.

Starting with Visual Studio "14," we will stop releasing new versions of the CRT with each release of Visual Studio. Whereas before we would have released msvcr140.dll in this forthcoming release, then msvcr150.dll in the next release, we will instead release one new CRT in Visual Studio "14" then update that version in-place in subsequent releases, maintaining backwards compatibility for existing programs.

We are also working to unify the CRTs used for different platforms. In Visual Studio 2013, we built separate "flavors" of the CRT for different platforms. For example, we had separate CRTs for desktop apps, Windows Store apps, and Windows Phone apps. We did so because of differences in which Windows API functions are available on different platforms.

In Windows Store and Windows Phone apps, only a subset of the Windows API is available for use, so we need to implement some functions differently and cannot implement other functions at all (for example, there's no console in Windows Store and Windows Phone apps, so we don't provide the console I/O functionality in the CRT). The CRT for desktop apps must run on all supported operating systems (in Visual Studio 2013 this included Windows XP) and must provide the full set of functionality, including legacy functionality.

In order to unify these different CRTs, we have split the CRT into three pieces:

  1. VCRuntime (vcruntime140.dll): This DLL contains all of the runtime functionality required for things like process startup and exception handling, and functionality that is coupled to the compiler for one reason or another. We may need to make breaking changes to this library in the future.

  2. AppCRT (appcrt140.dll): This DLL contains all of the functionality that is usable on all platforms. This includes the heap, the math library, the stdio and locale libraries, most of the string manipulation functions, the time library, and a handful of other functions. We will maintain backwards compatibility for this part of the CRT.

  3. DesktopCRT (desktopcrt140.dll): This DLL contains all of the functionality that is usable only by desktop apps. Notably, this includes the functions for working with multibyte strings, the exec and spawn process management functions, and the direct-to-console I/O functions. We will maintain backwards compatibility for this part of the CRT.

While I've named the release DLLs in the list, there are also equivalent debug DLLs and release and debug static CRT libraries for each of these. The usual lib files (msvcrt.lib, libcmt.lib, etc.) are built such that the newly refactored CRT is a drop-in replacement for the old CRT at build-time, so long as /nodefaultlib is not used.

While we have retained the version number in the DLL for this CTP, we plan to remove it from the AppCRT and DesktopCRT before the final release of Visual Studio "14," since we will be updating those DLLs in-place. Finally, we are still working on the final packaging of functionality, so we may move things among the DLLs before the final release.

Windows Store and Windows Phone apps will be able to use the functionality from the VCRuntime and the AppCRT only; desktop apps will be able to use all of that functionality plus the functionality from the DesktopCRT. In this first Visual Studio "14" CTP, all apps depend on all three parts of the refactored CRT; this is merely a temporary state of affairs that will be fixed eventually.

The Problem of Maintainability

One of the biggest problems that we had to solve in order to consider stabilizing the libraries in this way was the problem of maintainability. The CRT is a very old codebase, with many source files dating back to the 1980s. In many parts of the code, optimization techniques that were valid and useful decades ago not only obfuscated the code and made it difficult to maintain, but also hampered the modern compiler's ability to optimize the code. In other areas, years of bolted-on features and bug fixes had turned once-beautiful C code into a hideous maintenance nightmare. If we were going to consider stabilizing the libraries so that we could update them in-place, we'd need to improve the maintainability first, otherwise we'd incur great cost to fix bugs and make improvements later.

The "best" example of this maintainability problem could be found in the old implementation of the printf family of functions. The CRT provides 142 different variations of printf, but most of the behavior is the same for all of the functions, so there are a set of common implementation functions that do the bulk of the work. These common implementation functions were all defined in output.c in the CRT sources(1). This 2,696 line file had 223 conditionally compiled regions of code (#ifdef, #else, etc.), over half of which were in a single 1,400 line function. This file was compiled 12 different ways to generate all of the common implementation functions. Even with the large number of tests that we have for these functions, the code was exceedingly brittle and difficult to modify.

This isn't merely a theoretical problem. In Visual Studio 2013, we added many of the C99 functions that were previously missing (see Pat's blog post from last year). However, there were a number of things that we were unable to implement. Two of the most conspicuous missing features were [1] the snprintf function and [2] the format string enhancements like the z and t length modifiers for the size_t and ptrdiff_t types. It was late in the product cycle when we started looking at implementing these, and decided we simply could not implement them with confidence that we weren't breaking anything.

So, as part of this great refactoring of the CRT, we have done an enormous amount of work to simplify and improve the quality of the code, so that it is easier to add features and fix bugs in the future. We have converted most of the CRT sources to compile as C++, enabling us to replace many ugly C idioms with simpler and more advanced C++ constructs. The publicly callable functions are still declared as C functions, of course (extern "C" in C++), so they can still be called from C. But internally we now take full advantage of the C++ language and its many useful features.

We have eliminated most of the manual resource management in the code through the introduction of several special-purpose smart pointer and handle types. Enormous functions have been split into smaller, maintainable pieces. We have eliminated 75%(2) of the conditional compilation preprocessor directives (#ifdef, #else, etc.) by converting internal implementation details to use C++ features like templates and overloading. We have converted most of the CRT source files to use a common coding style.

As part of this work, we've completely rewritten the core implementations of the printf and scanf functions (now with no #ifdefs!). This has enabled us to implement the remaining C99 features for the stdio library, to improve correctness checks in the library, and to fix many conformance bugs and quirks. Just as importantly, this work has enabled us to discover and fix substantial performance issues in the library.

Before this refactoring, the sprintf functions, which write formatted data to a character buffer, were implemented by wrapping the result buffer in a temporary FILE object and then deferring to the equivalent fprintf function. This worked and produced the correct result, but it was exceedingly inefficient. When writing characters to a FILE we need to be careful to handle many cases like buffer exhaustion, end-of-line conversions, and character conversions. When writing characters to a string, we should simply be able to write through and increment the result pointer. After the refactoring, we were easily able to identify this performance problem and, more importantly, fix it. The sprintf functions are now up to 8 times faster than they were in previous releases.

This is merely one example of where we've done major work and how that work has helped us to improve the quality of the library. In the next article we will enumerate all of the major features, bug fixes, and breaking changes to the CRT in the Visual Studio "14" CTP, similar to what Stephan wrote last week for the STL.

What Next?

We are nearing completion of the CRT refactoring. Undoubtedly there are bugs, and we encourage you to try out the Visual Studio "14" CTP and report any bugs that you find on Microsoft Connect. If you report bugs now, there's a really good chance that we can fix them before the final release of Visual Studio "14." We've already gotten a few bug reports; thank you to those of you who reported them!

We are investigating opportunities for similar stabilization efforts with other libraries. Given that the separately-compiled STL components (msvcp140.dll) are also very commonly used, we are considering our options for a similar stabilization of that functionality.

Note that near-term we are only considering stabilization of the separately-compiled code. We do not plan to make stability guarantees about any C++ Standard Library types or any inline code in the C++ headers. So, for example, if you pass a std::vector to a function, both the caller and callee will still need to be compiled with the same STL headers and options. There are very long-term efforts to try to come up with a solution to this more general problem; for example, see Herb Sutter's recent C++ Standardization Committee proposal N4028: Defining a Portable C++ ABI.

James McNellis (james.mcnellis@microsoft.com)
Senior Software Development Engineer, Visual C++ Libraries


(1) We ship most of the sources for the CRT with Visual Studio; you can find them in the Visual Studio installation directory under VC\crt\src.

(2) In Visual Studio 2013 there are 6,830 #if, #ifdef, #ifndef, #elif, and #else directives in the sources that we ship with the product; in the Visual Studio "14" CTP there are 1,656. These numbers do not include directives in headers and they do include the STL source files which are largely untouched by this refactoring effort, so this isn't a perfect measurement, but it's indicative of the amount of cleanup that's been done.

Leave a Comment
  • Please add 1 and 3 and type the answer here:
  • Post
  • Great job! Very cool.

    One thing you didn't mention that I'm curious about -- what is the behavior when I compile something using a new CRT feature in VS2015, but the user only has VS2014's CRT installed and tries to run the app?

  • Not sure about the new names, shouldn't they begin 'ms'. Makes them more immediately recognisable as Microsoft product.

  • This is very good news indeed. My team was struggling with the zoo of C++ runtime libraries required since time immemorial.

    But what about compatibility of static libraries between compiler releases? As currently stands, it seems Microsoft guarantees forward/backward compatibility within VS release (2010, 2012, 2013, etc.), but there are no compatibility guarantees between compiler versions. There are not even any workarounds (like limiting features used in APIs)  or guidelines to make sure that, for example, VS2013 can successfully build against VS2010-produced .lib.

    In a large org feeding into one binary, this makes VS adoption all-or-nothing deal, slowing it considerably.

  • Removing the version number from the dll name will make it harder to maintain backwards compatibility in the future (from your perspective). From my perspective as a developer I'm not interested in a dll that carries all the baggage from the past. Please reconsider this change.

    On another note I have always wondered why you aren't able to deploy the crt through Windows Update as important updates? It makes no sense that every single application compiled with VS must include the CRT redistributable.

  • What about the deployment story?  Sad as it is, using vcredist seems to be a lingering, but frequent means of deploying the vc runtime (plus MFC et al.), but that has its own headaches (oh- you need the update 4 version, but you only have the update 2 version installed....ugh.)

  • Great work!

    Here's to hoping we will one day see C99 support in full. The compiler seems to have some important pieces already in place in C++ mode. Our project (neovim) uses some C99 features like declare-anywhere, compound literals, VLAs (but we could live without that, only have one in the codebase), automatic cast from void (looks so much cleaner), designated initializers (cleanliness as well) and that's about it. We'd love to see it work with MSVC as well. As it stands, we'll have to get mingw/gcc or clang to compile it, to provide a working version to our windows users. I'll be watching out for news!

  • Any changes related to the "known dll" msvcrt.dll system component that may affect mingw/mingw-w64 toolchain users?

  • Nicolas> declare-anywhere, compound literals [...] designated initializers

    Implemented in VS 2013. See blogs.msdn.com/.../c-11-14-stl-features-fixes-and-breaking-changes-in-vs-2013.aspx from last year.

    > automatic cast from void (looks so much cleaner)

    This isn't a C99 feature. C89 allowed void * to implicitly convert to X *. (C++98 sensibly removed this massive hole in the type system.)

  • "Here's to hoping we will one day see C99 support in full. "

    Please, please, please.

  • awesome, finally, thank you thank you thank you! The missing backwards compatibility always had me release many versions of my library, instead of a single one. I'm so happy when this is over.

  • Thanks for a great write-up!

    I'm wondering functions like _snprintf. _snprintf (not _sprintf) has been there for a long time. So far, I used a wrapper function for simulating snprintf behavior. It looks like _snprintf is a separate implementation. Can you give us more information about _-prefixed functions? Thanks!

  • "In order to unify these different CRTs, we have split the CRT into three pieces:"

    please read that.

  • I wonder how this compares to MSVCRT from the VC4.2/5/6 days.

  • Was this work inspired by the refactoring made in GCC and LLVM?

    It might sound nuts for a Microsoft product but didn't you guys think of using LLVM instead of playing catch up? The license is permissive, you don't have to contribute code if you don't want to and you can use it at your own desire... I think everyone would win from that.

    Btw, I understand you've put a lot of effort in but you should seriously consider this option. The cloud and the server adapted to the reality of the current situation...

  • I agree with Lilian, how awesome would it be to have Visual Studio with clang instead. It seems the clang guys are working on it, but Microsoft is trying to catch up with their own compiler.

Page 1 of 4 (55 items) 1234