"C and Pointers" Reading Notes (Chapter 11 Dynamic Memory Allocation)

0 Introduction

In actual development (C language), the elements of the array are stored in consecutive locations in memory. However, there is a disadvantage of using arrays to store data, that is, we need to know its size before the program is run. In actual development, we cannot always accurately grasp the memory that needs to be applied for. If we do not take other means, it will make the development The personnel are in dire straits.

As the big brother of C language, C++ can obviously sit back and relax. When faced with many complex scenarios, it can often use containers to deal with it calmly and with ease. For details, please refer to the link: C++ Common Containers Catch All

C language also has its own response to such problems. In order to break this deadlock, today's protagonist comes to us affectionately and lightly. This is dynamic memory allocation (the same applies to C++).

Overview of the contents of this article:Insert image description here


1 Why use dynamic memory allocation

As mentioned above, many times, we don't know how much memory we need to apply for to store data. If it is too large, it wastes space, and if it is too small, it is not enough.

2 malloc and free

Malloc and free are brothers. The former is responsible for applying for memory, and the latter is responsible for releasing memory. Clear division of labor, simple and efficient. The prototypes of these two functions are as follows:

void *malloc(size_t  size);
void free(void *pointer);

Malloc allocates a piece of contiguous memory. At the same time, the actual allocated memory may be slightly more than what we applied for, and the specific size depends on the compiler .

If the memory pool is empty, or the available memory cannot satisfy the request, mallocthe function requests the operating system for more memory and performs the allocation task on this new memory. If the operating system cannot mallocprovide more memory, a NULLpointer will be returned.

freeThe argument to must either be NULLor be a value previously returned from malloc, callocor . reallocPassing freeone NULLto has no effect.

Specific cases will be mentioned in subsequent content.

3 calloc and realloc

There are also two memory allocation functions callocand realloc. Their prototypes look like this:

void *calloc(size_t num_elements, size_t element_size);
void *realloc(void *ptr, size_t new_size);

callocThere mallocare two differences between:

  • Formally, mallocwhat is passed in is the total number of bytes, and callocwhat is passed in is the number of elements and the number of bytes occupied by each element.
  • From a functional point of view, mallocit is only responsible for applying for memory space, callocnot just applying for memory space, but also initializing it 0.

It can also be seen from the name: calloc = clear + malloc , which means clearing and applying for memory .


realloc modifies/reapplies for a piece of memory.

  1. If there isp enough space to append after the space pointed to by p, it will be appended directly and the original starting address will be returned .
  2. If there is not enough space to add after the space pointed to by p , reallocthe function will find a new memory area, reopen new_sizethe dynamic memory space one by one, and copy the data of the original memory space back to release the old memory space. Return it to the operating system, and finally return the starting address of the newly opened memory space .

The first case is shown in the figure below:
Insert image description here
The second case is shown in the figure below:
Insert image description here

It can also be seen from the name: realloc = re + malloc , which means to re-apply for memory .

4 Using dynamically allocated memory

There is an example in the book, as follows:

	int  *pi;
	pi = malloc(100);
	if (pi == NULL)
	{
    
    
		printf("Out of memory!\n");
		exit(1);
	}

This example is easy to understand. We allocate a 100-byte memory. If the allocation fails, print out the error and exit the currently executing program.
Of course, we can also write a simple program ourselves, as follows:

#include<stdio.h>
#include<stdlib.h>
//定义返回值类型
typedef enum res
{
    
    
	FASLE,
	TRUE
}res_bool;
//分配内存并初始化、打印输出
res_bool fun_malloc(int const size)
{
    
    
	int *p;
	p = malloc(sizeof(int) * 25);
	if(p == NULL)
		return FASLE;
	else
	{
    
    
		for (int i = 0; i < size; i++)
			p[i] = i;
	}
	for (int i = 0; i < size; i++)
		printf("%d\t", p[i]);
	free(p);
	p = NULL;
	return TRUE;
}
int main()
{
    
    
	if (fun_malloc(25) == TRUE)
	{
    
    
		printf("内存分配成功!");
	}
	else
	{
    
    
		printf("内存分配失败!");
	}
	system("pause");
	return 0;
}

This is a relatively complete case, allocating memory, initializing it, and verifying whether the memory allocation is successful.

5 common dynamic memory errors

There are two common types of dynamic memory errors:

  1. One is to use it directly without judging whether the memory application is successful. This may cause unexpected problems.
  2. One is that the operation exceeds the boundaries of allocated memory, and unexpected problems may also occur.

It is difficult to write specific cases for memory errors. You only need to pay attention to them in daily programming.

6 Memory allocation examples

6.1 Sorting a list of integer values

Sorting algorithms are the most common and classic algorithms in engineering development. There are ten common sorting algorithms. If you are interested, please go to: Top Ten Classic Sorting Algorithms
(implemented in C language).
The examples given below are given in the book. Use It is a library function qsortfor sorting, and it is said that the bottom layer uses a quick sort algorithm.

