动态分配,结构,联合

(一)动态内存分配:

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;
	}
	else
		printf("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,linux下gcc的默认对齐数是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.

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

最后激励自己:女生不一定比男生差~~~

时间: 2024-07-28 17:33:01

动态分配,结构,联合的相关文章

SQL SERVER 2012 第四章 连接 JOIN语句的早期语法结构 &amp; 联合UNION

1/内部连接的早期语法结构 INNER JOIN SELECT * FROM Person.Person JOIN HumanResources.Employee ON Person.Person.ID = HumanResources.Employee.ID 等价于早期的也就是老版本的 SELECT * FROM Person.Person,HumanResources.Employee WHERE Person.Person.ID = HumanResources.Employee.ID 2

c动态分配结构体二维数组

这个问题我纠结了蛮久了,因为前面一直忙(自己也懒了点),所以没有能好好研究这个.希望这篇文章能够帮助你们. 1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <stddef.h> 4 5 typedef struct LNode { 6 int F; 7 struct LNode* next; 8 }LNode, *LinkList; 9 int main() 10 { 11 LNode** map = (LNo

联合索引在B+树上的存储结构及数据查找方式

能坚持别人不能坚持的,才能拥有别人未曾拥有的.关注编程大道公众号,让我们一同坚持心中所想,一起成长!! 引言 上一篇文章<MySQL索引那些事>主要讲了MySQL索引的底层原理,且对比了B+Tree作为索引底层数据结构相对于其他数据结构(二叉树.红黑树.B树)的优势,最后还通过图示的方式描述了索引的存储结构.但都是基于单值索引,由于文章篇幅原因也只是在文末略提了一下联合索引,并没有大篇幅的展开讨论,所以这篇文章就单独去讲一下联合索引在B+树上的存储结构.本文主要讲解的内容有: 联合索引在B+树

数据封装-结构体

结构体类型作用 *结构体类型允许程序员把一些分量聚合成一个整体,用一个变量表示. *一个结构体的各个分量都有名字,把这些分量称为成员(member). *由于结构体的成员可以是各种类型的,程序员能创建适合于问题的数据集合. 结构体类型的定义 *定义结构体类型中包括哪些分量 *格式: struct 结构体类型名{ 字段声明; }; 注意 *字段名可与程序中的变量名相同 *在不同的结构体中可以有相同的字段名 *结构体成员的类型可以是任意类型,当然也可以是结构体类型 结构体变量的初始化 student

小甲鱼PE详解之IMAGE_OPTIONAL_HEADER32 结构定义即各个属性的作用(PE详解03)

咱接着往下讲解IMAGE_OPTIONAL_HEADER32 结构定义即各个属性的作用! (视频教程:http://fishc.com/a/shipin/jiemixilie/) 接着我们来谈谈 IMAGE_OPTIONAL_HEADER 结构,正如名字的意思,这是一个可选映像头,是一个可选的结构,但是呢,实际上上节课我们讲解的 IMAGE_FILE_HEADER 结构远远不足以来定义 PE 文件的属性.因此,这些属性在 IMAGE_OPTIONAL_HEADER 结构中进行定义. 因此这两个结

读陈浩的《C语言结构体里的成员数组和指针》总结,零长度数组

原文链接:C语言结构体里的成员数组和指针 复制如下: 单看这文章的标题,你可能会觉得好像没什么意思.你先别下这个结论,相信这篇文章会对你理解C语言有帮助.这篇文章产生的背景是在微博上,看到@Laruence同学出了一个关于C语言的题,微博链接.微博截图如下.我觉得好多人对这段代码的理解还不够深入,所以写下了这篇文章. 为了方便你把代码copy过去编译和调试,我把代码列在下面: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 #include <stdio.h>

PE详解之IMAGE_OPTIONAL_HEADER32 结构定义即各个属性的作用(PE详解03)

咱接着往下讲解IMAGE_OPTIONAL_HEADER32 结构定义即各个属性的作用! (视频教程:http://fishc.com/a/shipin/jiemixilie/) 接着我们来谈谈 IMAGE_OPTIONAL_HEADER 结构,正如名字的意思,这是一个可选映像头,是一个可选的结构,但是呢,实际上上节课我们讲解的 IMAGE_FILE_HEADER 结构远远不足以来定义 PE 文件的属性.因此,这些属性在 IMAGE_OPTIONAL_HEADER 结构中进行定义. 因此这两个结

C语言结构体的字节对齐原则

转载:http://blog.csdn.net/shenbin1430/article/details/4292463 为什么要对齐? 现代计算机中内存空间都是按照byte划分的,从理论上讲似乎对任何类型的变量的访问可以从任何地址开始,但实际情况是在访问特定类型变量的时候经常在特 定的内存地址访问,这就需要各种类型数据按照一定的规则在空间上排列,而不是顺序的一个接一个的排放,这就是对齐. 对齐的作用和原因:各个硬件平台对存储空间的处理上有很大的不同.一些平台对某些特定类型的数据只能从某些特定地址

IMAGE_OPTIONAL_HEADER32 结构作用

IMAGE_OPTIONAL_HEADER32 结构作用 接 着我们来谈谈 IMAGE_OPTIONAL_HEADER 结构,正如名字的意思,这是一个可选映像头,是一个可选的结构,但是呢,实际上上节课我们讲解的 IMAGE_FILE_HEADER 结构远远不足以来定义 PE 文件的属性.因此,这些属性在 IMAGE_OPTIONAL_HEADER 结构中进行定义. 因此这两个结构联合起来,才是一个完整的 “PE文件结构” . 那么我们接着就应该顺理成章地来谈谈 [1]  IMAGE_OPTION

数组强制转换成结构体指针,结构体内部指针的指向问题

如果直接操作结构体成员是不会取到不期望的值 但是对于要求连续数据格式的时候需要考虑对齐的问题 例如通讯中的数据帧格式等 ,如 ip数据包等#pragma   pack(1) struct   tagStruct {     ... } t; #pragma   pack() 的方式来强制连续存放 其中前面   pack(1)   是指对齐边界为   1 1.几个结构体例子: struct{short a1;short a2;short a3;}A; struct{long a1;short a2