1

32ビット整数をIPアドレスに変換するためのこのサンプルコードがあります。


#include <stdio.h>
int main()
{
 unsigned int c ;
 unsigned char* cptr  = (unsigned char*)&c ;
 while(1)
 {
  scanf("%d",&c) ;
  printf("Integer value: %u\n",c);
  printf("%u.%u.%u.%u \n",*cptr, *(cptr+1), *(cptr+2), *(cptr+3) );
 }
}

このコードは、入力に対して誤った出力を提供します2249459722。しかし、私が交換するとき

scanf("%d",&c) ;
scanf("%u",&c) ;
出力は正しいものになります。

PS:私はとについて知っていinet_ntopますinet_pton
私はそれらを提案する以外の答えを期待しています。

4

4 に答える 4

13

あなたは「罪深く」コーディングしています(遅かれ早かれあなたを傷つけるであろう多くの間違いを犯します-ほとんどの場合もっと早く)。まず、整数が正しいエンディアンであると想定しています。一部のマシンでは、間違っています-Intelマシン、PowerPCまたはSPARCマシンのいずれかです。

一般に、間違った結果が得られたと言うだけでなく、実際に得られた結果を表示する必要があります。また、期待される結果を表示する必要があります。それは人々があなたの期待をデバッグするのに役立ちます。


これが私の修正バージョンのコードです-入力を要求する代わりに、指定した値を想定しているだけです。

#include <stdio.h>
int main(void)
{
    unsigned int c = 2249459722;
    unsigned char* cptr  = (unsigned char*)&c;
    printf("Integer value:  %10u\n", c);
    printf("Integer value:  0x%08X\n", c);
    printf("Dotted decimal: %u.%u.%u.%u \n", *cptr, *(cptr+1), *(cptr+2), *(cptr+3));
    return(0);
}

Mac(Intel、リトルエンディアン)でコンパイルすると、出力は次のようになります。

Integer value:  2249459722
Integer value:  0x8614080A
Dotted decimal: 10.8.20.134 

私のSun(SPARC、ビッグエンディアン)でコンパイルすると、出力は次のようになります。

Integer value:  2249459722
Integer value:  0x8614080A
Dotted decimal: 134.20.8.10 

(SPARCでGCC 4.4.2を使用すると、警告が表示されます。

xx.c:4: warning: this decimal constant is unsigned only in ISO C90

MacでGCC4.2.1を使用する-多くの警告が有効になっている(gcc -std=c99 -pedantic -Wall -Wshadow -Wpointer-arith -Wstrict-prototypes -Wmissing-prototypes -Werror)-その警告は表示されません。これは興味深いことです。)U整数定数に接尾辞を追加することで、警告を削除できます。


問題を調べる別の方法は、次のコードと上記の非常に煩雑なコンパイラ設定で示されています。

#include <stdio.h>

static void print_value(unsigned int c)
{
    unsigned char* cptr  = (unsigned char*)&c;
    printf("Integer value:  %10u\n", c);
    printf("Integer value:  0x%08X\n", c);
    printf("Dotted decimal: %u.%u.%u.%u \n", *cptr, *(cptr+1), *(cptr+2), *(cptr+3));
}

int main(void)
{
    const char str[] = "2249459722";
    unsigned int c = 2249459722;

    printf("Direct operations:\n");
    print_value(c);

    printf("Indirect operations:\n");
    if (sscanf("2249559722", "%d", &c) != 0)
        printf("Conversion failed for %s\n", str);
    else
        print_value(c);
    return(0);
}

これは(-Werror設定のために)コンパイルに失敗し、次のメッセージが表示されます。

cc1: warnings being treated as errors
xx.c: In function ‘main’:
xx.c:20: warning: format ‘%d’ expects type ‘int *’, but argument 3 has type ‘unsigned int *’

設定を削除する-Werrorとコンパイルされますが、次に発生する問題が表示されます。失敗する可能性のある関数からのエラー表示をチェックしないという問題です。

Direct operations:
Integer value:  2249459722
Integer value:  0x8614080A
Dotted decimal: 10.8.20.134 
Indirect operations:
Conversion failed for 2249459722

