《Linux C程序设计 王者归来》这门课程的第三章是函数,函数的作用就是使得程序的模块性更强,不同作用的函数放在不同的地方,更重要的就是便于代码的修改和阅读。
学习函数首先要了解函数的本质,函数的本质是一段机器指令代码。而函数名的本质就是一个标号,该标号的值等于内存中存储函数代码的首地址。函数调用时会使空间的栈不断增长,从当前进程中的栈顶的位置到函数保存返回值的位置,这一块内存成为函数的栈帧。所有函数中定义的局部变量都存储在函数的栈帧上,当函数调用结束的时该块栈帧就消失了。如下图所示:
调用者函数开始调用之后,函数栈帧开始生长,调用完毕,该栈帧消失。一般地函数调用分为4个步骤:
将参数压入栈堆
保存寄存器的值
保存返回地址
跳转
这四步中的前三步都要访问内存,访问内存在计算机操作中很消耗时间,因此,函数调用比较好费时间,程序执行时应尽量减少函数的调用,对程序起到一定的优化作用。
从上图函数的本质中可以看出,C语言程序中的局部变量存储在栈上,全局变量存储在数据段上,也就是内存中。因为全局变量在整个程序的执行过程中一直存在,如果存在寄存器中会降低程序的运行效率。之前也提到过,尽量减少内存访问能够很大的提高程序的效率。局部变量会存储在寄存器中,因此一种程序的优化方式就是不要将循环算子等高频率使用的变量设置为全局变量或者静态变量。例如下边两个函数a.c和b.c:
a.c:
1 int i; 2 3 void f() 4 5 { 6 7 int a[10]; 8 9 for(i=0;i<100;i++) 10 11 a[i]=i; 12 13 }
b.c:
1 void f() 2 3 { 4 5 int i; 6 7 int a[10]; 8 9 for(i=0;i<100;i++) 10 11 a[i]=i; 12 13 }
从上边的两个程序对比可以看出,循环算子是i的程序中,每次访问全局变量的时候都要从内存中去数据。每次调用全局变量都要经过从内存中读取变量i的值,对变量i进行累加,将变量i的值重新存回到内存中这三步,而拒不变量只需要做累加变量所在的寄存器的值,从这两个简单程序的对比就能看出局部变量的执行效率要比全局变量高的多,如果要进行多次访问的话尽量采用局部变量。
函数作用就是使得程序的模块性更强,为了能够使程序模块化更强,代码易于管理,需要将同一类型的代码放在一个文件中,这样将代码分为若干个模块放在不同的文件中,在调用和编译的时候需要从不同的文件中去调用函数,这就会导致多个C语言文件链接的问题。链接中经常会发生会出现的就是一个文件可能需要引用另一个文件中定义的全局变量或者函数。在这一章讲了链接时符号的解析规则,这是最基本的但是容易被忽略的东西。
在C语言中,声明和定义是有区别的,声明只需要告知编译器变量的存在,不需要分配存储空间,例如:
定义不仅要告诉编译器变量的存在还有为变量分配存储空间,例如:
int a=1;
但是,当一个变量在作用域范围内只有声明,没有定义时,编译器会自动将第一个声明认定为变量的定义,下面我们来看C语言中的符号解析规则。
规则1:不允许有多个对同一变量或者函数的定义。
比如a.c中函数为:
1 int a=123; 2 3 int main(void) 4 5 { 6 7 printf(“hello world”); 8 9 return 0; 10 11 }
b.c中的函数为:
1 int a=121; 2 3 int main(void) 4 5 { 6 7 printf(“hi world”); 8 9 return 0; 10 11 }
这两个函数在进行链接的时候会出现错误,连个文件中都对同一个函数main()进行了定义,而且他们还对同一个变量进行了定义,这两条都违反了规定一,因此在调用的时候会出粗。
规则2:如果有一个符号定义和多个符号的声明,则选择被定义的符号。
比如两个函数a.c和b.c:
a.c:
b.c:
两个文件中都对a进行了定义和声明,运行结果如下图所示,当出现一个变量定义和多个变量声明的时候应该要符合规则2,选择变量的定义。所以这个程序中全局变量a在f()中被修改为121,结果保存在全局变量a的存储空间中。
规则3:如果有多个字符的声明,则从其中任选一个作为符号的定义。
a.c
b.c
这个程序中有a的定义和a的声明,根据解析规则2是不会造成链接错误的,根据规则2我们知道b.c程序中对于a的操作实际是对a.c中的全局变量a进行操作的。但是在b.c中的声明的变量是双精度的,双精度的变量是8个字节,整型的数字是4个字节。因此a在进行赋值之后会把b的空间也占掉就会出现下面的错误显示。
书上的程序是b.c中赋值为0.0打印出来之后最后a,b的值都为0,解释为将8个字节都清0了。后来我处于好奇觉得如果赋值为其他的数字,比如3.5会不会显示成a=3,b=5。然后改完程序发现,a打印整数显示0,b打印整数显示为1074528256。紧接着我去查了double类型数字在机内的指数形式表示分为四部分,数符,尾数,指数符,指数四个部分,double类型数字数符加尾数占48位,指数符加指数占16位。因此,会出现乱码这种情况,至于具体的为什么产生的是0和1074528256,我觉得肯定是可以算出来为什么的,但是具体数符,尾数,指数符,指数是怎么存储的,占多少位我还不清楚,接下来有时间时的话会去算一算,深究一下。
总结这几天的学习来说,学习编程,主要还是在于实践,一定要敲代码。我在博客中举的例子一般都是我第一次看书的时候没有完全理解的东西,有些话说的很简单,看似理解其实并没有理解,只是理解了字面意思,甚至来说书上的例子都不能够完全帮助你去理解。每次遇到这些不懂得东西,我都会把书上的例子敲出来,运行然后去理解,不知道为什么的话去修改一下参数,加断点去调试去看,这样能够比较好的理解。在看书和敲书上代码的时候要多问一下为什么看看能不能自己去解答,通过改代码编译实现来验证自己的解答,如果正确那说明自己理解的没有问题,如果错误那就要找到错在哪,最终才能真正的理解。