質問をすることでしか得られない、回答やアドバイスがある。

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

ただいまの
回答率

87.50%

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

解決済

回答 5

投稿

  • 評価
  • クリップ 0
  • VIEW 1,133

score 15

前提・実現したいこと

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

該当のソースコード

#include <assert.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <string.h>

int inet_pton( const char *src, void *dst ){

    // 変数の宣言
    int suuti1, suuti2, suuti3, suuti4;

    // 共用体の型の定義
    union number{
        uint32_t number1;
        uint8_t number2[4];
    };

    // 共用体の変数名の宣言
    union number No;

    // srcの文字列を数値に変換し、それが4つあるか判定
    if ( sscanf( src, "%d.%d.%d.%d", &suuti1, &suuti2, &suuti3, &suuti4 ) != 4 ){
       return( 0 );
    }

    // 数値に変換したものを変数に格納する
    No.number2[0] = suuti1;
    No.number2[1] = suuti2;
    No.number2[2] = suuti3;
    No.number2[3] = suuti4;

    // number1のメモリをdstにコピーする
    memcpy( dst, &No.number1, 4 );
    return( 1 );
}

int main(){
    int result;
    struct in_addr in_addr;

    result = inet_pton("130.0.7.23", &in_addr);
    assert(result == 1 && in_addr.s_addr == ((23<<24)|(7<<16)|(0<<8)|130));

    result = inet_pton("130.01.7.23", &in_addr);
    assert(result == 0);

    return(0);
}
  • 気になる質問をクリップする

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

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

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

質問への追記・修正、ベストアンサー選択の依頼

  • jimbe

    2019/11/27 16:44

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

    キャンセル

  • yumetodo

    2019/11/28 01:34

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

    キャンセル

  • kazuma-s

    2019/11/28 01:56

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

    キャンセル

  • maisumakun

    2019/11/28 09:59

    > アドレスの途中が「01」となっており、アドレスの表記の仕方が間違っているので

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

    キャンセル

回答 5

checkベストアンサー

+3

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


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

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

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

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2019/11/29 15: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" となって不正が検出できません。

    キャンセル

  • 2019/11/29 16:07

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

    キャンセル

  • 2019/11/29 16:15

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

    キャンセル

+3

こんにちは。

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

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2019/11/28 05:57

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

    キャンセル

  • 2019/11/28 09:52

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

    キャンセル

  • 2019/11/28 10:21

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

    キャンセル

+2

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

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

https://url.spec.whatwg.org/#ipv4-number-parser
  3. 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/28 01:23

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

    キャンセル

  • 2019/11/28 01:30

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

    キャンセル

  • 2019/11/28 01:32

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

    キャンセル

+1

コンセプトとしては 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 だと正規表現の処理が面倒か……

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

+1

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

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

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

#include <string.h>  // for strtok(), strcpy()
#include <stdlib.h>  // for atoi()
#include <ctype.h>   // for isdigit()

int user_pton( const char *src, void *dst )
{
    // 共用体の型の定義
    union number{
        uint32_t number1;
        uint8_t number2[4];
    };

    // 共用体の変数名の宣言
    union number No;

    char buff[30];   // BUFSIZE (== 512) も要らない
    char *p;         // strtok() で切り出した数字列
    int digit = 0;   // number2[] のインデックス

    strcpy(buff, src);      // 書き換えられる配列にコピー
    p = strtok(buff, ".");  // 最初の数字列 "130" を得る
    while (p != NULL) {
        //    printf("DEBUG: [%d] = [%s]\n", digit, p);
        /*
         * ここで除外するのは "01" のように、
         * 先頭が '0' で、さらに数字が続く場合
         * ただし、p[1] が '\0' なら正常("0"とか)
         */
        if (p[0] == '0' && isdigit(p[1]))
            return 0;              // 不正、"01"のような場合

        // 数値に変換し、然るべき場所に格納(念の為にキャスト)
        No.number2[digit] = (uint8_t) atoi(p);
        digit++;                   // 次の桁へ

        // 2つ目以降の数字列を切り出す
        p = strtok(NULL, ".");
    }

    // number1のメモリをdstにコピーする
    memcpy( dst, &No.number1, 4 );
    return 1;
}


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

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

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

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

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

  • ただいまの回答率 87.50%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る