警告を要求/抑止するオプション

 次に,ワーニングを出すか出さないかを制御するオプションについて説明します.

■-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++ &amp;&amp; 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から追加変更のあったオプションの補足と検証の続きを解説する予定です.

Copyright 2003 岸 哲夫

Copyright 1997-2024 CQ Publishing Co.,Ltd.

 

NEW記事内インデックス    連載インデックスはこちら   Interfaceのトップ
C言語の方言を扱うオプション
LINK関連のオプション/メッセージ関連のオプション
プリプロセッサ関連のオプション
◆警告を要求/抑止するオプション