Does it inline?
One of C and C++'s strengths is being able to inline a function and preform additional optimizations on it. Most of us have no idea when something will inline or not. Let's play a game called "Does It Inline?". Every round we'll show you multiple source files with a comment pointing out a difference and you'll have to guess if main will compile down to `return 0`. You can follow along by writing
g++ -march=native -O2 main.cpp && gdb -batch -ex 'file a.out' -ex 'disassemble main'
If you rather use lldb you can write lldb ./a.out -s myscript with myscript containing
disassemble -n main exit
Let's start off easy
Round 1:
A:
inline int fn(int v) { return v; } int main(int argc, char *argv[]) { return fn(0); }
B:
//remove inline int fn(int v) { return v; } int main(int argc, char *argv[]) { return fn(0); }
C:
//move function body int fn(int v); int main(int argc, char *argv[]) { return fn(0); } int fn(int v) { return v; }
D:
//extern C and non zero param extern "C" int fn(int v); int main(int argc, char *argv[]) { return fn(123); } int fn(int v) { return v*0; }
Result: All inline in both clang and gcc
Round 2:
Examples will be the header file, the source of all are
#include "header.h" int main(int argc, char *argv[]) { return fn(123); }
A:
//Using a header without inline keyword int fn(int v) { return v*0; }
B:
//using noinline __attribute__ ((noinline)) int fn(int v) { return v*0; }
C:
//add the inline keyword __attribute__ ((noinline)) __inline int fn(int v) { return v*0; }
D:
//Using both noinline and always_inline __attribute__ ((noinline)) __attribute__((always_inline)) __inline int fn(int v) { return v*0; }
E:
//Same but swap __attribute__((always_inline)) __attribute__ ((noinline)) __inline int fn(int v) { return v*0; }
Result:
- clang and gcc both inline
- clang ignores the attribute and inline, gcc does not
- Neither inline, however gcc skips setting the function parameter
- same but now gcc warns that always_inline conflicts with noinline
- Both will inline. gcc warns that noinline conflicts with always_inline
Round 3:
A:
#include <stdlib.h> int main(int argc, char *argv[]) { return atoi("0"); }
B:
//using strtol #include <stdlib.h> int main(int argc, char *argv[]) { return strtol("0", 0, 10); }
C:
//using strtod which returns a double #include <stdlib.h> int main(int argc, char *argv[]) { return strtod("0", 0); }
Result: A: clang inline, gcc inline atoi but atoi calls strtol which isn't inlined. B) Same as previous C: Both call strtod
Round 4:
A:
#include <vector> int main(int argc, char *argv[]) { std::vector<int> v; v.push_back(1000); return 0; }
B:
//Added an if #include <vector> int main(int argc, char *argv[]) { std::vector<int> v; v.push_back(1000); if (v.size() > 0) { return 0; } return 1; }
C:
//Added a pop inside the if and returned the size #include <vector> int main(int argc, char *argv[]) { std::vector<int> v; v.push_back(1000); if (v.size() > 0) { v.pop_back(); return v.size(); } return 1; }
Amazingly clang inline all of them including C. gcc doesn't inline any
Round 5:
A:
class B { public: virtual int test() { return 0; }}; class D : public B { public: int test() override { return 1; }}; int main(int argc, char *argv[]) { D d; B*b = &d; auto p = dynamic_cast<D*>(b); return !p; }
B:
//Add final to D class B { public: virtual int test() { return 0; }}; class D final : public B { public: int test() override { return 1; }}; int main(int argc, char *argv[]) { D d; B*b = &d; auto p = dynamic_cast<D*>(b); return !p; }
C:
//Both branches are 0 class B { public: virtual int test() { return 0; }}; class D final : public B { public: int test() override { return 1; }}; int main(int argc, char *argv[]) { D d; B*b = &d; auto p = dynamic_cast<D*>(b); if (p) return 0; return 0; }
D:
//replace the first return class B { public: virtual int test() { return 0; }}; class D final : public B { public: int test() override { return 1; }}; int main(int argc, char *argv[]) { D d; B*b = &d; auto p = dynamic_cast<D*>(b); if (p) return ((D*)p)->test()-1; return 0; }
E:
//What happens if you change the cast to reinterpret_cast class B { public: virtual int test() { return 0; }}; class D final : public B { public: int test() override { return 1; }}; int main(int argc, char *argv[]) { D d; B*b = &d; auto p = reinterpret_cast<D*>(b); if (p) return ((D*)p)->test()-1; return 0; }
Result: Neither inline A and B. gcc inline C and D, both inline E
Round 6:
A:
//main.cpp inline int getzero(); int main(int argc, char *argv[]) { return getzero(); } //getzero.cpp int getzero() { return 0; }
B:
//A again but add in -flto
C:
//Use -flto #include <stdlib.h> int main(int argc, char *argv[]) { return strtol("0", 0, 10); }
D:
//For a laugh, recursive fibonacci int fibonacci(int n) { if(n == 0) return 0; else if(n == 1) return 1; else { return (fibonacci(n-1) + fibonacci(n-2)); } } int main() { return fibonacci(0); }
A: Both warn and use a jump. B: Using -flto both will inline C: gcc continues to not inline (even with -static) and clang had always inlined. D: Neither compilers are clever enough to see it doesn't need to call fibonacci
For the past few weeks we the Bolin team have been playing with an experimental feature, using a C header to inline C code into Bolin. It might make the next release