Link-time optimization works by having the compilation phases drive the compilation to the GIMPLE tree stage, then writing it out and having the link stage invoke the compiler again (via collect2, or via a linker plugin: the latter is preferable because then you can run it over the contents of .a archives as well) to read in all the GIMPLE trees and optimize them. At that stage you've got all the trees written out by all the compilation phases in memory at once. And a good few optimizations will, unless bounded explicitly, start using up crazy amounts of memory when hit with large programs all at once. (Of course they *are* bounded explicitly. At least all of them that anyone noticed are.)
-combine -fwhole-program has a similar memory hit, for similar reasons.
C++ is a good bit worse memory-wise than C, mostly because the headers make C headers look like child's toys: they have a lot of definitions in them and a large number of very complex declarations. I've known compiling *single C++ files* to eat a couple of gigabytes before (although, admittedly, those were exceptional, things like large yacc parsers compiled with C++: mysql's SQL parser is a good example).