局部变量也称为自动变量,他总是分配在栈帧上。

3.1 程序的堆和栈

3.1.1 程序栈

程序栈是支持函数执行的内存区域,通常和堆共享。也就是说,程序栈和堆共享同一块内存区域。程序栈通常占据这块区域的下部,而堆用的则是上部。

程序栈存放栈帧(stack frame),栈帧有时也称为活跃记录或活跃帧。栈帧存放函数参数和局部变量。堆管理动态内存。

调用函数时,函数的栈帧被推到栈上,栈向上“长出”一个栈帧。当函数终止时,其栈帧从程序栈上弹出。栈帧所使用的内存不会被清理,但最终可能被推到程序栈上的另一个栈帧覆盖。

动态分配的内存来自堆,对向下“生长”。随着内存的分配和释放,堆中会布满碎片。尽管堆是向下生长的,但这只是个大体方向,实际上内存可能在堆上的任意位置分配。

3.1.2 栈帧的组织

栈帧由以下几种元素组成:

返回地址:函数完成后要返回的程序内部地址

局部数据存储:为局部变量分配的内存

参数存储:为函数参数分配的内存

栈指针和基指针:运行时系统来管理栈的指针

栈指针通常指向栈顶部。基指针(帧指针)通常存在并指向栈帧内部的地址。这两个指针都不是C指针。它们是运行时系统管理程序栈的地址。

将栈帧推到程序栈上时,系统可能会耗尽内存,这种情况称为栈溢出。要牢记每个线程通常都会有自己的程序栈。

3.2 通过指针传递和返回数据

传递参数(包括指针)时,传递的是他们的值。也就是说,传递给函数的是参数的一个副本。当涉及大型数据结构时,传递参数的指针会高效。

3.2.1 用指针传递数据

void swap(int *pnum1, int *pnum2) { int tmp; tmp = *pnum1; *pnum1 = *pnum2; *pnum2 = tmp; }

3.2.2 用值传递数据

如果不通过指针传递参数,那么交换就不会发生。num1和num2中保存的只是参数的副本。修改形参不会影响实参。

void swap(int num1, int num2) { int tmp; tmp = num1; num1 = num2; num2 = tmp; }

3.2.3 传递指向常量的指针

如果只是传递指针,数据就能被修改,如果不希望数据被修改,就要传递指向常量的指针。

void pass(const int * num1,int num2) 不能修改通过指向常量的指针传进来的值

3.2.4 返回指针

函数返回指针时可能存在几个潜在的问题:

返回为初始化的指针

返回指向无效地址的指针

返回局部变量的指针

返回指针但是没有释放内存

3.2.5 局部数据指针

返回局部变量的指针,一旦函数执行完栈帧就被弹出了,尽管数据可能还在,但如果之后继续执行其他函数,该内存就会被覆盖。

如果把变量声明为static,这样把函数的作用域限制在函数内部,但是分配在栈帧外面,避免其他函数复写变量值。但每次调用该函数都会重复利用这个变量,这样相当于每次都把上一次调用的结果覆盖掉了。此外,静态数组必须声明为固定长度,这样会限制函数处理变长数组的能力。

3.2.7 传递指针的指针

