プロが教える店舗&オフィスのセキュリティ対策術

C言語について質問があります。
以下のようにアドレスの個数とアドレスを入力として与えると、

入力
2
255.22.1.0
40.3.5.0

それぞれのアドレスが「.」か、0以上255以下の数値で構成されているなら、Yes、
そうでないなら、Noを出力させようと思い、プログラムを作成しました。
そして、以下のような入力に対して、以下の様な出力結果になると予想し、実行したところ、

入力
2
256.71.99.0
31.55.6.0
出力
No
Yes

下のような出力結果になりました。
出力
No
Abort trap: 6

また、入力値を変えて実行してみたところ、Yesと出力されるはずが、Noが出力されたり、Noと出るはずが、Yesと出力されました。
どなたか、原因が分かる方いましたら、回答お願いします。

補足ですが、配列suchiの生成時に、配列の大きさを変えてみると、出力結果が変わることがありますが、それでも同じような結果になりました。

「作成したプログラム」
#include <stdio.h>
#include <ctype.h>
#include <stdlib.h>
int main(void){
int M = 0;//アドレスの数
scanf("%d",&M);
char IP[M][101];//M個のアドレス
int i = 0;
for(i = 0;i < M;i++){
scanf("%s",IP[i]);
}
for(i = 0;i < M;i++){
int j =0;
int hantei = -1;//文字列が書式に会ったものかを判定した結果を格納
while(IP[i][j]!='\n'){
char suchi[5];//数字を保管するためのもの
//現在の文字が数値の時
if(isdigit((unsigned char)IP[i][j])){
//文字が数値の間、連結する
while(isdigit((unsigned char)IP[i][j])){
sprintf(suchi,"%s%c",suchi,IP[i][j]);
j++;
}
//数値が条件を満たしている時
if(atoi(suchi)<=255&&atoi(suchi)>=0)hantei=1;
else {
hantei=-1;
break;
}
//配列suchiの中身を空にする
suchi[0] ='\0';
}
//現在の文字が'.'のとき
else if(IP[i][j]=='.'){
j++;
hantei = 1;
}
else if(IP[i][j]=='\0'){
break;
}
//いずれの条件にも一致しない時
else{
j++;
hantei = -1;
}
if(IP[i][j]=='\n'){
break;
}
if(hantei<0){
break;
}
}
if(hantei==1)printf("Yes\n");
else if(hantei==-1) printf("No\n");
}
return 0;
}

A 回答 (5件)

うまくいかない原因はほかの方が回答されていますが、


それ以前の問題として、IPアドレスのチェックの要件が不足しています。
ソースから読み取れる、チェック内容は以下の内容です。
1.数字は0~255の数値であること
2.数字(0~9)、ドット(.)以外の文字は使用しないこと
しかしながら、更に以下のチェックも必要なはずです。
3.ドットの数は3個であること
4.ドットの両端に数字があること
従って、
1)ドットで始まるアドレスはエラー
2)ドットで終わるアドレスはエラー
3)ドットが連続するアドレスはエラー
となります。
5.数字の先頭が0の場合は、次に数字が来ないこと
例えば、00、012、のような数字はエラーとすること
(これについては異論があるかも知れないが、ここではこのケースはエラーということにする)

尚、IP[i,j]のように2次元の配列を操作していますが、今回のようなケースでは
1次元の処理(1つの文字列の処理)を複数回繰り返すようにしたほうが、簡単ですっきりします。
又、atoi関数に渡す文字列をchar suchi[5]で定義し、そこに数字を格納していますが、
数字の開始位置を記憶し、その位置のアドレスをatoiに渡すようにしています。
(12.34 の場合、1のアドレス、3のアドレスをatoiに渡します。)

上記を踏まえて、作り直したものが、以下のプログラムです。
関数、check_addrに1つの文字列(IPアドレス)を渡し、チェックOKなら1を返します。
チェックNGの場合は、-1でなく、エラーの理由も判別できるように-1から-9の値を返します。
--------------------------------------------------------------------------------------
#include <stdio.h>
#include <ctype.h>
#include <stdlib.h>
#define ORIGINAL (1)
//アドレスチェック
//戻り値=1:正常終了
//戻り値<0:異常終了
int check_addr(char ip[]){
int i = 0;
int dot_ctr = 0; //ドット(.)のカウンター
int bx = -1; //数字列の開始位置の添え字
while(ip[i] != '\0'){ //文字列の最後の\nはscanfは取り込まないので\0で判定する
if (isdigit((unsigned char)(ip[i]))){
if (bx == -1){
bx = i; //数字列の先頭の位置を記憶する
}else{
if (ip[bx] == '0') return -1; //先頭の数字が0なら以降に数字があるのはエラー
}
if (i-bx > 2) return -2; //数字列が3桁を超過ならエラー
}else if (ip[i] == '.'){
dot_ctr++;
if (dot_ctr > 3) return -3; //.の数が3個を超過ならエラー
if (bx == -1) return -4; //.がいきなり出現場合はエラー
if (atoi(&ip[bx]) > 255) return -5; //数字列の値が255を超過(マイナスにはならないので上限のみのチェックで良い)
bx = -1; //開始位置の添え字を初期化
}else{
return -6; //数字、ドット(.)以外はエラー
}
i++; //次の文字を処理
}
if (dot_ctr != 3) return -7; //.の数が3個でないのはエラー
if (bx == -1) return -8; //.で終了しているのはエラー
if (atoi(&ip[bx]) > 255) return -9; //数字列の値が255を超過ならエラー
return 1; //正常終了
}
#if ORIGINAL
int main(void){
int M = 0;//アドレスの数
scanf("%d",&M);
char IP[M][101];//M個のアドレス
int i = 0;
for(i = 0;i < M;i++){
scanf("%s",IP[i]);
}
for(i = 0;i < M;i++){
int hantei;//文字列が書式に会ったものかを判定した結果を格納
hantei = check_addr(IP[i]);
if(hantei==1)printf("Yes\n");
else printf("No:%d\n",hantei); //hanteiが1以外ならエラー
}
return 0;
}
#else
int main(void){
int M = 0;//アドレスの数
char *IP[] = {
"1.2.3.4",
"255.255.255.255",
"0.0.0.0",
"01.2.3.4",
"1.00.3.4",
"1111.2.3.4",
"1.2.3.4.5",
".2.3.4",
"1.2..4",
"256.2.3.4",
"a.2.3.4",
"1.2.3",
"1.2.3.",
"1.2.3.256",
};
M = sizeof(IP)/sizeof(char*);
int i = 0;
for(i = 0;i < M;i++){
int hantei = -1;//文字列が書式に会ったものかを判定した結果を格納
printf("<%s>",IP[i]);
hantei = check_addr(IP[i]);
if(hantei==1)printf("Yes\n");
else printf("No:%d\n",hantei); //hanteiが1以外ならエラー(エラーコードも印字)
}
return 0;
}
#endif

--------------------------------------------------------------------------------------
尚、プログラムは、IPアドレスを画面から入力する方法(従来の方法)とIP用の文字列を予め作成しておき
それを渡す方法(第二の方法)のどちらかを選択できるようにしてあります。
#define ORIGINAL (1) とすると従来の方法 になります。
#define ORIGINAL (0) とすると第二の方法 になります。

第二の方法の実行結果は、以下の通りです。
<1.2.3.4>Yes
<255.255.255.255>Yes
<0.0.0.0>Yes
<01.2.3.4>No:-1
<1.00.3.4>No:-1
<1111.2.3.4>No:-2
<1.2.3.4.5>No:-3
<.2.3.4>No:-4
<1.2..4>No:-4
<256.2.3.4>No:-5
<a.2.3.4>No:-6
<1.2.3>No:-7
<1.2.3.>No:-8
<1.2.3.256>No:-9
    • good
    • 0

「配列suchiの生成時に、配列の大きさを変えてみると、出力結果が変わることがありますが、それでも同じような結果になりました」の原因は #3 だなぁ.



ちなみに sprintf で入出力の範囲が重なるときは #1 に書いたように未定義動作>#2. この辺の規定は C89 では文章でしか書きようがなかったんだけど, C99 で restrict が導入されたことによりコードを見るだけで分かるようになりました.
    • good
    • 0
この回答へのお礼

細かいところまで、ご指摘ありがとうございます。指摘していただいたところを修正したところ、予想通りの出力がされました。ありがとうございます。

お礼日時:2016/08/27 12:24

>//配列suchiの中身を空にする


>suchi[0] ='\0';

最初のループの時、配列suchiには何が入ってるんでしょうかね?
宣言したローカル変数は全ビットが0になっているんですか??
    • good
    • 0
この回答へのお礼

指摘していただいた箇所を修正したところ、入力に対して、予想した出力がされました。
ありがとうございます。

お礼日時:2016/08/27 12:20

sprintf(suchi,"%s%c",suchi,IP[i][j]);



入出力で範囲が重なるときって、ちゃんと動くんでしたっけ?
https://linuxjm.osdn.jp/html/LDP_man-pages/man3/ …
    • good
    • 0
この回答へのお礼

無事に、予想した出力結果がでました。
ありがとうございます。

お礼日時:2016/08/27 12:22

あんまりまじめにチェックしてないけどとりあえず


・while(IP[i][j]!='\n'){ の判定条件がおかしい
・sprintf(suchi,"%s%c",suchi,IP[i][j]); は未定義動作
と 2点指摘しておく.
    • good
    • 0

お探しのQ&Aが見つからない時は、教えて!gooで質問しましょう!