c语言 函数 理解的函数原型怎样理解

扫二维码下载作业帮
1.75亿学生的选择
下载作业帮安装包
扫二维码下载作业帮
1.75亿学生的选择
关于C语言函数的声明,怎么理解能简单点
扫二维码下载作业帮
1.75亿学生的选择
许多人分不清声明与定义.先来说说变量的声明与定义: //
定义了变量i,不是声明,只是没有初始化int i(10)或者int i=10; // 定义了变量i并进行初始化 // 声明了一个变量,可能在其它地方定义再来看看函数:int fun(int, int); // fun函数的声明,告诉编译器该函数的定义写在其它地方,当前声明了函数的存在和形式,下面可以使用与该参数类型匹配的调用.int fun(int a, int b) { return a+b;} // fun函数的定义,包含{}作用域,完整的定义的该函数的实现.
为您推荐:
其他类似问题
扫描下载二维码扫二维码下载作业帮
1.75亿学生的选择
下载作业帮安装包
扫二维码下载作业帮
1.75亿学生的选择
C语言中关于gets函数一切如图,还有那个gets的函数原型是什么啊
扫二维码下载作业帮
1.75亿学生的选择
函数名: gets 功
能: 从流中取一字符串 用
法: char *gets(char *string); 程序例: #include
int main(void) {
char string[80];
printf("Input a string:");
gets(string);
printf("The string input was: %s\n",
return 0; } 函数名: puts 功
能: 送一字符串到流中 用
法: int puts(char *string); 程序例: #include
int main(void) {
char string[] = "This is an example output string\n";
puts(string);
return 0; }
为您推荐:
其他类似问题
扫描下载二维码深入理解 C 语言的函数调用过程 - 文章 - 伯乐在线
& 深入理解 C 语言的函数调用过程
本文主要从进程栈空间的层面复习一下C语言中函数调用的具体过程,以加深对一些基础知识的理解。
先看一个最简单的程序:
主函数main里定义了4个局部变量,然后调用同文件里的foo1()函数。4个局部变量毫无疑问都在进程的栈空间上,当进程运行起来后我们逐步了解一下main函数里是如何基于栈实现了对foo1()的调用过程,而foo1()又是怎么返回到main函数里的。为了便于观察的粒度更细致一些,我们对test.c生成的汇编代码进行调试。如下:
.file "test.c"
.globl foo1
.type foo1
pushl %ebp
movl %esp, %ebp
subl $16, %esp
movl 12(%ebp), %eax
movl 8(%ebp), %edx
leal (%edx,%eax), %eax
addl 16(%ebp), %eax
movl %eax, -4(%ebp)
movl -4(%ebp), %eax
.size foo1, .-foo1
.section .rodata
.string "result=%d\n"
.globl main
.type main
pushl %ebp
movl %esp, %ebp
andl $-16, %esp
subl $32, %esp
movl $11, 16(%esp)
movl $22, 20(%esp)
movl $33, 24(%esp)
movl 24(%esp), %eax
movl %eax, 8(%esp)
movl 20(%esp), %eax
movl %eax, 4(%esp)
movl 16(%esp), %eax
movl %eax, (%esp)
movl %eax, 28(%esp)
movl $.LC0, %eax
movl 28(%esp), %edx
movl %edx, 4(%esp)
movl %eax, (%esp)
call printf
movl $0, %eax
.size main, .-main
.ident "GCC: (GNU) 4.4.4
(Red Hat 4.4.4-13)"
.section .note.GNU-stack,"",@progbits
<div class="crayon-num" data-line="crayon-58dec<div class="crayon-num crayon-striped-num" data-line="crayon-58dec<div class="crayon-num" data-line="crayon-58dec<div class="crayon-num crayon-striped-num" data-line="crayon-58dec<div class="crayon-num" data-line="crayon-58dec<div class="crayon-num crayon-striped-num" data-line="crayon-58dec<div class="crayon-num" data-line="crayon-58dec<div class="crayon-num crayon-striped-num" data-line="crayon-58dec<div class="crayon-num" data-line="crayon-58dec<div class="crayon-num crayon-striped-num" data-line="crayon-58dec<div class="crayon-num" data-line="crayon-58dec<div class="crayon-num crayon-striped-num" data-line="crayon-58dec<div class="crayon-num" data-line="crayon-58dec<div class="crayon-num crayon-striped-num" data-line="crayon-58dec<div class="crayon-num" data-line="crayon-58dec<div class="crayon-num crayon-striped-num" data-line="crayon-58dec<div class="crayon-num" data-line="crayon-58dec<div class="crayon-num crayon-striped-num" data-line="crayon-58dec<div class="crayon-num" data-line="crayon-58dec<div class="crayon-num crayon-striped-num" data-line="crayon-58dec<div class="crayon-num" data-line="crayon-58dec<div class="crayon-num crayon-striped-num" data-line="crayon-58dec<div class="crayon-num" data-line="crayon-58dec<div class="crayon-num crayon-striped-num" data-line="crayon-58dec<div class="crayon-num" data-line="crayon-58dec<div class="crayon-num crayon-striped-num" data-line="crayon-58dec<div class="crayon-num" data-line="crayon-58dec<div class="crayon-num crayon-striped-num" data-line="crayon-58dec<div class="crayon-num" data-line="crayon-58dec<div class="crayon-num crayon-striped-num" data-line="crayon-58dec<div class="crayon-num" data-line="crayon-58dec<div class="crayon-num crayon-striped-num" data-line="crayon-58dec<div class="crayon-num" data-line="crayon-58dec<div class="crayon-num crayon-striped-num" data-line="crayon-58dec<div class="crayon-num" data-line="crayon-58dec<div class="crayon-num crayon-striped-num" data-line="crayon-58dec<div class="crayon-num" data-line="crayon-58dec<div class="crayon-num crayon-striped-num" data-line="crayon-58dec<div class="crayon-num" data-line="crayon-58dec<div class="crayon-num crayon-striped-num" data-line="crayon-58dec<div class="crayon-num" data-line="crayon-58dec<div class="crayon-num crayon-striped-num" data-line="crayon-58dec<div class="crayon-num" data-line="crayon-58dec<div class="crayon-num crayon-striped-num" data-line="crayon-58dec<div class="crayon-num" data-line="crayon-58dec<div class="crayon-num crayon-striped-num" data-line="crayon-58dec<div class="crayon-num" data-line="crayon-58dec<div class="crayon-num crayon-striped-num" data-line="crayon-58dec<div class="crayon-num" data-line="crayon-58dec<div class="crayon-num crayon-striped-num" data-line="crayon-58dec
.file "test.c"&&&&&&&&.text.globl foo1&&&&&&&&.type foo1foo1:&&&&&&&&pushl %ebp&&&&&&&&movl %esp, %ebp&&&&&&&&subl $16, %esp&&&&&&&&movl 12(%ebp), %eax&&&&&&&&movl 8(%ebp), %edx&&&&&&&&leal (%edx,%eax), %eax&&&&&&&&addl 16(%ebp), %eax&&&&&&&&movl %eax, -4(%ebp)&&&&&&&&movl -4(%ebp), %eax&&&&&&&&leave&&&&&&&&ret&&&&&&&&.size foo1, .-foo1&&&&&&&&.section .rodata.LC0:&&&&&&&&.string "result=%d\n"&&&&&&&&.text.globl main&&&&&&&&.type mainmain:&&&&&&&&pushl %ebp&&&&&&&&movl %esp, %ebp&&&&&&&&andl $-16, %esp&&&&&&&&subl $32, %esp&&&&&&&&movl $11, 16(%esp)&&&&&&&&movl $22, 20(%esp)&&&&&&&&movl $33, 24(%esp)&&&&&&&&movl 24(%esp), %eax&&&&&&&&movl %eax, 8(%esp)&&&&&&&&movl 20(%esp), %eax&&&&&&&&movl %eax, 4(%esp)&&&&&&&&movl 16(%esp), %eax&&&&&&&&movl %eax, (%esp)&&&&&&&&call foo1&&&&&&&&movl %eax, 28(%esp)&&&&&&&&movl $.LC0, %eax&&&&&&&&movl 28(%esp), %edx&&&&&&&&movl %edx, 4(%esp)&&&&&&&&movl %eax, (%esp)&&&&&&&&call printf&&&&&&&&movl $0, %eax&&&&&&&&leave&&&&&&&&ret&&&&&&&&.size main, .-main&&&&&&&&.ident "GCC: (GNU) 4.4.4
(Red Hat 4.4.4-13)"&&&&&&&&.section .note.GNU-stack,"",@progbits
上面的汇编源代码和最终生成的可执行程序主体结构上已经非常类似了:
1]# gcc -g -o test test.s
1]# objdump -D test & testbin
1]# vi testbin
//… 省略部分不相关代码
$0x10,%esp
0xc(%ebp),%eax
0x8(%ebp),%edx
(%edx,%eax,1),%eax
0x10(%ebp),%eax
%eax,-0x4(%ebp)
-0x4(%ebp),%eax
$0xfffffff0,%esp
$0x20,%esp
c7 44 24 10 0b 00 00
$0xb,0x10(%esp)
c7 44 24 14 16 00 00
$0x16,0x14(%esp)
c7 44 24 18 21 00 00
$0x21,0x18(%esp)
8b 44 24 18
0x18(%esp),%eax
89 44 24 08
%eax,0x8(%esp)
8b 44 24 14
0x14(%esp),%eax
89 44 24 04
%eax,0x4(%esp)
8b 44 24 10
0x10(%esp),%eax
%eax,(%esp)
e8 a9 ff ff ff
89 44 24 1c
%eax,0x1c(%esp)
b8 04 85 04 08
$0x8048504,%eax
8b 54 24 1c
0x1c(%esp),%edx
89 54 24 04
%edx,0x4(%esp)
%eax,(%esp)
e8 c0 fe ff ff
b8 00 00 00 00
//… 省略部分不相关代码
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152
[root&&1]# gcc -g -o test test.s[root&&1]# objdump -D test & testbin[root&&1]# vi testbin //… 省略部分不相关代码80483c0:&&&&&& ff d0&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& call&& *%eax 80483c2:&&&&&& c9&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& leave 80483c3:&&&&&& c3&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& ret
: 80483c4:&&&&&& 55&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&push&& %ebp 80483c5:&&&&&& 89 e5&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& mov&&&&%esp,%ebp 80483c7:&&&&&& 83 ec 10&&&&&&&&&&&&&&&&&&&&&&&&&&sub&&&&$0x10,%esp 80483ca:&&&&&& 8b 45 0c&&&&&&&&&&&&&&&&&&&&&&&&&&mov&&&&0xc(%ebp),%eax 80483cd:&&&&&& 8b 55 08&&&&&&&&&&&&&&&&&&&&&&&& mov&&&&0x8(%ebp),%edx 80483d0:&&&&&& 8d 04 02&&&&&&&&&&&&&&&&&&&&&&&& lea&&&&(%edx,%eax,1),%eax 80483d3:&&&&&& 03 45 10&&&&&&&&&&&&&&&&&&&&&&&& add&&&&0x10(%ebp),%eax 80483d6:&&&&&& 89 45 fc&&&&&&&&&&&&&&&&&&&&&&&&&&mov&&&&%eax,-0x4(%ebp) 80483d9:&&&&&& 8b 45 fc&&&&&&&&&&&&&&&&&&&&&&&&&&mov&&&&-0x4(%ebp),%eax 80483dc:&&&&&& c9&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& leave 80483dd:&&&&&& c3&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& ret 080483de: 80483de:&&&&&& 55&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& push&& %ebp 80483df:&&&&&& 89 e5&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& mov&&&&%esp,%ebp 80483e1:&&&&&& 83 e4 f0&&&&&&&&&&&&&&&&&&&&&&&&&&&&and&&&&$0xfffffff0,%esp 80483e4:&&&&&& 83 ec 20&&&&&&&&&&&&&&&&&&&&&&&&&& sub&&&&$0x20,%esp 80483e7:&&&&&& c7 44 24 10 0b 00 00&&&&&& movl&& $0xb,0x10(%esp) 80483ee:&&&&&& 00 80483ef:&&&&&& c7 44 24 14 16 00 00&&&&&&&&movl&& $0x16,0x14(%esp) 80483f6:&&&&&& 00 80483f7:&&&&&& c7 44 24 18 21 00 00&&&&&&&&movl&& $0x21,0x18(%esp) 80483fe:&&&&&& 00 80483ff:&&&&&& 8b 44 24 18&&&&&&&&&&&&&&&&&&&&&&mov&&&&0x18(%esp),%eax 8048403:&&&&&& 89 44 24 08&&&&&&&&&&&&&&&&&&&&mov&&&&%eax,0x8(%esp) 8048407:&&&&&& 8b 44 24 14&&&&&&&&&&&&&&&&&&&&mov&&&&0x14(%esp),%eax 804840b:&&&&&& 89 44 24 04&&&&&&&&&&&&&&&&&&&&mov&&&&%eax,0x4(%esp) 804840f:&&&&&& 8b 44 24 10&&&&&&&&&&&&&&&&&&&& mov&&&&0x10(%esp),%eax 8048413:&&&&&& 89 04 24&&&&&&&&&&&&&&&&&&&&&&&& mov&&&&%eax,(%esp) 8048416:&&&&&& e8 a9 ff ff ff&&&&&&&&&&&&&&&&&&&& call&& 80483c4 804841b:&&&&&& 89 44 24 1c&&&&&&&&&&&&&&&&&&&& mov&&&&%eax,0x1c(%esp) 804841f:&&&&&& b8 04 85 04 08&&&&&&&&&&&&&&&& mov&&&&$0x8048504,%eax 8048424:&&&&&& 8b 54 24 1c&&&&&&&&&&&&&&&&&&&& mov&&&&0x1c(%esp),%edx 8048428:&&&&&& 89 54 24 04&&&&&&&&&&&&&&&&&&&& mov&&&&%edx,0x4(%esp) 804842c:&&&&&& 89 04 24&&&&&&&&&&&&&&&&&&&&&&&& mov&&&&%eax,(%esp) 804842f:&&&&&& e8 c0 fe ff ff&&&&&&&&&&&&&&&&&&&& call&& 80482f4 8048434:&&&&&& b8 00 00 00 00&&&&&&&&&&&&&&mov&&&&$0x0,%eax 8048439:&&&&&& c9&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&leave 804843a:&&&&&& c3&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&ret 804843b:&&&&&& 90&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& nop 804843c:&&&&&& 90&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& nop //… 省略部分不相关代码
用GDB调试可执行程序test:
在main函数第一条指令执行前我们看一下进程test的栈空间布局。因为我们最终的可执行程序是通过glibc库启动的,在main的第一条指令运行前,其实还有很多故事的,这里就不展开了,以后有时间再细究,这里只要记住一点:main函数执行前,其进程空间的栈里已经有了相当多的数据。我的系统里此时栈顶指针esp的值是0xbffff63c,栈基址指针ebp的值0xbffff6b8,指令寄存器eip的值是0x80483de正好是下一条马上即将执行的指令,即main函数内的第一条指令“push %ebp”。那么此时,test进程的栈空间布局大致如下:
然后执行如下三条指令:
pushl %ebp
//将原来ebp的值0xbffff6b8如栈,esp自动增长4字节
movl %esp, %ebp
//用ebp保存当前时刻esp的值
andl $-16, %esp
//内存地址对其,可以忽略不计
pushl %ebp&&&&&&&& //将原来ebp的值0xbffff6b8如栈,esp自动增长4字节movl %esp, %ebp&&&&//用ebp保存当前时刻esp的值andl $-16, %esp&&&&//内存地址对其,可以忽略不计
行完上述三条指令后栈里的数据如上图所示,从0xbffff630到0xbffff638的8字节是为了实现地址对齐的填充数据。此时ebp的值0xbffff638,该地址处存放的是ebp原来的值0xbffff6b8。详细布局如下:
第28条指令“subl $32, %esp”是在栈上为函数里的本地局部变量预留空间,这里我们看到main主函数有4个int型的变量,理论上说预留16字节空间就可以了,但这里却预留了32字节。GCC编译器在生成汇编代码时,已经考虑到函数调用时其输入参数在栈上的空间预留的问题,这一点我们后面会看到。当第28条指令执行完后栈空间里的数据和布局如下:
然后main函数里的变量x,y,z的值放到栈上,就是接下来的三条指令:
movl $11, 16(%esp)
movl $22, 20(%esp)
movl $33, 24(%esp)
movl $11, 16(%esp)movl $22, 20(%esp)movl $33, 24(%esp)
这是三条寄存器间接寻址指令,将立即数11,22,33分别放到esp寄存器所指向的地址0xbffff610向高位分别偏移16、20、24个字节处的内存单元里,最后结果如下:
注意:这三条指令并没有改变esp寄存器的值。
接下来main函数里就要为了调用foo1函数而做准备了。由于mov指令的两个操作数不能都是内存地址,所以要将x,y和z的值传递给foo1函数,则必须借助通用寄存器来完成,这里我们看到eax承担了这样的任务:
movl 24(%esp), %eax
movl %eax, 8(%esp)
movl 20(%esp), %eax
movl %eax, 4(%esp)
movl 16(%esp), %eax
movl %eax, (%esp)
movl 24(%esp), %eaxmovl %eax, 8(%esp)movl 20(%esp), %eaxmovl %eax, 4(%esp)movl 16(%esp), %eaxmovl %eax, (%esp)
当foo1函数所需要的所有输入参数都已经按正确的顺序入栈后,紧接着就需要调用call指令来执行foo1函数的代码了。前面的博文说过,call指令执行时分两步:首先会将call指令的下一条指令(movl %eax, 28(%esp))的地址(0x0804841b)压入栈,然后跳转到函数foo1入口处开始执行。当第38条指令“call foo1”执行完后,栈空间布局如下:
call指令自动将下一条要执行的指令的地址0x0804841b压入栈,栈顶指针esp自动向低地址处“增长”4字节。所以,我们以前在C语言里所说的函数返回地址,应该理解为:当被调用函数执行完之后要返回到它的调用函数里下一条马上要执行的代码的地址。为了便于观察,我们把foo1函数最后生成指令再列出来:
.globl foo1
.type foo1
pushl %ebp
movl %esp, %ebp
subl $16, %esp
movl 12(%ebp), %eax
movl 8(%ebp), %edx
leal (%edx,%eax), %eax
addl 16(%ebp), %eax
movl %eax, -4(%ebp)
movl -4(%ebp), %eax
.size foo1, .-foo1
123456789101112131415
.globl foo1&&&&&&&&&& .type foo1 foo1:&&&&&&&&&& pushl %ebp&&&&&&&&&& movl %esp, %ebp&&&&&&&&&& subl $16, %esp&&&&&&&&&& movl 12(%ebp), %eax&&&&&&&&&&movl 8(%ebp), %edx&&&&&&&&&&leal (%edx,%eax), %eax&&&&&&&&&&addl 16(%ebp), %eax&&&&&&&& movl %eax, -4(%ebp)&&&&&&&&&&movl -4(%ebp), %eax&&&&&&&&&&leave&&&&&&&&&&ret&&&&&&&&&&.size foo1, .-foo1
进入到foo1函数里,开始执行该函数里的指令。当执行完第6、7、8条指令后,栈里的数据如下。这三条指令就是汇编层面函数的“序幕”,分别是保存ebp到栈,让ebp指向当前栈顶,然后为函数里的局部变量预留空间:
接下来第9和第10条指令,也并没有改变栈上的任何数据,而是将函数输入参数列表中的的x和y的值分别转载到eax和edx寄存器,和main函数刚开始时做的事情一样。此时eax=22、edx=11。然后用了一条leaf指令完成x和y的加法运算,并将运算结果存在eax里。第12条指令“addl 16(%ebp), %eax”将第三个输入参数p的值,这里是实参z的值为33,同样用寄存器间接寻址模式累加到eax里。此时eax=11+22+33=66就是我们最终要得计算结果。
因为我们foo1()函数的C代码中,最终计算结果是保存到foo1()里的局部变量x里,最后用return语句将x的值通过eax寄存器返回到mian函数里,所以我们看到接下来的第13、14条指令有些“多此一举”。这足以说明gcc人家还是相当严谨的,C源代码的函数里如果有给局部变量赋值的语句,生成汇编代码时确实会在栈上为本地变量预留的空间里的正确位置为其赋值。当然gcc还有不同级别的优化技术来提高程序的执行效率,这个不属于本文所讨论的东西。让我们继续,当第13、14条指令执行完后,栈布局如下:
将ebp-4的地址处0xbffff604(其实就是foo1()里的第一个局部参数x的地址)的值设置为66,然后再将该值复制到eax寄存器里,等会儿在main函数里就可以通过eax寄存器来获取最终的计算结果。当第15条指令leave执行完后,栈空间的数据和布局如下:
我们发现,虽然栈顶从0xbffff5f8移动到0xbffff60c了,但栈上的数据依然存在。也就是说,此时你通过esp-8依旧可以访问foo1函数里的局部变量x的值。当然,这也是说得通的,因为函数此时还没有返回。我们看栈布局可以知道当前的栈顶0xbffff60c处存放的是下一条即将执行的指令的地址,对照反汇编结果可以看到这正是main函数里的第18条指令(在整个汇编源文件test.s里的行号是39)“movl %eax, 28(%esp)”
leave指令其实完成了两个任务:
1、将栈上为函数预留的空间“收回”;
2、恢复ebp;
也就是说leave指令等价于下面两条指令,你将leave替换成它们编译运行,结果还是对的:
movl %ebp,%esp
movl %ebp,%esppopl %ebp
前面我们也说过,ret指令会自动到栈上去pop数据,相当于执行了“popl %eip”,会使esp增大4字节。所以当执行完第16条指令ret后,esp从0xbffff60c增长到0xbffff610处,栈空间结构如下:
现在已经从foo1里返回了,但是由于还没执行任何push操作,栈顶“上部”的数据依旧还是可以访问到了,即esp-12的值就是foo1里的局部变量x的值、esp-4的值就是函数的返回地址,当执行第39条指令“movl %eax,28(%esp)”后栈布局变成下面的样子:
第39条指令就相当于给main里的result变量赋值66,如上红线标注的地方。接下来main函数里要执行printf(&#8220;result=%d\n&#8221;,result)语句了,而printf又是C库的一个常用的输出函数,这里就又会像前面调用foo1那样,初始化栈,然后用“call printf的地址”来调用C函数。当40~43这4条指令执行完后,栈里的数据如下:
movl $.LC0, %eax
movl 28(%esp), %edx
movl %edx, 4(%esp)
movl %eax, (%esp)
movl $.LC0, %eaxmovl 28(%esp), %edxmovl %edx, 4(%esp)movl %eax, (%esp)
上图为了方便理解,将栈顶的0x替换了成字符串“result=%d\n”,但进程实际运行时此时栈顶esp的值是字符串所在的内存地址。当第44条指令执行完后,栈布局如下:
由于此时栈已经用来调用printf了,所以栈顶0xbffff610“以上”部分的空间里就找不到foo1的任何影子了。最后在main函数里,当第46、47条指令执行完后栈的布局分别是:
当main函数里的ret执行完,其实是返回到了C库里继续执行剩下的清理工作。
所以,最后关于C的函数调用,我们可以总结一下:
1、函数输入参数的入栈顺序是函数原型中形参从右至左的原则;
2、汇编语言里调用函数通常情况下都用call指令来完成;
3、汇编语言里的函数大部分情况下都符合以下的函数模板:
.globl fun_name
.type fun_name
pushl %ebp
movl %esp, %ebp
&函数主体代码&
.globl fun_name.type fun_namefun_name:&&&&&&&&pushl %ebp&&&&&&&&movl %esp, %ebp&&&&&&&&&函数主体代码& &&&&&&&&leave&&&&&&&&ret
如果我们有个函数原型:int funtest(int x,int y int z char* ptr),在汇编层面,当调用它时栈的布局结构一般是下面这个样子:
而有些资料上将ebp指向函数返回地址的地方,这是不对的。正常情况下应该是ebp指向old ebp才对,这样函数末尾的leave和ret指令才可以正常工作。
可能感兴趣的话题
讲解的很详细
关于伯乐在线博客
在这个信息爆炸的时代,人们已然被大量、快速并且简短的信息所包围。然而,我们相信:过多“快餐”式的阅读只会令人“虚胖”,缺乏实质的内涵。伯乐在线内容团队正试图以我们微薄的力量,把优秀的原创文章和译文分享给读者,为“快餐”添加一些“营养”元素。
新浪微博:
推荐微信号
(加好友请注明来意)
&#8211; 好的话题、有启发的回复、值得信赖的圈子
&#8211; 分享和发现有价值的内容与观点
&#8211; 为IT单身男女服务的征婚传播平台
&#8211; 优秀的工具资源导航
&#8211; 翻译传播优秀的外文文章
&#8211; 国内外的精选文章
&#8211; UI,网页,交互和用户体验
&#8211; 专注iOS技术分享
&#8211; 专注Android技术分享
&#8211; JavaScript, HTML5, CSS
&#8211; 专注Java技术分享
&#8211; 专注Python技术分享
& 2017 伯乐在线1502人阅读
原文网址:
&&&&& 对函数的“定义”和“声明”不是一回事。函数的定义是指对函数功能的确立,包括指定函数名,函数&#20540;类型、形参及其类型以及函数体等,它是一个完整的、独立的函数单位。而函数的声明的作用则是把函数的名字,函数类型以及形参的类型、个数和顺序通知编译系统,以便在调用该函数时进行对照检查(例如,函数名是否正确,实参与形参的类型和个数是否一致),它不包括函数体。
————谭浩强 ,《C程序设计》(第四版),清华大学出版社,2010年6月,p182
&&&&& 这段论述包含了许多概念性错误,这些概念错误在许多C语言书中都同样普遍存在。为了说明这些错误,首先来回顾一下C语言演变和发展的一些情况。
&&&&&&最早,C语言的代码可以这样写:
&&printf(&hello,world!\n&);
&&&&&&注意,这段代码对标识符printf没有进行任何说明。这是因为printf()函数的返回&#20540;为int类型。当时的C语言规定,对于没有任何说明的函数名,编译器会默认为返回&#20540;为int类型,因此对这样的函数名可以不做任何说明。那个时期的C语言,很多情况下int可以不写。例如main()函数返回&#20540;的类型为int就可以不写。
&&&&&&但是需要特别说明的是,这种“省劲”的写法已经过时,从C90标准起,这种写法就步入了被逐步抛弃的过程(尽管当时还没有完全立即废止)。C99废除了隐式函数声明法则(remove implicit function declaration),另外,省略main()前面的int也已经不再容许了。
&&&&&&在C语言早期,尽管有时不需要对函数名进行说明,但有些情况下对函数名进行说明还是必须的,比如:
double&sqrt();
int&main()
&&printf(&%f\n&&,
&&&&&&这是因为函数sqrt()返回&#20540;的类型不是int类型而是double类型,编译器编译时需要知道sqrt(9.)这个表达式的类型。
&&&&&&不难注意到这种对函数名的说明非常简单,这是最早期的一种函数类型说明的形式。这种说明只着重说明函数名是一个函数及其返回&#20540;类型,如果程序员在调用函数时存在参数类型或个数方面的错误编译器是无法察觉的,因为函数类型说明中“()”内没有任何信息。
&&&&&&这种办法只说明了函数名与()进行运算的结果也就是函数返回&#20540;的数据类型,无法进一步检查参数方面的错误是这种写法的不足之处。
&&&&&&如果不写函数类型说明,也可以把函数定义写在函数调用之前:
double&square
( double&x)
&&&return&x
int&main(void)
&&printf(&%f\n&&,
square(3.) );
&&return&0;
&&&&&&这表明函数定义也具有对函数名的类型加以说明的效果,因此从这个意义上来说,函数定义也是一种对函数类型的说明。这种办法可以检查出函数调用时在参数个数和类型方面的错误。
&&&&&&但是,用这种办法说明函数名并不好,因为这样做在编程时还需要考虑应该把哪个函数定义写在前面,哪个写在后面的问题。假如函数A调用函数B,函数B调用函数C,函数C又调用函数A,究竟如何安排函数定义的顺序就会让人感到无所适从。此外这种办法也不利于代码的组织,在由多个源文件组成的源程序时,这种写法就更会捉襟见肘、漏洞百出。因此,在1990年,C标准借鉴C&#43;&#43;语言规定了一种新的说明函数名的方法,这就是函数原型(Function
Propotype)式说明函数类型的方法:
double&square
( double&);&
int&main(void)
&&printf(&%f\n&&,
square(3.) );
&&&&return&0;
double&square
( double&x)
&&&return&x
&&&&&&使用这种办法,不但可以检查函数调用时参数类型和个数方面的错误,同时解决了源代码的组织问题,因为程序员不必再考虑该把哪个函数写在前面、哪个写在后面这种无聊的问题了。这种办法全面地说明了函数名的数据类型。此外要说明的是,把形参及其数据类型写在“()”内形式的函数定义也属于函数原型(Function
Propotype)的范畴。
&&&&&&由此可见,古老的、不对参数进行任何说明的函数类型说明方式、函数定义以及函数原型式的函数类型说明方式都具有说明函数名意义的效用。从这个意义上讲它们都是函数声明。在C语言中,声明(Declaration)这个词的本义就是指定标识符的意义和性质(A declaration specifies the interpretation
and attributes of a set of identifiers.),某个标识符的定义(Definition)同时也是这个标志符的“声明”(Declaration)。函数定义(Function definition)则意指包括函数体。(A definition of an identifier is a declaration for that identifier that: ……for a function, includ)。函数原型则特指包括说明参数类型的函数声明,它同样包含用这种方式写出的函数定义。
&&&&&&现在回过头来看样本中的第一句话:“对函数的“定义”和“声明”不是一回事”。由于函数定义本身就是一种函数声明,怎么可以说它们不是一回事呢?这句话的逻辑就如同说“男人”和“人”不是一回事。你可以说男人和女人不是一回事,因为他们没有交集。但没法说男人和人不是一回事,因为男人是人的子集,男人就是人的一种,怎么可以说男人和人不是一回事呢?
&&&&&&那么,不带函数体的函数声明应该如何称呼呢?在C语言中,它们叫被做“函数类型声明”(Function type declaration)。函数类型声明最主要的特点是声明了函数名是一个函数及其返回&#20540;的类型,如果也声明了参数的类型,则是函数原型式的函数类型声明。
&&&&&&样本中的“而函数的声明的作用则是把函数的名字,函数类型以及形参的类型、个数和顺序通知编译系统,以便在调用该函数时进行对照检查(例如,函数名是否正确,实参与形参的类型和个数是否一致),它不包括函数体”这句话同样不通。其主要错误是它混淆了“函数原型式类型声明”与“函数声明”这两个概念,前一个概念只是后一个概念的子集。函数声明中不但包含“函数类型声明”,也包含“函数定义”和老式的“函数类型声明”。由于函数定义本身就是一种函数声明,所以无法断定函数的声明是否包括函数体;而且老式的函数类型声明(例如double
sqrt();)也属于函数声明,这种函数声明并不检查参数类型及个数方面的错误。此外函数声明也并没有检查“函数名”正确与否的功能。
&&&&&&这段文字中的“函数类型”这个概念也有错误,函数类型所描述的不但包括函数返回&#20540;类型,也可能一并描述参数的个数和类型(如果是函数原型),因此不能与“形参的类型、个数”相提并论。
&&&&&&现代的C语言的函数定义和函数类型声明都采用函数原型式的风&#26684;,C99把旧的非原型形式视为过时,这意味着非原型形式以后可能被禁止。
关于申明和定义:
标准编译器 对函数的调用 都要
先申明 才能 调用
(生产的时候就这样设定的)。。我推荐试用
vs2005,vc2000.
Dev-cpp ,Turboc 3.0 (2.0 不支持鼠标有点 麻烦)
这里 就涉及:
申明和定义之间的区别。
申明-- 不开辟内存,仅仅告诉编译器,申明的部分存在,要预留一点空间。
定义-- 开辟内存。
申明 属于 预编译范畴的。最好写在头文件中(楼主,自定义头文件学了没有啊?)(申明都写在头文件中,只是一种好的编程习惯)
因为 在编译的时候,编译器先处理一些特殊数据(宏定义,函数的申明,变量的申明),在这个过程中,编译器 通过申明 可以预测整个定义需要的内存大小,并且 把这些大小预留起来,留给定义的时候使用(因为虽然定义,但是在 main函数里面没有调用,一样的不分配内存)。
如果不事先申明,直接定义,有可能会造成系统崩溃,出现内存不足,不能分配。 然而现在内存都是1G左右了,所以一般空间都足够,所以你也可以不申明直接定义函数。
但是一旦内存紧张,就会出错,而且程序简洁性就很低。(不看到定义,还不知道原来还定义了这么一个函数!!!)
还有 变量也一样,标准的都是 先申明在定义。
int i=0; //定义并初始化为0
.。。。。。。。。。。。。。。。。。
所以 从现在其要养成良好的编程习惯
先申明,在定义。
申明最好都放在 一块,并且都在头文件中(为了便于管理)。
参考知识库
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:308174次
积分:4010
积分:4010
排名:第6577名
原创:113篇
转载:135篇
评论:23条
(3)(4)(2)(3)(39)(3)(5)(16)(22)(19)(18)(17)(8)(3)(2)(10)(15)(29)(17)(11)(2)

我要回帖

更多关于 c语言函数原型 的文章

 

随机推荐