Fun with templates
different size but same name. Function names were different though, so
we believed no symbol clash would occur.
We thought if we used the headers from each library in a different
compilation unit we'd be safe.
But we were wrong. The code compiled fine but crashed at runtime. Why? Because we used templates within these compilation units, and this generated weak symbols with the very same name. The linker then discarded duplicate symbols, and since structure size was different we got a crash.
Let's reproduce this on a small example:
/* A.h */
#ifndef A_H
#define A_H
struct Data { int an_int; };
#endif
/* B.h */
#ifndef B_H
#define B_H
struct Data { char a_buffer[65]; };
#endif
/* UsingA.cpp */
#inlude "A.h"
#inlude <deque>
static std::deque<Data> a_datas;
std::size_t get_a_size() { return a_datas.size(); }
/* UsingB.cpp */
#inlude "B.h"
#inlude <deque>
static std::deque<Data> b_datas;
std::size_t get_b_size() { return b_datas.size(); }
/* Main.cpp */
int main(int argc, char* argv[]) { return 0; }
We compile these files:
$ g++ -c -I . UsingA.cpp UsingB.cpp Main.cpp $ g++ -o Main UsingA.o UsingB.o Main.o
and inspect what got generated in
UsingA.o, choosing the call tosize() to reduce the output:$ nm -C UsingA.o | grep '::size()' 0000000000000000 W std::deque<Data, std::allocator<Data> >::size() const
Likewise in
UsingB.o:$ nm -C UsingB.o | grep '::size()' 0000000000000000 W std::deque<Data, std::allocator<Data> >::size() const
g++ has generated code for the deque we're using. That's how templateswork: they get "expanded" in the compilation unit using them. So far so good.
Now if we look in Main:
$ nm -C Main | grep '::size()' 0000000000400a64 W std::deque<Data, std::allocator<Data> >::size() const
we see there's only a single symbol in the resulting binary.
Because the symbols are weak (see the W in nm output), g++ silently discards one version, say the version in UsingB.o. (If they were not weak, link would fail because of duplicate symbols. And every C++ program would fail to link.) Therefore any call on b_datas is likely to fail: the deque code called will not use the right definition of Data.
Lesson learned: avoid data structures with the same name.