-------------------------------------------------------函数---------------------------------------------------------
将输入的数据按照指定算法进行处理 --------> 处理的过程需要各种控制流的参与 ------> 需要申请不定大小的空间 --> 函数栈
·为什么使用函数?
1) 函数的使用可以省去重复代码的编写
2) 函数的使用更具模块化,从而有利于程序的阅读修改和完善
· 复合语句体格式
返回值类型 函数名 ( 形 参 )
{
变量;
表达式;
控制流;
调用外部函数;
}
E.g:
int main(int argc, char **argv) ----> 当前函数执行完毕返回一个int型的数值
----> 当前函数执行时,需要传入两个值,一个 int ,一个 char **
{
..... ----> 函数的执行内容
.....
}
在Linux的终端中,输入
nm test
返回:
08048494 T main ----> 08048494 是当前main函数代码的地址
可见当test运行时,系统会将08048494地址上的数据及代码拷贝到函数栈(函数体的运行内存空间)中;
-----------------------------------------------------------------------------------------------------------
·在标准C 中,main()是C语言程序的唯一主入口。
编译成功的可执行程序中,除了main入口 还有汇编入口。
test.c ----> 实现了main的定义 以及 实现了相关的自定义函数。
经过编译后的test.c ,加载了编译器提供的各种库,还有汇编入口文件。
/lib/libxxx.so -----> 库
//汇编入口文件
/usr/lib/i386-linux-gnu/crt1.o
/usr/lib/i386-linux-gnu/crti.o
/usr/lib/i386-linux-gnu/crtn.o
内容分析:
nm point
0804846c T _fini
08048488 R _fp_hw
08048294 T _init
08048300 T _start ----> 汇编程序入口
080483b4 T main ----> main函数的代码段地址(存放在point程序中)
/usr/lib/i386-linux-gnu/crt1.o
00000000 R _fp_hw
00000000 T _start
/usr/lib/i386-linux-gnu/crti.o
00000000 T _fini
00000000 T _init
结论:
标准C 程序编译后,通过编译器提供的汇编代码,从汇编的入口跳转到main执行。
----------------------------------------------------------------------------------------------------------------------
当从 080483b4 执行main函数时,系统将main函数对应的代码,开辟一个新的内存空间进行运行。
通过调试器跟踪函数运行内存:
gdb
使用GDB ,在编译程序时,加上 -g 选项:
如: gcc -g point.c -o point
使用gdb:
gdb point
gdb常用命令:
l ----> list 陈列当前程序代码
l 行号 ---> 成列所在行号附近的代码
p ----> print 打印数据,查看数据的值 ---> p A ----> 打印A的值
p main ----> 在程序运行前,可以查看main函数存在内存的初始位置
----> 在程序运行后,可以通过info frame X 查看main函数存在内存的栈位置
r ---> run 运行调试器,执行代码
---> 一般在run前设置断点,方便调试;
c ---> 从当前断点,跳转到下一个断点
b ---> break;
b 行号 -----> 在当前行设置断点,按 r 运行到当前行头即停止(还未执行本行代码)
-----> 断点一般设置在赋值或者执行语句前;
info b ---> 查看当前断点信息
d 断点号 -->删除断点
q ---> 退出debug
bt ----> 查看当前函数栈
n ---> next 执行下一步(不进入函数体)
s ---> step 单步执行,进入函数体
(gdb) bt
#0 main (argc=1, argv=0xbffff3e4) at point.c:8 ---> 当前有一个函数栈,编号为0
//查看栈信息
(gdb) info frame 0
Stack frame at 0xbffff350: -----> main函数运行的栈地址
eip = 0x80483b7 in main (point.c:8); saved eip 0xb7e384d3
source language c.
Arglist at 0xbffff348, args: argc=1, argv=0xbffff3e4
Locals at 0xbffff348, Previous frame's sp is 0xbffff350
Saved registers:
ebp at 0xbffff348, eip at 0xbffff34c
-------------------------------------------------------------------------------------------------------
·或许会有一个疑问,如果虚拟地址都是随机分配的话,那么程序编译后的程序执行地址是否冲突?
很显然,答案是否定的,因有MMU存储管理单元的存在,它会将每个程序执行的虚拟地址关联到具体的物理地址。
linux C 程序 生成的镜像,执行地址均为虚拟地址。 ------> 为了实现多任务共存
-------> 解决内存使用问题 ----> 需要内存规划
------> 每个程序均拥有一个虚拟地址表 ----> 32bit ----> 4G
-----> 具体的虚拟地址的物理地址空间 ----> 由MMU完成转化
-------------------------------------------------------------------------------------------------------------------
局部函数的存储位置,在对应的函数栈中,随着函数的运行而分配,随着函数的退出而消亡。
&A ---> 取变量A的地址;
装载地址的变量 ----> 指针变量 -----> 规范指针变量的使用 ----> 指针变量也有类型
char A; -----> &A -----> 变量A 的地址 -----> 装载变量A地址的变量 -----> char *pA;
int B;
char A ====> char (*)pA ----> 变量pA 是一个(*) 指针,指向char 型的变量的地址
pA = &A;
指针定义:
int *pA;
使用指针:
int A;
pA = &A;
获取A的值:
*pA ----> 去除对应地址上的数据
存在指针的必要性?
------------------------------------------demo--------------------------------------------
void swap(int x,int y)
{
int temp;
temp = x;
x = y;
y = temp;
}
int main(int argc,char **argv)
{
int a = 5;
int b = 6;
add(a,b);
swap(a,b);
printf("%d %d,a,b\n");
return 0;
}
结论:
swap() 只实现了传递的数据在函数体内值交换。(函数的调用是值传递)所以a=5 b=6
·如果想通过swap()实现 a 与 b 的内部值交换,需要操作a与b的存储空间 ------> 操作存储地址 -----> 操作地址变量 ----> 指针
改进方法:
void Swap(int *x,int *y)
{
int temp;
temp = *x;
*x = *y;
*y = temp;
}
int main(int argc,char **argv)
{
int a = 5;
int b = 6;
add(a,b);
Swap(&a,&b);
printf("a = %d b = %d \n",a,b);
printf("%d %d,a,b\n");
return 0;
}
输出: a=6 b=5
文章评论(0条评论)
登录后参与讨论