Xcode のコンパイラを Apple LLVM に変えてみる

SPECIAL


Xcode4 のコンパイラーを Apple LLVM compiler 3.0 に変更してみる

Xcode3 から Xcode4 に乗り換えていろいろプロジェクト設定を調整していた時に、選択できるコンパイラーがいくつかあるのが気になりました。

Compiler for C/C++/Objective-C - Xcode4

"GCC" といえば Linux でお馴染みのコンパイラですけど、それとあわせて Xcode では "LLVM GCC" と "Apple LLVM compiler" というものも選択できるようになっています。

これらについて少し調べてみたところ、なにやら GCC と比べて Apple LLVM compiler の方が、同じコードでもビルドしたバイナリの実行速度が速くなったり、コンパイルの速度が速くなったり、コンパイル時のエラー情報も判りやすく出力してくれるとのことでした。

ちなみに "LLVM GCC" というのは、構文解析を GCC で行うことで、互換性を高めようとしているもののようです。

 

なかなか良さそうな感じだったので、試しにこれまで作成していた iPhone アプリのコンパイラーを "LLVM GCC" から "Apple LLVM compiler" に変更してビルドを試してみたところ、エラーメッセージがたくさん出てきてしまいました。

最初は面食らってしまいましたけど、そこからエラーを解消していったところ、意外と簡単かつ的確にエラーをなくすことができたので、今回はその辺りについて記してみたいと思います。

 

まず、最初に膨大に出たエラーメッセージは、次のようなものでした。

'autorelease' is unavailable: not available in automatic reference counting mode

これは [obj release] などの release メソッドを呼び出しているところで検出されました。

ただ、これは LLVM GCC と Apple LLVM compiler との互換性ではなくて、たまたま設定の違いによって発生したもののようでした。

iPhone アプリの場合、iPhone デバイスの制約上、自動的なメモリー管理は利用できなくなっているので、今回コンパイルしたソースコード上では明示的に relase を実行していたのですけど、どうやら Apple LLVM compiler がそれを見つけて、エラーとして検出してくれていたようでした。

iPhone アプリの場合は release を明示的に実行しなければいけないので、Apple LLVM compiler の "Objective-C Automatic Reference Counting" の設定を "No" にすることで、このエラーを解消することができました。

このとき、"Objective-C Automatic Reference Counting" の設定箇所が "Apple LLVM compiler 3.0 - Language" と "Apple LLVM compiler 3.0 - Warning" とがありますけど、ここでは "Warning" の方ではなく "Language" の方を調整する必要があることに注意します。

これで、設定の間違いによるエラーは解消されたので、続いて GCC と LLVM との互換性から発生するコード修正に移って行きます。

 

これまで GCC ではコンパイルが通っていたコードでしたけど、LLVM に変更したところ、まずは次のエラーが発生しました。

Declaration of 'struct ifa_data' will not be visible outside of this function

このエラーは、引数に struct ifa_data 型を取るメソッドで発生しました。

この ifa_data という構造体の定義を再確認してみたところ、実際は <ifaddrs.h> で定義されている ifaddrs 構造体のメンバー変数として void* 型の ifa_data が定義されていました。つまり struct ifa_data というのは存在せず、このメソッドの引数で指定する型は void* が正解でした。

念のため Xcode3 でも ifa_data の定義を確認してみたのですけど、やはりこちらでも void* が適切なようでした。

-(void)setDataFromIfaData:(struct ifa_data*)argIfaData;

⇒ -(void)setDataFromIfaData:(void*)argIfaData;

よって、このように (struct ifa_data*) を (void*) に修正することで、このエラーは適切に解消されました。

 

また、#define で独自定義したマクロで、次のエラーが検出されました。

Expression result unused

これは、例えば次のような、値を何も定義していないマクロを、ソースコード内で引数付きで呼び出していたのが原因でした。

そこで、値を定義しないマクロであっても、定義の際に引数を取るように定義を修正することで、このエラーは解消されました。

