Migrating from VC6 to VC9
|
A year ago, my company started the migration path of moving from Visual C++ 6.0 to Visual Studio 2005 (VC8). This was mainly done because after Vista was released, it was disclosed that VC6 had limited support on Vista. If and when our development computers needed to be upgraded, XP may have limited availability. So we decided then that moving to the newer compiler was pretty much required.
The problem is that, truthfully, migrating compilers is not something customers care about nor will pay for. So when you have all your developers dedicated to new features and bug fixes, compiler migration always seems to take a back seat.
So throughout 2007, I put in cycles where I can to ensure our code base compiles using VC8. However, in 2008, Visual Studio 2008 (VC9) was released. Since we had not moved to 2005 yet, I decided to take the extra jump to 2008.
In February of 2008, I had all the developers start doing their development using VC9. However, I continued to build our Alpha, Beta and Release builds using VC6. This required some redundant work with projects and some fixes which worked with VC9 that would not work with VC6.
In April, I made the switch for our Alpha builds and later for the new Beta and Release builds to VC9. This went as well as could be expected.
Below are some of the more common issues that we faced.
1. For loop variable scope differences
In VC6, the following is valid:
for (int i = 0; i < 10; i++) { } _tprintf(_T("%i/n"), i);
However, in VC9, by default, the above code is not valid. Instead, the variable i is local for the for loop and cannot be referenced outside the loop. To maintain similar functionality, we modified our code to be like the following:
int i = 0; for (i = 0; i < 10; i++) { } _tprintf(_T("%i/n"), i);
There is a project setting which we could have set to allow the old behaviour. But I decided to keep the default and modify our code. I think it's better and, IMO, makes more sense.
2. STL iterators behave differently
In VC6, the following used to be valid:
std::list<int> mylist; mylist.push_back(5); std::list<int>::iterator it = mylist.begin(); // (*it) == 5 it++; // it == mylist.end() it++; // it still == mylist.end()
Now, when debugging, the second ++ will produce an assertion failure and release will crash. Some of our algorithms needed to be updated because they were taking advantage of the fact that you could iterate past end() and still have end().
Another change is the fact that you cannot -- again begin(). Example:
std::list<int>::iterator it = mylist.begin(); it--; // would go one before begin()
In hindsight, this is kind of silly, but if the libraries support it, why not use it? We used this because std::list::erase() only takes an iterator, not a reverse_iterator, so to get around this, we could use an interator and work backwards through the list deleting as we went along until we got to the element "before" begin().
After migrating, this also needed to change.
As it turns out, I think the STL changes showed us exactly where many of the problems and crashes were in our code base. For a long time, I felt that many crash reports we were getting were related to this code, but it would never crash here. Instead, it invalidated some data somewhere and would crash later on making debugging, especially remotely, nearly impossible. These changes ensured that improper use of STL would crash the software then and there. At first, we faced many crashes in our alpha builds. But after time, the code base became rock solid.
3. Some 3rd-party libraries need to be recompiled
We were using some 3rd-party libraries such as libpng, zlib, libxml. These DLLs needed to be recompiled since they were initially compiled using MSVCRT, but now must use MSVCR90. Not a big deal. Just recompile and re-link.
4. Data type size changes
Some data types changed sizes. For example, time_t moved from 4 bytes to 8 bytes. We cared about this because we were writing some time_t values to file as a 4 byte integer. We had to change our code to use __time32_t and time32() instead. It's possible to set some defines to force time_t to continue to be 4 bytes, but we decided to embrace the change.
5. "Safe" C-library functions
Microsoft added some "safe" versions of many functions such as strcpy(), makepath(), etc. They called them _strcpy_s() and _makepath_s() respectfully. When compiling the old code, many warnings were displayed about using the "unsafe" version and that the "safe" version should be used instead. Again, there is a define that could be set to suppress this warning, but instead I decided to embrace the change. This one I actually liked. In order to accomplish this, I created my own set of these same functions. When compiling against VC6, we linked to my custom versions. When compiling against VC9, we linked to Microsoft's. After it was done, it was painless to maintain.
After implementation, I feared that we'd get many assertions about improper use of the standard C-library functions. However, we've not had one incidence of an issue because of this. I think this is a fantastic change, but it did not help us in actuality.
6. Previous posts related to our migration
Looking back, the biggest hit to us was the STL changes. However, the way they were changed forced our code base to improve and become more solid. For this reason, I accept the changes.
Migrating wasn't easy. But now that I've migrated, I'll never go back to VC6 unless absolutely required.