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

我有这个示例代码,用于将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
我期待的答案不是建议那些答案。

你正在编写“ 有罪 ”的编码(制造一些迟早会伤害你的错误 – 大多数时间更早)。 首先,您假设整数具有正确的字节序。 在某些机器上,您将出错 – 无论是在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也存在)。
  • 在使用启用了大量警告的编译器时,它不能完全编译 – 这是有充分理由的。
  • 它不会检查可能失败的function是否实际成功。

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

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

回答你的主要问题:

 scanf("%d", &c); 

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

在C中, int类型保证能够存储-32767+32767范围内的值。 unsigned int的保证值介于065535之间。 所以,就这样, 2249459722甚至不需要适合unsigned int 。 但是, unsigned long可以存储unsigned long4294967295 (2 32 -1)的值,因此您应该使用unsigned long

 #include  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 然后使用uint32_t而不是unsigned longscanf()调用变为scanf("%" SCNu32, &c);

正确的字节顺序安全的方式来写这个

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