大一学《计算系统基础》的时候,没有讲这一块的内容,导致后面遇到指针就头疼,今天特来梳理一下这块的内容。
1.变量的类型
类型的语言,即要求定义变量的时候必须制定类型,然后编译器会根据类型来分配内存空间,只要定义就会分配。对于一个变量,本质上的两个要素是地址和类型。
地址:这是变量名本身的含义,变量在内存中的首地址,即位置。底层的东西就是符号表了,编译器在编译时将每一个遇到的变量插入一条记录,《变量名,地址》这样的记 录。
类型:有了地址还不足以处理对变量的操作,因为不知道大小。现在要读取一个变量值,知道了地址,就知道了从哪里拿,但是拿多少呢?这就是一个问题,而解决拿多少的关 键就是知道变量的类型。如果是int,那么就从首地址开始向后拿4个字节,char的话就拿一个字节。即,类型指明了变量的存储大小。当然这是对于char,int这样的基本 数据类型而言。另外,对于复合类型的数据,比如strust,那么类型还指明了内存的分配方式。比如下面这个:
struct S{
int a;
char b;
};
如果我们要读取一个S变量的内容,就得知道他的内存是怎么分配的,很显然是一个int类型,再跟一个char类型。如果我们要读取s.b,就得知道b变量的首地址,通过s的 首地址加上4的字节的位移就可以得到了。
总而言之,这里明白了变量的两个要素。地址和类型。
2.指针
指针本身的类型是可以确定的,就是存放了一个地址,这个没什么好说的。但是指针所指向的内存所存放的数据的类型是很重要的,这个“类型”就是我们上面说的那个类型,指明了变量的大小和分配方式。所以我们认识指针也要从地址和类型开始。地址的话就是指针的值了,类型就是*号前面的内容。
那么void *是什么意思?是说这个指针所指向的内存的类型不确定。也就是说我们只知道所指向变量的地址,其余的全部不知道。我们所能操作的也就是这个地址了。如果我们想通过*p的方式来读取内容,这样是不行的。引文我们并不知道类型,不知道要从这个地址开始,向后拿多少内存。所以对于* void,我们只能操作地址,地址所指向的内存内容无法得到。
最后,这个void *有什么作用?它通常用来作为函数的参数或者返回值,因为它可以接受或返回任意类型的指针,在函数的内部,它就是一块内存,其余的什么都不知道,我们只能对这一整块内存进行整体的处理,如memcpy()函数进行拷贝,或者malloc()函数,进行内存空间的返回。
所以对于void *,我们只知道地址为不知道具体的内容。
3.变量为止
C语言中的变量会被分配到3个内存空间中:
静态数据区:存放的是全局或者静态变量,在编译时候分配,当然这个区域还可以细细分。
栈区:函数内部定义的局部变量,函数执行时分配,函数返回时回收,绝大部分的变量都在这里,因为程序就是函数构成的。
堆区:动态分配的内存。在程序执行时才分配,大小可以动态决定。
4.动态分配方式
主要有malloc(),alloc()和realloc()函数
三个函数的申明分别是:
void* malloc(unsigned size);
void* realloc(void* ptr, unsigned newsize);
void* calloc(size_t numElements, size_t sizeOfElement);
三个函数都会返回分配以后内存的首地址,如果失败返回NULL。所以我们最好是验证一下返回值,看是不是NULL;
realloc是把prt指向的内存重新调整为newsize大小,如果扩大,原有内存的内容不会改变,如果缩小,则会丢失一部分数据。返回的新地址有可能会与ptr不同,但是这个不一定,要看内存块所剩余的空间是否还够用,这样的话,大一些的newsize通常会导致首地址不一样,同时也会导致内存的拷贝。
calloc是分配numElements个大小为sizeOfElement的连续内存。
返回的内存是void *的,因此无法对内存的内容操作,我们通常会用另一个非void *的指针来接受void *的值,但是必须强制转型,这样就可以执行*p这样的操作了。
标签:变量,void,C语言,地址,内存,动态内存,类型,指针 From: https://blog.51cto.com/u_15873544/5844098