基本的に、sscanf()関数は文字列を符号付き整数に変換できなかったことを報告します(値が大きすぎて収まらないため、GCC 4.4.2の警告を参照)が、コードはからのエラーリターンをチェックしていなかったためsscanf()、当時残っていた値を使用しcていました。

したがって、コードには複数の問題があります。

  • 特定のアーキテクチャを想定しています(ビッグエンディアンも存在することを認識するのではなく、リトルエンディアン)。
  • 多くの警告が有効になっているコンパイラを使用すると、正しくコンパイルされません-正当な理由があります。
  • 失敗する可能性のある関数が実際に成功したかどうかはチェックされません。

Alokのコメント

はい、テストsscanf()は間違っています。これが、コードレビューがある理由であり、テストしているコードを投稿するのに役立つ理由でもあります。

私は今少し戸惑っています-すぐには説明できない一貫した行動をとっています。明らかな改訂(MacOS X 10.6.2、GCC 4.2.1、32ビットおよび64ビットのコンパイルでのテスト)で、私はあまり正気ではない答えを1つ得ます。よりモジュール化して書き直すと、正気の答えが得られます。

+ cat yy.c
#include <stdio.h>

static void print_value(unsigned int c)
{
    unsigned char* cptr  = (unsigned char*)&c;
    printf("Integer value:  %10u\n", c);
    printf("Integer value:  0x%08X\n", c);
    printf("Dotted decimal: %u.%u.%u.%u \n", *cptr, *(cptr+1), *(cptr+2), *(cptr+3));
}

int main(void)
{
    const char str[] = "2249459722";
    unsigned int c = 2249459722;

    printf("Direct operations:\n");
    print_value(c);

    printf("Indirect operations:\n");
    if (sscanf("2249559722", "%d", &c) != 1)
        printf("Conversion failed for %s\n", str);
    else
        print_value(c);
    return(0);
}


+ gcc -o yy.32 -m32 -std=c99 -pedantic -Wall -Wshadow -Wpointer-arith -Wstrict-prototypes -Wmissing-prototypes yy.c
yy.c: In function ‘main’:
yy.c:20: warning: format ‘%d’ expects type ‘int *’, but argument 3 has type ‘unsigned int *’


+ ./yy.32
Direct operations:
Integer value:  2249459722
Integer value:  0x8614080A
Dotted decimal: 10.8.20.134 
Indirect operations:
Integer value:  2249559722
Integer value:  0x86158EAA
Dotted decimal: 170.142.21.134 

値170.142.21.134についての適切な説明がありません。しかし、現時点では、私のマシンでは一貫しています。

+ gcc -o yy.64 -m64 -std=c99 -pedantic -Wall -Wshadow -Wpointer-arith -Wstrict-prototypes -Wmissing-prototypes yy.c
yy.c: In function ‘main’:
yy.c:20: warning: format ‘%d’ expects type ‘int *’, but argument 3 has type ‘unsigned int *’


+ ./yy.64
Direct operations:
Integer value:  2249459722
Integer value:  0x8614080A
Dotted decimal: 10.8.20.134 
Indirect operations:
Integer value:  2249559722
Integer value:  0x86158EAA
Dotted decimal: 170.142.21.134 

同じ値-32ビットではなく64ビットでも。おそらく問題は、私が未定義の振る舞いを説明しようとしていることです。これは、定義上、多かれ少なかれ説明できない(説明できない)ものです。

+ cat xx.c
#include <stdio.h>

static void print_value(unsigned int c)
{
    unsigned char* cptr  = (unsigned char*)&c;
    printf("Integer value:  %10u\n", c);
    printf("Integer value:  0x%08X\n", c);
    printf("Dotted decimal: %u.%u.%u.%u \n", *cptr, *(cptr+1), *(cptr+2), *(cptr+3));
}

static void scan_value(const char *str, const char *fmt, const char *tag)
{
    unsigned int c;
    printf("Indirect operations (%s):\n", tag);
    fmt = "%d";
    if (sscanf(str, fmt, &c) != 1)
        printf("Conversion failed for %s (format %s \"%s\")\n", str, tag, fmt);
    else
        print_value(c);
}

int main(void)
{
    const char str[] = "2249459722";
    unsigned int c = 2249459722U;

    printf("Direct operations:\n");
    print_value(c);
    scan_value(str, "%d", "signed");
    scan_value(str, "%u", "unsigned");

    return(0);
}

