Fun with templates

At work we had to use two C libraries defining structures with
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 to
size() 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 templates
work: 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.