Boris Bauer wrote:
Are there any good pages on portable C/C++ code (including library and
system specific compilation dependencies)? Maybe even a tutorial by any
of the SDL pro’s?
I’ve been doing a little bit of portability stuff (unofficially) here at
Creature Labs.
As I go, I’m trying to put together a list of portability guidelines.
We’re using C++, primary platform is win32, Visual C/C++. I’ve been
doing stuff with the latest gcc versions under Linux. MacOS, OSX,
Playstation 2, Codewarrior are also platforms/tools of interest, but I
haven’t done any serious research on them.
Here is what I’ve come up with so far - I’d love it if people had
items to add
IFDEF USAGE============
Generally, it’s a good idea to switch on feature ifdefs rather than
platform ifdefs. Some examples:
- use BIGENDIAN where appropriate instead of MACOS.
- instead of #ifdef LINUX, try using #ifdef POSIX - most calls you do
under linux are general enough to apply to other unix-based systems (and
most probably OSX)
I generally use a project or library-specific prefix for these #defines.
Don’t mix up compiler defines and platform defines.
eg:
#ifdef _MSC_VER
#pragma turn-off-pesky-warning-messages
#endif // _MSC_VER
Note that the compiler symbol (_MSC_VER) is used, NOT the platform
define (_WIN32).
COMPILER DIFFERENCES
Visual C/C++ will let you get away with a lot of stuff it probably
shouldn’t.
Visual C does have a ‘strict’ option buried away somewhere, but it is
left off by default to allow MFC code to compile. Hmm.
In general, using multiple compilers is a good thing for ensuring you
write standard code.
Some specifics:
Recent versions of gcc seem ok with regards to templates and exceptions
(it’s traditional weak points). It does produce big debug executables
though - one project I have produces a 50 meg exe under gcc vs about 6
meg with VC. I suspect this is due to template usage.
Invalid comparisons eg:
unsigned int foo = getvalue();
if( foo == -1 ) …
gcc will point out that the ‘if’ statement condition is invalid
(because foo is unsigned) but visual C seems to let it through OK.
ANSI requires that you provide an explict cast when assigning to
a pointer type from a void*. VC seems a more casual about this than gcc.
gcc is more strict on casting.
eg: int32 is not automatically cast to int.
typedef unsigned long int int32;
int32 x=5,y=10;
Vector2D v(x,y);
If Vector2D provides Vector2D(int,int) or Vector2D(float,float), gcc
won’t automatically assume you mean the integer one.
Q: Is this ansi or just gcc?
Later versions of g++ (I.E. X-mas '99 onward are very strict with regard
to const references for some reason - this is a pervasive change)
gcc (2.95.2) bugette:
bool wibble()
{
printf(“Wibble!\n”);
}
gcc doesn’t seem to mind the lack of return value.
(Maybe -Wall option would help?)
gcc (at least without -Wall) doesn’t seem to pick up when you use an
uninitialised var.
eg:
int i;
switch(foo)
{
case 0: i=0; break;
case 1: i += 10; break // !!!
case 2: i = 42; break;
}
compiles without warning…
std::string being passed in to va_start() for printf-style formatting.
eg:
void foo( std::string fmt, … );
gcc warns you that fmt is a string and that you can’t pass strings as
varargs. VC lets it through.
The code would probably be OK if you use fmt.c_str() in the
vsprintf() line.
Visual C has some very non-ansi behaviour when it comes to variables
declared in for loops, eg:
for( int i=0; i<10; ++i ) {…}
Ansi states that i is only in scope for the duration of the loop, but VC
leaves it in scope.
C++ LIBRARY STUFF
Quite often a dodgy bit of code will work fine under one std C++
library/STL implementation, but will fall over badly on another. Those
libraries are generally pretty specific about what you can or can’t
assume about their implementation.
For example, keeping raw pointers into std::string objects.
Dodgy because the contents can be reallocated and copied about by the
implementation.
In general, any pointers obtained using data() or c_str() should be
considered invalid after any other operations on the string object.
(what does ansi say?)
The C++ library standard has only recently been finalised. Many (all?)
compilers come with libs which don’t adhere exactly to the standard for
one reason or another.
Eg:
std::stringstream isn’t currently available under the bog-standard gcc
distribution.
The VC std c++ headers have some problems with max and min due to
clashes in the windows header files.
GENERAL
Don’t use lightweight wrapper classes to wrap platform specific APIs -
instead try to abstract the
interface out to platform independence.
Filename case sensitivity can be a fiddly problem. win32 doesn’t care
about case (although I think you can set up NTFS to be case sensitive),
but most unix-based systems do. Using all lower-case filenames is
probably a reasonable solution.
Also, path separation characters differ - win32 uses ‘’ and ‘:’, others
use ‘/’, mac uses ‘:’ (I think).
Win32 also supports ‘/’ in most cases. Not sure about MacOS.
File security is pretty much non-existant on most target win32 systems
(except NT). Unix-based systems are a lot more strict. You can’t always
assume you’ll be able to open a specific file for writing. eg if you’ve
installed you game data files anywhere other than the users homedir
chances are that it’ll be read-only access.
Make sure all your #include paths are the correct case (ie #include
<stdio.h> instead of #include
<StdIO.h>) and use forward-slash (’/’) instead of back-slash (’’) path
separators.
And of course, C++ is still a bit of a moving target as far as
portablity goes - sticking with ANSI C is good if you can get away with
it
Phew.
Hope all this is of interest to someone! Feedback most welcome.
BTW anyone else in this newsgroup going to GDC in march?
Ben.
–
Ben Campbell (Antipodean Straggler)
Programmer, Creature Labs
ben.campbell at creaturelabs.com
www.creatures.co.uk