[c] C 메모리 관리

저는 항상 C에서 기억을 관리하는 방법을 지켜봐야한다고 들었습니다. 그리고 저는 아직 C를 배우기 시작했지만 지금까지 관련 활동을 관리하는 기억을 전혀 할 필요가 없었습니다. 저는 항상 변수를 해제하고 모든 종류의 추악한 일을해야한다고 상상했습니다. 그러나 이것은 사실이 아닌 것 같습니다.

누군가 “메모리 관리”를해야 할 때의 예를 (코드 예제와 함께) 보여줄 수 있습니까?



답변

변수를 메모리에 넣을 수있는 두 곳이 있습니다. 다음과 같은 변수를 생성 할 때 :

int  a;
char c;
char d[16];

변수는 ” 스택 “에 생성됩니다 . 스택 변수는 범위를 벗어날 때 (즉, 코드가 더 이상 도달 할 수없는 경우) 자동으로 해제됩니다. “자동”변수라고하는 소리를들을 수도 있지만 유행에서 벗어났습니다.

많은 초보자 예제는 스택 변수 만 사용합니다.

스택은 자동이기 때문에 좋지만 두 가지 단점도 있습니다. (1) 컴파일러는 변수의 크기를 미리 알아야하며 (b) 스택 공간이 다소 제한됩니다. 예 : Windows의 Microsoft 링커에 대한 기본 설정에서 스택은 1MB로 설정되며 모든 변수를 변수에 사용할 수있는 것은 아닙니다.

컴파일 타임에 배열의 크기를 모르거나 큰 배열 또는 구조체가 필요한 경우 “플랜 B”가 필요합니다.

플랜 B를 ” ” 이라고합니다 . 일반적으로 운영 체제에서 허용하는만큼 큰 변수를 만들 수 있지만 직접 수행해야합니다. 이전 게시물은 다른 방법이 있지만 할 수있는 한 가지 방법을 보여주었습니다.

int size;
// ...
// Set size to some value, based on information available at run-time. Then:
// ...
char *p = (char *)malloc(size);

(힙의 변수는 직접 조작되지 않고 포인터를 통해 조작됩니다.)

힙 변수를 생성하면 문제는 컴파일러가 작업이 끝났음을 알 수 없기 때문에 자동 해제 기능을 잃게된다는 것입니다. 그것이 당신이 언급 한 “수동 해제”가 들어오는 곳입니다. 이제 여러분의 코드는 변수가 더 이상 필요하지 않은시기를 결정하고 다른 목적으로 메모리를 사용할 수 있도록 해제 할 책임이 있습니다. 위의 경우 :

free(p);

이 두 번째 옵션을 “불쾌한 비즈니스”로 만드는 것은 변수가 더 이상 필요하지 않은시기를 항상 쉽게 알 수 없다는 것입니다. 필요하지 않을 때 변수를 해제하는 것을 잊으면 프로그램이 필요한 것보다 더 많은 메모리를 소비하게됩니다. 이 상황을 “누수”라고합니다. “누수 된”메모리는 프로그램이 종료되고 OS가 모든 리소스를 복구 할 때까지 아무것도 사용할 수 없습니다. 실제로 사용 하기 전에 실수로 힙 변수를 해제하면 더 어려운 문제도 발생할 수 있습니다 .

C 및 C ++에서는 위와 같이 힙 변수를 정리해야합니다. 그러나 다른 접근 방식을 사용하는 Java 및 .NET 언어와 같은 언어 및 환경이 있으며 힙이 자체적으로 정리됩니다. “가비지 수집”이라고하는이 두 번째 방법은 개발자에게 훨씬 더 쉽지만 오버 헤드와 성능에 대한 패널티를 지불합니다. 균형입니다.

(나는 더 간단하지만 더 평등 한 답변을 제공하기 위해 많은 세부 사항을 설명했습니다)


답변

여기에 예가 있습니다. 문자열을 복제하는 strdup () 함수가 있다고 가정합니다.

char *strdup(char *src)
{
    char * dest;
    dest = malloc(strlen(src) + 1);
    if (dest == NULL)
        abort();
    strcpy(dest, src);
    return dest;
}

그리고 이것을 다음과 같이 부릅니다.

main()
{
    char *s;
    s = strdup("hello");
    printf("%s\n", s);
    s = strdup("world");
    printf("%s\n", s);
}

프로그램이 작동하는 것을 볼 수 있지만 메모리를 해제하지 않고 (malloc을 통해) 메모리를 할당했습니다. strdup을 두 번째로 호출했을 때 첫 번째 메모리 블록에 대한 포인터를 잃었습니다.

이 적은 양의 메모리에 대해서는 큰 문제가 아니지만 다음과 같은 경우를 고려하십시오.

for (i = 0; i < 1000000000; ++i)  /* billion times */
    s = strdup("hello world");    /* 11 bytes */

이제 11 기가 바이트의 메모리를 사용했으며 (메모리 관리자에 따라 더 많을 수 있음) 충돌하지 않은 경우 프로세스가 매우 느리게 실행될 수 있습니다.

수정하려면 malloc () 사용을 마친 후 얻은 모든 것에 대해 free ()를 호출해야합니다.

s = strdup("hello");
free(s);  /* now not leaking memory! */
s = strdup("world");
...

이 예제가 도움이되기를 바랍니다.


답변

