C 언어 구세주(동적 메모리 관리--13)

콘텐츠

동적 메모리 할당이 존재하는 이유

1.1 동적 메모리 기능: malloc

역할: 메모리 공간을 동적으로 엽니다.

1.2 무료

3. 동적으로 요청된 메모리를 해제하지 않을 때 프로그램이 종료되면 동적으로 요청된 메모리는 운영 체제에서 자동으로 회수합니다. 그러나 프로그램이 종료되지 않으면 동적 메모리가 자동으로 회수되지 않아 메모리 누수 문제가 발생합니다.

1.3 콜록

1.4 재할당

2. 일반적인 동적 메모리 오류

2.1 NULL 포인터의 역참조 연산, 메모리에 대한 잘못된 액세스

2.2 동적으로 개발된 공간에 대한 경계를 벗어난 액세스

2.3 동적으로 할당되지 않은 메모리에 대해 자유 해제 사용

2.4 동적으로 할당된 메모리의 일부를 해제하려면 free를 사용하십시오.

2.5 동일한 동적 메모리를 여러 번 해제

2.6 동적으로 메모리를 열고 해제하는 것을 잊어버림(메모리 누수)

기억하십시오: 동적으로 생성된 공간은 올바르게 해제 및 해제되어야 합니다.

3. 클래식 필기 시험 문제

3.1 주제 1:

3.2 주제 2:

3.3 주제 3:

3.4 주제 4:

4. C/C++ 프로그램을 위한 메모리 할당의 여러 영역:

5. 유연한 어레이

C99에서 구조체의 마지막 요소는 "유연한 배열" 멤버라고 하는 알 수 없는 크기의 배열이 될 수 있습니다.

5.1 유연한 어레이의 특징:

5.2 유연한 배열의 사용

5.3 유연한 어레이의 장점 


동적 메모리 할당이 존재하는 이유

우리가 마스터한 메모리 개발 방법은 다음과 같습니다.

int val = 20;//在栈空间上开辟四个字节
char arr[10] = {0};//在栈空间上开辟10个字节的连续空间

그러나 위에서 언급한 공간 개방 방식에는 두 가지 특징이 있습니다.

1. 공간개발 규모는 고정되어 있다.

2. 배열을 선언할 때 배열의 길이를 지정해야 하며 필요한 메모리는 컴파일 시 할당됩니다.

3. 동적 메모리 관리는 힙 영역에 메모리를 할당합니다.


1.1 동적 메모리 기능: malloc

역할: 메모리 공간을 동적으로 엽니다.

void *malloc( size_t size );

지침:

1. 이 함수는 메모리에서 연속적인 여유 공간을 요청하고 이 공간에 대한 포인터를 반환합니다.

2. 개발에 실패하면 NULL 포인터를 반환하므로 malloc의 반환값을 확인해야 한다 .

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <stdlib.h>

int main()
{
	int* ptr = (int*)malloc(INT_MAX);
	if (ptr == NULL)
	{
		perror("malloc");
		return 1;
	}

	return 0;
}

3. 반환값의 타입은 void*이므로 malloc 함수는 열린 공간의 타입을 알지 못하며 언제 사용할지는 사용자가 결정한다(강제변환).

int* ptr = (int*)malloc(40);

4. 공간이 열린 후에는 사용이 끝나면 공간을 해제해야 하며 free 함수는 동적으로 열린 메모리를 해제하는 데 사용됩니다.

5. 매개변수 크기가 0이면 malloc의 동작은 표준에 의해 정의되지 않으며 컴파일러에 따라 다릅니다.

1.2 무료

int main()
{
	int* ptr = (int*)malloc(40);
	int* p = ptr;//记住起始地址
	if (p == NULL)
	{
		perror("malloc");//检查返回值
		return 1;
	}
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		*p = i;
		p++;
	}
	//释放空间
	free(ptr);
	ptr = NULL;//规避野指针
	return 0;
}

지침:

1. ptr 매개변수가 가리키는 공간이 동적으로 할당되지 않으면 free 함수의 동작이 정의되지 않습니다.

2. ptr 매개변수가 NULL 포인터이면 함수는 아무 작업도 수행하지 않습니다.

3. 동적으로 요청된 메모리를 해제하지 않을 때 프로그램이 종료되면 동적으로 요청된 메모리는 운영 체제에서 자동으로 회수합니다. 그러나 프로그램이 종료되지 않으면 동적 메모리가 자동으로 회수되지 않아 메모리 누수 문제가 발생합니다.

int main()
{
	while (1)
	{
	malloc(1000);//程序运行时观察内存使用情况
	}
	return 0;
}

1.3 콜록