このように関数の引数を使用すると、GCCは偽の形式を見つけることができなくなります。

+ gcc -o xx.32 -m32 -std=c99 -pedantic -Wall -Wshadow -Wpointer-arith -Wstrict-prototypes -Wmissing-prototypes xx.c


+ ./xx.32
Direct operations:
Integer value:  2249459722
Integer value:  0x8614080A
Dotted decimal: 10.8.20.134 
Indirect operations (signed):
Integer value:  2249459722
Integer value:  0x8614080A
Dotted decimal: 10.8.20.134 
Indirect operations (unsigned):
Integer value:  2249459722
Integer value:  0x8614080A
Dotted decimal: 10.8.20.134 

結果はここで一貫しています。

+ gcc -o xx.64 -m64 -std=c99 -pedantic -Wall -Wshadow -Wpointer-arith -Wstrict-prototypes -Wmissing-prototypes xx.c


+ ./xx.64
Direct operations:
Integer value:  2249459722
Integer value:  0x8614080A
Dotted decimal: 10.8.20.134 
Indirect operations (signed):
Integer value:  2249459722
Integer value:  0x8614080A
Dotted decimal: 10.8.20.134 
Indirect operations (unsigned):
Integer value:  2249459722
Integer value:  0x8614080A
Dotted decimal: 10.8.20.134

そして、これらは32ビットの場合と同じです。私は公式に困惑しています。主な観察結果は正確なままです。注意して、コンパイラの警告に注意して(そしてコンパイラの警告を引き出して)、「すべての世界がIntelチップ上で実行されている」と想定しないでください(以前は「すべての世界がVAX」、昔々!)。

于 2010-01-09T08:06:54.923 に答える
5

%dは符号付き整数用です

%uは符号なし整数用です

編集:

入力が実際にどのように解釈されるかを確認するには、プログラムを次のように変更してください。

#include <stdio.h>
int main()
{
 unsigned int c ; 
 unsigned char* cptr  = (unsigned char*)&c ;
 while(1)
 {
  scanf("%d",&c) ;
  printf("Signed value: %d\n",c);
  printf("Unsigned value: %u\n",c);
  printf("%u.%u.%u.%u \n",*cptr, *(cptr+1), *(cptr+2), *(cptr+3) );
 }
}

INT_MAXより大きい数値を指定すると、左端のビットは1になります。これは、負の値を持つ符号付き整数であることを示しています。数値は2の補数として解釈されます

于 2010-01-09T07:55:45.917 に答える
1

あなたの主な質問に答えるには:

scanf("%d", &c);

scanf()変換される入力をデータ型に表現できない場合のの動作は未定義です。 2249459722お使いのマシンはに収まらないintのでscanf()、ゴミをに保存するなど、何でもできますc

Cでは、 typeは〜intの範囲の値を格納できることが保証されています。との間の値が保証されます。したがって、そのように、さえ収まる必要はありません。 ただし、 (2 32 -1)までの値を格納できるため、次を使用する必要があります。-32767+32767unsigned int0655352249459722unsigned intunsigned long4294967295unsigned long

#include <stdio.h>
int main()
{
    unsigned long c ;
    unsigned char *cptr  = (unsigned char*)&c ;
    while(1)
    {
        if (scanf("%lu", &c) != 1) {
            fprintf(stderr, "error in scanf\n");
            return 0;
        }
        printf("Input value: %lu\n", c);
        printf("%u.%u.%u.%u\n", cptr[0], cptr[1], cptr[2], cptr[3]);
    }
    return 0;
}

C99コンパイラを使用している場合は、の代わりに#include <inttypes.h>使用できます。呼び出しはになりますuint32_tunsigned longscanf()scanf("%" SCNu32, &c);

于 2010-01-09T23:45:09.863 に答える
1

これを書くための正しいエンディアンセーフな方法は次のとおりです。

printf("Dotted decimal: %u.%u.%u.%u \n", (c >> 24) & 0xff, (c >> 16) & 0xff, (c >> 8) & 0xff, (c >> 0) & 0xff);
于 2010-01-10T17:27:23.613 に答える