【问题标题】:Unexpected behavior when printing 4-byte integer byte by byte逐字节打印 4 字节整数时的意外行为
【发布时间】:2011-01-03 05:10:02
【问题描述】:

我有这个将 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",&amp;c) ;
by
scanf("%u",&amp;c) ;
输出结果是正确的。

P.S :我知道inet_ntopinet_pton
除了建议之外,我希望得到其他答案。

【问题讨论】:

  • 你得到了什么结果?你期待什么结果?
  • 烂标题。它不会告诉读者您遇到的问题任何信息
  • 2249459722 不需要适合intunsigned int。使用unsigned long,或者如果你有它,uint32_t。在这种情况下,scanf() 的格式变为 "%lu"/"%" SCNu32

标签: c ip-address endianness scanf


【解决方案1】:

%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 的补码的研究。
  • 它可以被解释为二进制补码,或二进制补码,或任何底层编码。从技术上讲,scanf() 可以做任何事情,因为输入不适合数据类型时的行为是未定义的。
【解决方案2】:

您正在编码“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 中的任何值。

所以,您的代码存在多个问题:

  • 它假定一个特定的架构(小端而不是承认大端也存在)。
  • 在使用启用了很多警告的编译器时,它编译不干净 - 这是有充分理由的。
  • 它不会检查可能失败的函数实际上是否成功。

阿洛克的评论

是的,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”,很久以前!)。

【讨论】:

  • 好点。我想在我的编辑中提到这一点,但我认为它不会被理解。
  • “unsigned only in C90”警告的原因是C中整数文字的类型在C90和C99之间的规则。在 C90 中,“尝试”的类型是 intlong intunsigned long int。在 C99 中,类型为 intlong intlong long int。常量 2249459722 在 C90 中的类型为 unsigned long int,在 C99 中的类型为 long long int。添加U 使其无处不在。
  • 从技术上讲,printf() 函数可能会失败,也应该检查一下。但是,在示例代码中这样做是不常见的 - 而检查输入,例如 sscanf()(或原始中的 scanf())是至关重要的,即使在示例代码中也是如此。
  • 您在scanf() 通话中的意思是!= 1 而不是!= 0
  • @Jonathan:你会因此恨我的,但在你的yy.c,你的sscanf 呼叫扫描"2249559722",而不是str,它是"2249459722"。看到不同? 2249 4 59722 与 2249 5 59722。现在? :-)
【解决方案3】:

回答你的主要问题:

scanf("%d", &c);

scanf() 的行为在被转换的输入无法表示为数据类型时未定义。你机器上的2249459722 不适合int,所以scanf() 可以做任何事情,包括在c 中存储垃圾。

在 C 中,int 类型保证能够存储-32767+32767 范围内的值。 unsigned int065535 之间的保证值。因此,2249459722 甚至不需要适合 unsigned int。但是,unsigned long 最多可以存储 4294967295 (232−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 &lt;inttypes.h&gt;,然后使用 uint32_t 而不是 unsigned longscanf() 调用变为 scanf("%" SCNu32, &amp;c);

【讨论】:

    【解决方案4】:

    正确的字节序安全方式是

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-01-29
      • 2015-04-24
      • 1970-01-01
      • 2019-02-12
      • 1970-01-01
      相关资源
      最近更新 更多