I have a large program that’s generating object files that are much larger than I expect. My suspicion is that somewhere in the program, someone is using inefficient template metaprogramming that’s generating O(n**2) template types. Is there a command-line tool that I can use to list all of the template types that exist in an object file (.o)?
Normally I would suspect nm
or objdump
is the right tool for this kind of thing, but it’s not obvious to me what flags to pass to list the template types.
I’ve verified that the information is in the .o file using this simple test program:
template <typename T, typename... Ts>
struct foo : public foo<Ts...> {};
template <>
struct foo<int> {};
void bar() {
foo<int, int, int, int, int, int, int, int> x;
}
Then running:
gcc -g -c test.c -o test.o && strings test.o
Outputs:
foo<int, int, int, int, int, int, int, int>
GNU C++17 13.2.0 -mtune=generic -march=x86-64 -g -fasynchronous-unwind-tables
_Z3barv
foo<int, int, int, int, int, int, int>
foo<int, int, int, int>
foo<int, int, int, int, int, int>
foo<int, int, int>
foo<int>
foo<int, int>
foo<int, int, int, int, int>
/tmp
test.cc
/tmp
test.cc
test.cc
GCC: (Debian 13.2.0-10) 13.2.0
test.cc
_Z3barv
.symtab
.strtab
.shstrtab
.text
.data
.bss
.rela.debug_info
.debug_abbrev
.rela.debug_aranges
.rela.debug_line
.debug_str
.debug_line_str
.comment
.note.GNU-stack
.rela.eh_frame
I’m looking for a command that will output foo<int>
, foo<int, int>
, etc. from test.o.
2
Answers
Using
nm
is one way to do this.the
-C
flag will give a readable output that shows templates rather than the mangled names:nm -C myfile.o
example output:
No. But you might be able to manage anyway.
By the time you’ve got to an object file, classes have no representation; therefore
classes that instantiate templates have no representation. An object file is
ready for consumption by the linker, and the linker knows nothing of classes. It knows only of public
symbols, which are either function symbols or data symbols. When, for example,
this C++ source file is compiled:
the public symbols in
example.o
will represent just the fully qualified mangled namesof the functions and data objects that are defined by compiling the source file, these being:
pi2
,pd3
, which happen to be objects of distinct class types that both instantiatetemplate struct point<T,Dims>
template struct point<T,Dims>
, which are actually called.If you simply
nm
the global symbol table of object file:you see the mangled symbols. But if you ask for them to be demangled:
it’s quite obvious that the source file1: –
pd3
andpi2
point<double, 3ul>
andpoint<int, 2ul>
from a templatepoint<T,unsigned long>
,point<double, 3ul>::point<double, double, double>(double, double, double)
andpoint<int, 2ul>::point<int, int>(int, int)
point<int, 2ul>::size() const
You cannot see that
pd3
pi2
are the objects created by the constructor invocations, but you don’t need know that. Theoutput ought to be sufficient for you to identify massively bloated template instantiations, provided you can cope with
the volume of files and output you need to survey. Scripting and regexes to the rescue perhaps.
different constructors that have the same demangling, by design. You can spot that by looking carefully at
the corresponding mangled names,
_ZN5pointIdLm3EEC1IdJddEEET_DpT0_
v._ZN5pointIdLm3EEC2IdJddEEET_DpT0_
and noting where they differ.For technical reasons C++ actually may emit up to three versions of a constructor/destructor.