一.动态内存分配和函数指针指针
1. 动态内存分配
先来介绍三个动态内存分配的函数:malloc,calloc和realloc。说来惭愧,以前只知道malloc。现在来看下他们的区别:
malloc:最常用的分配内存块,但是不对内存进行初始化。
calloc:分配内存块,但是对内存块进行清零操作,这就造成此函数的效率要比malloc要低。
realloc:调整(增加或者减少)之前分配内存块的大小。
由于上面的函数只是开辟了一段内存,因此无法知道你要利用这段内存来存储什么类型的数据,因此只是返回一个void *类型的值,当然,void *可以和任何指针类型互相转换。
当分配内存失败时(可能是内存不足或者其他原因),以上的函数都会返回一个空指针,那么我们安全期间,在我们使用这块分配的内存前,都应该进行一次空指针的验证。
int main (void) { int *p; p=(int *)malloc(sizeof(*p)*1000); if(p==NULL) { exit(EXIT_FAILURE); } printf("success"); return 0; }
注意上面的两点,一个是空指针的验证,另外一个是分配内存的大小,这里我们常常是用类型的大小乘以存储成员的数量来计算分配。如果是字符串,我们就不要忘记了加1,来存储\0。
#include#include int main (void) { char *p; p=(char *)malloc(sizeof(*p)*1000+1); if(p==NULL) { exit(EXIT_FAILURE); } printf("success"); return 0; }
接下来我们看一下calloc元素的原型:
void * __cdecl calloc(_In_ size_t _Count, _In_ size_t _Size);
从上面我们可以看到calloc函数有两个参数,分配是,数量和大小。由此可以说明,calloc是C语言用来分配数组空间最好的选择。那么我们就把第一段代码改一下:
int main (void) { int *p; p=(int *)calloc(1000,sizeof(int)); if(p==NULL) { exit(EXIT_FAILURE); } printf("success"); return 0; }
这样就更合适了。
最后是realloc,我们也来看一下realloc元素的原型:
void * __cdecl realloc(_Post_ptr_invalid_ void * _Memory, _In_ size_t _NewSize);
当我们之前分配了一个数组的大小,但是后来我们却发现这个大小不够用了,或者是太大了,那么我们就可以利用realloc来调整我们的占用内存大小:
int main (void) { int *p; p=(int *)calloc(1000,sizeof(int)); if(p==NULL) { exit(EXIT_FAILURE); } realloc(p,sizeof(int)*100); if(p==NULL) { exit(EXIT_FAILURE); } printf("success"); return 0; }
也别忘了检验p是否为空指针的情况。
在C标准中,并没有对realloc的实现做以规定,但是对于大部分编译器来说,如果是把原地址空间缩小,他会尽量地不去移动原来的数据。如果是把空间增大,那么他会尽量首先在原地址的末尾去分配内存,如果不足以分配,那么编译器才会去寻找新的地址块,并且把原地址空间内的数据转移到新的地址上。
2. 释放空间
习惯了Java/C#的我们,似乎已经忘记了要回收垃圾的习惯,在C/C++中,是没有GC的,因此我们要记得,当我们在堆上分配了一块内存,并且不在使用时,我们要使用free函数来释放掉空间。看下free的原型:
void __cdecl free(_Post_ptr_invalid_ void * _Memory);
很简单,不再赘述。
3. 指向指针的指针
在读大学时,我一直对这个概念不是很理解,现在我更愿意这样去理解指针。
当我们声明了int *p=malloc(1000)的时候,我们可以这样来理解:
其实我更愿意把p就理解成一个地址的值,p=0x1111(0x1111是分配的1000字节内存的首地址)。那么什么是指向指针的指针呢?
这里的q就是指向指针的指针,q的值就是0x0004,也就是p所在的地址。
以此类推,我们还可以知道指向指针的指针的指针。
4. 函数指针
我们来看C语言里提供了qsort函数:
_CRTIMP void __cdecl qsort(_Inout_bytecap_x_(_NumOfElements * _SizeOfElements) void * _Base, _In_ size_t _NumOfElements, _In_ size_t _SizeOfElements, _In_ int (__cdecl * _PtFuncCompare)(const void *, const void *));
最后一个参数就是一个函数指针,其实不用这么麻烦,我们来看个简单的函数指针的原型:
double (*function)(int);
这个就是最简单的函数指针的原型,与返回指针类型的函数相比,他们相差的只是*和函数名之间要用括号括起来。
当传进来一个函数指针时,我们便可以在函数中适用这个传进来的参数(函数指针)了。例如在qsort里,我们便可以自己制定比较规则,不再多说。
二.位域结构体和volatile限定符
1. 位域
我们来看一个表示日期的结构体:
typedef struct { unsigned int year; unsigned int month; unsigned int day; }MyDate;
但是我们可以发现,其实year最大也不会超过四位数,month也就是12,而day最大也就是31。但是我们在上述的结构体中,却为其分配了4*3=12字节的内存,是不是很浪费呢?
C语言为了解决这个问题,提出了一个概念,叫位域。看段代码:
typedef struct { unsigned int year:7; unsigned int month:4; unsigned int day:5; }MyDate;
这个的意思是,我为结构体中的每一个字段分配指定的位数,比如,我为year分配了7位。这样就有效地节省了内存。
网上有着有限的几篇关于位域的文章,我觉得在CSDN的一个帖子中,对于关于位域分配内存的情况说的最为详细,原封不动地拿下来了:
C99规定int、unsigned int和bool可以作为位域类型,但编译器几乎都对此作了扩展,
允许其它类型类型的存在。
使用位域的主要目的是压缩存储,其大致规则为:
1) 如果相邻位域字段的类型相同,且其位宽之和小于类型的sizeof大小,则后面的字
段将紧邻前一个字段存储,直到不能容纳为止;
2) 如果相邻位域字段的类型相同,但其位宽之和大于类型的sizeof大小,则后面的字
段将从新的存储单元开始,其偏移量为其类型大小的整数倍;
3) 如果相邻的位域字段的类型不同,则各编译器的具体实现有差异,VC6采取不压缩方
式,Dev-C++采取压缩方式;
4) 如果位域字段之间穿插着非位域字段,则不进行压缩;
5) 整个结构体的总大小为最宽基本类型成员大小的整数倍。
补充:
1)位域不允许跨越两个字节,因此位域的长度不能大于一个字节, 不能超过八位二进制;
2)位域可以无位域名,这时它只用来作填充或调整位置。无名的位域是不能使用的.
2. 再谈联合体
在之前,我们谈到过使用联合体来节约内存空间,但是联合体也经常被用于一个其他的目的:从两个或更多的角度去看待内存块。
例如:
union int_Date { int i; MyDate dt; };
在上面的程序中,我们就可以用两种视角(整数视角和日期视角)来看待一个数据。
我们知道,在X86的机器中,我们常常会用到4个16位寄存器,这四个16位寄存器又可以分成8个8位的寄存器。例如,当我们改变ax的时候,其实al和ah也同时发生了改变,那么我们可以把这个关系用联合体来表示。
typedef union { struct { short ax,bx,cx,dx; }word; struct { char al,ah,bl,bh,cl,ch,dl,dh; }byte; }Regs; int main (void) { Regs regs; regs.byte.ah=0x12; regs.byte.al=0x34; printf("%x",regs.word.ax); return 0; }
3. volatile限定符
volatile告诉编译器,这段内存空间所存储的值是易变的。volatile通常用于指向易变内存空间的指针。有个例子我觉得非常恰当。
我们假设*p指向的内存空间用于存放用户通过键盘所输入的字符,然后我们读取到这个字符,再将其放入到一个数组中。
但是一些优秀的编译器会发现,在这个过程中,p和*p都未被程序显式地改变,这样编译器就会对其作出优化,使*p只被取一次,这样就读入了数组中一些重复的数据,这明显不是我们想要的。
但是当我们在*p前加上volatile限定符,其实就是在告诉编译器,不要对该段程序进行优化,因为这段程序是易变的,每次的读取都要从内存中去重新取得。
在C#中也有volatile关键字,目的也是一样,volatile关键字代表某个变量可能会被多个并发的线程进行修改,所以通知编译器不要对其进行优化,这样就能保证每次取出来的值不是被缓存的,而是最新的值。
1
2
3
4
|
XBYTE[2]=0x55;
XBYTE[2]=0x56;
XBYTE[2]=0x57;
XBYTE[2]=0x58;
|
优化器在用到这个变量时必须每次都小心地重新读取这个变量的值,而不是使用保存在寄存器里的备份
http://baike.baidu.com/link?url=jsgE7JkbU0wb1svb8JAhSTBhRQ0UH-AmfUKy1AiGIpH3VFpvkdg9l-_1ck6sjimH