Thursday, June 07, 2012

Nerd Food: C++-11 with GCC

Nerd Food: C++-11 with GCC

Standard Disclaimer: as with all posts in Nerd Food, this is a summary of my notes and experience on the subject. Its likely there will be incorrect bits of information so don't start building your personal nuclear power station using this article. Or if you do, don't blame me.

Compiling a compiler has become a necessity for all of us living in the bleeding edge of C++-11. After all, who wants to wait for the next stable release of INSERT_FAVOURITE_COMPILER and then for INSERT_FAVOURITE_DISTRIBUTOR to start packaging them! We've already covered how to build and install Clang from source and use it to compile and run a trivial hello world; it seems only fitting to do the same thing for GCC.

A couple of notes on the content that follows:

  • the post focuses more on Linux but we also try to give some pointers for Windows (MinGW) and MacOSX. Testing was done on Windows XP and OSX Lion respectively.
  • we're targeting version 4.7 but you should be able to apply these instructions to svn trunk builds too - assuming you have the correct versions of the dependencies. We may cover that specific scenario on a later post.
  • we are - of course - assuming that you are using an older version of GCC to build GCC. You could try to use Clang or another compiler, but we didn't.

Prerequisites

Rather unfortunately, one of the requirements for building a compiler is a working compiler. In addition, there is always a bit of fiddling required. Lets go through each platform in turn.

Debian Testing

Debian is going through the multi-arch transition at the moment, which complicates things. For instance, for reasons unknown we need to install 32-bit glibc development headers - even when building on 64-bit. The easiest way to do so is to install multi-platform support for the system GCC:

sudo apt-get install g++-multilib

In addition, its probably best to set the LIBRARY_PATH so we can tell the linker about it:

export LIBRARY_PATH=/usr/lib/x86_64-linux-gnu

If you don't, you'll probably experience weird C runt-time errors:

/usr/bin/ld: cannot find crt1.o: No such file or directory
/usr/bin/ld: cannot find crti.o: No such file or directory
collect2: ld returned 1 exit status

MacOSX

Grab XCode - or even better, just the Command Line Tools bundle - and make sure its installed and working. If all has gone well, the following should work when you open up a terminal:

gcc --version

MinGW

We need to get a compiler on Windows before we can compile. We'll only cover the MinGW scenario. First get the main installer from MinGW and run it. During the setup choose the following options:

  • Repository catalogues: download latest repository catalogues.
  • Set destination location: Choose something like C:\MinGWRoot. The post-fix root is actually important because we will use c:\mingw later. Basically, choose any name other than \mingw on the chosen given drive.
  • Select Components: make sure "C Compiler", "C++ Compiler", "MSYS Basic System" and "MinGW Developer Toolkit" are ticked.

Click install and wait - it will take a while as it downloads all the packages. When finished, start the MinGW shell from the Start Menu and make sure the following works:

gcc --version

The next thing to do - a bit of a hack really - is to create an "identity" "mount". This is another way of saying that the MSYS1 root is actually the same as the windows root. The "cleanest" way to do this is to use junction which you can easily grab from SysInternals:

mingw-get install msys-wget
wget http://live.sysinternals.com/junction.exe
junction c:\\mingw C://MinGWRoot/msys/1.0

Now set the linker path pointing it to the identity mount:

export LIBRARY_PATH="/mingw/lib"

If you don't, you may experience the following error:

