深入探究 printf 和 sprintf 函数中参数类型不匹配导致的问题及解析

深入探究 printf 和 sprintf 函数中参数类型不匹配导致的问题及解析

前言

在 C 语言编程中,我们经常会用到printf和sprintf这样强大的函数来进行格式化输出。然而,在实际使用过程中,如果不注意参数类型与格式说明符的匹配,就很容易出现一些意想不到的问题。尤其是当涉及到32位和64位整数时,格式化字符串与实际参数类型不匹配可能会引发各种奇怪的错误,甚至导致程序崩溃。今天,咱们就一起来深入探讨一下这背后的原因以及相关的解决办法。

一、问题现象示例

先来看下面这段代码示例:

void main(void) 
{  
  _int64 a = 1;
 
  char * str = "hello widebright";
 
  printf("%d  %s\n",a,str);     //这个是打印32位数的,非预期结果
  printf("%lld  %s\n",a,str);    //正确的打印64位数的写法
 
 //上面把64位数当作32位数打印的时候,就把这数的高32位当作第二个参数%s使用了,
 //因为这时64位数的高32位还是0,就是空指针,printf程序对空指针专门处理了,
 //所以程序不会崩溃,下面改了高32位的,下面还当作32位打印的时候,%s第二个参数就是
  //访问0x1地址了,程序就崩溃了。
int * b = (int *)&a;
  *++b = 1;
 
  printf("%I64X  %s\n",a,str);   //正确的打印64位数的写法
 
  printf("%I32d  %s\n",a,str);   //这个是打印32位数的,程序崩溃
  printf("%d  %s\n",a,str);     //这个是打印32位数的,程序崩溃
  printf("%ld  %s\n",a,str);     //这个是打印32位数的,程序崩溃   
}

short b = 1;
// 把short当作32位数打印就不会有问题
printf (“%d %s\n”,a,str);

//把32位int 数当作64位数打印也是不行的,同样导致后面参数解析出错。
int a =1;
char str[] = “hello widebright”;
printf (“%lld %s\n”,a ,str); //把32位整型当作64位打印,打印出来的数字和后面字符都是乱码了。传禁区的str指针应该会被解析成64位数的一部分了。str被解析成后续的未预期内存了
printf (“%I64X %s\n”,a ,str); //把32位整型当作64位打印,打印出来的数字和后面字符都是乱码了。

short b =1;
printf (“%d %s\n”,a ,str); //但把short单做32位数打印就不会有问题,应该是默认都转换成32位数再传给printf了

从这段代码可以看到,当我们将 64 位整数a使用%d(用于 32 位整数的格式说明符)去打印时,就出现了各种不符合预期的情况,甚至导致程序崩溃。而且不仅仅是printf函数,sprintf函数也存在类似的问题,比如:

int a = 1;
char  str[] = "hello widebright";
// 把32位整型当作64位打印,打印出来的数字和后面字符都是乱码了
printf ("%lld %s\n",a,str);  
printf ("%I64X %s\n",a,str);  

二、汇编分析

short b =1;
printf ("%d %s\n",a ,str);  //但把short单做32位数打印就不会有问题,应该是默认都转换成32位数再传给printf了
 
short b =1;
0041158B  mov       eax,1 
00411590  mov        word ptr [ebp-4Ch],ax 
printf ("%d %s\n",a ,str);  //但把short单做32位数打印就不会有问题,应该是默认都转换成32位数再传给printf了
00411594  mov       esi,esp 
00411596  lea       eax,[ebp-40h] 
00411599  push       eax  
0041159A  mov        ecx,dword ptr [ebp-24h] 
0041159D  push       ecx                     //还是32数push到栈上的,所以
0041159E  push       offset string "%d %s\n" (417808h) 
004115A3  call       dword ptr [__imp__printf (41A400h)] 
004115A9  add       esp,0Ch 
004115AC  cmp       esi,esp 
004115AE  call      @ILT+415(__RTC_CheckEsp) (4111A4h) 
_int64 a = time(NULL);
a =1;
sprintf(buffer,"lX%s\n",a,str);
00411511  push       0    
00411513  call       time (4116D0h) 
00411518  add       esp,4 
0041151B  mov        dword ptr [a],eax 
0041151E  mov        dword ptr [ebp-20h],edx 
a =1;
00411521  mov        dword ptr [a],1 
00411528  mov        dword ptr [ebp-20h],0 
sprintf(buffer,"lX%s\n",a,str);
0041152F  mov       esi,esp 
00411531  mov        eax,dword ptr [str] 
00411534  push       eax  
00411535  mov        ecx,dword ptr [ebp-20h] 
00411538  push       ecx          //64数的高32位 被push 进栈了
00411539  mov        edx,dword ptr [a] 
0041153C  push       edx          //64位数的低32位 被push 进栈了
0041153D  push       offset string "X\n" (417800h) 
00411542  mov        eax,dword ptr [buffer] 
00411545  push       eax  
00411546  call       dword ptr [__imp__sprintf (41A404h)] 
0041154C  add       esp,14h 
0041154F  cmp       esi,esp 
00411551  call      @ILT+415(__RTC_CheckEsp) (4111A4h) 

