我写了一段代码,用来研究不同库和函数的行为。在此过程中,我偶然发现了sscanf的一些奇怪行为。
我有一段代码,它将一个输入读入缓冲区,然后尝试将该值放入一个数字变量。
当我使用输入缓冲区从main调用sscanf时,如果输入字符串比缓冲区短,格式说明符%x产生一个垃圾值。假设我输入0xff,每次都得到一个任意大的随机数。但是,当我将该缓冲区传递给函数时,所有对scanf的调用结果都是255 (0xff),就像我期望的那样,不管类型和格式说明符是否不匹配。
我的问题是,为什么这种情况发生在函数main中,而不是在函数test中?
这是代码:
#include <stdio.h>
int test(char *buf){
unsigned short num;
unsigned int num2;
unsigned long long num3;
sscanf(buf, "%x", &num);
sscanf(buf, "%x", &num2);
sscanf(buf, "%x", &num3);
printf("%x", num);
printf("%x", num2);
printf("%x", num3);
return 0;
}
void main(){
char buf[16];
unsigned long long num;
printf("%s","Please enter the magic number:");
fgets(buf, sizeof(buf),stdin);
sscanf(buf, "%x", &num);
printf("%xn", num);
test(&buf);
}
我期望行为是内聚的;所有的调用都应该失败,或者所有的调用都应该成功,但事实并非如此。
我试着阅读文档,用不同的类型、格式说明符等做实验。此行为存在于所有数值类型。
我试过在不同的平台上编译;gcc和Linux的行为相同,Windows和msvc也是如此。
我还反汇编了二进制文件,以查看对sscanf的调用在main()和test()之间是否不同,但该汇编是相同的。它将指向缓冲区的指针加载到寄存器中,并将该寄存器压入堆栈,然后调用sscanf.
现在澄清一下:这种情况经常发生,main中的num永远不会等于test中的num、num2或num3,但是num、num2和num3总是彼此相等的。我希望这会导致未定义的行为,并且不一致。运行时的输出-每次
./main
Please enter the magic number: 0xff
0xaf23af23423 <--- different every time
0xff <--- never different
0xff <--- never different
0xff <--- never different
我目前的理由是,在一个实例中,sscanf比另一个实例解释更多的字节。它似乎一直在评估整个缓冲区,受到内存中剩余数据的影响。
我知道我可以使它的行为正确填充缓冲区,最后一个字节是一个新的行或使用正确的格式说明符来匹配指针类型。"% llx"在本例中为main。所以这不是我想知道的;我是故意犯那个错误的。
我想知道为什么当代码运行时,使用错误的格式说明符在一种情况下有效,但在另一种情况下不一致。
sscanf
与%x
只能使用有一个unsigned int
的地址当传递另一个对象的地址时,该行为不是由C标准定义的。
使用指向更宽对象的指针,对象中的附加字节可能包含其他值(可能是从启动代码准备进程并称为main
时遗留下来的)。对于指向较窄对象的指针,sscanf
可以在对象外部写入字节。通过编译器优化,可以实现各种附加行为。这些不同的可能性可能表现为大量数据、数据损坏、程序崩溃或其他行为。
另外,C标准没有定义使用不正确的转换说明符进行打印,这可能导致printf
在试图处理传递给它的参数时出错。
使用%hx
扫描到unsigned short
。使用%lx
扫描到unsigned long
。使用%llx
扫描到unsigned long long
。在打印相应的类型时也使用这些转换说明符。
我的问题是,为什么这种情况发生在函数main中,而不是在函数测试中?
一种可能是启动代码在设置进程时使用了一点堆栈空间,这在字节中留下了一些非零数据,这些数据后来在main
中用于num
。堆栈上较低的字节保存0值,这些字节稍后在test
中用于num3
。
这个调用中的参数表达式
test(&buf);
的类型是char ( * )[16]
,但是函数期望的参数类型是char *
int test(char *buf){
这些指针类型之间没有隐式转换。
你需要像
那样调用函数test( buf );
似乎还有一个错别字
printf("%s","Please enter the magic number:");
printf("%xn", num);
未初始化变量num
unsigned long long num;
//...
sscanf(buf, "%x", &num);
您正在使用类型为unsigned long long int *
的第三个参数,但转换规范"%x"
期望使用类型为unsigned int *
的参数。因此调用具有未定义行为。
你需要写
sscanf(buf, "%llx", &num);
对于类型为unsigned short
的变量num
也存在同样的问题
unsigned short num;
//...
sscanf(buf, "%x", &num);
你必须写
sscanf(buf, "%hx", &num);
与调用printf
时需要使用的长度修饰符相同printf("%hx", num);
printf("%x", num2);
printf("%llx", num3);
这是一个示范程序。
#include <stdio.h>
int main( void )
{
char buf[] = "0xffn";
unsigned short num;
unsigned int num2;
unsigned long long num3;
sscanf( buf, "%hx", &num );
sscanf( buf, "%x", &num2 );
sscanf( buf, "%llx", &num3 );
printf( "%hxn", num );
printf( "%xn", num2 );
printf( "%llxn", num3 );
}
程序输出为
ff
ff
ff