Home / Highlights / FAQ / Examples / Quick Start / Roadmap / Download / About Us

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:

  1. clang and gcc both inline
  2. clang ignores the attribute and inline, gcc does not
  3. Neither inline, however gcc skips setting the function parameter
  4. same but now gcc warns that always_inline conflicts with noinline
  5. Both will inline. gcc warns that noinline conflicts with always_inline
To my surpise if you remove __inline clang will inline both C and D. Optimizers can be fickle

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