代码之家  ›  专栏  ›  技术社区  ›  sud03r

逐字节打印4字节整数时出现意外行为

  •  0
  • sud03r  · 技术社区  · 15 年前

    我有一个将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) ;
    输出结果是正确的。

    附笔 我知道 inet_ntop inet_pton .
    我希望得到答案而不是建议。

    4 回复  |  直到 14 年前
        1
  •  12
  •   Jonathan Leffler vy32    15 年前

    您正在编码' sinfully (犯了很多错误,迟早会伤害到你——大多是更早)。首先,假设整数的尾数是正确的。在某些计算机上,您可能是错误的-在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,Little Endian)上编译时,输出为:

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

    在Sun上编译时(sparc,big endian),输出为:

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

    (在SPARC上使用GCC4.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 *’
    

    移除 -错误 设置并编译,但随后显示下一个问题,即不检查可能失败的函数的错误指示:

    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中的警告),但您的代码没有检查返回的错误。 sSCAN() ,所以您使用的是 c 当时。

    因此,您的代码存在多个问题:

    • 它假设了一个特定的体系结构(小endian而不是承认big endian也存在)。
    • 当使用启用了大量警告的编译器时,它不会干净地编译——这是有原因的。
    • 它不检查可能失败的函数是否实际成功。

    Alok评论

    是的,测试在 sSCAN() 是错的。这就是为什么您有代码评审,以及为什么它有助于发布您正在测试的代码。

    我现在有点困惑了——我无法立即解释始终如一的行为。有了明显的修改(在MacOS X 10.6.2、GCC4.2.1、32位和64位编译上测试),我得到了一个不太明智的答案。当我更模块化地重写时,我得到了一个明智的答案。

    + 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 
    

    相同的值-即使是64位而不是32位。也许问题是我试图解释不明确的行为,这或多或少是由定义无法解释(无法解释)。

    + 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位的情况是一样的。我正式感到困惑。主要观察结果仍然准确-小心,注意编译器警告(并引发编译器警告),不要假设“所有的世界都在英特尔芯片上运行”(很久以前它曾是“不要假设所有的世界都是一个VAX”)。.

        2
  •  5
  •   hobodave    15 年前

    %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。这表示它是一个带负号的有符号整数。然后将数字解释为 two's complement

        3
  •  1
  •   Alok Singhal    15 年前

    要回答您的主要问题:

    scanf("%d", &c);
    

    scanf() 当要转换的输入无法表示为数据类型时,的行为未定义。 2249459722 你的机器不适合 int 如此 SCAN() 可以做任何事情,包括在 c .

    在C中, int 类型保证能够存储范围内的值 -32767 +32767 . 安 unsigned int 保证值介于 0 65535 .所以,就这样, 二十二亿四千九百四十五万九千七百二十二 甚至不需要适合 无符号整型 . unsigned long 但是,最多可以存储值 4294967295 (二) 三十二 -1),因此您应该使用 无符号长 以下内容:

    #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_t 而不是 无符号长 . 这个 SCAN() 呼叫变为 scanf("%" SCNu32, &c);

        4
  •  1
  •   starblue    15 年前

    正确的结尾安全的方式是

    printf("Dotted decimal: %u.%u.%u.%u \n", (c >> 24) & 0xff, (c >> 16) & 0xff, (c >> 8) & 0xff, (c >> 0) & 0xff);