[learn C together] dynamic memory allocation

Posted by stuffradio on Sun, 26 Sep 2021 11:57:15 +0200

Dynamic memory allocation

preface:

If you can open up memory at will and open it as you use it, can you make the use of memory more efficient? There will be no situation where more development can't be used up and less development can't be used up. Dynamic memory development solves this problem.

Open up location

Memory can be roughly divided into three locations:

  1. Stack area: store local variables and function parameters
  2. Static area: store static variables and global variables
  3. Heap: dynamic variables

The dynamic memory we are studying today is stored in the heap

malloc introduction

Function prototype

Header file stdlib.h or malloc.h

void* malloc(sizze_t num);

Num represents the number of bytes. malloc opens up all num bytes for our use.

Specific use:

#include<stdlib.h>
int main()
{
	int* p = (int*)malloc(40);
	if (p == NULL)
	{
		printf("malloc fail\n");
	}
	else
	{
		for (int i = 0; i < 10; i++)
		{
			*(p + i) = 0;
		}
	}
    free(p);
	return 0;
}

matters needing attention:

  1. Remember to use pointers to accept. Because malloc returns the address in memory, you need to record the address in memory, so you need to use a pointer to accept it
  2. ==To cast to another type== Because malloc's function is of void * type, its purpose is to be forcibly converted to other types of numbers for ease of use.
  3. After malloc is completed, you must remember whether the pointer from the segment is empty, because the development may not be successful. If the development fails, NULL will be returned
  4. malloc must be free after use. Otherwise, it will always occupy the space in memory and affect the memory

These four are particularly important and easy to ignore

free introduction

After malloc memory, we must not forget to free them. Let's take a specific look at the nature of free

a key:

When we free the developed memory, our program will release the developed memory and return the memory to the operating system, and the content will become a random value. However, the accepted pointer still records the address of that place, so there may be a risk of memory leakage. So we need to set the value of free to NULL

Before free:

After free:

It can be seen that after free, although it is returned to the operating system, the value of P is still recorded. So be sure to set p to NULL

calloc introduction

Calloc is very similar to malloc, except that one more calloc is initialized to 0

Function prototype:

void* calloc(size_t num,size_t size);

be careful:

  1. num is the size of the memory to be opened up
  2. size is the number of bytes of memory opened up
  3. calloc will automatically initialize the newly opened memory to 0

Concrete implementation

#include<string.h>
#include<errno.h>
int main()
{
	int* p = (int*)calloc(100000000000000, sizeof(int));
	if (p == NULL)
	{
		printf("%s", strerror(errno));
		//perror("");
	}
	else
	{
		for (int i = 0; i < 10; i++)
			printf("%d", *(p + i));
	}
	return 0;
}

realloc introduction

Function prototype:

void* realloc(void* ptr,size_t size)
  • ptr is the address where you want to adjust the memory.

  • Size is the total size of the new memory after adjustment

  • There are two situations when realloc adjusts memory

    Case 1

    The memory capacity behind realloc is large enough to open up size bytes of memory.


At this time, the ptr point remains unchanged, but the memory is expanded to twice.

Case 2

The memory capacity behind realloc is not enough to open up size bytes of memory. You need to re find a unit with size contiguous memory in the memory system.

Concrete implementation

int main()
{
	int* p = (int*)malloc(10);
	if (p == NULL)
		printf("%s", strerror(errno));
	else
	{
		int* ptr = (int*)realloc(p, 20);//Create a new pointer to save
		if (ptr == NULL)//NULL if realloc fails
		{
			printf("%s", strerror(errno));
		}
		else//If successful, give the pointer of the newly opened space to the new pointer
		{
			p = ptr;
		}
	}
	free(p);
}

Common memory errors

void test()
{
int i = 0;
int *p = (int *)malloc(10*sizeof(int));
if(NULL == p)
{
	exit(EXIT_FAILURE);
}
for(i=0; i<=10; i++)
{
	*(p+i) = i;//Cross border access when i is 10
}
	free(p);
}

