Thursday, October 22, 2009

Porting C++

Blowing the dust off this old blog, I notice that I never finished my previous post about vector-based sets. I've been using one for a while, and the performance is great, but unless some reader shows an enormous amount of interest, I'm not going to bother finishing the explanation. Instead, I'm going to complain about porting.

As part of my work, I write cross-platform code. Most of the time, however, I just write in Visual Studio, and every few months we check that everything still compiles and runs in GCC, preferably with zero warnings. Compiling on GCC & Visual Studio isn't everything, but it's a pretty good litmus test. While doing this, I've found a few important "gotchas" where Visual Studio zigs and GCC zags. Here, for your delectation, is my list:

1. Templates. There are so many issues in porting Visual Studio templates to GCC that I'll just brush gently over the top and move on. It's tricky. Visual Studio allows horrible syntax problems within template code, and won't tell you there's a problem until the code is instantiated and used in a fully specified type. GCC is much stricter. GCC also sticks more tightly to the rules in the standard. If you're having problems with a template that worked in Visual Studio, some good tips are:

- Try adding the word "typename" in front of template parameters when they refer to a type. In some rare cases, this can disambiguate the syntax. Visual Studio delays evaluation of the syntax until it can check the symbol table, but GCC requires it to be unambiguous up front.

- Visual Studio will often accept a partial name (i.e. TypeName) where GCC requires a full name (i.e. TypeName< A, B >). Using full names is almost never wrong. This is very often true if you combine inheritance and templates. Also be aware that you probably need to specify the full template name when referring to member variables in a base class, i.e. "BaseClass< A, B >::member_var" rather than just "member_var". Again, there are ambiguity problems in the dark, smelly corners of the C++ language.

2. Newlines at the end of a file. Visual Studio doesn't care, GCC warns you if there isn't one.

3. Initialization order. The C++ standard requires you to initialize member variables in the order they're declared in the class. If there's a mismatch between the initializer list in the constructor and the declaration order, GCC throws a warning at you. Visual Studio doesn't care. Keep the order the same and GCC will be happy. It took me hours to fix all of these last time, and fixing them is a mistake-prone process.

4. GCC is more aggressive in warning you about local variables which were declared & initialized but never used. This is mostly helpful, honestly. It is a bit annoying when it starts picking out file-local (static) variables that are only used in debug mode, though :P. In the end, the workaround for that one was not using a static variable.

5. GCC has a 'uint' type, short for 'unsigned int'. Visual Studio does not. We get around this with an ifdef/typedef block in a common header, since we like uint.

6. The two compilers have different pragmas. #pragma warning does not work in GCC. If you don't want to be warned about unrecognized pragmas, you should wrap #ifdef _MSC_VER around your pragmas (that's a flag to test for whether you're using Visual Studio, roughly). #ifdef WIN32 is not as good, because GCC defines that symbol when you're compiling under Windows ;).

7. Your life will be better if you don't inherit std::exception. If you must, though, ensure that your child class has a destructor with the "throw()" specifier, since the auto-generated destructor has no such specifier and this will cause compile errors in GCC.

8. GCC objects (warning) if you use "switch" with an enumerated type but do not include all possible values of the enumeration in the "case:" statements. Visual Studio doesn't care. If you stick in a blank/useless "default:" case, then all is well.

9. GCC warns about type mismatches if you compare unsigned and signed integers using == and !=. Visual Studio doesn't care.

10. All of Microsoft's secured functions are non-standard (sscanf_s and so on). You can't use them in GCC. Either do it the C++ way (cin, istream, etc.), roll your own (if you're complaining about how slow the C++ ones are, then you should, because you'll probably go faster), or use the unsafe/standard functions and define _CRT_SECURE_NO_DEPRECATE to avoid the idiotic warnings in Visual Studio.

These are a few things I've run into myself recently. Some of them are a bit tricky to figure out, so maybe I shall aid some poor Google searcher :P. Happy porting!

1 comment:

Nate Fries said...

you can also use macros or inline functions to get around missing "safe" functions in GCC. Also, GCC's glibc has many "safe" functions as well, but with different names. Example: sprintf_s and snprintf can be used exactly the same.
#ifdef __GNUC__
#define sprintf_s snprintf
#elif !defined(_MSC_VER)
#define sprintf_s(buffer, size, fmt, ...) sprintf(buffer, size, fmt, __VA_ARGS__)
#endif