当前位置:  开发笔记 > IOS > 正文

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

如何解决《逐字节打印4字节整数时出现意外行为》经验,为你挑选了2个好方法。

我有这个示例代码,用于将32位整数转换为ip地址.

#include 
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_ntopinet_pton.
我期待的答案不仅仅是建议那些答案.



1> Jonathan Lef..:

你正在编写" 有罪 "的编码(制造一些迟早会伤害你的错误 - 大多数时间更早).首先,假设整数具有正确的字节序.在某些机器上,您将出错 - 无论是在Intel机器上还是在PowerPC或SPARC机器上.

一般来说,你应该显示你得到的实际结果,而不是仅仅说你得到了错误的结果; 你还应该显示预期的结果.这有助于人们调整您的期望.


这是我修改后的代码版本 - 它不是请求输入,而是假定您指定的值.

#include 
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 

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 

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 

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",很久以前!).


@Jonathan:你会恨我的,但在你的`yy.c`中,你的`sscanf`调用扫描`"2249559722"`,而不是`str`,这是``2249459722"`.看到不同?2249**4**59722 vs 2249**5**59722.现在?:-)
"仅在C90中无符号"警告的原因是关于C中整数文字的类型在C90和C99之间的规则.在C90中,"尝试过"的类型是`int`,`long int`和`unsigned long int`.在C99中,类型是`int`,`long int`和`long long int`.常量`2249459722`在C90中是`unsigned long int`,在你的机器上是C99中的`long long int`.添加"U"使其无处不在.

2> hobodave..:

%d用于有符号整数

%u用于无符号整数

编辑:

请按如下方式修改您的程序,看看您的输入是如何被真正解释的:

#include 
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.这表示它是一个带负值的有符号整数.然后将该数字解释为它的两个补码

推荐阅读
Gbom2402851125
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有