void allocateArray(int **arr, int size, int value) { *arr = (int *)malloc(size * sizeof(int));//解引整数指针的指针得到的是整数指针 if (*arr != NULL){ for (int i = 0; i < size; i++) *(*arr + i) = value; } }

这个函数可用下面代码测试:

int *vector = NULL; allocateArray(&vector, 5, 6);

实现自己的free函数:

void saferFree(void **pp){ if(pp != NULL && *pp != NULL){ free(*pp); *pp = NULL; }}

使用指针的指针允许修改传入的指针,而使用void类型则可以传入所有类型的指针。如果调用这个函数时没有显示的把指针类型转换为void会产生警告,执行显示转换就不会有警告。下面这个safeFree宏调用saferFree函数,执行类型转换,并使用了取地址操作符,这样就省去函数使用者做类型转换和传递指针的地址:

#define safeFree(p) safeerFree( (void**) &p )

3.3 函数指针

函数指针是持有函数地址的指针。

3.3.1 声明函数指针

类型标识符 (* 指针变量名)( )

void (* foo)( ) void:返回类型 foo:函数指针变量名 参数

使用函数指针时一定要小心,因为C不会检查参数传递是否正确。以下是声明函数指针的一些例子:

int (*f1)(double); //传入 double,返回 int void (*f2)(char*); //传入 char 指针,没有返回值 double* (*f3)(int,int); //传递两个整数,返回 double 指针

不要把返回指针值的函数和函数指针搞混淆。

int *f4(); //f4是一个函数,返回一个整数指针 int (*f5)(); //f5是一个返回整数的函数指针 int* (*f6)(); //f6是一个返回整数指针的函数指针

3.3.2 使用函数指针

int (*fptr)(int); int square(int num){ return num*num; } ... int n = 5; fptr = square; // fptr = □ 让指针等于函数的入口地址。 printf("%d squard is %d\n",n,fptr(n));

在这种上下文环境中,编译器会忽略取地址符操作,所以你可以写出来,但没必要这么做。

因为fptr是一个函数指针,那么*fptr就是该指针所指向的函数,所以(*fptr)( )就是调用该函数的方式,ANSIC标准允许简写fptr( )。

为函数指针声明一个类型定义会比较方便,类型定义的名字是声明的最后一个元素。

typedef int (*funcptr)(int); ... funcptr fptr1; fptr1 = square; printf("%d squard is %d\n",n,fptr1(n));

3.3.3 传递函数指针

传递函数指针很简单,只要把函数指针声明作为函数参数即可。

int add(int num1, int num2) {

return num1 + num2;

}

int sub(int num1, int num2) {

return num1 - num2;

}

typedef int(*fptr)(int, int);

int compute(fptr operation, int num1, int num2) {

return operation(num1, num2);

}

printf("%d\n", compute(add, 5, 6));

printf("%d\n", compute(sub, 5, 6));

3.3.4 返回函数指针

返回函数指针需要把函数的返回类型声明为函数指针。

int add(int num1, int num2) {

return num1 + num2;

}

int sub(int num1, int num2) {

return num1 - num2;

}

typedef int(*fptr)(int, int);

fptr select(char opcode) {

switch (opcode){

case '+':return add;

case '-':return sub;

}

return 0;

}

int evaluate(char opcode, int num1, int num2) {

fptr operation = select(opcode); //函数指针赋值

return operation(num1, num2);

}

printf("%d\n", evaluate('+', 5, 6));

printf("%d\n", evaluate('-', 5, 6));

3.3.5 使用函数指针数组

类型标识符( *指针变量名[ ] ) ( );

int (*operations[128])(int, int)={ NULL }; 这个数组的所有元素都被初始化为NULL

也可以用typedef来声明这个数组:

typedef int (*operation)(int, int); 如果数组的初始化值是一个语句块,系统会将块中的值赋给连续的数组元素。

operation operations[128]={ NULL };

#include

#include

int plus(int a, int b){

return(a + b);

}

int minus(int a, int b){

return(a - b);

}

int multiplied(int a, int b){

return(a * b);

}

int divided(int a, int b){

return(a / b);

}

int percent(int a, int b){

return(a % b);

}

void main1()

{

//int(*p)(int a, int b); 函数指针

int(*pn[5])(int a, int b) = { plus,minus, &multiplied ,÷d,percent }; //函数指针数组初始化

int ax = 100; int by = 20;

for (int i = 0; i < 5; i++) //下标方式循环数组

printf("%d\n", pn[i](ax, by)); //分别调用五种运算输出结果

system("pause");

}

void main() //函数指针数组名是二级函数指针

{

int(*pn[5])(int a, int b) = { plus,minus, multiplied ,divided,percent };

int ax = 100; int by = 20;

//通过二级函数指针的方式遍历函数指针数组

for (int(**p)(int a, int b) = pn; p < pn + 5; p++) //指针循环

{

printf("%d\n", (*p)(ax, by));

}

system("pause");

}

3.3.6 比较函数指针

可以用相等和不等操作符来比较函数指针。

3.3.7 转换函数指针

我们可以将指向某个函数的指针转换为其他类型的指针,不过要谨慎使用,因为运行时系统不会验证函数指针所用的参数是否正确。也可以再转回来,得到的结果和原指针相同,但函数指针的长度不一定相等。

int add(int num1, int num2) {

return num1 + num2;

}

typedef int(*fptrToSing)(int);

typedef int(*fptrToInts)(int, int);

void main()

{

fptrToInts fptrFirst = add;

fptrToSing fptrSecond = (fptrToSing)fptrFirst;

fptrFirst = (fptrToInts)fptrSecond;

printf("%d\n", fptrFirst(5, 6));

无法保证函数指针和数据指针转换后正常工作。

void* 指针不一定能用在函数指针上。也就是说不应该像下面这样把函数指针赋给void * 指针: void * pv = add;

不过在交换函数指针时,通常会见到如下声明所示的“基本”函数指针类型。这里把fptrBase声明为指向不接受参数也不返回结果的函数的函数指针。

typedef void (*fptrBase)( );