可以看到 64位的_int64其实是当作两个int被push到栈上了,看起来就像传了多了一个int参数一样,那些可变参数支持的va_list 什么的应该是直接从栈上解析出来这些参数的,所以如果64位和32位格式化参数没有指定好是会导致最后的解析出错的。这种情况应该是32位机器上,64位支持不好导致的? 系统需要用两个int来模拟一个_int64, printf默认又当作32位int来解析,所以就会出现这种问题。

三、问题根源剖析

(一)可变参数函数的参数传递机制
printf和sprintf这类函数属于可变参数函数,它们内部依赖va_list机制来从栈中解析参数。在函数调用时,参数会按照一定顺序被压入栈中,而va_list相关的宏会依据格式字符串里给定的转换说明符去尝试确定每个参数的类型和大小,进而从栈上读取相应字节的数据来完成输出操作。

(二)32 位机器对 64 位数据的处理方式
在 32 位机器环境下,硬件层面的寄存器以及数据总线宽度基本都是 32 位,对于 64 位数据的处理能力是有限的。所以,当我们要处理 64 位整数(比如_int64类型)时,系统往往会采用两个 32 位整数来模拟表示一个 64 位整数。这就意味着,在进行函数调用时,一个 64 位整数实际上是被拆分成两个 32 位整数依次压入栈中的,就好像传递了两个额外的int参数一样。

(三)格式说明符与实际参数不匹配的影响
当我们在代码中使用的格式说明符与实际传入的参数类型不一致时,va_list机制就没办法正确地解析栈上的参数了。例如,我们传入了一个 64 位整数,但是却使用了针对 32 位整数的%d格式说明符,那么printf函数只会从栈上读取前 32 位数据当作要输出的整数部分,而剩下的 32 位数据就会被误当作下一个参数(比如在上面代码中原本对应的字符串参数)来处理,这显然就破坏了参数原本的解析顺序,从而导致输出结果错误甚至程序崩溃。

(四)不同类型数据转换的默认规则影响
像short类型的数据,在作为参数传递给类似printf这样的函数时,会默认被转换成 32 位数再传递到栈上,这也就是为什么把short当作 32 位数打印时通常不会出现像 64 位整数那样严重的参数解析问题的原因所在。

四、总结

在C语言中,使用 printf 等可变参数函数时,必须确保格式化字符串与参数类型严格匹配。特别是32位和64位整数的处理,稍有不慎就会引发错误或程序崩溃。以下是关键点总结:

  1. 64位整数:
    • 使用 %lld 或 %I64d (Windows平台)打印64位整数。
    • 在32位系统上,64位整数会被拆分成两个32位整数存储在栈上。
  2. 32位整数:
    • 使用 %d 或 %u 打印32位整数。
  3. short 类型:
    • 在调用 printf 时, short 会被隐式提升为32位整数,因此使用 %d 打印 short 类型是安全的。
  4. 调试建议:
    • 使用编译器警告选项(如 -Wall )来检测潜在的格式化字符串问题。
    • 在调试时,检查栈上的参数传递情况,确保参数类型与格式化字符串一致。

通过理解这些原理和注意事项,你可以更好地编写安全、可靠的C语言代码,避免因格式化字符串问题而导致的错误和崩溃。

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Geek__1992

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值