void* calloc (size_t num, size_t size);

 함수의 기능은 크기 크기의 num 요소에 대한 공간을 열고 공간의 각 바이트를 0으로 초기화하는 것입니다.

함수 malloc과의 유일한 차이점은 calloc이 주소를 반환하기 전에 요청된 공간의 각 바이트를 모두 0으로 초기화한다는 것입니다.

       

int main()
{
	//int*p = (int*)malloc(40);
	//申请10个整形的空间
	int* p = (int*)calloc(10, sizeof(int));//calloc申请的空间会被初始化为0
	if (p == NULL)
	{
		perror("calloc");
		return 1;
	}
	//使用
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		printf("%d ", *(p + i));
	}
	free(p);
	p = NULL;
	return 0;
}

요청된 메모리 공간의 내용을 초기화해야 하는 경우 calloc 함수를 사용하여 작업을 완료하는 것이 매우 편리합니다.


1.4 재할당

void *realloc( void *memblock, size_t size );

 기능: 동적으로 열린 메모리 공간 조정 및 메모리 블록 재할당

ptr은 조정할 메모리 주소입니다. size 조정 후 새 크기입니다.

int main()
{
	int*p = (int*)malloc(40);
	
	if (p == NULL)
	{
		perror("malloc");
		return 1;
	}
	//使用
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		*(p + i) = i;//0 1 2 3 4 5 6 7 8 9
	}

	//空间不够,希望能放20个元素,考虑扩容
    //p = (int*)realloc(p, 80);//错误的写法,万一空间不够,把原地址也弄丢了
	int*ptr = (int*)realloc(p, 80);
	if (ptr != NULL)
	{
		p = ptr;//考虑如果扩容失败,也要判断realloc的返回值
	}

	//扩容成功了,开始使用

	//不再使用,就释放
	free(p);
	p = NULL;

	return 0;
}

지침:

1. 반환 값은 조정 후 메모리 시작 위치입니다.

2. 원래 메모리 공간의 크기 조정을 기반으로 이 기능은 원래 메모리의 데이터도 새 공간으로 이동합니다 .


2. 일반적인 동적 메모리 오류

2.1 NULL 포인터의 역참조 연산, 메모리에 대한 잘못된 액세스

int main()
{
	int* p = (int*)malloc(1000);
	int i = 0;
	//使用
	for (i = 0; i < 250; i++)
	{
		*(p + i) = i;
	}
	return 0;
}

올바른 맞춤법

int main()
{
	int* p = (int*)malloc(1000);
	int i = 0;
	if (p == NULL)
	{
		//....
		return 1;
	}
	//使用
	for (i = 0; i < 250; i++)
	{
		*(p + i) = i;
	}

	free(p);
	p = NULL;

	return 0;
}

솔루션: malloc 함수의 반환 값을 판단합니다. 

2.2 동적으로 개발된 공간에 대한 경계를 벗어난 액세스

int main()
{
	int* p = (int*)malloc(100);
	int i = 0;
	if (p == NULL)
	{
		//....
		return 1;
	}
	//使用
	for (i = 0; i <= 25; i++)//越界访问了
	{
		*(p + i) = i;
	}
	return 0;
}

솔루션: 메모리 경계 확인


2.3 동적으로 할당되지 않은 메모리에 대해 자유 해제 사용

매개변수 ptr이 가리키는 공간이 동적으로 할당되지 않은 경우 free 함수의 동작은 정의되지 않습니다. 

int main()
{
	int a = 10;

	int* p = &a;
	//.....

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

해결책: 자신에게 약간의 큰 주머니를 마련하십시오.


2.4 동적으로 할당된 메모리의 일부를 해제하려면 free를 사용하십시오.

int main()
{
	int* p = (int*)malloc(100);
	if (p == NULL)
	{
		return 1;
	}
	//使用
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		*p = i;
		p++;
	}
	//释放空间
	free(p);//err
	p = NULL;

	return 0;
}

솔루션: 이때 p 포인터는 더 이상 시작 주소를 가리키지 않고 p는 11번째 정수 공간을 가리킵니다.공간을 해제하면 시작 주소의 공간을 해제해야 합니다.


2.5 동일한 동적 메모리를 여러 번 해제

int main()
{
	int* p = malloc(100);
	if (p == NULL)
		return 1;
	
	free(p);
	//....
	free(p);//err

	p = NULL;

	return 0;
}

2.6 동적으로 메모리를 열고 해제하는 것을 잊어버림(메모리 누수)

        

void test()
{
	int* p = malloc(100);
	//使用
	if (1)
		return;//没有释放,也没人知道起始地址

	free(p);
	p = NULL;
}

int main()
{
	test();
	//.....
	while (1)
	{
		;
	}

	return 0;
}