It is easy to use memory that does not belong to our development, which leads to cross-border access. This is something we should pay attention to.

Use free release for non dynamic memory

For non dynamically opened memory, it does not belong to the memory in the heap, so free can not be used to free it

void test()
{
	int a = 10;
	int *p = &a;
	free(p);//ok?
}

Use free to release a part of dynamic memory

void test()
{
	int *p = (int *)malloc(100);
	p++;
	free(p);//p no longer points to the beginning of dynamic memory
}   

This is also wrong, because the content to be free must be the first pointer of the memory we open up.

You can't just free out part of the open memory.

Multiple releases of the same dynamic memory

void test()
{
	int *p = (int *)malloc(100);
	free(p);
	free(p);//Repeated release
}

Dynamic memory forget release (memory leak)

void test()
{
	int *p = (int *)malloc(100);
	if(NULL != p)
	{
		*p = 20;
	}
}
int main()
{
	test();
	while(1);
}

This is the memory that you forget to open up. If the memory is released, it will cause memory leakage. It is very dangerous, so you must pay attention to free it.

Classic interview questions

Classical value passing and address passing problems

Judge whether the following actions are correct:

void GetMemory(char* p)
{
	p = (char*)malloc(100);
}
//p is a formal parameter. The content of the formal parameter is changed, but the content of the argument cannot be changed. So the value of str is still NULL
void Test(void)
{
	char* str = NULL;
	GetMemory(str);
	strcpy(str, "hello world");
	//For strcpy, neither pointer can be NULL, that is, there can be no pointer to it.
	//So when I got here, the program crashed.
	printf(str);
}
int main()
{
	Test();
}

Correct approach: address passing solves the problem that str is NULL above

void GetMemory(char** p)
{
	*p = (char*)malloc(100);
}
void Test(void)
{
	char* str = NULL;
	GetMemory(&str);
	strcpy(str, "hello world");
	printf(str);
    free(str)//Don't forget to release
}
int main()
{
	Test();
	return 0;
}

Direct return address

char* GetMemory(char* p)
{
	p = (char*)malloc(100);
	return p;
}
void Test(void)
{
	char* str = NULL;
	str = GetMemory(str);
	strcpy(str, "hello world");
	printf(str);
	free(str);//Don't forget to release
}
int main()
{
	Test();
	return 0;
}

Follow the return address above

For the above question, can all return addresses to remedy errors? No.

For the following:

//********************************Next change***********************************
char* GetMemory(void)
{
	char p[] = "hello world";
	return p;
}
//Like the previous change, can I return the address?
void Test(void)
{
	char* str = NULL;
	str = GetMemory();
	printf(str);
}
int main()
{
	Test();
	return 0;
}
The answer is no, because different from the previous change, the previous one is the memory opened on the heap, and the function will not be destroyed.
However, this is the memory opened on the stack. There is a function p It's not recorded hello world It's just an ordinary value
 So it will return to "hot hot hot hot hot hot hot","2333333333333

Therefore, this tells us an important conclusion:

Only the memory opened on the heap can be returned, and the memory opened on the stack cannot be returned.

int* test()
{
	int n = 10;
	return &n;
}
int main()
{
	int* i = test();
	printf("hehe\n");
	printf("%d", *i);
}

Although the above program is very simple, it also returns the address on the stack. Although there is no printf("hehe\n"); it is likely to output the value of n (not overwritten), but there is

After printf("hehe\n");, a random value of 5 is returned after the value of n is overwritten.

Therefore, we must remember that we can't return the address of the development variable on the stack. It's understandable on the heap, but it's really not on the stack.

Don't forget to drop the first address of free

What's wrong with the following? How to correct it?

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

be careful:

