一,数组与指针问题
- 数组名是一个指向数组首元素的指针常量,一经定义,不可更改,其内容是数组的地址即数组首元素地址,其地址是自身在内存中的地址;指针是指针变量,定义之后仍可更改,其类型在定义时确定,其内容是所指元素的地址,其地址是自身地址。
- 对指针使用*操作符时,结果是指针所指元素的值,如*a,要求a存储的数据是另一个存储单元的地址,*a得到的值所指元素的数据而不是a的数据,a的数据是地址。
- 当对数组名使用 sizeof 和 & 操作符时,数组名不再当成指向一个元素的指针常量来使用,sizeof(数组名)得到的是整个数组所占字节数,&数组名得到的数组首元素的地址。举例如下:
int arr[3] = {1,2,3};
prinf(“ox%x\n”,&arr);
printf(“ox%x\n”,&arr+1);
第二行打印的地址与第一行打印的地址相差12,即数组所占字节的大小,int占4一个字节
当对指针使用sizeof和&时,指针仍当成指向一个元素的指针变量来使用。举例如下:
int *p = arr;
sizeof(p)的结果是指针变量p所占字节数,在32位系统为4,64位系统为8,注意sizeof(p)并不是arr首元素所占字节数,可以认为sizeof(x)所关注的是x本身类型所占的空而间大小,而与x所指目标无关,这个x的类型可以是数组,指针,变量,表达式等。
&p的结果是指针变量p的地址。
4,对于使用指针和数组下标的选择:
1)系统在使用数组下标对数组成员变量进行访问时,开销比较大,指针的访问效率是远远大于数组名的访问效率的;
2)只有在指针正确访问时,使用指针才比下标法更有效率;
3)下标法更加容易理解,在可读性方面,也更加的具有优势;
二,结构体的理解:
1,结构体成员的地址是相对地址,是相对与结构体地址的偏移量,如下:
struct User{
int i;
char *p;
};
struct User u;
结构体实例u的地址是&u,其成员i的地址是&u+0x0(即第一个成员的地址就是结构体的地址),成员p的地址是&u+0x4因为int占4个字节,所以p相对于i偏移为4。
如果一个结构体没有初始化,访问其成员地址将得到的是偏移量,访问其成员数据时程序将出错,因为这时成员的地址是偏移量,不是一个元素的真是地址。
如下:
struct People{
struct User *u;
};
struct People p = {0};
struct People p = NULL;
printf(p.u->p); //访问地址为0x04的数据,将出错
printf(“0x\n”,p.u->p);//得到的是p的偏移量4
注意:在计算结构体偏移量的时候要考虑内存对齐,偏移量不一定就是前面元素所占字节数。
三,内存管理
1,4个内存区,分别为代码区,静态区,栈区,堆区。
- 代码区,程序加载到内存时,代码,常亮,字符串等加载到该区,函数包括main在该区,程序运行期间不变。
- 静态区,存储全局变量,静态变量
- 栈区,存储自动变量(即函数形参和非static局部变量),程序运行期间可变,自动的含义是变量超出作用域{}(即代码块和函数体),自动从栈中弹出。每个线程都有专属的栈,栈最大尺度固定,超出即栈溢出。变量离开作用域,其栈上内存自动释放,如函数体内定义的变量,在函数结束自动释放内存,如果函数内某变量是通过malloc分配内存的,则需要手动调用free释放,函数结束该变量的内存不会自动释放,因为它的内存在堆区;如果函数内某变量以static修饰,则该变量在函数结束时不会自动释放,其内存在静态区,并且该变量只能在所在函数内部访问。
- 堆区,程序运行期间可变,其容量远大于栈,堆内存空间的申请(malloc)和释放(free)都需要手动编写代码来完成(malloc与free要成对存在)。
四,变量
- 全局变量(外部变量),代码块{}外部定义的变量
- 局部变量(自动变量),代码块{}内部定义的变量,可用auto显示定义
- 静态变量,程序运行期间内存地址不变,以static修饰,代码块{}内部定义的静态变量只能在代码块内访问,代码块{}外部定义的静态变量只能被定义这个变量的文件访问
- C语言中函数默认都是全局的,可用static来修饰为静态函数,静态函数只能被定义它的文件访问