动态分配,结构,联合

来源:互联网 发布:java web kettle 编辑:程序博客网 时间:2024/05/24 00:41

(一)动态内存分配:

1.为什么要动态内存分配呢?比如,我要做一个学生成绩管理系统,这里可能需要存储每个班级所有学生的信息,但

是,我们到底要分配多大的空间呢??每个班的人数有可能并不相等,按多分配 ,那样多浪费;按少分配,不够。所以

动态内存分配就有自己的作用了~~

2.动态内存分配函数:

(1)void *malloc(unsigned int size);-------size是需要分配的字节数。

(2)void *calloc(unsigned int num_elements,unsigned int elements_size);----num_elements是分配的元素个

数,elements_size是每个元素占的字节数。

(3)void *realloc(void *p,unsigned int new_size);----new_size是修改后的字节数,p是原先的内存首地址。

由于这些函数都是在堆(heap)里开辟的空间,使用完后需要释放~所以free就来了~~

(4)void free(void *p);-----p是需要释放的空间的首地址。

乍一看,malloc和calloc好像是一样的,其实并不是。calloc在返回指向内存的指针之前就将这块内存初始化为0.所以

我们可以认为calloc的工作相当于malloc加memset。所以calloc在分配过程中可能就要慢一点~~

前边3个函数都需要free,如果不free就可能造成内存泄漏~关于这个问题在这篇文章点击打开链接中有详细解释~

free释放完成后需要置为NULL。我们在使用指针之前必须要有一个原则----那就是使用之前先判断是否为空、使用之

后需要置为NULL。看下边一段代码:

