I've been producing a fair amount of source code recently and have become convinced that using two compilers during the process is well worth it and that the normal way that I set up conditionals is flawed.
Two compilers
I've been a Studio fan for all of my time at Sun (through all of the name changes), especially since that was all that the Solaris group used until OpenSolaris was released. Two recent events have made me decide that compiling and testing with gcc in addition to Studio is worth the effort.
While I was working on the code for the stack blog, I decided to try gcc and it flagged something that Studio did not. One of the interfaces I used was umem_cache_create whose first argument is a char *. I was building it from a structure which contained something such as this:
struct foo {
...
char *name;
...
}
initialized this way:
struct foo names[] = {
{ ..., "32 KB stack cache",... },
};
gcc caught the possibility that a compiler might choose to put "32 KB stack cache" into a read-only section of the program and that a potentially read-only datum was being passed to a routine that was not guaranteeing to preserve the data. As I understand the standards, umem_cache_debug() should use "const char *" to indicate that the data passed in won't be changed.
The previous reason is a bit esoteric but the example that really turned my head was when I was overflowing a buffer. How I did it doesn't really matter but a buffer on the stack was overflowed. With Studio's data allocations, I never saw any side effects from doing so. Everything seemed to work just fine with my test cases. When I compiled with gcc, it laid out the data differently and I was able to see that something was going wrong, find the problem and fix it. gcc never told me directly that I was overflowing a buffer but the different data layouts did allow me to see the problem.
Whether you prefer gcc or the Studio compilers, it's worth your time to compile and do testing with the other compiler. You may find things that you never suspected in that code you thought was working perfectly.
In gmake, it's quite trivial to set up your make file to use a different compiler. I use something such as this:
CC = cc
OBJECTS = stacks.o
CFLAGS = -g -xO3 -K pic -I. $(XCFLAGS)
LDFLAGS = -znodefs -G -o $(REALLIB) -h $(LNTARGET) -lumem
ifeq ($(CC),gcc)
CFLAGS=-g -O2 -fpic -std=c99 -pedantic -Wall
LDFLAGS=-shared -fpic -o $(REALLIB) -lumem -lc
endif
This uses Studio by default but I can say gmake CC=gcc and build with gcc.
I haven't figured out how to do this with make yet.
Conditionals
I've always structured my conditional tests of the form:
if (rv == 0) ...
I've now been bitten a few too many times by
if (rv = 0) ...
which compiles just fine but has interesting results.
if (0x0 == rv) ...
If you mess this up by forgetting one of the '=' signs, the compiler now flags the error for you. gcc's version:
error: invalid lvalue in assignment
It looks a bit strange if you've had the classic K+R wired into your finger tips but it does make life much, much easier.
I do see that gcc with a -Wall will delicately tell you
suggest parentheses around assignment used as truth value
which may help to avoid the issue, although I much prefer lint's message:
warning: assignment operator "=" found where "==" was expected
Studio just lets you do it and gcc without the magic flag to catch it will let you do it as well. lint flags the error but it seems not to be used as much during development and test as it should. I certainly don't run it every time I compile.