실제 상황: 실행 중 프로그램이 갑자기 멈춤 실행 후 정상적으로 실행될 수 있지만 malloc이 메모리를 먹고 있기 때문에 일정 시간이 지나면 다시 멈춥니다.

기억하십시오: 동적으로 생성된 공간은 올바르게 해제 및 해제되어야 합니다.


3. 클래식 필기 시험 문제

3.1 주제 1:

#include <string.h>
#include <stdio.h>

void GetMemory(char *p)
{
 p = (char *)malloc(100);
}

void Test(void)
{
 char *str = NULL;
 GetMemory(str);
 strcpy(str, "hello world");
 printf(str);//正常写法,printf只需要知道字符串地址即可打印
}

int main()
{
	Test();
	return 0;
}

실행 결과: 프로그램 충돌

이유 분석:

1. str은 포인터 변수이고, char* p는 포인터 변수이며, p는 str의 임시 복사본입니다. 전달되는 것은 str의 주소가 아니지만 str 자체의 형식 매개변수는 NULL입니다. GetMemory 함수는 시작 주소가 p에 저장되고 나가는 함수 주소 p가 파괴되는 malloc에 ​​의해 열린 100바이트 메모리 공간인 Destruction 함수에서 나오며 메모리 누수가 있습니다.

2. strcpy(NULL, "hello world")가 복사되고 NULL 포인터에 적용이 취소되고 이 단계에서 프로그램이 충돌합니다.

올바르게 수정됨:

void GetMemory(char** p)
{
	*p = (char*)malloc(100);
}

void Test(void)
{
	char* str = NULL;
	GetMemory(&str);
	strcpy(str, "hello world");
	printf(str);//?
	free(str);
	str = NULL;
}
int main()
{
	Test();
	return 0;
}

3.2 주제 2:

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

작업 결과:

이유 분석: 함수가 종료된 후 char p 배열이 파괴되고 p의 주소가 반환된 다음(이 때 p 포인터는 와일드 포인터임) 인쇄되어 메모리에 대한 잘못된 액세스가 발생합니다.


3.3 주제 3:

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;
}

실행 결과: print hello

이유 분석: p는 그것이 NULL 포인터인지 여부를 판단하지 않습니다; 동시에 malloc에 ​​의해 적용된 공간이 해제되지 않았으므로 해제하고 NULL 포인터로 설정하기만 하면 됩니다.


3.4 주제 4:

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;
}

작업 결과:

이유 분석: 복사 후 free가 해제되고 malloc 공간이 회수되었지만 str 포인터는 여전히 시작 주소를 기억하고 str에 사용된 공간도 복사되어 잘못된 연산으로 인해 와일드 포인터가 발생합니다.

그래서 우리가 자유로워지면 즉시 포인터를 NULL로 설정하십시오. 좋은 습관입니다.


4. C/C++ 프로그램을 위한 메모리 할당의 여러 영역:

  1. 스택 영역(스택): 함수 실행 시 해당 함수의 로컬 변수 저장 단위를 스택에 생성할 수 있으며, 이 저장 단위는 함수 실행이 종료되면 자동으로 해제됩니다. 스택 메모리 할당 작업은 프로세서의 명령어 세트에 내장되어 있어 매우 효율적이지만 할당된 메모리 용량은 제한적입니다. 스택 영역은 주로 로컬 변수, 함수 매개변수, 반환 데이터, 반환 주소 등을 실행하는 함수에 의해 할당된 저장합니다.

2. 힙: 일반적으로 프로그래머가 할당 및 해제하는 것으로 프로그래머가 해제하지 않으면 프로그램 종료 시 OS에서 회수할 수 있습니다. 할당 방법은 연결 목록과 유사합니다.

3. 데이터 세그먼트(정적 영역)(정적)는 전역 변수와 정적 데이터를 저장합니다. 프로그램 종료 후 시스템에 의해 해제됩니다.

4. 코드 세그먼트: 함수 본문(클래스 멤버 함수 및 전역 함수)을 저장하는 이진 코드입니다.      

사실 일반 지역변수는 스택 영역에 공간이 할당되는데 스택 영역의 특징은 위에서 생성한 변수가 범위를 벗어나면 소멸된다는 점이다.

단, static으로 수정한 변수는 데이터 세그먼트(정적 영역)에 저장되는데, 위에서 생성한 변수는 프로그램이 종료될 때까지 소멸되지 않아 라이프 사이클이 길어지는 것이 데이터 세그먼트의 특징이다.


5. 유연한 어레이

C99에서 구조체의 마지막 요소 는 "유연한 배열" 멤버라고 하는 알 수 없는 크기의 배열이 될 수 있습니다.

