逐字节打印4字节整数时出现意外行为 [英] Unexpected behavior when printing 4-byte integer byte by byte

查看:81
本文介绍了逐字节打印4字节整数时出现意外行为的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

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

此代码为输入<$ c $提供错误的输出c> 2249459722 。
但是当我用<$ p替换

  scanf(%d,& c);  

时$ p> scanf(%u,& c);


输出正确。



PS :我知道 inet_ntop inet_pton

我希望得到的答案不是建议的。

解决方案

您正在编码 认真(会犯一些错误,迟早会伤害您-大多会早点伤害您)。首先,您假设整数具有正确的字节序。在某些计算机上,您可能是错误的-在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(点分十进制:%u。%u。%u。%u \n,* cptr,*(cptr + 1),*(cptr + 2),*(cptr + 3)) ;
return(0);
}

在Mac(Intel,little-endian)上编译时,输出为:

 整数值:2249459722 
整数值:0x8614080A
点分十进制:10.8.20.134

在我的Sun上编译时(SPARC,大端),输出为:

 整数值:2249459722 
整数值:0x8614080A
点分十进制:134.20.8.10

(在SPARC上使用GCC 4.4.2,我得到警告:

  xx.c:4:警告:该十进制常数仅在ISO C90中是无符号的

在Mac上使用GCC 4.2.1-启用了许多警告( gcc -std = c99 -pedantic -Wall -Wshadow -Wpointer-arith -Wstrict-prototypes -Wmissing-prototypes -Werror )-我没有得到警告,这很有趣。)我可以通过在整数常量后缀添加 U 后缀来删除该警告。






l的另一种方式下面的代码和上面显示的极其繁琐的编译器设置说明了如何解决问题:

  #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(点分十进制:%u。%u。%u。%u \n,* cptr,*(cptr + 1),*(cptr + 2),*(cptr + 3)) ;
}

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

printf(直接操作:\n);
print_value(c);

printf(间接操作:\n);
if(sscanf( 2249559722,%d,& c)!= 0)
printf(%s\n转换失败,str);
else
print_value(c);
return(0);
}

这无法编译(由于 -Werror 设置),并显示以下消息:

  cc1:将警告视为错误
xx。 c:在函数'main'中:
xx.c:20:警告:格式'%d'期望类型为'int *',但是参数3的类型为'unsigned int *'

删除 -Werror 设置并进行编译,但显示下一个问题您有-不检查可能失败的功能的错误指示的一种方法:

 直接操作:
整数值:2249459722
整数值:0x8614080A
点分十进制:10.8.20.134
间接操作:
2249459722


基本上, sscanf()函数报告它未能将字符串转换为有符号整数(因为该值太大而无法容纳-请参阅GCC 4.4.2的警告),但您的代码未检查从 sscanf()返回错误,因此您使用的是当时 c 剩下的任何值。



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




  • 假设

  • 使用启用了许多警告的编译器时,它并不能干净地编译-出于充分的理由。

  • li>
  • 它不会检查可能失败的功能是否成功。






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(点分十进制:%u。%u。%u。%u \n,* cptr,*(cptr + 1),*(cptr + 2),*(cptr + 3)) ;
}

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

printf(直接操作:\n);
print_value(c);