void Test(){char *p = (char *)malloc(20*sizeof(char));if (p == NULL){printf("out of memory");exit(1);}strcpy(p,"hello");printf(p);free(p);if (p != NULL){strcpy(p,"yaoyao");printf(p);}//free(p);}

程序究竟会输出什么呢??没错,是helloyaoyao,内存已经释放,为什么还能拷贝呢??是因为释放后并没有置为

NULL。虽然p已经跟那块内存断了关系,可以p里边还存着人家的地址,所以~~~

realloc函数到底是用来干什么的?分配内存,对,还可以扩容,缩容(下边的提到的扩容就是指扩容或者缩容)。

下边我来详细剖析这个函数:

函数原型是void *realloc(void *p,unsigned int new_size);当p是NULL时,函数的作用是开辟空间,当p不为NULL,

函数的作用就是扩容。结合下边的代码,我来详细分析:

void Test(){char *p = (char *)malloc(10 * sizeof(char));char *preal = NULL;if (p == NULL){printf("out of memory");exit(1);}//如果此时我发现10个空间根本不够,所以需要扩容preal = (char *)realloc((void *)p,12);if (preal != NULL){p = preal;}elseprintf("out of memory");free(p);}

这段程序,乍一看,貌似preal这个变量是不需要的,其实是完全需要。当我们扩容时,有时会扩容不成功(缩容不存

在这个问题),此时realloc就会返回NULL,如果我们直接将realloc函数的返回值赋值给p,此时p就变成了NULL,之

前的数据也就找不到了(这样划得来吗??)如果分配成功,新的地址赋给p,分配不成功,就直接报错离开,最后

直接释放p就可以了。如果你仔细看,你会发现realloc的参数1我强制类型转换了,其实不转化在我的编译器下也是可

以的。在你的编译器下我就不知道了~~~

上边程序中多次用到exit(),下边我来给出exit函数的整理:

exit()函数:

所在头文件:stdlib.h

功能:关闭所有文件,终止正在执行的进程。

exit(1)表示异常退出,这个1是返回给操作系统的。

exit(x)(x != 0)都表示异常退出。

exit(0)表示正常退出。

exit的参数会传给操作系统的。

exit和return的区别:

按照ANSI C,在最初调用的main()中使用return和exit的效果一样。

如果main函数在一个递归程序中,使用exit仍然会终止程序,但return会将控制权移交给递归的前一级,此时return才

会终止程序。

另一个区别:即使在除main函数之外的函数中调用exit,它也将终止程序。

_exit和exit的区别:

_exit所在头文件:unistd.h

作用:直接使进程停止运行,清除其所用的内存空间,并销毁其在内核中的各种数据结构。

exit:作用 在基础上做了一些包装,在执行退出前做了若干道工序。

最大的区别:exie函数在调用exit系统调用之前要检查文件的的打开情况,把文件缓冲区的内容写回文件。

3.常见的内存分配错误:对NULL 指针进行解引用(直接原因就是没有检测是否分配成功),对分配的内存进行操作时

越过边界,释放并非动态分配的内存,释放动态分配内存的一部分,内存释放后继续使用。

这几个错误都比较好理解,这里就不给出例子了。但是,我还是想给出防止第一个错误发生的办法~~

下边给出一种方法:

#define  malloc#define  MALLOC(num,type) (type *)alloc((num )*sizeof(type))extern void *alloc(unsigned int size)

MALLOC宏接收元素的数目以及元素的类型,计算总共需要的字节数,并调用alloc获得内存,alloc调用malloc并进行

检查,确保返回的指针不是NULL。

4.其实动态内存分配并不是你想的那样,当你申请空间时,系统其实在这块空间之前会预留一块空间,用来存放这个

内存的大小吧。(或者是所放元素的个数)

给一个3行4列的数组动态分配空间:

错误方法1:

int **a = (int **)malloc(3*4*sizeof(int));
这是自己骗自己的方法。二维数组不等同于二级指针,之前的文章中有重点提到过。

错误方法2:

void test(){int i = 0;int *p = NULL;for (i = 0;i < 3;i++){p = (int *)malloc(4*sizeof(int));}//释放省略}
这个会存在被覆盖的问题。

下边给出正确的方法:

1.

void test(){int i = 0;int *p[3] = { NULL };for (i = 0;i < 3;i++){p[i] = (int *)malloc(4*sizeof(int));}for (i = 0;i < 3;i++){free(p[i]);}}

2.
void test(){int(*p)[4] = (int(*)[4])malloc(4*3*sizeof(int));free(p);}

上边的代码没有实现任何功能,只是给出了申请空间~~


(二)结构体:

1.定义:

struct  Stu{    int age;    char name[12];}student;
以上给出了一各简单结构体的定义,struct Stu 是类型名,类比于int,double之类的,student就相当于一个结构体变

量,记住,类型名是struct Stu ,不是Stu ,Stu 是标签,所以有时写起来比较麻烦,我们可以用typedef来解决。

typedef struct Stu{    int age;    char name[12];}stu;
之后你如果需要定义这个结构体类型的变量直接用stu这个类型名。
typedef  struct Stu{    int age;    char name[12];    struct Stu *next;}stu;
如果在结构体内部要定义在结构体指针,不能用”新名字“,原因就是新名字还没有出现~~~

<pre name="code" class="cpp">
struct {    int age;    char name[12];}s1,s2;


这是一个没有标签的结构体的定义,定义了两个结构体变量,然而你,之后就不能定义别的变量~~因为没有变量名~

先有鸡还是先有蛋???结构体里的鸡与蛋问题:

struct B;struct A{    int age;    char name[12];    struct  B  b;};struct B{    int age;    char name[12];    struct  A  a;};
B里有A的变量,A里有B的变量,所以只能牺牲一个~~

2.访问:有两种方式,直接访问和间接访问。

比如:

struct Stu{    int age;    char name[12];}s,*ps;

s是结构体变量,ps是指向结构体的指针,如果要访问结构体的age成员,我们可以这样访问:

s.age

ps->age

(*ps).age

第一种是直接引用,后边两种是间接引用~~

3.当结构体作为参数需要传递给另一个函数时,可以传结构体,也可以传结构体的地址,不过我们一般传地址呀,因

为地址只占4字节(32位系统下),节省空间,但是传地址过去就比较自由了,函数里想改就改,如果不希望被修

改,那你就用const修饰吧~~

4.位段:位段的声明和结构体类似,但它的成员是1个或多个位段,这些不同长度放的字段实际上存储在一个或多个

整形变量中。位段的成员必须声明为int、unsigned int、signed int;在成员的后边是一个冒号和一个整数,这个整数

指定该位段所占的位的数目。

struct A{    int a:10;    int b:2;    int c:15;};

sizeof(struct A) = 4;
虽然规定里边说,结构体的成员的类型只能是int系列的,但是我们发现,char类型的也可以,但是你千万不要得寸进
尺----将int和char混合使用。
struct A{    char a:4;    char b:2;    char c:3;}sa;int main(){    sa.a = 15;    sa.b = 13;    sa.c = 9;}
看上边的例子,我们看一下这些变量都是怎么存储的。当我把这段代码敲在vc6下时,运行时的内存情况如图:

a占4比特位,所以存储是1111,b占2比特位,所以只能存储后两比特,所以存储01,由于第1字节存储不下c,所以c
进入下一字节,从后向前占3位。机器是小端存储,所以存储情况是那样的。
注意注意:可移植性的程序避免使用位段(因为不同平台类型的所占字节不同~~int在某平台占2字节)
位段在不同的系统有不同的结果。int位段被当做有符号数还是无符号数。
位段中位的最大数目,比如,int在某平台占2字节,最大数目是16,在某平台是4字节,最大数目是32.
位段中的成员在内存中是从左向右还是从右向左(应该是大小端问题)
不足以放下下一个成员时,机器是如何处理~在这个不够的字节上存一部分,其他存储在下1字节,还是直接全部存储
在下1字节。
(三)联合体
联合体union。它的所有成员在内存中的起始位置相同。利用这个特性可以用来判断机器的大小端~~前边的文章中都
有介绍过。
(四)枚举类型
类型定义:enum 枚举类型名 {所有取值列表};(取值列表没有类型,每个都是标识符,并且通常是大写)
变量定义:enum 枚举类型名 变量名;
枚举类型的值,当没有赋值时,默认是从0开始,下边的一次递增。利用这个特性,当我们做通讯录或者计算器用到
switch语句时,每个case我们可以写在枚举类型里,这样使用时就比较方便。
比如:
enum  OS{    WINDOWS,    LINUX,    UNIX};

这几个标识符默认是0,1,2.

(五)内存对齐:(以下代码以vs为例)

说到内存对齐,我们应该想一下:为什么要内存对齐??内存对齐有什么用??

看这个代码:

struct A{    char c;    int i;};

我们来想一下sizeof(struct A)到底是多少??我想大多数人会以为是5,不应该是i的大小加c的大小吗??其实并不是

这样。它的结果是8.在给这个结构体分配空间时,就考虑到了内存对齐,看图。


这就是以空间换取时间。在说内存对齐之前,我不得不先说一下这两个概念:

默认对齐数:vs下的默认对齐数是8,linuxgcc的默认对齐数是4,当然这个不是不可改的~~

#pragma pack(n)//编译器将按照n字节对齐

#pragma pack()//编译器将取消自定义对齐方式

对齐数:自身大小与默认对齐数的最小值。

内存对齐有哪些规则??

(1)第一个成员必须对齐于0地址处。

(2)每个成员必须对齐于对齐数的整数倍地址处。

(3)结构体的总大小必须是最大对齐数的整数倍。

(4)嵌套结构体的默认对齐方式是该结构体中所有对齐数中最大的一个的整数倍。

(5)嵌套的总大小是所有最大对齐数的整数倍。

下面看这个例子:

struct A{    char c;    char c1;    int i;};
这个结构体的大小是8,这个不难确定。c的地址:0x0000,c1:0x0001,i:0x0004,所以总共是8.
struct A{    char c;    int i;    char c1;};

我们又很容易的确定这个结构体的大小是12,明明跟上边的那个结构体一样,就是成员顺序不一样,大小就不一样

吗。顺序不一样,为了内存对齐,就会浪费一些空间,所以,定义结构体的时候,考虑一下内存的感受哈~~大笑

说了这么久,好像都比较好做,我们来弄个稍微难一点的吧~~上代码~

struct B{    char c;    double d;};struct A{    int a;    char c[5];    struct B b;};

分析结构体A的大小是多少~32.a占4字节,默认对齐是8,自身大小4字节,所以占内存4字节,数组就相当于,5个

char类型的数,所以占5字节。而结构体B的最大对齐数是8.

所以a的首地址:0x0000,c:0x0004(结构体A里的c),c:0x0016,d:0x0024,所以总共是32.不给出图,你可

以自己画一下哦~~

sizeof操作符可以得出一个结构体的整体长度,包括浪费的那些字节。如果你必须确定结构的某个成员的实际位置,

应该考虑边界对齐因素,可以使用offset宏。

这个宏在stddet.h头文件。

offset(type,member)

type是结构的类型,member是你需要的成员名,表达式的结果死一个无符号整形,表示这个指定成员开始存储的位

置距离结构体开始存储的位置偏移几个字节。用就近的代码举个例子:

offset(struct B,d)结果是8.//B的开始地址是0x0016,成员d的开始地址是0x0024,所以结果是8.

关于结构体,和内存分配的知识先整理这么多,以上如有问题,希望指出~~谢谢~微笑

原创粉丝点击