#define XXX

⇒ #define XXX(...)

至極自然な修正ですね。

 

それと、プロパティーの宣言部分のところで、次のエラーが発生しました。

Method name referenced in property setter attribute must end with ':'

これは、分かりやすいエラーメッセージですね。プロパティーのセッターとして指定するメソッドは、最後が ":" で終わっている必要があるとのことです。

ソースコードを確認してみると、確かに "setter=" で指定しているメソッドが ":" で終わっていませんでした。もちろん、これまで正しく動作していたソースコードなので、セッターに指定したメソッドの実際の定義では、引数を 1 つ取るように定義されています。

単純に末尾の ":" の記載し忘れでしたので、次のように修正しました。

@property (readwrite,setter=setSockaddrTargetDirectly) struct sockaddr_in socketaddrTarget;

⇒ @property (readwrite,setter=setSockaddrTargetDirectly:) struct sockaddr_in socketaddrTarget;

これでこのエラーは解消されましたけど、よくこれで、今までコンパイルが通っていたものです。

 

そして、警告表示になりますけれど、ヘッダーファイルの #import の行でも、次の警告が発生しました。

Extra tokens at end of #import directive

これは #import でヘッダーファイルをインポートした後には ";" が必要ないところ、記載されていたことが原因で発生しました。

Apple LLVM compiler では面白いことに、この警告と併せて「Fix-it Insert "//"」という選択肢が表示されていて、これを選ぶと ";" の前に "//" を挿入して、コメントアウトしてくれる機能が備わっていました。

 

また、sprintf の書式指定の間違いも、警告で教えてくれました。

Extra tokens at end of #import directive

Conversion specifies type 'int' but the argument has type 'UInt32' (aka 'unsigned long')'

これは、UInt32 型で定義した変数を sprintf で "%d" を使って受けるソースコードを書いたときの警告メッセージなのですけど、つまり UInt32 は unsigned long 型で定義されているところ、sprintf の %d は int 型であるため、型が違うということで警告を表示してくれた感じです。

これは、変数の型を "SInt16" や "int" に直すか、sprintf で "%lu" で受けることで解消しました。

 

それと switch 文で列挙型を使った場合に、定義されている列挙子を全て switch で網羅していないと、次のような警告メッセージが表示されました。

Enumeration value 'EzCallHistoryTypeUnknown' not handled in switch

この警告を解消したい場合には、全ての列挙子を case 句で網羅するか、default 句を使って case 句にない列挙子を処理するコードを記載することで解消されました。

これはたとえば「列挙子を追加したけど switch 文の修正を忘れた」なんていうミスにコンパイルの段階で気づけて、個人的にはとても嬉しい機能です。

 

それともう一つ、警告としての検出でしたけど、致命的なコーディングミスも、ここにきて検出することができました。

Incompatible pointer types assigning to 'UILabel *' from 'NSString *'

つまりこれは、UILabel 型のインスタンスに、".text" プロパティーの指定を忘れて NSString 型の文字列を代入してしまったのが原因ですけど、これってかなり致命的なコーディングミスだったりすることも多いと思います。

 

上記のような箇所を修正していったところ、Apple LLVM compiler でも、正常にコンパイルできるようになりました。

それにしても、ソースコードを Apple LLVM compiler に合わせて修正していて思ったことは、どれにおいてもコーディングの不備を適切な形に直すに尽きた感じですね。よくこれで、今までちゃんとコンパイルができていたものだと思えるくらいです。

警告メッセージをしっかり出してくれるところも、個人的にはとても好感が持てるところでした。警告メッセージに沿って厳密なコード記載を心がければ、より多くのバグを未然に防ぐことができそうですね。

それでいて、コンパイルにかかる時間も、コンパイルされたプログラミングの動作速度も、どちらも GCC よりも改善されるというのだから、これはぜひとも可能な限り Apple LLVM compiler に切り替えていきたいと思えるところでした。