- 面向过程:接口(api)、过程的封装和设计
内存中的四个区域
数据类型本质分析
- 数据类型的本质是一个固定内存块大小的别名
- sizeof是操作符,不是函数;sizeof测量的实体类型的大小为编译期间就已确定。
- 指针运算的步长和类型有关,比如:
int array[10] = {0};
printf("&array = %p, array = %p, &array+1 = %p, array+1 = %p\n", &array, array, &array+1, array+1);
//前两个输出地址相同,都是array首元素地址值
//第三个输出值比前两个输出地址值大40,因为&array的类型是(int[10])*
//最后一个输出值比前两个地址值大4,因为array的类型是int *
- void *是万能指针
- 栈区 vs. 栈:栈区不是栈,栈是一种数据结构,而栈区是一种内存,只不过栈区存放数据的方式是栈的方式(后进先出)。
- 通过指针在堆区开辟一块内存移动要结合NULL来操作,防止野指针问题。比如:
char *p = NULL;
p = (char *)malloc(100);
if(p == NULL) {
fprintf(stderr, "malloc error\n");
return;
}
函数的调用模型
- 函数调用变量传递分析:
以在main函数中调用子函数func1、func1中调用func2为例:
总结:
- main函数中可以在栈/堆/全局分配内存,都可以被func1和func2使用.
- func2在栈上分配的内存,不能被func1和main函数使用
- func2中malloc的内存(堆),可以被main和func1函数使用。
- func2中全局分配“abcdefg”(常量全局区)内存,可以被func1和main函数使用.
- 栈的开口方向(或增长方向):向低地址方向生长,即压栈时栈指针减小,退栈时栈指针增大。
指针的进一步理解
- 段错误(segmentation fault):
- 对只读区域进行写操作。比如:char *p = “hello”; p[0] = ‘H’;
- 对非访问区域进行读写操作。
- stack空间耗尽。 比如:函数内部 int arr[N]= {1}; #define N 100000000
- 如果要通过函数的参数传递来修改调用函数的变量,那么必须传地址而非传值,从而被调用的函数形参必须为相应类型的指针变量。这对于任意数据类型都成立。比如,要通过一个子函数为main函数动态申请一块堆内存:
int get_mem(int **pp1, int len1) { //在堆区申请一块内存供主函数main使用
*pp1 = malloc(len1);
if(*pp1) return 1;
return 0;
} //本来该函数可以没有返回值,之所以设置一个int型返回值并在结尾返回0是为了以后便于扩展,