c语言printf d n m函数压栈问题

1151人阅读
先来看这样一段程序:
#include &string.h&
#include &stdlib.h&
#include &stdio.h&
void print1(int a,int b,int c)
printf(&%p\n&,&a);
printf(&%p\n&,&b);
printf(&%p\n&,&c);
int main(void)
print1(1,2,3);
它的输出是:
0022FF48发现a,b,c的地址是逐渐增大的,差值是4个字节。这和我所知道的:C函数参数入栈的顺序是从右到左 是相匹配的,而且地址的增大值也
与变量所占的字节数相匹配。
不过当把程序稍微做一下修改,如下:
#include &string.h&
#include &stdlib.h&
#include &stdio.h&
void print2(char a,char b,char c)
printf(&%p\n&,&a);
printf(&%p\n&,&b);
printf(&%p\n&,&c);
int main(void)
print2(1,2,3);
}再观察一下它的输出:
0022FF24怎么和上面的效果是相反的!虽然我知道这肯定编译器的一个技巧,不过 参数入栈的顺序是从右到左的 概念却动摇了。
为了弄清楚其中的道理,必须观察程序生成的中间.s文件,为此,我执行了以下一条命令:
gcc -S test.c(当前C文件中保存的程序是文章一开始的那个) 在当前目录下生成test.s文件使用vim打开test.s文件(只截取主要内容了):
esp是指向栈顶的指针,ebp是用来备份这个指针的。栈的形状如下:
|____________________________________________________
栈的最大值 && 栈的最小值
每压入一个参数入栈,就执行 &esp = esp - sizoeof(参数)。不过在esp值变之前,先备份一下 ebp = esp,这样不管最后esp指到哪里去了,函数结束时就用这个ebp就能顺利回到调用者了。
pushl %ebp//6.先把ebp压栈,保存这个指针
%esp, %ebp//7.使ebp这个指针保存着esp这个指针指向的地址值
$8, %esp//8.使esp - 8,也就是说空下8个字节以便实现某个功能
8(%ebp), %eax//9.把(ebp + 8)的地址给eax 这个地方为什么要+8 因为这个函数在经历第5,6步的时候存在着压了两个4字节入栈的操作。此时+8就指向了实参1
%eax, 4(%esp)//10.这个时候就用到第8步空下来的8个字节中的4个了,原来是保存值,原理就是用C语言写两个数交换值时的那个第三个变量,即缓冲区
$.LC0, (%esp) //11.把字符串“%p\n”压栈
从第10,11步来看,两个参数的入栈顺序,其实不管顺序了,两个参数,最右边的在高地址,最左边的在低地址
printf//12.调用函数printf,又是压栈出栈的操作了 到此可以得到8个字节的缓冲区全部用完了
12(%ebp), %eax//13.同第9步,此时获取的是实参2的地址
%eax, 4(%esp)//14.我想说同上
$.LC0, (%esp) //15.我想说同上
printf//16.我想说同上
16(%ebp), %eax//17.同第9步,此时获取的是实参3的地址
%eax, 4(%esp)//18.我想说同上
$.LC0, (%esp) //19.我想说同上
printf//20.我想说同上。到了此处我们就知道,printf打印参数的地址,这个地址是在main函数中压栈时分配的,是什么就是什么,符合参数入栈的顺序是从右到左这个说法。
.size print1, .-print1
.globl main
.type main, @function
4(%esp), %ecx
$-16, %esp
pushl -4(%ecx)
pushl %ebp
%esp, %ebp
pushl %ecx
$20, %esp//1.先把栈预留20个字节,这其中的原因(为什么是20)估计与什么算法有关
$3, 8(%esp)//2.看!,先把3放入esp + 8
$2, 4(%esp)//3.再看!,把2放入esp + 4
$1, (%esp)//4.最后把1放入esp
print1//5.调用函数print1。至此可以看到参数1,2,3是从右往左压入栈的,3在最高最地址(相对于1的保存地址来说),而1就在最低地址了(相对于3的保存地址来说)
$0, (%esp)
.size main, .-main
好的,这个程序分析完了,再来看有疑问的程序吧:
pushl %ebp//5.我想说同上
%esp, %ebp//6.我想说同上
$24, %esp//7.这个就不同上了,比上面那个esp - 8大很多吗,不过要记住,这24个字节是个缓冲区
8(%ebp), %eax//8.把实参1放入eax
12(%ebp), %edx//9.把实参2放入edx
16(%ebp), %ecx//10.把实参3放入ecx
%al, -4(%ebp)//11.把eax的低字节放入ebp - 4
%dl, -8(%ebp)//12.把edx的低字节放入ebp - 8
%cl, -12(%ebp)//13.把ecx的低字节放入ebp -12。从这个地方就可以发现问题的出现原因了,此时的实参1所存放的地址高于存放实参3的地址。到此,24字节的缓冲区已经使用了12
-4(%ebp), %eax//14.把实参1存放的地址放入eax
%eax, 4(%esp)//15.把实参1放入esp + 4
$.LC0, (%esp) //16.把字符串“%p\n”放入esp
printf//17.调用函数printf。从此依然可以看出函数参数的入栈地址是最右边的在高地址,最左边的在低地址。到此24字节的缓冲区使用了20个,还余下4个没有用
-8(%ebp), %eax//18.我想说同上
%eax, 4(%esp)//19.我想说同上
$.LC0, (%esp) //20.我想说同上
printf//21.我想说同上
-12(%ebp), %eax//22.我想说同上
%eax, 4(%esp)//23.我想说同上
$.LC0, (%esp) //24.我想说同上
printf//25.我想说同上
.size print2, .-print2
.globl main
.type main, @function
4(%esp), %ecx
$-16, %esp
pushl -4(%ecx)
pushl %ebp
%esp, %ebp
pushl %ecx
movl $3, 8(%esp)//1.和上面那个程序一样的压栈操作
movl $2, 4(%esp)//2.我想说同上
movl $1, (%esp)//3.我想说同上
print2//4.我想说同上
$0, (%esp)
.size main, .-main
结束了,知道了原因了。这计算机执行函数的时候不停的压栈出栈,执行这些精细的操作真是太牛了,不过计算机没有情感,它不会评估这个复杂度,只要一条条执行就行了,就像我们抄作文似的,作文
最后好不好不是我们能决定的,而是作文的作者。我暴露了-_-|||。
谢谢观赏!
此处的汇编语言是AT&T汇编,相关学习资料地址:
本文参考文章地址:
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:57850次
积分:1194
积分:1194
排名:第17172名
原创:61篇
评论:40条
(2)(9)(2)(1)(1)(2)(4)(1)(1)(1)(2)(1)(5)(8)(5)(3)(2)(2)(2)(2)(2)(2)(2)(2)printf函数的参数压栈顺序问题
[问题点数:40分,结帖人u]
printf函数的参数压栈顺序问题
[问题点数:40分,结帖人u]
不显示删除回复
显示所有回复
显示星级回复
显示得分回复
只显示楼主
相关帖子推荐:
2007年6月 总版技术专家分月排行榜第三
2007年6月 VC/MFC大版内专家分月排行榜第一
匿名用户不能发表回复!|
每天回帖即可获得10分可用分!小技巧:
你还可以输入10000个字符
(Ctrl+Enter)
请遵守CSDN,不得违反国家法律法规。
转载文章请注明出自“CSDN(www.csdn.net)”。如是商业用途请联系原作者。新手园地& & & 硬件问题Linux系统管理Linux网络问题Linux环境编程Linux桌面系统国产LinuxBSD& & & BSD文档中心AIX& & & 新手入门& & & AIX文档中心& & & 资源下载& & & Power高级应用& & & IBM存储AS400Solaris& & & Solaris文档中心HP-UX& & & HP文档中心SCO UNIX& & & SCO文档中心互操作专区IRIXTru64 UNIXMac OS X门户网站运维集群和高可用服务器应用监控和防护虚拟化技术架构设计行业应用和管理服务器及硬件技术& & & 服务器资源下载云计算& & & 云计算文档中心& & & 云计算业界& & & 云计算资源下载存储备份& & & 存储文档中心& & & 存储业界& & & 存储资源下载& & & Symantec技术交流区安全技术网络技术& & & 网络技术文档中心C/C++& & & GUI编程& & & Functional编程内核源码& & & 内核问题移动开发& & & 移动开发技术资料ShellPerlJava& & & Java文档中心PHP& & & php文档中心Python& & & Python文档中心RubyCPU与编译器嵌入式开发驱动开发Web开发VoIP开发技术MySQL& & & MySQL文档中心SybaseOraclePostgreSQLDB2Informix数据仓库与数据挖掘NoSQL技术IT业界新闻与评论IT职业生涯& & & 猎头招聘IT图书与评论& & & CU技术图书大系& & & Linux书友会二手交易下载共享Linux文档专区IT培训与认证& & & 培训交流& & & 认证培训清茶斋投资理财运动地带快乐数码摄影& & & 摄影器材& & & 摄影比赛专区IT爱车族旅游天下站务交流版主会议室博客SNS站务交流区CU活动专区& & & Power活动专区& & & 拍卖交流区频道交流区
空间积分0 信誉积分100 UID阅读权限20积分843帖子精华可用积分843 专家积分5 在线时间1 小时注册时间最后登录
丰衣足食, 积分 843, 距离下一级还需 157 积分
帖子主题精华可用积分843 专家积分5 在线时间1 小时注册时间最后登录
论坛徽章:0
使用%d类型输出一个int64整型变量和一个int整型变量,这两个变量输出先后有关系么?
我看了一下好像是如果int64整型变量在前会导致后面int整型变量数值不正确;反之没有问题。
这个是什么原因啊?是不是入栈先后顺序的问题?请大侠们指教一下,多谢了。
想看看但是没有找到printf函数源码,
环境:Intel(R) Pentium(R) 4& &linux 2.6.18-1.2798.fc6
&&nbsp|&&nbsp&&nbsp|&&nbsp&&nbsp|&&nbsp&&nbsp|&&nbsp
空间积分6 信誉积分2209 UID阅读权限100积分15228帖子精华可用积分15230 专家积分65 在线时间8709 小时注册时间最后登录
帖子主题精华可用积分15230 专家积分65 在线时间8709 小时注册时间最后登录
论坛徽章:1
%d是用来输出int类型的,如果int64和int不是相同长度,是不可以的。
这与具体编译器相关。
echo '++++++++++[&++++++++++[&+&-]&-]&&-.+++++++.---------.++++++++.&&++++[&++++[&+&-]&-]&&+.-------..' | sed '
s/\([-+]\)/\1\1*p;/g
s/&/p--;/g
s/&/p++;/g
s/\./putchar(*p);/g
s/\[/while(*p){/g
1s/^/main(){char*p=calloc(1,6);/
/./!d'|gcc -xc - 2&/dev/null&&./a.out
空间积分0 信誉积分446 UID阅读权限90积分11991帖子精华可用积分11991 专家积分10 在线时间379 小时注册时间最后登录
大富大贵, 积分 11991, 距离下一级还需 8009 积分
帖子主题精华可用积分11991 专家积分10 在线时间379 小时注册时间最后登录
论坛徽章:0
空间积分0 信誉积分100 UID阅读权限20积分843帖子精华可用积分843 专家积分5 在线时间1 小时注册时间最后登录
丰衣足食, 积分 843, 距离下一级还需 157 积分
帖子主题精华可用积分843 专家积分5 在线时间1 小时注册时间最后登录
论坛徽章:0
谢谢楼上两位关注,是我写错了,应该用%lld来输出int64的变量。^_^
& & printf (&int: %d int64: %d \n&,
& && && && & (&int_value)-&data[0].v_int,
& && && && & (&int64_value)-&data[0].v_int64);
& & printf (&int64: %lld int: %d \n&,
& && && && &(&int64_value)-&data[0].v_int64,
& && && && &(&int_value)-&data[0].v_int);
上面的输出就是想要的了。
之前的错误:应该是告诉printf要输出两个%d,结果人家就从栈中挨个找,在小端机上就把int64变量的前半部分当成两个int变量来处理了,是不是这样请大侠们指教一下,再问一下,在大端的机器上是不是以前用%d输出int64变量的代码会有不同的输出?
空间积分0 信誉积分340 UID9418874阅读权限100积分25208帖子精华可用积分25208 专家积分439 在线时间13570 小时注册时间最后登录
帖子主题精华可用积分25208 专家积分439 在线时间13570 小时注册时间最后登录
论坛徽章:0
昨天晚上试验算法导论上的算法时就碰到了这个问题,输出的格式影响输出结果
空间积分0 信誉积分100 UID阅读权限20积分843帖子精华可用积分843 专家积分5 在线时间1 小时注册时间最后登录
丰衣足食, 积分 843, 距离下一级还需 157 积分
帖子主题精华可用积分843 专家积分5 在线时间1 小时注册时间最后登录
论坛徽章:0
不知道上面的想法对不对,那位在大端的机器上试试。
空间积分0 信誉积分2415 UID阅读权限100积分137025帖子精华可用积分137025 专家积分55 在线时间4590 小时注册时间最后登录
帖子主题精华可用积分137025 专家积分55 在线时间4590 小时注册时间最后登录
论坛徽章:11
原帖由 玄铁寒冰 于
07:52 发表
谢谢楼上两位关注,是我写错了,应该用%lld来输出int64的变量。^_^
& & printf (&int: %d int64: %d \n&,
& && && && & (&int_value)-&data[0].v_int,
& && && && & (&int64_value)-&g ...
看下 va_arg 等宏的实现会有帮助。
I can explain it for you, but I can’t understand it for you.
空间积分0 信誉积分100 UID阅读权限10积分51帖子精华可用积分51 专家积分0 在线时间64 小时注册时间最后登录
白手起家, 积分 51, 距离下一级还需 149 积分
帖子主题精华可用积分51 专家积分0 在线时间64 小时注册时间最后登录
论坛徽章:0
在参数传递的时候,有一个规则是提升(promotion),这个规则规定:
1 当参数为char, short,等小于4个字节的数据时,提升为4个字节; 当参数为float这种数据类型的时候,提升为8个字节。
2 当参数大于8个字节的时候,则按照字节数进行栈的分配。
套用该规则,可以解释lz最开始的用法为何错误;用lld输出为何正确!
这个应该与是否是大小端没关系!

我要回帖

更多关于 c语言中printf的用法 的文章

 

随机推荐