#include <stdlib.h>
#include <stdio.h>
//该函数由qsort调用,用于比较整型值
int compare_integers(void const *a, void const *b)
{
    
    
	register int const *pa = a;
	register int const *pb = b;
	return *pa > *pb ? 1 : *pa < *pb ? -1 : 0;
}
//主函数
int main()
{
    
    
	int *array;
	int n_values;
	int i;
	//观察共有多少个值
	printf("How many values are there?");
	if (scanf_s("%d", &n_values) != 1 || n_values <= 0)
	{
    
    
		printf("Illegal number of values.\n");
		exit(EXIT_FAILURE);
	}
	//分配内存,用于存储这些值
	array = malloc(n_values * sizeof(int));
	if (array == NULL)
	{
    
    
		printf("Can't get memory for that many values.\n");
		exit(EXIT_FAILURE);
	}
	//读取这些值
	for (i = 0; i < n_values; i += 1)
	{
    
    
		printf("?");
		if (scanf_s("%d", array + i) != 1)
		{
    
    
			printf("error.\n");
			free(array);
			exit(EXIT_FAILURE);
		}
	}
	//对这些值排序
	qsort(array, n_values, sizeof(int), compare_integers);
	//打印这些值
	for (i = 0; i < n_values; i += 1)
		printf("%d\n",array[i]);
	//释放内存并推出
	free(array);
	system("pause");
	return EXIT_SUCCESS;
}

Run and print output:
Insert image description here
Basically there is no difficulty. The only difficulty is that compare_integersthe return value of the function uses a nested conditional expression. The conditional expression is a simplified version of the conditional statement (not all cases can be "simplified"), a little It’s a bit convoluted. For conditional expressions, you can refer to Section 2.1.8 of the reading notes (Chapter 5 Operators and Expressions) in "C and Pointers" .

6.2 Copy string

There are also ready-made library functions that can be used to copy strings. The examples in the book only open up space for new strings, nothing more (slightly modified).

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
char *my_strdup(char const *string)
{
    
    
	char *new_string;
	new_string = (char *)malloc(strlen(string) + 1);
	if (new_string != NULL)
		strcpy(new_string, string);
	return new_string;
}
int main()
{
    
    
	char *new_p;
	char base_char[] = "Hello World!";
	//复制字符串
	new_p = my_strdup(base_char);
	//检查是否顺利复制
	if (new_p == NULL)
	{
    
    
			printf("error.\n");
			free(new_p);
			exit(EXIT_FAILURE);
	}
	//检查复制结果
	for (int i = 0; i < (int)(strlen(base_char)); i++)
	{
    
    
		if (new_p[i] != base_char[i])
		{
    
    
			printf("new_p[%d] != base_char[%d]", i, i);
			free(new_p);
			exit(EXIT_FAILURE);
		}
			
	}
	printf("success.\n");
	free(new_p);
	return  0;
}

Run and print the output:
Insert image description here
you can see that the string is copied successfully. From this example, we can also see the convenience of dynamic memory allocation in development.

6.3 Creation and destruction of variant records

The final example illustrates how dynamic memory allocation can be used to eliminate wasted memory space caused by using variant records. The knowledge of structures and unions is used in the program. If you want to know the relevant knowledge, please go to: "C and Pointers" Reading Notes (Chapter 10 Structures and Unions)

First create a header file to define the structures you need to use

#pragma once
//包含零件专用信息的结构
typedef struct {
    
    
	int cost;
	int supplier;
}Partinfo;
//存储配件专用信息的结构
typedef struct {
    
    
	int n_parts;
	struct SUBASSYPART{
    
    
		char partno[10];
		short quan;
	} *part;
}Subassyinfo;
//存货记录结构,一个变体记录
typedef struct {
    
    
	char partno[10];
	int quan;
	enum {
    
    PART, SUBASSY} type;
	union {
    
    
		Partinfo *part;
		Subassyinfo *subassy;
	}info;
}Invrec;

Then write the relevant procedures for creating variant records:

#include <stdio.h>
#include <stdlib.h>
#include "inventor.h"
Invrec *creat_subassy_record(int n_parts)
{
    
    
	Invrec *new_rec;
	//试图为Inverc部分分配内存
	new_rec = malloc(sizeof(Invrec));
	if (new_rec != NULL)
	{
    
    
		//内存分配成功,现在存储SUBASSYPART部分
		new_rec->info.subassy = malloc(sizeof(Subassyinfo));
		if (new_rec->info.subassy != NULL)
		{
    
    
			//为零件获取一个足够大的数组
			new_rec->info.subassy->part = malloc(n_parts * sizeof(struct SUBASSYPART));
			if (new_rec->info.subassy->part != NULL) 
			{
    
    
				//获取内存,填充我们已知道的字段,然后返回
				new_rec->type = SUBASSY;
				new_rec->info.subassy->n_parts = n_parts;
				return new_rec;
			}
			//内存已用完,释放我们原先分配的内存
			free(new_rec->info.subassy);
		}
		free(new_rec);
	}
	return NULL;
}

There are also related procedures for variant record destruction:

#include <stdlib.h>
#include "inventor.h"
void discard_inventory_record(Invrec *record)
{
    
    
	//删除记录中的变体部分
	switch (record->type)
	{
    
    
	case SUBASSY:
		free(record->info.subassy->part);
		free(record->info.subassy);
		break;
	case PART:
		free(record->info.part);
		break;
	}
	//删除记录的主体部分
	free(record);
}

This example is relatively complex, as it contains nested structures, which involves applying for memory layer by layer and then releasing it layer by layer.

7 Summary

This chapter doesn't have a lot of content, but it's very practical. When an array is declared, its length must be known at compile time. Dynamic memory allocation allows a program to allocate memory space for an array whose length is known only at runtime.

---END---

Guess you like

Origin blog.csdn.net/weixin_43719763/article/details/131176301