C/C++のためのデバッグツール
この記事はISer Advent Calendar 2020の一環です。
C/C++のデバッグに有用なソフトや機能を紹介したいと思います。
MacにおけるGCCとClang
MacでXcodeを入れるとClangがインストールされ、しかもなぜかgccコマンドでClangが起動されるようになります。これは
gcc --version
とすると確認できます。この状態でGCCを入れるには
brew install gcc
とすればよいのですが、これでもなおgccコマンドではClangが起動されます。この状態でGCCを使うにはgcc-10コマンドを使います。GCCが起動されることを確認するには
gcc-10 --version
とすればよいです。ただし、GCCのバージョンが上がった場合には、gcc-11, gcc-12, ...とする必要があります。
静的解析
ソースコードの解析には動的解析と静的解析があります。動的解析は実際にプログラムを実行して行う解析で、静的解析はプログラムを実行することなく行う解析です。動的解析は普段printfデバッグやデバッガで行っていると思うので、ここでは静的解析を紹介します。新たにソフトを入れなくても、GCCとClangには静的解析機能が付いています。GCCでは-fanalyzer、Clangでは--analyzeを付けてコンパイルすると、静的解析が行われます。また、いくつかのプログラムで試したのですが、どうやらGCCとClangの静的解析機能は異なるもののようです。
静的解析の例
以下のコードを静的解析してみます。
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main(void)
{
srand((unsigned)time(NULL));
int array_size;
scanf("%d", &array_size);
int *array = malloc(sizeof(int) * array_size);
for (int i = 0; i < array_size; i++) {
array[i] = rand();
}
for (int i = 0; i < array_size; i++) {
printf("%d\n", array[i]);
}
return 0;
}
GCCでは
gcc-10 sa.c -fanalyzer
としてコンパイルします。
Clangでは
gcc sa.c --analyze
としてコンパイルします。
GCCの場合は警告を吐いてくれないのですが、Clangの場合は以下のような警告を吐いてくれます。
sa.c:17:12: warning: Potential leak of memory pointed to by 'array'
return 0;
^
1 warning generated.
Xcode
Xcodeでも静的解析機能が使えて、おそらくClangと同一のものと思われます。使い方は簡単で、Product->Analyzeを押すだけです。GUIで操作できてとても便利です。
Sanitizers
Sanitizersという便利機能の集まりみたいなものがあって、それがGCCおよびClangから利用できます。
AddressSanitizer
AddressSanitizerはSanitizersの1つで、C/C++のメモリに関係するバグを検出してくれます。GCCまたはClangで-fsanitize=addressを付けてコンパイルすると、AddressSanitizerが有効になった実行ファイルが生成されます。また、-fno-omit-frame-pointerを付けるとスタックトレースが改善されるそうです。
AddressSanitizerの例
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main(void)
{
srand((unsigned)time(NULL));
size_t array_size;
scanf("%zu", &array_size);
int *array = malloc(sizeof(int) * array_size);
for (size_t i = 0; i < array_size; i++) {
array[i] = rand();
}
for (size_t i = array_size - 1; i >= 0; i--) {
printf("%d\n", array[i]);
}
free(array);
return 0;
}
GCCでは
gcc-10 as.c -fsanitize=address -fno-omit-frame-pointer
としてコンパイルします。
Clangでは
gcc as.c -fsanitize=address -fno-omit-frame-pointer
としてコンパイルします。
そして、生成された実行ファイルを(いつもの方法で)実行すると、heap-buffer-overflowというエラーが出ます。
UndefinedBehaviorSanitizer
UndefinedBehaviorSanitizerはSanitizersの1つで、C/C++のいろいろな未定義動作を検出してくれます。基本的にはGCCまたはClangで-fsanitize=undefinedとすると有効になるのですが、一部機能は個別に指定しないと有効になりません。
UndefinedBehaviorSantizerの例
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#define ARRAY_SIZE 10
int main(void)
{
int array[ARRAY_SIZE];
srand((unsigned)time(NULL));
for (size_t i = 0; i < ARRAY_SIZE; i++) {
array[i] = rand();
}
for (size_t i = ARRAY_SIZE - 1; i >= 0; i--) {
printf("%d\n", array[i]);
}
return 0;
}
GCCではこの場合エラーを検出してくれません。
Clangでは
gcc us.c -fsanitize=undefined -fsanitize=bounds
としてコンパイルし、生成された実行ファイルを(いつもの方法で)実行すると
us.c:15:24: runtime error: index 18446744073709551615 out of bounds for type 'int [10]'
というエラーが出ます。ここで、-fsanitize=boundsは「配列の範囲が静的に決まっている場合の範囲外アクセスを検知する」というオプションです。
Discussion