C:\MinGWRoot\mingw32\bin\ld.exe: cannot find dllcrt2.o: No such file or directory
collect2.exe: error: ld returned 1 exit status
make[3]: *** [libgcc_s.dll] Error 1
make[3]: Leaving directory `/home/marco/gcc-4.7.0_obj/i686-pc-mingw32/libgcc'

Building and Installing

Let's get started with the actual compilation. First we need to install the dependencies; you can peruse the prerequisites page for more details. I'm normally lazy and go with GMP, MPFR and MPC. This means we're not getting all the Graphite goodies which require PPL2.

The correct order of dependencies is as follows: GCC depends on MPC, which depends on MPFR, which depends on GMP; so the order of installation is:

  • GMP
  • MPFR
  • MPC
  • GCC

So we start by installing GMP (replace the --jobs 2 flag with the number of cores available on your machine):

wget ftp://ftp.gmplib.org/pub/gmp-5.0.4/gmp-5.0.4.tar.bz2
tar xjf gmp-5.0.4.tar.bz2
cd gmp-5.0.4
./configure --prefix=${HOME}/local
make --jobs 2
make install
cd ..

If you are on a recent version of Linux you can use the brand-spanking xaf incantation of tar, which unpacks any archive type; in the interest of backwards compatibility we're sticking with the old invocations here.

Now we can install MPFR:

wget http://www.mpfr.org/mpfr-current/mpfr-3.1.0.tar.bz2
tar xjf mpfr-3.1.0.tar.bz2
cd mpfr-3.1.0
./configure --prefix=${HOME}/local --with-gmp=${HOME}/local
make --jobs 2
make install
cd ..

Note that we are using --with-gmp instead of using the traditional CFLAGS and LDFLAGS. This is a pattern followed through on GCC configuration; I highly recommend you follow it as I'm sure a lot of other things are happening on the background other than setting those environment variables. In fact you may want to even make sure these variables are not set to avoid any weird compilation problems.

MPC is installed next:

wget http://www.multiprecision.org/mpc/download/mpc-0.9.tar.gz
tar xf mpc-0.9.tar.gz
cd mpc-0.9
./configure --prefix=${HOME}/local --with-gmp=${HOME}/local \
--with-mpfr=${HOME}/local
make --jobs 2
make install
cd ..

Finally, we can now install GCC. Up til now we've been lazy and done in-source builds. This is normally frowned upon, but doesn't have any major consequences - that is, with the exception of GCC. The documentation states explicitly that this is not a good idea:

First, we highly recommend that GCC be built into a separate directory from the sources which does not reside within the source tree. This is how we generally build GCC; building where srcdir == objdir should still work, but doesn't get extensive testing; building where objdir is a subdirectory of srcdir is unsupported.

We're not that brave so we'll follow the recommendation. We compile GCC as follows:

wget http://ftp.gnu.org/gnu/gcc/gcc-4.7.0/gcc-4.7.0.tar.bz2
tar xjf gcc-4.7.0.tar.bz2
mkdir gcc-4.7.0_obj
cd gcc-4.7.0_obj
../gcc-4.7.0/configure --prefix=${HOME}/local \
--enable-ld=yes --disable-nls --with-gmp=${HOME}/local \
--with-mpfr=${HOME}/local --with-mpc=${HOME}/local \
--program-suffix=-4.7 --enable-checking=release --enable-languages=c,c++
make --jobs 2
make install

Lets see in detail the more important GCC configuration options:

  • disable-nls: unless you are into internationalisation, you don't really need NLS. This shaves off a bit of build time.
  • enable-checking: lets make a few checks - no one wants a compiler that generates broken code.
  • enable-languages: we are only interested in C/C++, so no point in building ADA, Java, etc.
  • enable-ld: we're being old-school here and using traditional ld instead of gold, the new linker written in C++.

On the whole, these basic instructions are sufficient to build GCC on Linux, MacOSX and Windows (MinGW). However, when you leave Linux there is always a bit of platform-specific fiddling required. And to be fair, Debian testing also had a couple of wrinkles.

Compiling Hello World

In order to prove that we have a valid setup, lets create a trivial hello world, with a hint of C++-11:

#include <iostream>

int main(void) {
    auto hello = std::string("hello world");
    std::cout << hello << std::endl;
}

First lets make sure we have our prefix directory on the path:

export PATH=$PATH:${HOME}/local

Now we can then compile with our shinny new GCC:

g++-4.7 -c hello.cpp -std=c++11
g++-4.7 -o hello hello.o -std=c++11

Running Hello World

Running hello world is a bit trickier. We now need to tell the program loader about it and this varies from operative system to operative system.

Debian

Due to the multi-arch changes, we need to point the program loader directly to the arch specific directory:

export LD_LIBRARY_PATH=${HOME}/local/lib64

To verify that your binaries are setup correctly, use ldd:

$ ldd hello
    linux-vdso.so.1 =>  (0x00007fffda7ff000)
    libstdc++.so.6 => /home/marco/local/lib64/libstdc++.so.6 (0x00007f6931fa2000)
    libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007f6931cf8000)
    libgcc_s.so.1 => /home/marco/local/lib64/libgcc_s.so.1 (0x00007f6931ae2000)
    libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f693175b000)
    /lib64/ld-linux-x86-64.so.2 (0x00007f69322d1000)

Note that both the C++ run-time and the GCC's shared object were picked up from the correct location.

MacOSX

Apple - thinking differently as usual - doesn't use binutils. This means ld, gold, as etc are all a bit different. Darwin doesn't use ELF like every other sensible UNIX but it uses MACH-O instead - a bad nerd pun, if I ever seen one. In general this shouldn't really affect you - except you can't use familiar tools such as ldd etc.

Lets start by getting the program loader to point to our lib directory:

export DYLD_LIBRARY_PATH=${HOME}/local/lib

To verify that your binaries are setup correctly, use otool:

otool -L hello
hello:
    /Users/marco/local/lib/libstdc++.6.dylib (compatibility version 7.0.0, current version 7.17.0)
    /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 159.1.0)
    /Users/marco/local/lib/libgcc_s.1.dylib (compatibility version 1.0.0, current version 1.0.0)

Here you can see that the C++ run-time and the GCC dynamic library where picked up from the correct location.

MinGW

On Windows the program loader path is not important, it's the main PATH variable you have to worry about3. Thus the DLLs must be on the PATH rather than on the usual variables for the program loader path. To do so:

$ export PATH=${HOME}/local/bin:${PATH}

Notice we placed our directory first in the path - this is to avoid picking up the wrong DLLs from the stock compiler. Running produces the desired output:

$ ./hello.exe
hello world

To double-check we are indeed using the correct DLLs we need to install Dependency Walker. I installed mine to C:\packages - hey, why not. You can then run:

$ c:/packages/depends.exe

And you should see something akin to this:

Dependency Walker

Dependency walker with hello world

Uninstalling

Word of caution: notably, GCC isn't built with uninstall in mind. It doesn't support the unistall target some tarballs provide and many dicussions strongly advise against its complete manual uninstall. This is why we chose a careful location to install it, so we can simple do:

rm -rf ${HOME}/local

Conclusions

As you can see its really easy to build GCC from source, install it and start using it to compile C++-11 programs. In future we will cover how to compile auxiliary libraries such as boost with C++-11 support so you can use the full power of the language.

Footnotes:

1 : I won't go in to too much detail on the finer points between MinGW and MSYS and so on; if you are interested, check the post MinGW, Cygwin and Wine.

2 I had one or two problems when compiling PPL in the past so I gave up on it, and haven't noticed significant optimisation problems yet. Of course, if this was a production compiler I certainly would compile it with PPL.

3 Or better yet, the windows loader uses only one path variable for both the executables and the shared libraries.

Date: 2012-06-08 08:43:47 BST

Org version 7.8.02 with Emacs version 23

Validate XHTML 1.0

No comments: