🎄teratailクリスマスプレゼントキャンペーン2024🎄』開催中!

\teratail特別グッズやAmazonギフトカード最大2,000円分が当たる!/

詳細はこちら
C

C言語は、1972年にAT&Tベル研究所の、デニス・リッチーが主体となって作成したプログラミング言語です。 B言語の後継言語として開発されたことからC言語と命名。そのため、表記法などはB言語やALGOLに近いとされています。 Cの拡張版であるC++言語とともに、現在世界中でもっとも普及されているプログラミング言語です。

Q&A

解決済

5回答

2110閲覧

数値の先頭が0か判定したいです。

ht3433

総合スコア19

C

C言語は、1972年にAT&Tベル研究所の、デニス・リッチーが主体となって作成したプログラミング言語です。 B言語の後継言語として開発されたことからC言語と命名。そのため、表記法などはB言語やALGOLに近いとされています。 Cの拡張版であるC++言語とともに、現在世界中でもっとも普及されているプログラミング言語です。

0グッド

0クリップ

投稿2019/11/27 05:32

前提・実現したいこと

数値の先頭が0か判定したいです。
ソースコードのassertの内容をみればわかるとは思いますが、アドレスの途中が「01」となっており、アドレスの表記の仕方が間違っているので、これをエラーにしたいのですが、
if( strstr( src, "01." ) ){
return( 0 );
のようなソースコードしか思い浮かばず、詰まっています。
また、作成したソースコードはinet_ptonのような動きをします。
お力添えいただければ幸いです。
よろしくお願い致します。

該当のソースコード

C言語

1#include <assert.h> 2#include <arpa/inet.h> 3#include <stdio.h> 4#include <string.h> 5 6int inet_pton( const char *src, void *dst ){ 7 8 // 変数の宣言 9 int suuti1, suuti2, suuti3, suuti4; 10 11 // 共用体の型の定義 12 union number{ 13 uint32_t number1; 14 uint8_t number2[4]; 15 }; 16 17 // 共用体の変数名の宣言 18 union number No; 19 20 // srcの文字列を数値に変換し、それが4つあるか判定 21 if ( sscanf( src, "%d.%d.%d.%d", &suuti1, &suuti2, &suuti3, &suuti4 ) != 4 ){ 22 return( 0 ); 23 } 24 25 // 数値に変換したものを変数に格納する 26 No.number2[0] = suuti1; 27 No.number2[1] = suuti2; 28 No.number2[2] = suuti3; 29 No.number2[3] = suuti4; 30 31 // number1のメモリをdstにコピーする 32 memcpy( dst, &No.number1, 4 ); 33 return( 1 ); 34} 35 36int main(){ 37 int result; 38 struct in_addr in_addr; 39 40 result = inet_pton("130.0.7.23", &in_addr); 41 assert(result == 1 && in_addr.s_addr == ((23<<24)|(7<<16)|(0<<8)|130)); 42 43 result = inet_pton("130.01.7.23", &in_addr); 44 assert(result == 0); 45 46 return(0); 47}

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

jimbe

2019/11/27 07:44

> アドレスの途中が「01」となっており、アドレスの表記の仕方が間違っている なぜ "01" が間違いなのか教えて頂けますか. ゼロ始まりだからなのか, 8進数になってしまうからなのか, (ipアドレスとして) 130.0.7.23 でなければならないのに 130.1.7.23 だからなのか….
yumetodo

2019/11/27 16:34

そもそも論ですが、 #include <arpa/inet.h> してらっしゃるのですが、そのヘッダーにinet_ptonは定義されているのですから同名の関数は定義できませんよ。 includeをやめるか名前を変えましょう。
kazuma-s

2019/11/27 16:56

おそらく、質問者は C と C++ の区別がつかず、ソースファイルの拡張子を .cpp にしてしまって C++ としてコンパイルされているのではないでしょうか? C++ では引数の異なる同名の関数を定義できます。なお、ヘッダにあるのは、定義ではなく、宣言です。
maisumakun

2019/11/28 00:59

> アドレスの途中が「01」となっており、アドレスの表記の仕方が間違っているので すでに他の方からも指摘がありますが、「**.01.**.**」のような書き方でも、IPv4アドレスとしては「正当」とされます。それでもチェックしたいのでしょうか。
guest

回答5

0

ベストアンサー

C

1 char buf[32]; 2 sprintf(buf, "%d.%d.%d.%d", suuti1, suuti2, suuti3, suuti4); 3 if (strcmp(src, buf)) return 0;

inet_pton の中の sscanf の次にこのコードを追加するというのはどうでしょうか?

追記
uint8_t へのキャストを付けたほうが、値の範囲チェックもできてよいでしょう。

C

1 char buf[32]; 2 sprintf(buf, "%d.%d.%d.%d", 3 (uint8_t)suuti1, (uint8_t)suuti2, (uint8_t)suuti3, (uint8_t)suuti4); 4 if (strcmp(src, buf)) return 0;

投稿2019/11/27 10:07

編集2019/11/27 23:11
kazuma-s

総合スコア8224

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

rubato6809

2019/11/27 22:39

逆転の発想ですよね。案外これが簡単で確実だったりします。
ht3433

2019/11/28 02:32

回答していただき、ありがとうございます。 if (strcmp(src, buf)) return 0; 上記のコードですが、これはsrcとbufをstrcmpで比較しているのは分かるのですが、比較してどうだったらreturn 0;をするという条件がないように思うのですがどうなのでしょうか? 理解力がなくて大変申し訳ございませんが教えていただけるとありがたいです。
rubato6809

2019/11/28 02:41

strcmp() 関数が何を返すか、調べましたか?
ht3433

2019/11/28 02:51

strcmp() 関数が何を返すかは src = bufのとき: 0 src > bufのとき: 正の整数 src < bufのとき: 負の整数 というふうに条件によって変わるのですよね?
rubato6809

2019/11/28 03:17

そうです。 では、次、if (条件式) について。 int 型変数 x があり、 if (x) printf("A\n"); else printf("B\n"); というコードがあったら、どういう意味になるか、どこかで習わなかった?
ht3433

2019/11/28 04:42

if (x) はif(x!=0)という意味で、C言語では、0以外の値は真、0は偽になるので、もしxが0でなかったらAを出力し、0であればBを出力するという意味になると思います。 となると、 if (strcmp(src, buf))return 0;は もしsrcとbufを比べて違いがあれば、return 0;をするという意味になるのでしょうか?
rubato6809

2019/11/28 04:51

そうです。 じゃあ、どんな時に2つの文字列が違うのか、一致するのか・・・
ht3433

2019/11/28 05:37

このコードが実行されたときはsrcとbufの文字列は同じですが、 result = inet_pton("130.01.7.23", &in_addr); このコードが実行されたときはsrcの文字列が変化して、bufと違う文字列になり、2つの文字列が異なるということでしょうか?
rubato6809

2019/11/28 05:48

聞いてばかりいないで、自分の手を動かして試してみたら良いのでは
ht3433

2019/11/28 08:03

確かにおっしゃるとおりですね。 デバックで調べてみると、bufの文字列は130.0.7.23で固定されますが、 srcの文字列はresult = inet_pton("130.01.7.23", &in_addr);のようなコードが実行されるたびに 文字列が変換されていました。 これで、問題がなんとか解決できました。 ありがとうございました。
ht3433

2019/11/29 06:11

解決済みの質問で申し訳ないのですが、 uint8_t へのキャストを付けたほうが、値の範囲チェックもできてよいでしょう。 というコメントについて質問です。 srcの文字列に「256」があってもsrcとbufの文字列は異なるとなるのでsyが、これはなぜでしょうか? 下記のようなデバックをして確かめてみたのですが、srcとbufの文字列が同じだったので。 inet_pton (src=0x400beb "130.256.7.23", dst=0x7fffffffe4a0) at kadai.cc:21 21 if ( sscanf( src, "%u.%u.%u.%u", &suuti1, &suuti2, &suuti3, &suuti4 ) != 4 ){ (gdb) n 26 sprintf( buf, "%u.%u.%u.%u", ( uint8_t )suuti1, ( uint8_t )suuti2, ( uint8_t )suuti3, ( uint8_t )suuti4 ); (gdb) p suuti1 $1 = 130 (gdb) p suuti2 $2 = 256 (gdb) p suuti3 $3 = 7 (gdb) p suuti4 $4 = 23 (gdb) n 29 if ( strcmp( src, buf ) ){ (gdb) n 30 return( 0 );
kazuma-s

2019/11/29 06:55

gdb 使っているんだったら、buf や (uint8_t)suuti2 の値を見ましょう。 (gdb) p src = $3 = 0x100403017 "130.256.7.23" (gdb) p buf $4 = "130.0.7.23\000\200\001\000\000\000@\373'\200\001\000...\000\000" (gdb) p suuti2 $5 = 256 (gdb) p (uint8_t) suuti2 $6 = 0 '\000' (gdb) quit uint8_t は、unsigned char です。符号なし8ビット整数です。 0~255 の範囲の値しか取れません。16進で 0x00~0xff です。 uint8_t にキャストすると 256(0x100) は 0(0x00) になります。 src の "130.256.7.23 が buf の "130.0.7.23" になって、 元の src が不正だったことが分かります。 uint8_t にキャストしないと buf が "130.256.7.23" となって不正が検出できません。
kazuma-s

2019/11/29 07:07

ソースファイルは kadai.cc ですか。 g++ でなく gcc でコンパイルしても、C++ としてコンパイルされます。 だから、<arpa/inet.h> に宣言されている inet_pton とは引数の異なる関数として定義できたんですね。 いいですか、あなたは C++ のプログラムを書いていますよ。
ht3433

2019/11/29 07:15

回答していただき、ありがとうございます! おかげさまで悩みがすっきりしました!
guest

0

こんにちは。

*srcが'0'でないこと(先頭が文字 '0' でない)、および、途中に ".0"がないことの2点を判定すればよいように思います。
でも、数字と'.'以外の文字が含まれていないなどのチェックは不要なのでしょうか?
十分なチェックを簡単に行うなら正規表現ライブラリを使うと楽です。(ちょっと学習はたいへんですが。)

投稿2019/11/27 05:53

編集2019/11/27 05:54
Chironian

総合スコア23272

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

yumetodo

2019/11/27 06:03

十分なチェックを正規表現だけでやるのはちょっと無理そう(specみつつ
ht3433

2019/11/27 06:45

回答していただき、ありがとうございます。 私としては、 if( strstr( src, ".0" ) ){ if( ! strstr( src, "0." ) ) { return( 0 ); } } のように「.0」のとき「0.」でないで判定できると思ったのですが、うまくいきませんでした。 原因を教えていただけると幸いです。
Chironian

2019/11/27 07:15

"130.0.7.23"と"130.01.7.23"は共に、"0."と".0"を含んでいるので、この2つで判定すると結果は同じになります。 また、strstrはアドレスを返却します。NULLの否定はtrueになると思いますが、有効なアドレスの否定はfalseにならないような。 ところで、私の回答も全然ダメですね。 `.`でトークン分解して4つのトークンが「2桁以上かつ0で始まるとエラー」みたいに真面目にチェックしないとうまくいかなさそうです。ついでに「各トークンが数字のみで構成されている」も含めるとそこそこ良さげな気がします。
ht3433

2019/11/27 08:05

回答していただき、ありがとうございます。 トークンで分解して4つのトークンが「2桁以上かつ0で始まるとエラー」になるように下記のソースコードを追加してみたのですが、 char *p; int num; int digit = 0; while( p != NULL ){ p = strtok( src, "." ); while( num != 0 ){ num = num / 10; ++digit; } if( digit >= 2 && src[0] == 0 ){ return( 0 ); } } で、下記のようなエラーが出てしまいました。 エラー: ‘const char*’ から ‘char*’ への無効な変換です [-fpermissive] p = strtok( src, "." ); エラー: initializing argument 1 of ‘char* strtok(char*, const char*)’ [-fpermissive] extern char *strtok (char *__restrict __s, const char *__restrict __delim) これはどのように対処すればよいのでしょうか?
Chironian

2019/11/27 08:47

strtokはsrcを書き換えますので char const*側をうけとれません。(その旨のエラーメッセージがでています。) 現在、srcへは"130.0.7.23"等の文字列定数を与えているので、srcは char const* 型でないと処理できません。 従って、文字列バッファを確保してsrcをそこへコピーし、コピー先をstrtokへ渡すことが考えられます。
ht3433

2019/11/27 09:16

回答していただき、ありがとうございます。 文字列バッファを確保してsrcをそこへコピーし、コピー先をstrtokへ渡してエラーもなかったのですが、 うまくいきませんでした。 if文の条件として、2桁以上かつ0で始まるとエラーにしているはずなのになぜならないのか不思議です。 何度も質問して申し訳ございませんが、教えていただけるとありがたいです。 追加したソースコード char buf[BUFSIZ]; char *p; int num; int digit = 0; strcpy( buf, src ); while( p != NULL ){ p = strtok( buf, "." ); while( num != 0 ){ num = num / 10; ++digit; } if( digit >= 2 && src[0] == 0 ){ return( 0 ); } }
Chironian

2019/11/27 09:36

「2桁以上かつ0で始まるとエラーにしている」となっていません。 パッとみて分かるレベルです。デバッグはご自身で実施下さい。
退会済みユーザー

退会済みユーザー

2019/11/27 15:56

---- 流れとは全然関係ないし、別に細かいことですが、横からちょっとだけ訂正させてください。割と上のコメントに 「NULLの否定はtrueになると思いますが、有効なアドレスの否定はfalseにならないような」 とありますが、慣習的に有効なアドレスの否定はtrueになりますよ。if文がおかしかったのは先頭を見ていないからです。 ---- C++で恐縮ですが、参考情報を貼っておきます。 暗黙の変換-ブーリアン変換 https://ja.cppreference.com/w/cpp/language/implicit_conversion ----
dodox86

2019/11/27 16:27

過去のご質問とそれらへいただいた回答へのコメントからの推測ですが、質問者ht3433さんは条件判断ifや繰り返しwhile, forなどを用いての制御構造をまだ充分に使いこなせていないように思えます。それが、「コードをこう書いたがうまくいかなかった」との記し方に表れています。コードは、頭の中でのひとつひとつの動作のイメージを表すだけのものとしなければダメです。具体的には、ご自分で書いたコードの一行一行の意味をご自分で説明できなければなりません。
rubato6809

2019/11/27 20:57

num,pに値が入ってないのに使ってるんじゃ? 初心者によくある数値と数字の区別がついてない? 基礎的なミスが見受けられる
rubato6809

2019/11/28 00:52

strtok()の使い方、やはりおかしい。こいつはちょっと厄介な関数ですよ。 使い方が分かるだけでも大変って感じかな。
dodox86

2019/11/28 01:21

この質問のコメントにおいてstrtokを使い始めたのは質問者さん本人ですが、strtokを使うにしても、そもそも使い方を理解しないまま本番コードに適用しようとしているのがまず誤りだと思うのです。その説明まで必要になってしまうので混乱しています。私のコメントもこれ以上は混乱を招きそうですので、以降は控えます。
guest

0

先頭が0か見るなら単にs[0]=='0'とかすればいいと思いますが、思うにやりたいのはIPv4のアドレスのパースでは・・・?
https://url.spec.whatwg.org/#concept-ipv4-parser
にあるようにもっと複雑なパースが必要になります。

あと前置の0は8進数として解釈するようです。

https://url.spec.whatwg.org/#ipv4-number-parser

  1. Otherwise, if input contains at least two code points and the first code point is U+0030 (0), then:

    1. Set validationErrorFlag.

    2. Remove the first code point from input.

    3. Set R to 8.

投稿2019/11/27 06:03

yumetodo

総合スコア5852

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

maisumakun

2019/11/27 06:52

%iを使えば、8進・10進・16進にも対応できそうですね。
yumetodo

2019/11/27 12:02

それだとチェックが不十分ですからstrtolを使うことになります。
dodox86

2019/11/27 16:08 編集

> あと前置の0は8進数として解釈するようです。 8進数を使うことが今は極端に少ない(umask以外、ほぼ皆無?)なので、"012"などで10進整数の12とならず、10となって???となる人をたまに見かけますね。
yumetodo

2019/11/27 16:23

それはたしかにそうですがそれはIPv4の仕様を策定した時代にさかのぼって文句を言うべきであって現代の我々は黙って従うより他にないのです・・・。なのでそもそも質問の0から始まるのを弾くというそのものが無意味だというのは私の回答です。
dodox86

2019/11/27 16:30

単なる余談で、yumetodoさんのご回答への指摘のつもりではありませんでした。タイミング的に不適切だったかもしれません。失礼致しました。
yumetodo

2019/11/27 16:32

大丈夫ですよ、こちらも余談のつもりでコメントしているので。
guest

0

4つの数字列を strtok() で切り出すことも、もちろん可能。
コードを見てもらえば、strtok() の呼出しは2箇所にあり、引数が違うことがわかるだろう。

  • p = strtok(buff, "."); 1回目
  • p = strtok(NULL, "."); 2回目以降

1回目は「全体の文字列("130.0.7.23")を渡すから、ここから順次、トークン(数字列)を切り出してくれ」みたい気持ちで、文字列(配列 buff)を渡す。
2回目以降は、NULLを渡して「さっきの続きで、次のトークンを切り出してくれ」、みたいな感じですね。

C

1#include <string.h> // for strtok(), strcpy() 2#include <stdlib.h> // for atoi() 3#include <ctype.h> // for isdigit() 4 5int user_pton( const char *src, void *dst ) 6{ 7 // 共用体の型の定義 8 union number{ 9 uint32_t number1; 10 uint8_t number2[4]; 11 }; 12 13 // 共用体の変数名の宣言 14 union number No; 15 16 char buff[30]; // BUFSIZE (== 512) も要らない 17 char *p; // strtok() で切り出した数字列 18 int digit = 0; // number2[] のインデックス 19 20 strcpy(buff, src); // 書き換えられる配列にコピー 21 p = strtok(buff, "."); // 最初の数字列 "130" を得る 22 while (p != NULL) { 23 // printf("DEBUG: [%d] = [%s]\n", digit, p); 24 /* 25 * ここで除外するのは "01" のように、 26 * 先頭が '0' で、さらに数字が続く場合 27 * ただし、p[1] が '\0' なら正常("0"とか) 28 */ 29 if (p[0] == '0' && isdigit(p[1])) 30 return 0; // 不正、"01"のような場合 31 32 // 数値に変換し、然るべき場所に格納(念の為にキャスト) 33 No.number2[digit] = (uint8_t) atoi(p); 34 digit++; // 次の桁へ 35 36 // 2つ目以降の数字列を切り出す 37 p = strtok(NULL, "."); 38 } 39 40 // number1のメモリをdstにコピーする 41 memcpy( dst, &No.number1, 4 ); 42 return 1; 43}

肝心の、先頭が0かの判定は、切り出した数字列の先頭2文字を検査すれば良いのではないか(数字と '.' だけで、英字等は含まれていないという条件)。
if (p[0] == '0' && isdigit(p[1]))

このような判定条件を考える時、いきなりプログラムを書こうとすると、考えが混乱するものです。いくつか具体的なデータ("0", "3", "02", "50"・・・)を紙に書きだして、考えを整理すると良いです。

ご覧の通り、数字列を切り出すから atoi() ですぐさま数値に変換でき、配列へ格納できます。strtok() は初心者にお勧めしにくい点があるけど、分かってしまえば、こんな風に手際よく処理できることがあります。
やり方はひとつではありません。基礎的レベルをおろそかにせず、ひとつひとつの動作をきちんと確認するなら、いろんなやり方を試せます・・・実は、これだけなら strtok() を使わなくても、ほぼ同じ程度のことができます。

投稿2019/11/28 13:19

編集2019/11/28 13:28
rubato6809

総合スコア1382

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

コンセプトとしては inet_pton の引数の文字列を . をターミネータとして分割(strpbrk を使う)
して、オクテット単位の複数の文字列に分割。分割結果が 4 以下で、それぞれが

  • 0 ただ一文字しかない
  • 0[1-7] か 0[1-7][0-7] か 0[123][0-7][0-7] ※8進数表現
  • [0-9] か [1-9][0-9] か 1[0-9][0-9] か 2[0-4][0-9] か 25[0-5]

の正規表現に合致するかどうか(全く合致しなければ弾く)、になるでしょうか。
C だと正規表現の処理が面倒か……

投稿2019/11/27 09:42

tacsheaven

総合スコア13703

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.36%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問