스택이 아닌 힙의 메모리를 사용하려면 “메모리 관리”를 수행해야합니다. 런타임까지 배열을 만들 크기를 모르는 경우 힙을 사용해야합니다. 예를 들어 문자열에 무언가를 저장하고 싶지만 프로그램이 실행될 때까지 내용이 얼마나 큰지 알 수 없습니다. 이 경우 다음과 같이 작성합니다.

 char *string = malloc(stringlength); // stringlength is the number of bytes to allocate

 // Do something with the string...

 free(string); // Free the allocated memory


답변

C에서 포인터의 역할을 고려하기 위해의 질문에 가장 간결하게 대답하는 방법이라고 생각합니다. 포인터는 가볍지 만 강력한 메커니즘으로 발을 쏠 수있는 엄청난 용량의 대가로 엄청난 자유를 제공합니다.

C에서 포인터가 자신이 소유 한 메모리를 가리 키도록하는 책임은 귀하와 귀하의 것입니다. 이것은 효과적인 C를 작성하기 어렵게 만드는 포인터를 포기하지 않는 한 체계적이고 체계적인 접근이 필요합니다.

현재까지 게시 된 답변은 자동 (스택) 및 힙 변수 할당에 중점을 둡니다. 스택 할당을 사용하면 자동으로 관리되고 편리한 메모리가 생성되지만 일부 상황 (대용량 버퍼, 재귀 알고리즘)에서는 스택 오버 플로라는 끔찍한 문제가 발생할 수 있습니다. 스택에 할당 할 수있는 메모리 양을 정확히 아는 것은 시스템에 따라 크게 달라집니다. 일부 임베디드 시나리오에서는 수십 바이트가 제한 일 수 있으며 일부 데스크탑 시나리오에서는 안전하게 메가 바이트를 사용할 수 있습니다.

힙 할당은 언어에 덜 내재되어 있습니다. 기본적으로 반환 ( ‘free’) 할 준비가 될 때까지 주어진 크기의 메모리 블록에 대한 소유권을 부여하는 라이브러리 호출 집합입니다. 간단하게 들리지만 프로그래머의 비통함과 관련이 있습니다. 문제는 간단하지만 (동일한 메모리를 두 번 해제하거나 전혀 [메모리 누수], 충분한 메모리를 할당하지 않음 [버퍼 오버플로] 등) 피하고 디버깅하기 어렵습니다. 고도로 훈련 된 접근 방식은 실제적으로 절대적으로 필수이지만 물론 언어가 실제로 그것을 요구하지는 않습니다.

다른 게시물에서 무시한 또 다른 유형의 메모리 할당을 언급하고 싶습니다. 함수 외부에서 변수를 선언하여 변수를 정적으로 할당 할 수 있습니다. 일반적으로 이러한 유형의 할당은 전역 변수에서 사용되기 때문에 나쁜 평가를받는다고 생각합니다. 그러나 이런 방식으로 할당 된 메모리를 사용하는 유일한 방법은 스파게티 코드의 엉망진창에서 규율이없는 전역 변수를 사용하는 것이라고 말하는 것은 없습니다. 정적 할당 방법은 힙 및 자동 할당 방법의 일부 함정을 피하기 위해 간단히 사용할 수 있습니다. 일부 C 프로그래머는 크고 정교한 C 임베디드 및 게임 프로그램이 힙 할당을 전혀 사용하지 않고 구성되었다는 사실에 놀랐습니다.


답변

여기에 메모리를 할당하고 해제하는 방법에 대한 몇 가지 훌륭한 답변이 있습니다. 제 생각에 C 사용의 더 어려운 측면은 할당 한 메모리 만 사용하도록하는 것입니다. 이 사이트의 사촌 (버퍼 오버 플로우)은 다른 애플리케이션에서 사용중인 메모리를 덮어 쓰고 매우 예측할 수없는 결과를 초래할 수 있습니다.

예 :

int main() {
    char* myString = (char*)malloc(5*sizeof(char));
    myString = "abcd";
}

이 시점에서 myString에 5 바이트를 할당하고 “abcd \ 0″으로 채웠습니다 (문자열은 null-\ 0으로 끝남). 문자열 할당이

myString = "abcde";

프로그램에 할당 한 5 바이트에 “abcde”를 할당하고 후행 널 문자는이 끝에 놓일 것입니다.이 부분은 사용을 위해 할당되지 않은 메모리의 일부입니다. 무료이지만 다른 애플리케이션에서 동일하게 사용할 수 있음-이는 메모리 관리의 중요한 부분으로, 실수로 인해 예측할 수없는 (때로는 반복 할 수없는) 결과가 발생합니다.


답변

기억해야 할 점은 포인터 를 항상 NULL로 초기화하는 것입니다. 초기화되지 않은 포인터는 포인터 오류가 조용히 진행되도록 할 수있는 의사 난수 유효 메모리 주소를 포함 할 수 있기 때문입니다. 포인터가 NULL로 초기화되도록 강제하면이 포인터를 초기화하지 않고 사용하는 경우 항상 포착 할 수 있습니다. 그 이유는 운영 체제가 가상 주소 0x00000000을 일반 보호 예외에 “연결”하여 널 포인터 사용을 트랩하기 때문입니다.


답변

또한 거대한 배열을 정의해야 할 때 동적 메모리 할당을 사용할 수도 있습니다 (예 : int [10000]). 스택에 넣을 수는 없습니다. 흠 … 스택 오버플로가 발생하기 때문입니다.

또 다른 좋은 예는 연결 목록 또는 이진 트리와 같은 데이터 구조의 구현입니다. 여기에 붙여 넣을 샘플 코드가 없지만 쉽게 구글링 할 수 있습니다.