函数本质
为什么会有函数?
在复杂程序中,整个程序被分成了多个源文件,一个文件分成多个函数,一个函数分成多个语句,这就是整个程序的组织形式。函数的出现是人(程序员和架构师)的需要,而不是机器(编译器、CPU)的需要。函数目的就是实现模块化编程,既让代码的可读性好,又方便分工,利于程序的组织。
函数书写一般原则
- 遵循一定格式:函数返回类型、函数名、参数列表等;
- 一个函数只做一件事:函数不能太长,也不宜太短;
- 传参不宜过多:在ARM体系下,传参不宜超过4个;若传参需要超过4个最好考虑结构体打包;
函数是动词、变量是名词
函数的实质是数据处理器
函数的基本使用
函数三要素:定义、声明、调用
函数定义时函数的根本,函数名表示该函数在内存中的首地址,所以可用函数名来调用执行该函数(实质是指针解引用访问?);函数定义中的函数体是函数的执行的关键,函数将来执行时主要是执行函数体。
函数声明作用是告诉编译器函数的原型;
函数原型和作用
main函数
在定义一个函数时,一般给函数设计了输入和输出,函数的形参是函数的输入,返回值是函数的输出。main函数很特殊,C语言规定了main函数是整个程序的入口,总是从main函数开始执行的,其他函数只有直接或间接被main函数调用才能被执行。其设计原则是把函数作为程序的构成模块,main函数就像是我们搭积木的主体,函数就是一块积木。
main函数返回值
在单片机的C语言代码中,main函数常常以void main(void)形式出现,单片机的编译器是允许该种形式出现,其C语言不是标准的C99,。在实际项目中,应该按照标准的形式:当你把程序从一个编译器移动另一个编译器时,照样能正常工作。
通常main函数最后是return 0,0就是main函数的返回值,返回0说明程序正常执行,程序运行结束,返回非0说明程序异常。所以main函数中返回值的意义,不仅仅是一个返回值,还说明结束程序,程序是否运行正常。
谁调用了main函数
在Linux系统中,使用./xxx执行一个可执行程序,也可通过shell脚本来调用执行一个程序,还可在程序中去调用一个程序(fork exec),但本质都相同。Linux中一个新程序的执行就是一个进程的创建、加载、运行和消亡。Linux中执行一个程序其实就是创建一个新的进程,然后把程序丢进这个进程中去执行知道结束。进程(除了三个特殊的进程之外)都是被它的父进程调用fork函数创建出来的。命令行本身就是一个进程,在命令行下执行./xxx方式执行一个新的程序,该新程序是作为命令行进程的一个子进程执行。main函数的返回值最终会返回给父进程,父进程判断子进程(新程序)是执行成功了还是执行失败了,做出要不要复活子进程继续运行等的决定。
main函数的传参
在Linux C中,main函数可以传参,也可以不传参,int main(void)形式就是没给main函数传参。但有时希望程序具有一定的灵活性,选择在执行程序时通过传参来控制程序的运行,达到不需要重新编译程序就可以改变程序的运行结果的目的。
主函数main函数的第一个参数argc(argument count)是命令行中的字符串个数,即程序运行时非main函数传递参数的个数。第二个参数argv(argument value)是一个指向字符串的指针数组,命令行中的每一个字符都会被存储到内存中,并且分配一个指针指向它。argv[0]是指给main函数的第一个传参及程序的名字,argv[1]是给main函数传的第二个参数。
#include<stdio.h>
#include<string.h>
#include<stdio.h>
#include<string.h>
int main(int argc,char *argv[]){
int i = 0;
printf("main函数传参个数是:%d.\n",argc);
for (i=0;i<argc;i++){
printf("第%d个参数是%s.\n",i,argv[i]);
}
return 0;
}
运行结果
./main_test
main函数传参个数是:1.
第0个参数是./main_test.
./main_test 12
main函数传参个数是:2.
第0个参数是./main_test.
第1个参数是12.
./main_test "asd"
main函数传参个数是:2.
第0个参数是./main_test.
第1个参数是asd.
在main传参,需要注意一下几点,否则容易出现问题:
- main()函数传参都是通过字符串传进去的;
- main()函数只有被调用时传参,各个参数(字符串)之间是通过空格来间隔的;
- 在程序内部如果要使用argv,那么一定要先检验argc。
递归函数
函数调用机制
C语言函数的调用在x86平台一般是用栈的方式来支持其操作(CallingConvention)。当函数发生调用时,函数以入栈的方式,将函数的返回地址、参数等进行压栈。C语言默认环境下调用规范为,参数从右向左一次压栈(如printf函数),这就是函数的调用机制。同时函数每调用一次,就会进行一次压栈,其所占的空间彼此独立,调用函数和被调用函数依靠传入参数和返回值彼此联系;
如一个main()函数调用sub(int a, int b)简单内存图形(待替换)如下:
int a |
int b 入 |
sub()返回地址 栈 |
main参数 |
main()返回地址 |
栈 |
递归函数
使用递归原则:收敛性、栈溢出
递归与循环区别
库函数
函数库本身不是C语言的一部分,是一些事先写含的函数的集合,给别人复用。函数是模块化的,因此可以被复用。常见的输入函数scanf和输出函数printf,就是所谓的库函数,即标准输入输出库函数。在stdio.h头文件中给出了相应函数声明,可直接调用。
函数库提供形式:静态链接库和动态链接库
静态库是商业公司将自己的函数库源代码经过只编译不链接形成.o的目标文件,然后用ar工具将.o文件归档成.a的归档文件,即静态链接库文件。用户通过.a库文件和.h头文件,在.c文件中直接调用这些库文件,在链接的时候链接器会去.a文件中找到对应的.o文件,链接后最终形成可执行程序。
动态链接库(共享库)相比静态链接库效率更高,动态链接库不是将库函数的代码直接复制进可执行程序中,只是做个链接标记,当应用程序在内存中执行,运行时环境发现它调用了一个动态库中的库函数时,会加载该动态库到内存中,无论有多少个应用程序在同时使用该函数,该库函数在内存中只有一份。
库函数使用
字符串库函数
字符串是由多个字符在内存中连续分布组成的字符结构,字符串的特点是指定了开头(字符串的指针)和结尾(结尾固定为字符'\0')而没有指定长度(长度由开头地址和结尾地址相减得到)。
man手则使用
常见字符串处理函数
C语言库中字符串处理函数的各种声明包含在string.h中,文件存放在如ubuntu系统中/usr/include中,函数有memcpy、memmove、memset....
链接是加-lm
GCC时加-lm参数是告诉链接器到libm中去查找用到的函数,使用gcc math.c -lm命令编译,此时编译则通过,并生成可执行文件。实战中发现在高版本中的GCC中,经常出现没加-lm也可编译链接,因为GCC自动寻找到了需要的库文件。
制作静态链接库
使用gcc -o只编译不连接,生成.o文件,然后使用ar工具打包成.a归档文件。库名不能随便乱起,一般是lib+库名称,后缀名.a表示是一个归档文件。
gcc demo.c -o demo.o -c
ar -rc libdemo.a demo.o
gcc 11_gab_var.c -o 11_gab_var.o -c
ar -rc lib11_gab_var.a 11_gab_var.o
制作头文件demo.h,只需声明函数即可;
void func1(void);
int func2(int a,int b);
注意:制作出静态库之后,发布时需要发布.a文件和.h文件。
使用静态链接库
把.a和.h都放在需要引用的文件夹下,然后在编写.c文件中包含库的.h,然后直接使用库函数。
gcc test.c -o test -ldemo -L.
注意
默认连接库里没有我们的库文件,需要告诉gcc去哪里去找我们的库文件;
当使用-lxxxx时,链接器试图去默认是链接库路径去寻找libxxx.a文件,但我们把libdemo.a放在了当前路径,不在默认链接库的目录下, 所以找不到。-L是指定链接器在哪个目录下寻找库文件,句点表示当前目录。
制作动态链接库
Linux下动态链接库的后缀名是.so,Windows系统则为.dll。
gcc demo.c -o demo.o -c -fPIC
gcc -o libdemo.so demo.o -shared
-fPIC是位置无关码,-shared是按照共享库的方式来链接。发布动态链接库时,发布libxxx.so和xxx.h即可。
使用自己制作的共享库
gcc test.c -o test -ldemo -L.
编译通过,运行时报错,error while loading shared libaries:libdemo.so :cannot open shared object file.
原因是动态链接库运行时需要被加载(运行时唤醒在执行test程序的时候发现动态链接了libdemo.so,于是回去固定目录尝试加载libdemo.so)
解决方法一:将libdemo.so方到固定目录下就可以了,固定目录一般为/usr/lib目录
cp libdemo.so /usr/lib
解决方法二:使用环境变量LD_LIBR ARY_PATH。将libdemo.so所在目录添加到环境变量LD_LIBR ARY_PATH中即可。
如/mnt/hgfs/Winshare/object/sotest目录就是动态库lxxx.so文件所在位置,执行一下语句
export LD_LIBR ARY_PATH = $LD_LIBR ARY_PATH:/mnt/hgfs/Winshare/object/sotest
注意:添加完动态库之后,可以用ldd命令查看共享库是够能被加载并被解析
[root@comput3 test]# ldd 11_gab_var
linux-vdso.so.1 => (0x00007ffeb3ada000)
libpthread.so.0 => /lib64/libpthread.so.0 (0x00007f5b0c647000)
libc.so.6 => /lib64/libc.so.6 (0x00007f5b0c279000)
/lib64/ld-linux-x86-64.so.2 (0x00007f5b0c863000)
由上知,11_gab_var.so文件没有被找;