函数调用与空间分配

    咱们在编程序的时候,都会把某一个特定功能封装在一个函数里面,对外暴露一个接口,而隐藏了函数行为的具体实现,一个大型的复杂系统里面包含了不少这样的小函数,咱们称之为过程git

    过程是相对独立的小模块,系统的运行须要这些过程的紧密合做,这种合做就是函数调用。github

    在一个函数执行时调用别的函数,好比 P 调用 Q,须要执行一些特定的动做。传递控制,在调用 Q 以前,控制权在 P 的手里,既然要调用 Q,那么就须要把控制权交给 Q;传递数据,就是函数传参;分配与释放内存,在开始时,Q 可能须要位局部变量分配空间,结束时又必须释放这些存储空间。算法

    大多数语言都使用栈提供的先进后出机制来管理内存,x86-64 能够经过通用寄存器传递最多 6 个整数值(整数或地址),若是超过 6 个,那就须要在栈中分配内存,而且经过栈传递参数时,全部数据的大小都要向 8 的倍数对齐。将控制权从 P 转交给 Q,只须要将 PC(程序计数器)的值置为 Q 代码的起始位置,并记录好 P 执行的位置,方便 Q 执行完了,继续执行 P 剩余的代码。编程

    在函数的传参、执行中,多多少少都须要空间来保存变量,局部数据能保存在寄存器中就会保存在寄存器中,若是寄存器不够,将会保存在内存中。除了寄存器不够用的状况,还有数组、结构体和地址等局部变量都必须保存在内存中。分配内存很简单,只须要减少栈指针的值就好了,一样释放也只须要增长栈指针。数组

    在函数执行过程当中,处理栈指针%rsp,其它寄存器都被分类为被调用者保存寄存器,即当过程 P 调用过程 Q 时,Q 必须保存这些寄存器的值,保证它们的值在 Q 返回到 P 时与 Q 被调用时是同样的。函数

    因此递归也就不难理解了,初学算法总以为递归有点奇妙,怎么本身调用本身,而实际上对于计算机来讲,它和调用其它函数没什么区别,在计算机眼里,没有自身与其它函数的区别,全部被调用者都是其它人。post

    数组是编程中不可或缺的一种结构,“数组是分配在连续的内存中”这句话已经烂熟于心了,历史上,C 语言只支持大小在编译时就能肯定的多维数组,这个多多少少有一些不便利,因此在ISO C99标准中就引入了新的功能,容许数组的维度是表达式。ui

int A[expr1][expr2]
复制代码

    由于数组是连续的内存,因此很容易就能访问到指定位置的元素,它经过首地址加上偏移量便可计算出对应元素的地址,这个偏移量必定意义上就是由索引给出。spa

    好比如今有一个数组A,那么A[i]就等同于表达式* (A + i),这是一个指针运算。C 语言的一大特性就是指针,既是优势也是难点,单操做符&*能够产生指针和简介引用指针,也就是,对于一个表示某个对象的表达式expr&expr给出该对象地址的一个指针,而对于一个表示地址的表达式Aexpr*Aexpr给出该地址的值。设计

    即便咱们建立嵌套(多维)数组,上面的通常原则也是成立的,好比下面的例子。

int A[5][3];

// 上面声明等价于下面
typedef int row3_t[3];
row3_t A[5];
复制代码

    这个数组在内存的中就是下面那个样子的。

image

    还有一个重要的概念叫作数据对齐,即不少计算机系统要求某种类型的对象的地址必须是某个值 K(通常是二、4 或 8)的倍数,这种限制简化了处理器和内存接口之间的设计,甚至有的系统没有进行数据对齐,程序就没法正常运行。

    好比如今有一个以下的结构体。

struct S1 {
    int i;
    char c;
    int j;
}
复制代码

    若是编译器用最小的 9 字节分配,那么将是下面的这个样子。

image

    可是上面这种结构没法知足 i 和 j 的 4 字节对齐要求,因此编译器会在 c 和 j 之间插入 3 个字节的间隙。

image

    在极客时间专栏中有这样一段代码。

int main(int argc, char *argv[]){
    int i = 0;
    int arr[3] = {0};
    for(; i <= 3; i++){
        arr[i] = 0;
        printf("Hello world!\n");
    }
    return 0;
}
复制代码

    这段代码神奇的是在某种状况下会一直循环的输出Hello world,并不会结束,在计算机系统漫游(补充)中也提到过。

    形成上面这种结果是由于函数体内的局部变量存在栈中,而且是连续压栈,而 Linux 中栈又是从高向低增加。数组arr中是 3 个元素,加上 i 是 4 个元素,恰好知足 8 字节对齐(编译器 64 位系统下默认会 8 字节对齐),变量i在数组arr以前,即i的地址与arr相邻且比它大。

    代码中很明显访问数组时越界了,当i为 3 时,实际上正好访问到变量i的地址,而循环体中又有一句arr[i] = 0;,即又把i的值设置为了 0,由此就致使了死循环。

相关文章
相关标签/搜索