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

时间:2010-01-09 07:51:51

标签: c ip-address endianness scanf

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

P.S :我知道inet_ntopinet_pton  我期待的答案不仅仅是建议那些答案。

4 个答案:

答案 0 :(得分:13)

你正在编码'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上使用GCC 4.4.2,我收到警告:

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

在Mac上使用GCC 4.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中遗留的任何值。

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

  • 它假设一个特定的架构(little-endian而不是认识到big-endian也存在)。
  • 在使用启用了大量警告的编译器时,它不能完全编译 - 这是有充分理由的。
  • 它不会检查可能失败的功能是否实际成功。

Alok的评论

是的,sscanf()上的测试是错误的。这就是为什么你有代码审查,以及为什么它有助于发布你正在测试的代码。

我现在有点困惑 - 得到一致的行为,我无法立即解释。通过明显的修订(在MacOS X 10.6.2,GCC 4.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“,很久以前!”。

答案 1 :(得分: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会发生什么。这表示它是带负值的有符号整数。然后将该数字解释为two's complement

答案 2 :(得分:1)

回答你的主要问题:

scanf("%d", &c);
当转换的输入无法表示为数据类型时,

scanf()的行为未定义。您的计算机上的2249459722不适合int,因此scanf()可以执行任何操作,包括在c中存储垃圾。

在C中,int类型保证能够存储-32767+32767范围内的值。 unsigned int保证在065535之间保证值。因此,即使是2249459722unsigned int也不一定适合。但是,unsigned long可以存储最多4294967295(2 32 -1)的值,因此您应该使用unsigned 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_t而不是unsigned longscanf()来电变为scanf("%" SCNu32, &c);

答案 3 :(得分:1)

正确的字节顺序安全的写法是

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