You are currently browsing the monthly archive for December 2009.

How many times have you ever legitimately used the comma operator in live C or C++ code? I’ve seen a Boost project use it as convenience notation for small compile-time datasets, but that’s about it. So, here’s an example of an absolutely terrible way to use it (yes… this is how I blow off steam at work):

wtf.c

#include <stdio.h>
#include <string.h>
int main(int argc, char **argv)
{
    return argc > 1 ? !strcmp(argv[1], "foo") ? \
        (puts("foo"),0) : (puts("not foo"),1) : 1;
}

This program prints “foo”, “not foo”, or nothing based on the first argument; then it returns true (UNIX-style) only if the first argument was “foo”. While most language experts will not bat an eye at this, it’s definitely on the Perl side of ugly.

operator, in action

Why was I playing with the comma operator? I was looking at the definition of assert() provided on my system (either part of libc or gcc, I’m not sure). If you disable assertions (-DNDEBUG) you’ll see a null statement like this:

test.c

#include <assert.h>
void test(int x, int y)
{
    assert(x < y);
}

gcc test.c -E -DNDEBUG

void test(int x, int y)
{
    ((void)(0));
}

If you leave assertions enabled, however, you’ll still see a null statement due to the somewhat-creepy comma-operator magic I’ve demonstrated above:

gcc test.c -E

void test(int x, int y)
{
    ((void)((x < y) ? 0 : \
        (__assert_fail("x < y", "test.c", 4, "test"), 0)));
}

So, assert is defined as an expression rather than a statement. That means that you can combine it with other chunks of code in some surprising fashions:

char *short_strdup(const char *str, size_t len)
{
    // don't allow anyone to pass in a null or over-long string
    return assert(str != NULL), assert(strlen(str) < len), strdup(str);
}

long infallible_atoi(const char *number)
{
    char *end;
    long value = strtol(number, &end, 10);
    // validate that there was no conversion error
    //          and that we consumed all input
    return assert(errno == 0), assert(*end == '\0'), value;
}

This is not recommended, however, because assert is not a general-purpose error-handling mechanism. I could imagine this being used as the basis for a hand-rolled assertion mechanisms in a large codebase (you could throw an exception, log an error with a stack trace, or cause monkeys to fly out of the original developer’s nose, for example).

Advertisements

I’ve seen a few people assert the precompiled headers are a pain in the butt, or not workable for large scale projects.  In fact, it’s incredibly easy to add precompiled headers to a GCC-based project, and it can be quite beneficial.

Since I’m mostly familiar with Makefiles, I’ll present an example here that uses Make. It trivially extends to other similar build systems such as SCons, NMake, or Ant, but I’m not so sure about Visual studio projects. This example builds a single static library and several test applications. I’ve stripped out most of my compiler flags for brevity.

# boilerplate settings...
SHELL = /bin/bash
CXX = g++ -c
CXXFLAGS += -std=c++98 -pedantic -MMD -g -Wall -Wextra
LD = g++
LDFLAGS += -rdynamic -fno-stack-protector
AR = ar

# generic build rules
# $@ is the target, $< is the first source, $^ is all sources
define compile
$(CXX) -o $@ $< $(CXXFLAGS)
endef

define link
$(LD) -o $@ $(filter %.o,$^) $(filter %.a,$^) $(LDFLAGS)
endef

define ar
$(AR) qsc $@ $(filter %.o,$^)
endef

