Clang Migration Notes

The Android OS switched to clang several years ago. Future versions of the NDK will remove GCC, so the sooner you start testing your project with clang the better!

How to switch to clang

For ndk-build, remove lines setting NDK_TOOLCHAIN or NDK_TOOLCHAIN_VERSION.

For cmake, remove lines setting ANDROID_TOOLCHAIN.

For standalone toolchains, use the clang/clang++ binaries instead of gcc/g++.

For other build systems, ask the owners of that build system.

How to fix common problems

When moving to Clang from GCC, you may notice some differences.

-Oz versus -Os

Warning: Do not use -Oz until Issue 573 is resolved if you use exceptions.

Clang Optimization Flags has the full details, but if you used -Os to optimize your code for size with GCC, you probably want -Oz when using Clang. Although -Os attempts to make code small, it still enables some optimizations that will increase code size (based on For the smallest possible code with Clang, prefer -Oz. With -Oz, Chromium actually saw both size and performance improvements when moving to Clang compared to -Os with GCC.


Normally the __aligned__ attribute is given an explicit alignment, but with no value means “maximum alignment”. The interpretation of “maximum” differs between GCC and Clang: Clang includes vector types too so for ARM GCC thinks the maximum alignment is 8 (for uint64_t), but Clang thinks it’s 16 (because there are NEON instructions that require 16-byte alignment). Normally this shouldn’t matter because malloc is always at least 16-byte aligned, and mmap regions are page (4096-byte) aligned. Most code should either specify an explicit alignment or use alignas instead.


When targeting Android (but no other platform), GCC passed -Bsymbolic to the linker by default. This is not a good default, so Clang does not do that. -Bsymbolic causes the following behavior change:

// foo.cpp
#include <iostream>

void foo() {
  std::cout << "Goodbye, world" << std::endl;

void bar() {
// main.cpp
#include <iostream>

extern void bar();

void foo() {
  std::cout << "Hello, world\n";

int main(int, char**) {
  foo(); // Prints “Hello, world!”
  bar(); // Without -Bsymbolic, prints “Hello, world!” With -Bsymbolic, prints “Goodbye, world!”

In addition to not being the “expected” default behavior on all other platforms, this prevents symbol interposition (used by tools such as asan).

You might however wish to add manually -Bsymbolic back because it can result in smaller ELF files because fewer relocations are needed. If you do want the non--Bsymbolic behavior but would like fewer relocations, that can be achieved via -fvisibility=hidden (and manually exporting the symbols you want to be public, using the JNI_EXPORT macro in JNI code or __attribute__ ((visibility("default"))) otherwise. Linker version scripts are an even more powerful mechanism for controlling exported symbols, but harder to use.


Especially for ARM and ARM64, Clang is much stricter about assembler rules than GCC/GAS. Use -fno-integrated-as if Clang reports errors in inline assembly or assembly files that you don't wish to modernize.