There is no problem with this implementation. The only thing that is easy to ignore is that there is no free memory for dynamic application
In addition, free does not have to be free inside the requested function. As long as you remember the first address of malloc, you can free anywhere

Don't forget to set it to NULL

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

What's the problem?

Forget to set NULL after free, and continue to use wild pointers.

Location of C/C + + in memory


**Several areas of C/C + + program memory allocation:**

  1. Stack: when a function is executed, the storage units of local variables in the function can be created on the stack. When the function is executed, these variables
    The memory unit is automatically released. The stack memory allocation operation is built into the instruction set of the processor, which is very efficient, but the allocated memory capacity is
    The stack area mainly stores the local variables, function parameters, return data, return address, etc. allocated for running the function
  2. heap: it is generally allocated and released by the programmer. If the programmer does not release it, it may be recycled by the OS at the end of the program. The distribution method is similar
    In the linked list.
  3. The data segment (static area) stores global variables and static data. Released by the system at the end of the program.
  4. Code snippet: it stores the binary code of the function body (class member function and global function), as well as the read-only constant: constant string

Special attention should be paid to:

Static variables:

In fact, ordinary local variables allocate space in the stack area. The characteristic of the stack area is that the variables created above are destroyed when they leave the scope.
However, the variables modified by static are stored in the data segment (static area). The data segment is characterized by the variables created on it, which are not destroyed until the end of the program
So the life cycle becomes longer.

Flexible array

A flexible array is an array in a structure. This kind of array can not specify the length of the array

requirement:

  1. There must be variables in front of the flexible array in the structure
  2. In the structure, the flexible array does not occupy the memory of the structure
  3. When using dynamic memory to open up space, it should be with flexible array + other variables. You can't only open up the size of flexible array, but the size of the whole array

Use of flexible arrays

struct type
{
	int i;
	int a[];//Flexible arrays must be preceded by variables
};
int main()
{
	struct type* p = (struct type*)malloc(sizeof(struct type) + 10 * sizeof(int));
	//Note: the space of the whole structure must be opened up
	//If the development fails
	if (p == NULL)
	{
		printf("%s", strerror(errno));
		return -1;
	}
	//It's a success
	p->i = 2;
	for (int j = 0; j < 10; j++)
	{
		p->a[j] = j;
	}
	for (int j = 0; j < 10; j++)
	{
		printf("%d ", p->a[j]);
	}
	//Not enough memory, open up an array of 20 sizes
	struct type* ptr = (struct type*)realloc(p, sizeof(struct type) + 20 * sizeof(int));
	if (ptr == NULL)
	{
		printf("%s", strerror(errno));
		return -1;
	}
	p = ptr;
	//use
	//Release - remember to release

	free(p);
	p = NULL;
	return 0;

}

Advantages of flexible arrays

For the same function, we can also adopt the following methods:

typedef struct st_type
{
	int i;
	int *p_a;
}type_a;
type_a *p = malloc(sizeof(type_a));
p->i = 100;
p->p_a = (int *)malloc(p->i*sizeof(int));
//Business processing
for(i=0; i<100; i++)
{
	p->p_a[i] = i;
}
//Free up space
free(p->p_a);
p->p_a = NULL;
free(p);
p = NULL;

From this way, we can see that we should not only open up structures, but also open up arrays of structures. In this way, you have to perform multiple free, which will cause the risk of memory leakage.

Advantage 1:

If our code is in a function for others, you make a secondary memory allocation in it and return the whole structure to the user. use
The user can release the structure by calling free, but the user does not know that members in the structure also need free, so you can't expect the user to send it
Now this thing. Therefore, if we allocate the memory of the structure and the memory required by its members at one time, and return a structure to the user
Pointer, the user can free all the memory once.

Advantage 2:

Continuous memory is beneficial to improve access speed and reduce memory fragmentation. (actually, I personally don't think it's too high. You can't run anyway
(to be addressed by the addition of offset)

Topics: C C++