typedef struct st_type
{
 int i;
 int a[0];//柔性数组成员,写成0不代表0个元素,而是代表大小是未知的
//int a[];//另一种写法
}type_a;

플렉서블 어레이는 동적 메모리를 사용해야 합니다.

5.1 유연한 어레이의 특징:

struct S3
{
	int num;//4
	int arr[];//柔性数组成员
};

int main()
{
	printf("%d\n", sizeof(struct S3));//?
	return 0;
}

4바이트를 출력한다

구조체의 가변 배열 멤버 앞에는 다른 멤버가 하나 이상 있어야 합니다.

sizeof에 의해 반환된 이 구조의 크기는 가변 배열의 메모리를 포함하지 않습니다.

가변 배열 멤버를 포함하는 구조는 malloc() 함수를 사용하여 메모리를 동적으로 할당하고 할당된 메모리는 가변 배열의 예상 크기를 수용하기 위해 구조 크기보다 커야 합니다.

struct S3
{
	int num;//4
	int arr[];//柔性数组成员
};

int main()
{
	struct S3* ps = (struct S3*)malloc(sizeof(struct S3)+40);//不能只开辟4个空间
    //假设我们希望数组能存放10个整形,+40,malloc总体开辟大小是44个字节
    //前4个字节给了num,后40个字节给了arr
	return 0;
}

5.2 유연한 배열의 사용

struct S3
{
	int num;//4
	int arr[];//柔性数组成员
};

int main()
{
	struct S3* ps = (struct S3*)malloc(sizeof(struct S3)+40);
	if (ps == NULL)
	{
		perror("malloc");
		return 1;
	}
	int i = 0;

	//扩容
	struct S3* ptr = (struct S3*)realloc(ps, sizeof(struct S3)+80);
	if (ptr == NULL)
	{
		perror("realloc\n");
		return 1;
	}
	else
	{
		ps = ptr;
	}

	for (i = 0; i < 20; i++)
	{
		ps->arr[i] = i;
	}
	for (i = 0; i < 20; i++)
	{
		printf("%d ", ps->arr[i]);
	}

	//释放
	free(ps);
	ps = NULL;
	return 0;
}

realloc은 가변 배열의 크기를 조정할 수 있습니다.


그런데 왜 우리는 유연한 어레이의 개념을 생각해 냈을까요? 다른 방법으로 유연한 배열을 시뮬레이션할 수도 있습니까?

struct S4
{
	int num;
	int* arr;
};

int main()
{
	struct S4* ps = (struct S4*)malloc(sizeof(struct S4));//让struct S4也放到堆上去
	if (ps == NULL)
	{
		return 1;
	}
	ps->arr = (int*)malloc(40);
	if (ps->arr == NULL)
	{
		free(ps);
		ps = NULL;
		return 1;
	}
	//使用
	//...
	
	//释放
	free(ps->arr);
	ps->arr = NULL;

	free(ps);
	ps = NULL;
	return 0;
}

5.3 유연한 어레이의 장점 

유연한 어레이 사용의 첫 번째 이점은 다음과 같습니다. 손쉬운 메모리 해제

S4가 가변 배열을 시뮬레이션할 때 메모리를 해제하려면 arr이 가리키는 공간을 먼저 해제한 다음 arr 포인터를 해제해야 합니다.유연한 배열은 한 번 malloced되고 한 번만 해제하면 됩니다.

우리 코드가 다른 사람이 사용하는 함수에 있는 경우 두 번째 메모리 할당을 수행하고 전체 구조를 사용자에게 반환합니다. 사용자는 free를 호출하여 구조체를 해제할 수 있지만, 사용자는 구조체의 멤버도 free해야 한다는 것을 모르기 때문에 사용자가 알아낼 것이라고 기대할 수 없습니다. 따라서 구조체의 메모리와 해당 멤버가 필요로 하는 메모리를 한 번에 할당하고 구조체 포인터를 사용자에게 반환하면 사용자는 한 번 free를 수행하여 모든 메모리를 해제할 수 있습니다.

두 번째 이점은 액세스 속도에 좋습니다.

연속 메모리는 액세스 속도 향상과 메모리 단편화 감소(메모리 활용도 향상)에 좋습니다. CPU는 레지스터의 데이터를 가져옵니다. 지역성의 원리에 따라 데이터에 액세스하면 주변 데이터를 레지스터에 로드합니다. 다음 데이터에 액세스할 때 CPU가 데이터에 액세스할 때 레지스터에 부딪힐 확률은 다음과 같습니다. 더 높으십시오.

레지스터에 데이터가 없으면 CPU는 해당 데이터를 찾을 때까지 캐시 -> 메모리 페치로 이동합니다.

추천

출처blog.csdn.net/weixin_63543274/article/details/124069463