printf(间接操作:\n);
if(sscanf( 2249559722,%d,& c)!= 1)
printf(%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:在函数'main'中:
yy.c:20:警告:格式'%d'期望类型为'int *',但是参数3的类型为'unsigned int *'


+ ./yy.32
直接运算:
整数值:2249459722
整数值:0x8614080A
点分十进制:10.8.20.134
间接运算:
整数值:2249559722
整数值:0x86158EAA
点分十进制: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:在函数'main'中:
yy.c:20:警告:格式'%d'期望类型为'int *',但是参数3具有类型'unsigned int *'


+ ./yy.64
直接运算:
整数值:2249459722
整数值:0x8614080A
点分十进制:10.8.20.134
间接运算:
整数值:2249559722
整数值:0x86158EAA
点分十进制: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(点分十进制:%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(间接操作(%s):\n,标记);
fmt =%d;
if(sscanf(str,fmt,& c)!= 1)
printf(%s转换失败(格式为%s \%s\)\n, str,tag,fmt);
else
print_value(c);
}

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

printf(直接操作:\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-原型-Wmissing-prototypes xx.c 


+ ./xx.32
直接运算:
整数值:2249459722
整数值:0x8614080A
点分十进制:10.8.20.134
间接运算(有符号):
整数值:2249459722
整数值:0x8614080A
点分十进制:10.8.20.134
间接操作(无符号):
整数值:2249459722
整数值:0x8614080A
点分十进制:10.8.20.134

结果在这里是一致的。

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



+ ./xx.64
直接操作:
整数值:2249459722
整数值: 0x8614080A
点分十进制:10.8.20.134
间接操作(有符号):
整数值:2249459722
整数值:0x8614080A
点分十进制:10.8.20.134
间接操作(无符号):
整数值:2249459722
整数值:0x8614080A
点分十进制:10.8.20.134

这些与32位情况相同。我正式感到困惑。主要观察结果仍然是准确的-请小心,注意编译器警告(并引起编译器警告),并且不要假设全世界都在Intel芯片上运行(过去通常是不要假设全世界都在Intel芯片上运行)。 VAX,很久以前!)。


I have this sample code for converting 32 bit integers to ip addresses.


#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) );
 }
}

This code gives incorrect output for input 2249459722 . But when i replace

scanf("%d",&c) ;

by

scanf("%u",&c) ;

The output comes out to be correct.

P.S : I know about inet_ntop and inet_pton.
I expect answers other than suggesting those.

解决方案

You are coding 'sinfully' (making a number of mistakes which will hurt you sooner or later - mostly sooner). First off, you are assuming that the integer is of the correct endian-ness. On some machines, you will be wrong - either on Intel machines or on PowerPC or SPARC machines.

In general, you should show the actual results you get rather than just saying that you get the wrong result; you should also show the expected result. That helps people debug your expectations.


Here's my modified version of your code - instead of requesting input, it simply assumes the value you specified.

#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);
}

When compiled on my Mac (Intel, little-endian), the output is:

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

When compiled on my Sun (SPARC, big-endian), the output is:

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

(Using GCC 4.4.2 on the SPARC, I get a warning:

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

Using GCC 4.2.1 on Mac - with lots of warnings enabled (gcc -std=c99 -pedantic -Wall -Wshadow -Wpointer-arith -Wstrict-prototypes -Wmissing-prototypes -Werror) - I don't get that warning, which is interesting.) I can remove that by adding a U suffix to the integer constant.


Another way of looking at the problems is illustrated with the following code and the extremely fussy compiler settings shown above:

#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);
}

This fails to compile (because of the -Werror setting) with the message:

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 *’

Remove the -Werror setting and it compiles, but then shows the next problem that you have - the one of not checking for error indications from functions that can fail:

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

Basically, the sscanf() function reports that it failed to convert the string to a signed integer (because the value is too large to fit - see the warning from GCC 4.4.2), but your code was not checking for the error return from sscanf(), so you were using whatever value happened to be left in c at the time.

So, there are multiple problems with your code:

  • It assumes a particular architecture (little-endian rather than recognizing that big-endian also exists).
  • It doesn't compile cleanly when using a compiler with lots of warnings enabled - for good reason.
  • It doesn't check that functions that can fail actually succeeded.

Alok's Comment

Yes, the test on sscanf() is wrong. That's why you have code reviews, and also why it helps to post the code you are testing.

I'm now a bit puzzled - getting consistent behaviour that I can't immediately explain. With the obvious revision (testing on MacOS X 10.6.2, GCC 4.2.1, 32-bit and 64-bit compilations), I get one not very sane answer. When I rewrite more modularly, I get a sane answer.

+ 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 

I do not have a good explanation for the value 170.142.21.134; but it is consistent on my machine, at the moment.

+ 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 

Same value - even in 64-bit instead of 32-bit. Maybe the problem is that I'm trying to explain undefined behaviour, which is more or less by definition unexplainable (inexplicable).

+ 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);
}

Using the function argument like this means GCC cannot spot the bogus format any more.

+ 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 

The results are consistent here.

+ 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

And these are the same as the 32-bit case. I'm officially bemused. The main observations remain accurate - be careful, heed compiler warnings (and elicit compiler warnings), and don't assume that "all the world runs on Intel chips" (it used to be "don't assume that all the world is a VAX", once upon a long time ago!).

这篇关于逐字节打印4字节整数时出现意外行为的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