次に,ワーニングを出すか出さないかを制御するオプションについて説明します.
■-Wformat
このオプションを指定すると,通常は間違っていてもワーニングを出さない,printf系やscanf系の関数のフォーマット部分のエラーをチェックします.
リスト28のコンパイルと実行の結果は以下のようになります.
〔リスト28〕printfなどのフォーマットをチェックする例(test154.c)
|
/*
*printfなどのフォーマットをチェックする
*/
int main()
{
printf("%d\n",9);
printf("%a\n",9);
return 0;
}
|
|
$ gcc test154.c
$ gcc test154.c -Wformat
test154.c: 関数 `main' 内:
test154.c:7: 警告: フォーマットは double ですが, 引数は different type です (引数 2)
$
■-Wno-format-y2k
-Wformatオプションを指定したとき,strftimeを使って2桁の年号を取り出そうとすると,-Wformatの機能でワーニングを出します.実際に2桁の年が欲しいのに大きなお世話なのですが,このオプションを指定することで,そのワーニングを出さないようにします.
リスト29のコンパイルと実行の結果は以下のようになります.
〔リスト29〕strftimeのフォーマット中の%gなどをチェックしない例(test155.c)
|
/*
*フォーマットをチェックするが
*strftimeのフォーマットの中で
*2桁の年を指定してもエラーにしない
*/
#include <stdlib.h>
#include <stdio.h>
#include <time.h>
int main()
{
char time_str[255];
struct tm *lt;
time_t t;
time(&t);
lt=localtime(&t);
strftime(time_str, strlen(time_str), "%g",lt);
printf("%s\n",time_str);
return 0;
}
|
|
$ gcc test155.c -o test155
フォーマットのチェックをしなければ何もメッセージを出しません.
$ gcc test155.c -o test155 -Wformat
test155.c: 関数 `main' 内:
test155.c:17: 警告: `%g' は年の下二桁だけをもたらします
ここで,-Wformatを指定するとワーニングメッセージを出します.
$ gcc test155.c -o test155 -Wformat -Wno-format-y2k
加えて,-Wno-format-y2kを指定するとワーニングメッセージを抑止します.
$ ./test155
03
$
■-Wno-format-extra-args
-Wformatオプションを指定したときに,printf系やscanf系の関数において引き数が超過していたときのワーニングメッセージを抑止します.
デバッグ中でもない限り,この状態を放置しておくのは混乱の元です.最終的にはこのオプションは取ってコンパイルすべきです.
リスト30のコンパイルと実行の結果は以下のようになります.
〔リスト30〕printfなどの引き数の超過をチェックしない例(test156.c)
|
/*
*フォーマットをチェックするが
*printfなどの引数が超過していても
*エラーにしない
*/
#include <stdio.h>
int main()
{
int arg1 = 0;
int arg2 = 1;
printf("%d\n",arg1,arg2);
return 0;
}
|
|
$ gcc test156.c -o test156
$ gcc test156.c -o test156 -Wformat
test156.c: 関数 `main' 内:
test156.c:12: 警告: フォーマットへの引数が多すぎます
$ gcc test156.c -o test156 -Wformat -Wno-format-extra-args
$ ./test156
0
$
■-Wformat-nonliteral
printf系やscanf系の引き数のフォーマット文字列が,リテラルでない場合にワーニングメッセージを出します.-Wformatオプションを指定したときに有効です.
該当の引き数が本当に意図したものになっているかどうかを確認するのには有効です.実際にフォーマット文字列引き数を文字列変数にした場合は,プログラマがチェックする以外に方法がありません.
リスト31のコンパイルと実行の結果は以下のようになります.
〔リスト31〕フォーマット文が文字列リテラルではない例(test157.c)
|
/*
*フォーマットが文字列リテラルではない例
*/
#include <stdio.h>
int main()
{
char *data;
int arg1 = 0;
int arg2 = 1;
data = "%d";
printf(data,arg1,arg2);
return 0;
}
|
|
$ gcc test157.c -o test157
$ gcc test157.c -o test157 -Wformat
$ gcc test157.c -o test157 -Wformat -Wformat-nonliteral
test157.c: 関数 `main' 内:
test157.c:12: 警告: フォーマットは文字列リテラルではありませんので,引数の型は検査されません
$
■-Wformat-security
関数の戻り値をprintf系関数に指定することはできますが,これがsprintfでメモリ上の転送先が重要な領域で,その関数がシェルだった場合などには,何をしこまれるかわかりません.
非常に簡単なチェックですが,printfなどの引き数が関数の戻り値である時にワーニングメッセージを出します.これも-Wformatオプションとともに使用します.
リスト32のコンパイルと実行の結果は以下のようになります.
〔リスト32〕printfなどの引き数が関数の戻り値である例(test158.c)
|
/*
*printfなどの引数が関数の戻り値である例
*/
#include <stdio.h>
char * testfunc();
char * testfunc_format();
char * testfunc_arg();
int main()
{
int *test;
char *data;
data = "%s%n\n";
printf(data,"-Wformat-securityの検証",test);
printf("%d\n",*test);
printf(testfunc_arg());
printf(testfunc_format(),testfunc(),test);
printf("%d\n",*test);
return 0;
}
char * testfunc()
{
return "test\n";
}
char * testfunc_format()
{
return "%s%n";
}
char * testfunc_arg()
{
return "test for -Wformat-security\n";
}
|
|
$ gcc test158.c -o test158
$ gcc test158.c -o test158 -Wformat
$ gcc test158.c -o test158 -Wformat -Wformat-security
test158.c: 関数 `main' 内:
test158.c:15: 警告: フォーマットは非文字列リテラルで,且つフォーマット引数を持ちません
$ ./test158
-Wformat-securityの検証
23
test for -Wformat-security
test
5
$
■-Wformat=2
現在のバージョンにおいて,このオプションは-Wformatオプションに-Wformat-securityオプションと-Wformat-nonliteralオプションの意味を追加したものです.
前述のソースtest158.c(リスト32)を使ってコンパイルすると以下のようになります.
$ gcc test158.c -o test158 -Wformat=2
test158.c: 関数 `main' 内:
test158.c:13: 警告: フォーマットは文字列リテラルではありませんので,引数の型は検査されません
test158.c:15: 警告: フォーマットは非文字列リテラルで,且つフォーマット引数を持ちません
test158.c:16: 警告: フォーマットは文字列リテラルではありませんので,引数の型は検査されません
$
■-Wmissing-braces
連載第7回で配列の初期化について説明しましたが,配列の初期化方法に関してワーニングメッセージを出すオプションです.
int tbl1[2][2] = { 0, 1, 2, 3 };
上の方法で多次元配列を初期化することは可能ですが,可読性に欠けます.
int tbl1[4] = { 0, 1, 2, 3 };
これは,上のコードとは意味合いが同じでも二つを混同すると問題が起きます.そこで,下記のようにプログラマが多次元配列だと認識して初期化するべきです.
int tbl1[2][2] = { { 0, 1 }, { 2, 3 } };
リスト33のコンパイルと実行の結果は以下のようになります.
〔リスト33〕配列の初期化について(test159.c)
|
/*
*配列の初期化
*/
#include <stdio.h>
int main(void)
{
int tbl1[2][2] = { 0, 1, 2, 3 };
int tbl2[2][2] = { { 0, 1 }, { 2, 3 } };
int tbl3[2][2] = { [0 ... 1][0 ... 1]=5};
printf("tbl1[0][0]=%d\n",tbl1[0][0]);
printf("tbl1[0][1]=%d\n",tbl1[0][1]);
printf("tbl1[1][0]=%d\n",tbl1[1][0]);
printf("tbl1[1][1]=%d\n\n",tbl1[1][1]);
printf("tbl2[0][0]=%d\n",tbl2[0][0]);
printf("tbl2[0][1]=%d\n",tbl2[0][1]);
printf("tbl2[1][0]=%d\n",tbl2[1][0]);
printf("tbl2[1][1]=%d\n\n",tbl2[1][1]);
printf("tbl3[0][0]=%d\n",tbl3[0][0]);
printf("tbl3[0][1]=%d\n",tbl3[0][1]);
printf("tbl3[1][0]=%d\n",tbl3[1][0]);
printf("tbl3[1][1]=%d\n\n",tbl3[1][1]);
return;
}
|
|
$ gcc test159.c -Wmissing-braces
test159.c: 関数 `main' 内:
test159.c:7: 警告: 初期化子のまわりのブレースを欠いています
test159.c:7: 警告: (`tbl1[0]' の初期化は不完全です)
$ ./test159
tbl1[0][0]=0
tbl1[0][1]=1
tbl1[1][0]=2
tbl1[1][1]=3
tbl2[0][0]=0
tbl2[0][1]=1
tbl2[1][0]=2
tbl2[1][1]=3
tbl3[0][0]=5
tbl3[0][1]=5
tbl3[1][0]=5
tbl3[1][1]=5
$
■-Wsequence-point
標準のC言語仕様で規定されていない計算の順序などで,問題が起こりそうなコードを見つけたらワーニングメッセージを出力します.
リスト34のコンパイルの結果は,以下のようになります.
〔リスト34〕演算の順序について(test160.c)
|
/*
*演算の順序について
*/
#include <stdio.h>
int main(void)
{
int a = 100;
int n = 200;
a = a+=1,(a++ && n++),a+=1,a+=10,n+=50;
return;
}
|
|
$ gcc test160.c
$ gcc test160.c -Wsequence-point
test160.c: 関数 `main' 内:
test160.c:9: 警告: `a' での演算が定義されていないと思われます
$
現在のマシン環境において,このような方法で速度を稼いだり,コードを小さくする方法は避けるべきではないかと思います.
生成されたアセンブラのリスト(リスト35)を見ると,a = a+=1,(a++ && n++),a+=1,a+=10,n+=50;の行は左から右に演算しているようです.
〔リスト35〕生成されたアセンブラソース(test160.s)
|
.file "test160.c"
.text
.align 2
.globl main
.type main,@function
main:
pushl %ebp
movl %esp, %ebp
subl $8, %esp
andl $-16, %esp
movl $0, %eax
subl %eax, %esp
movl $100, -4(%ebp)
movl $200, -8(%ebp)
leal -4(%ebp), %eax
incl (%eax)
movl -4(%ebp), %eax
movl %eax, -4(%ebp)
leal -4(%ebp), %eax
incl (%eax)
cmpl $1, -4(%ebp)
je .L2
leal -8(%ebp), %eax
incl (%eax)
.L2:
leal -4(%ebp), %eax
incl (%eax)
leal -4(%ebp), %eax
addl $10, (%eax)
leal -8(%ebp), %eax
addl $50, (%eax)
leave
ret
.Lfe1:
.size main,.Lfe1-main
.ident "GCC: (GNU) 3.2 20020903 (Red Hat Linux 8.0 3.2-7)"
|
|
■-Wunused-function
スタティックとして宣言した関数が使われなかったり,定義されていなかったときにワーニングメッセージを出します.
リスト36のコンパイル結果は以下のようになります.
〔リスト36〕スタティック関数の宣言と実体の矛盾例(test161.c)
|
/*
*スタティック関数の宣言と実体の矛盾
*/
#include <stdio.h>
static void test_func1();
static void test_func2();
void test_func3();
int main(void)
{
printf("main\n");
return 0;
}
/*static void test_func1()
{
printf("test_func1\n");
}*/
static void test_func2()
{
printf("test_func2\n");
}
|
|
$ gcc test161.c
$ gcc test161.c -Wunused-function
test161.c:5: 警告: `test_func1' が `static' と宣言されましたが未定義です
test161.c:18: 警告: `test_func2' が定義されましたが使われませんでした
$
■-Wunused-label
このオプションを付けると,プログラム中で使用していないラベルがある場合にワーニングメッセージを出力します.
それを抑止するには,GCC3.2.2の新機能であるunused attributeの指定をすることです.それに関しては「GCC2.95から追加変更のあったその他言語仕様の補足と検証」の回で解説する予定です.
リスト37のコンパイル結果は以下のようになります.
〔リスト37〕使っていないラベル(test162.c)
|
/*
*使っていないラベル
*/
#include <stdio.h>
int main(void)
{
printf("main\n");
label1:
return 0;
}
|
|
$ gcc test162.c
$ gcc test162.c -Wunused-label
test162.c: 関数 `main' 内:
test162.c:8: 警告: ラベル `label1' が定義されましたが使われていません
$
■-Wunused-parameter
このオプションを付けると,宣言した関数の引き数をプログラム中で使用していない場合にワーニングメッセージを出力します.
それを抑止するには,先と同様にGCC3.2.2の新機能であるunused attributeの指定をすることです.それに関しては,「GCC2.95から追加変更のあったその他言語仕様の補足と検証」の回で解説する予定です.
リスト38のコンパイル結果は,以下のようになります.
〔リスト38〕使っていない引き数(test163.c)
|
/*
*使用していない関数の引数
*/
#include <stdio.h>
void test_func2(int arg1,int arg2);
void test_func3();
int main(void)
{
printf("main\n");
return 0;
}
void test_func2(int arg1,int arg2)
{
printf("test_func2\n");
}
〔リスト39〕使っていない変数(test164.c)
/*
*使用していないローカル変数とスタティック変数
*/
#include <stdio.h>
void test_func2(int arg1,int arg2);
void test_func3();
static int a;
int main(void)
{
long b;
printf("main\n");
return 0;
}
void test_func2(int arg1,int arg2)
{
printf("test_func2\n");
}
|
|
$ gcc test163.c
$ gcc test163.c -Wunused-parameter
test163.c: 関数 `test_func2' 内:
test163.c:12: 警告: 引数 `arg1' が未使用です
test163.c:12: 警告: 引数 `arg2' が未使用です
$
■-Wunused-variable
このオプションを付けると,定義したスタティック変数やローカル変数をプログラム中で使用していない場合にワーニングメッセージを出力します.
それを抑止するには,先と同様にGCC3.3の新機能であるunused attribute の指定をすることです.それに関しては「GCC2.95から追加変更のあったその他言語仕様の補足と検証」の回で解説する予定です.
リスト39のコンパイル結果は以下のようになります.
〔リスト39〕使っていない変数(test164.c)
|
/*
*使用していないローカル変数とスタティック変数
*/
#include <stdio.h>
void test_func2(int arg1,int arg2);
void test_func3();
static int a;
int main(void)
{
long b;
printf("main\n");
return 0;
}
void test_func2(int arg1,int arg2)
{
printf("test_func2\n");
}
|
|
$ gcc test164.c
$ gcc test164.c -Wunused-variable
test164.c: 関数 `main' 内:
test164.c:10: 警告: 変数 `b' は使われませんでした
test164.c: トップレベル:
test164.c:7: 警告: `a' が定義されましたが 使われませんでした
$
次回は,GCC2.95から追加変更のあったオプションの補足と検証の続きを解説する予定です.
|