C语言动态内存创建

224 阅读5分钟

malloc/calloc/realloc

malloc

void *malloc(size_t size)  分配所需的内存空间,并返回一个指向它的指针。

size -- 内存块的大小,以字节为单位。

实例

int* p = (int*)malloc(sizeof(int) * 10);
int i = 0;
for (i = 0; i < 10; i++)
{
        *(p + i) = i;
}
for (i = 0; i < 10; i++)
{
        printf("%d ",* (p + i));
}printf("\n");
free(p);
p = NULL;

创建了10个int型大小的空间,并循环赋值然后打印。该空间使用完成后最好把空间释放掉(free),如果不释放,在后面代码运行时,这块空间一直存在,造成空间浪费。free释放空间时,指针p必须指向头地址,如果执行过p++,再进行free,是不能成功的

运行结果:

image.png

calloc

void *calloc(size_t n, size_t size);和malloc的区别就是calloc创建的空间中为0。参数是两个参数,第一个是元素个数,第二个是这个元素是几个字节大小。

n -- 要被分配的元素个数。 size -- 元素的大小。

realloc

realloc是用来管理动态内存的大小,重新调整之前调用malloc或calloc所分配的内存块的大小。例如:上面创建了40个字节,10的整型的内存块,如果此时内存不够用,就需要使用realloc追加。参数是追加后的总长度。

int* ptr = realloc(p, sizeof(int) * 20);//追加10的整形
//为了使代码可读性高,最好原来使用的p,现在还是使用p。
p = ptr;
int i = 0;
for (i = 10; i < 20; i++)
{
        *(p + i) = i;
}
for (i = 0; i < 20; i++)
{
        printf("%d ", *(p + i));
} 

追加内存注意事项:

因为有可能p地址后面的内存已经使用或者不够追加的大小,

  1. 如果p地址后面的大小足够追加的大小,则在p后面继续追加,返回p
  2. 如果p地址后面的大小不足追加的大小,则会创建一块新的内存,并拷贝p的数据,释放原来的内存空间
  3. 有可能创建的内存过大,返回空指针,使用原来的指针接收返回的指针,可能会使p指向空,为了不让原数据丢失,最好使用新的变量ptr来接收返回的指针。为了使代码可读性高,最好原来使用的指针p。

动态创建内存的例题:

  1. 运行完Test函数出现什么现象?有什么问题?
void GetMemory(char* p)
{
	p = (char*)malloc(100);
}
void Test()
{
	char* str = NULL;
	GetMemory(str);
	strcpy(str, "hello world");
	printf(str);
}
int main()
{
        Test();
        return 0;
}

解析:

  1. 首先GetMemory,传入的是str,属于值传递,没有传入str的地址,所以GetMemory里面申请的空间是属于p的,而str还是NULL。
  2. 空指针进行strcpy就出现程序崩溃
  3. 其次,没有free,可能存在内存泄漏问题
  4. str是以值传递的形式给p,p是GetMemory的形参,只在GetMemory内有效,等GetMemory函数返回后,动态内存尚未释放,并且找不到了。
  5. 在GetMemory内创建的内存,在函数运行结束时,把内存地址返回出去,这样是可以找到的,可以释放。

下面是正确代码(两种方法):

void GetMemory1(char** p)
{
	*p = (char*)malloc(100);
}
void Test1()
{
	char* str = NULL;
	GetMemory1(&str);
	strcpy(str, "hello world");
	printf("%s\n",str);

	free(str);
	str = NULL;
}
char* GetMemory2(char* p)
{
	p = (char*)malloc(100);
	return p;
}
void Test2()
{
	char* str = NULL;
	str = GetMemory2(str);
	strcpy(str, "hello world");
	printf("%s\n", str);

	free(str);
	str = NULL;
}

2. 运行完Test3函数出现什么现象?有什么问题?

char* GetMemory3(void)
{
	char p[] = "hello world";
	return p;
}
void Test3(void)
{
	char* str = NULL;
	str = GetMemory3();
	printf(str);
}
int main()
{
        Test3();
        return 0;
}

解析:

  1. 在GetMemory中创建的静态变量是存储在栈区内,函数运行结束就会还给操作系统,所以在Test3中使用str接收并打印,存在非法地址访问的问题。
  2. 如果局部变量使用static修饰,这个变量就会存储在静态区,是可以返回地址的。malloc创建的空间在堆区,是可以返回的。

正确代码:

char* GetMemory4(void)
{
	static char p[] = "hello world";
	return p;
}
void Test4(void)
{
	char* str = NULL;
	str = GetMemory4();
	printf(str);
}

3. 运行完Test5函数出现什么现象?有什么问题?

void GetMemory5(char** p, int num)
{
	*p = (char*)malloc(num);
}
void Test5()
{
	char* str = NULL;
	GetMemory1(&str, 100);
	strcpy(str, "hello");
	printf(str);
}
int main()
{
        Test5();
        return 0;
}

解析:

这个是可以正常运行,只是没有释放动态内存,存在内存泄露问题。

4. 运行完Test6函数出现什么现象?有什么问题?

void Test6()
{
	char* str = (char*)malloc(100);
	strcpy(str, "hello");
	free(str);
	if (str != NULL)
	{
		strcpy(str, "world");
		printf(str);
	}
}
int main()
{
        Test6();
        return 0;
}

运行结果:

image.png

解析:

str的动态内存已经释放,但是str不是空指针,里面还存放着地址,但是地址不能再使用,再使用str进行拷贝,造成非法访问地址的问题。应该把str释放后,str=NULL。此时就不会拷贝world,没有错误。

动态创建内存结构体与柔性数组:

结构体内存块创建

struct S {
	int a;
	int num[];
};
int main()
{
        struct S s;
	printf("%d\n", sizeof(s));
	struct S* s1 = (struct S*)malloc(sizeof(struct S) + 6 * sizeof(int));
	s1->a = 10;
	for (int i = 0; i < 6; i++)
	{
		s1->num[i] = i;
	}
}

柔性数组:

  1. 结构体中的最后一个元素如果是数组的话,那么他就是柔性数组
  2. 柔性数组可以不用定义大小

柔性数组特点:

  1. 结构中的柔性数组成员前面必须至少一个其他成员
  2. sizeof返回的这种结构大小不包括柔性数组的内存。
  3. 包含柔性数组成员的结构用malloc()函数进行内存的动态分配,并且分配的内存应该大于结构的大小,以适应柔性数组的预期大小。

代码解析:

创建了一块内存块,大小是sizeof(struct S) + 6 * sizeof(int),通过打印结构体的大小可以看出来,结构体的大小为4,说明char数组的大小不包括在结构体大小中。因为这个数组就是柔性数组。 内存块的大小就是4+6*4=28,定义了num数组长度为6。

运行结果:

image.png

结构体中数组空间追加(追加4的int型的大小):

struct S* ptr = realloc(s1, 44);
if (ptr != NULL)
{
        s1 = ptr;
}
for (int i = 6; i < 10; i++)
{
        s1->num[i] = i;
}
for (int i = 0; i < 10; i++)
{
        printf("%d ", s1->num[i]);
}printf("\n");
free(s1);
s1 = NULL;

运行结果:

image.png

使用指针实现柔性数组类似的功能

struct D {
	int a;
	int* ptr;
};
int main()
{
        struct D* d = (struct D*)malloc(sizeof(struct D));
	d->ptr = (int*)malloc(sizeof(int) * 6);
	d->a = 10;
	int i = 0;
	for (i = 0; i < 6; i++)
	{
		d->ptr[i] = i;
	}
	int* pp = realloc(d->ptr, 10 * sizeof(int));
	if (pp == NULL)
	{
		return 0;
	}
	else
	{
		d->ptr = pp;
	}
	for (i = 6; i < 10; i++)
	{
		d->ptr[i] = i;
	}
	for (i = 0; i < 10; i++)
	{
		printf("%d ", d->ptr[i]);
	}printf("\n");
	free(d->ptr);
	free(d);
	d = NULL;
}

解析:

使用指针也可以实现上面的功能,都可以实现结构体中数组的变大变小,但是有很大区别:

  1. 柔性数组创建的内存是连续的,第二种方式创建了两次空间,这两次空间是不连续的。
  2. 第二种方式需要释放的次数多
  3. 多次创建空间,会导致内存中使用多块空间,空间和空间直接产生内存碎片,会产生多个内存碎片
  4. 连续空间访问效率更高。

内存空间的访问速度:

硬盘->内存->缓存->寄存器

内存的读写速度较慢,而cpu读取非常快,所以,当cpu读取数据的时候,内存会把这个数据附近的数据放到缓存的里面,缓存又会把数据放到寄存器里面。所以当cpu要处理下一个数据的会很快。而当处理的数据不是下一个连续的。就会去内存中重新找数据。