# all library code is in src/
# all test applications are single-source
# e.g. testfoo is produced from from testfoo.cxx and libsseray.a
TEST_SRC = $(wildcard test*.cxx)
LIB_SRC = $(wildcard src/*.cxx)
TESTS = $(basename $(TEST_SRC))
LIB = libsseray.a
all : $(TESTS)

$(TESTS) : $(LIB)
$(TESTS) : % : %.o
	$(link)

%.o : %.cxx
	$(compile)

$(LIB) : $(LIB_SRC:cxx=o)
	$(ar)

# gcc-provided #include dependencies
-include $(TEST_SRC:cxx=d) $(LIB_SRC:cxx=d)

clean :
	rm -f $(LIB) $(TESTS) $$(find . -name '*.o' -o -name '*.d')

In order to use a precompiled header, this is what needs to be added to the Makefile. There are no source code modifications at all. I created a file pre.h that includes all of the system C and C++ headers that I use (in particular <iostream> is a big expense for the compiler).

# PCH is built just like all other source files
# CXXFLAGS must match everything else
pre.h.gch : pre.h
	$(compile)

# all object files depend on the PCH for build ordering
$(TEST_SRC:cxx=o) $(LIB_SRC:cxx=o) : pre.h.gch
# this is equivalent to adding '#include <pre.h>' to the top of every source file
$(TEST_SRC:cxx=o) $(LIB_SRC:cxx=o) : CXXFLAGS += -include pre.h

# pre.h.gch should be cleaned up along with everything else
clean :
	rm -f (...) pre.h.gch

The project itself is fairly small—16 source files totaling 1800 SLOC—but this small change just decreased my total build time from 12 to 8 seconds. This is entirely non-intrusive to the code, so adding it is a one-time cost (as opposed to the Microsoft stdafx.h approach, which is O(N) cost in the number of source files). It is also easy to disable for production builds, if you only want to use the PCH machinery for your day-to-day edit-compile-test cycle.

I like to sneak bitshifts into interviews—not because they’re used commonly in modern C++ code, but because they used to be common, as a way of getting good performance out of poor compilers. It’s very useful to know the tricks of the past, if you ever find yourself maintaining code written by an earlier generation of programmer. Take this example:

unsigned imul(unsigned x)
{
    return x * 10;
}

unsigned bitshift(unsigned x)
{
    return (x << 3) + (x << 1);
}

Below is the assembler produced by gcc -c -O3 -march=core2, using objdump -d --no-show-raw-insn to get the assembler from the compiled output. There are two interesting things to note:

  1. The compiler uses address calculation hardware for simple arithmetic: lea is basically a strided array access, base + stride * i.
  2. The compiler doesn’t use any shifts at all.
00000000 <imul>:
   0:	push   %ebp
   1:	mov    %esp,%ebp
   3:	mov    0x8(%ebp),%eax
   6:	pop    %ebp
   7:	lea    (%eax,%eax,4),%eax    # n + 4n
   a:	add    %eax,%eax             # (n + 4n) + (n + 4n)
   c:	ret    

00000010 <bitshift>:
  10:	push   %ebp
  11:	mov    %esp,%ebp
  13:	mov    0x8(%ebp),%eax
  16:	pop    %ebp
  17:	lea    0x0(,%eax,8),%edx     # 8n
  1e:	lea    (%edx,%eax,2),%eax    # 8n + 2n
  21:	ret    

For comparison, here is the same code compiled with gcc -c -O0 -march=i386. Note that shifts are used in both cases. If you try a few other values of -O and -march, you’ll see some other interesting results, but I’m not going to bother to paste them all here.

00000000 <imul>:
   0:	push   %ebp
   1:	mov    %esp,%ebp
   3:	mov    0x8(%ebp),%edx
   6:	mov    %edx,%eax
   8:	shl    $0x2,%eax    # n << 2
   b:	add    %edx,%eax    # (n << 2) + n
   d:	shl    %eax         # ((n << 2) + n) << 1
   f:	leave  
  10:	ret    

00000011 <bitshift>:
  11:	push   %ebp
  12:	mov    %esp,%ebp
  14:	mov    0x8(%ebp),%eax
  17:	lea    0x0(,%eax,8),%edx      # 8n
  1e:	mov    0x8(%ebp),%eax
  21:	shl    %eax                   # n << 1
  23:	lea    (%edx,%eax,1),%eax     # 8n + (n << 1)
  26:	leave  
  27:	ret    

If you go through some of the major Intel processor models, you will see that the actual assembler output varies quite a bit. What does this mean? Mostly that micro-optimizations designed to produce ever-so-slightly better assembler are usually the wrong approach for long-lived software. Yet, it’s a fact of software that this code will be seen on any sufficiently large system, and it must be understood and fixed if possible.

Developers working on embedded systems with a restricted set of compilers… YMMV. Sorry.