Clang と GCC の主な相違点¶
Clang と GCC は大部分で互換性がありますが、ここでは異なる部分を解説します。細かい所まで含めると膨大になるので、ここでは SOLID プロジェクトで実際に問題になったものだけを記載しています。
注釈
s008(exeClang)以降は Clang と LLVM lld のみのサポートとなります。以下の記述は exeGCC s007 以前にのみ当てはまります。
コンパイラドライバ¶
GCC は、コンパイラドライバである gcc.exe や g++.exe が渡されたオプションを解析し、実際のコンパイラ cc1.exe (C) や cc1plus.exe (C++)、アセンブラ (Binutils の as.exe)、リンカ (Binutils の ld.exe) に適切なオプションを指定して呼び出すという仕組みになっています。
一方 Clang コンパイラはオプションによって、単一の exe (C の場合は clang.exe、C++ の場合は clang++.exe) がコンパイラドライバ、コンパイラ、アセンブラの役割を果たすという構造になっています。また、SOLID ツールチェーンの設定では、リンク時には gcc.exe あるいは g++.exe を呼び出してリンクを行います。そのため、リンク時に、GCC がサポートしないオプションを渡すとエラーになる場合があります。
注釈
s007 より Clang コンパイラのみ LLVM のリンカ ld.lld.exe をサポートしました。リンクに lld を使用する場合、GCC は呼び出されません。
文字コード¶
GCC は CPP -finput-charset/CPP -fexec-charset で cp932 (Shift_JIS) 等を指定し、ソースコードやアセンブラ出力の文字コードをデフォルトの UTF-8 から変更できますが、Clang は常に UTF-8 のみとなります。
pragma, attribute 拡張機能¶
標準プラグマ¶
以下の STDC プラグマは C99 で標準化されているので、本来は GCC/Clang 両方で使用できるはずです。
#pragma STDC FENV_ACCESS ON/OFF/DEFAULT
#pragma STDC FP_CONTRACT ON/OFF/DEFAULT
#pragma STDC CX_LIMITED_RANGE ON/OFF/DEFAULT
しかし GCC は、AARCH64/ARM アーキテクチャでは、全ての標準プラグマが無視されます。(-Wall 指定時に警告が出ます。)Clang は FENV_ACCESS のみ警告が出ますが、他の 2 つが正しく実装されているかどうかは未検証です。exeGCC では未サポート扱いとします。
注釈
exeClang s008 の RISC-V アーキテクチャでは、FENV_ACCESS と FP_CONTRACT の動作が確認されています。(CX_LIMITED は情報が少なく、効果を確認できていません。)以下のサンプルコードも参考にしてください。
よく使われる例¶
pragma や attribute は基本的にそれぞれのコンパイラに固有のものとなります。できるだけ使用しないようにしてください。特に #pragma GCC ... の形式のプラグマは GCC のみ、 #pragma clang ... の形式のプラグマは Clang のみとなります。GCC と Clang の共通コードで pragma や attribute を使用する場合は、以下の例のように #ifdef __clang__ で切り替えてください。
GCC で、特定の関数のみ最適化オフする __attribute__((optimize("O0"))) は、Clang では __attribute__((optnone)) となります。GCC は逆に、特定の関数のみ任意の最適化レベルを指定することもできますが、Clang はできません。
#ifdef __clang__
__attribute__((optnone))
#else
__attribute__((optimize("O0")))
#endif
void foo() { ... }
GCC で、最適化オフの範囲指定 #pragma GCC optimize("O0") は、Clang では #pragma clang optimize off となります。Clang の #pragma clang optimize XXX では、最適化の on か off の指定のみ可能で、GCC のように最適化レベルは指定できません。また、on を指定した場合でも、最適化オプションが指定されていない場合は、最適化は行われません。
naked attribute¶
この attribute は(AARCH64/ARM は)Clang のみのサポートとなります。
long __attribute__((naked)) add_one(long a)
{
#if defined(__aarch64__)
__asm__ __volatile__("add x0, x0, #1; ret");
#elif defined(__arm__)
__asm__ __volatile__("add r0, r0, #1; bx lr");
#endif
}
これをコンパイルすると、Clang は以下のようにインラインアセンブラ構文で記述されたコードのみが生成されます。
0000000000000000 <add_one>:
0: 91000400 add x0, x0, #0x1
4: d65f03c0 ret
00000000 <add_one>:
0: f100 0001 add.w r0, r0, #1
4: 4770 bx lr
しかし GCC はそれ以外のコードも生成されています。
00000000 <add_one>:
0: f100 0001 add.w r0, r0, #1
4: 4770 bx lr
6: bf00 nop
8: 4618 mov r0, r3
注釈
アライメント調整のため、Clang でも記述していない nop 命令が生成されることがあります。
AARCH64 では正しく未サポート(無視)の警告が出ますが、ARM では出ません。
C:\GCC4\AARCH64\s007\test>gcc -c add_one.c
add_one.c:2:1: warning: 'naked' attribute directive ignored [-Wattributes]
2 | {
|
C:\GCC4\AARCH64\s007\test>objdump -d add_one.o
add_one.o: file format elf64-littleaarch64
Disassembly of section .text:
0000000000000000 <add_one>:
0: d10043ff sub sp, sp, #0x10
4: f90007e0 str x0, [sp, #8]
8: 91000400 add x0, x0, #0x1
c: d65f03c0 ret
10: d503201f nop
14: 910043ff add sp, sp, #0x10
18: d65f03c0 ret
AARCH64 は関数プロローグ・エピローグコードまで生成されているので、全くサポート(機能の実装が)されておらず、ARM は中途半端にサポートされているような印象です。ここらへんは以下の議論から進展が無いようです。(上記の確認コードは、以下の議論中に出てきたコードを使用しています。)
-mthumb-interwork オプション¶
このオプションは Clang には存在しないので、Clang にパッチを当て、無視するようにしています。Clang では常に ARM/Thumb interworking が有効なコードが生成されます。
-marm オプション¶
Clang にこのオプションを渡すと、内部で -mno-thumb と解釈されます。SOLID ツールチェーンの Clang は GCC をリンカとして使用しているため、GCC が未サポートの -mno-thumb オプションが GCC に渡されてしまうとリンクエラーになるため、Clang にパッチを当て、無視するようにしています。そのため Clang では、デフォルトで -mthumb を指定しておいて、ARM でコンパイルしなければいけないファイルのみ -marm を付けるということはできなくなっています。その場合は -mno-thumb を指定してください。(s002 以降)
specs ファイル¶
GCC は specs ファイルによりコンパイラのデフォルト設定を変更可能ですが、Clang には specs ファイル機能が存在しないので -specs オプションは無視されます。
ただし、exeGCC の Clang は GCC をリンカとして使用するため、-specs は Clang から呼び出された GCC にそのまま渡され(リンカに関する設定のみ)期待通りに機能します。GCC に渡されるオプションは Clang に -v オプションを渡すことで確認できます。(Clang のリンカに LLVM の lld を使用した場合は specs ファイルは機能しません。)
arm-*-eabi ターゲット時の enum サイズの仕様¶
GCC は arm-*-eabi ターゲット時は aapcs ABI となり、enum が取り得る値の範囲に応じた可変長バイトとなりますが、Clang は固定長(4 もしくは 8 バイト)です。これは Linux(arm-*-gnueabi* ターゲット)の aapcs-linux ABI と同等の設定になっています。SOLID ではわかりやすさや互換性等を考慮し、Clang の方に合わせた設定となっています。(s002 以降)