C 메모리 관리


90

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

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


답변:


231

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

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 언어와 같은 언어 및 환경이 있으며 힙이 자체적으로 정리됩니다. "가비지 수집"이라고하는이 두 번째 방법은 개발자에게 훨씬 더 쉽지만 오버 헤드와 성능에 대한 패널티를 지불합니다. 균형입니다.

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


3
스택에 무언가를 넣고 싶지만 컴파일 시간에 얼마나 큰지 모르는 경우, alloca ()는 스택 프레임을 확장하여 공간을 확보 할 수 있습니다. freea ()가 없으며 함수가 반환 될 때 전체 스택 프레임이 팝됩니다. 큰 할당에 alloca ()를 사용하는 것은 위험합니다.
DGentry

1
아마 당신은 글로벌 변수의 메모리 위치에 대해 하나 개 또는 두 개의 문장을 추가 할 수 있습니다
마이클 Käfer

의 C 결코 캐스트 대가로 malloc(), 그 원인의 UB, (char *)malloc(size);참조 stackoverflow.com/questions/605845/...
EsmaeelE

17

여기에 예가 있습니다. 문자열을 복제하는 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");
...

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


이 답변이 더 좋습니다. 하지만 약간의 부수적 인 질문이 있습니다. 나는 이와 같은 것이 라이브러리로 해결되기를 기대합니다. 기본 데이터 유형을 밀접하게 모방하고 변수가 사용되면 자동으로 해제되도록 메모리 해제 기능을 추가하는 라이브러리가 있습니까?
Lorenzo

표준의 일부가 아닙니다. C ++로 들어가면 자동 메모리 관리를 수행하는 문자열과 컨테이너를 얻게됩니다.
Mark Harrison

알겠습니다. 타사 라이브러리가 있습니까? 이름을 지어 주시겠습니까?
Lorenzo

9

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

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

 // Do something with the string...

 free(string); // Free the allocated memory

5

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

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

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

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

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


4

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

예 :

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

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

myString = "abcde";

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


여기에 5 바이트를 할당합니다. 포인터를 할당하여 풉니 다. 이 포인터를 해제하려고하면 정의되지 않은 동작이 발생합니다. 참고 C-Strings는 = 연산자를 오버로드하지 않으며 복사본이 없습니다.
Martin York

그러나 실제로 사용하는 malloc에 ​​따라 다릅니다. 많은 malloc 연산자는 8 바이트로 정렬됩니다. 따라서이 malloc이 머리글 / 바닥 글 시스템을 사용하는 경우 malloc은 5 + 4 * 2 (머리글과 바닥 글 모두 4 바이트)를 예약합니다. 그것은 13 바이트가 될 것이고 malloc은 정렬을 위해 추가로 3 바이트를 줄 것입니다. 나는 이것을 사용하는 것이 좋은 생각이라고 말하는 것이 아닙니다. 왜냐하면 그것은 malloc이 이와 같이 작동하는 시스템 만 할 것이기 때문입니다. 그러나 적어도 잘못된 일을하는 것이 왜 작동하는지 아는 것은 적어도 중요합니다.
kodai

Loki : strcpy()대신 사용하도록 답변을 편집했습니다 =. 나는 그것이 Chris BC의 의도라고 생각합니다.
echristopherson

저는 최신 플랫폼 하드웨어 메모리 보호가 사용자 공간 프로세스가 다른 프로세스의 주소 공간을 덮어 쓰는 것을 방지한다고 믿습니다. 대신 분할 오류가 발생합니다. 그러나 그것은 그 자체로 C의 일부가 아닙니다.
echristopherson

4

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


2

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

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


2

(지금까지의 답변이 정답이 아닌 것 같아서 쓰고 있습니다.)

언급 할 가치가있는 메모리 관리를해야하는 이유는 복잡한 구조를 만들어야하는 문제 / 솔루션이있을 때입니다. (한 번에 스택의 많은 공간에 할당하면 프로그램이 충돌하면 버그입니다.) 일반적으로 배워야 할 첫 번째 데이터 구조는 일종의 list 입니다. 여기에 링크 된 하나가 있습니다.

typedef struct listelem { struct listelem *next; void *data;} listelem;

listelem * create(void * data)
{
   listelem *p = calloc(1, sizeof(listelem));
   if(p) p->data = data;
   return p;
}

listelem * delete(listelem * p)
{
   listelem next = p->next;
   free(p);
   return next;
}

void deleteall(listelem * p)
{
  while(p) p = delete(p);
}

void foreach(listelem * p, void (*fun)(void *data) )
{
  for( ; p != NULL; p = p->next) fun(p->data);
}

listelem * merge(listelem *p, listelem *q)
{
  while(p != NULL && p->next != NULL) p = p->next;
  if(p) {
    p->next = q;
    return p;
  } else
    return q;
}

당연히 다른 몇 가지 기능이 필요하지만 기본적으로 이것이 메모리 관리가 필요한 것입니다. "수동"메모리 관리로 가능한 많은 트릭이 있음을 지적해야합니다. 예 :

  • malloc 이 (언어 표준에 의해) 보장 된다는 사실을 사용하여 4로 나눌 수있는 포인터를 반환합니다.
  • 자신의 사악한 목적을 위해 추가 공간을 할당하고
  • 메모리 풀 생성 중 ..

좋은 디버거를 구 하세요 ... 행운을 빕니다!


데이터 구조를 배우는 것은 메모리 관리를 이해하는 다음 핵심 단계입니다. 이러한 구조를 적절하게 실행하는 알고리즘을 배우면 이러한 문제를 극복하는 적절한 방법을 알 수 있습니다. 이것이 바로 같은 과정에서 가르치는 데이터 구조와 알고리즘을 찾을 수있는 이유입니다.
aj.toulan 2013

0

@ 유로 미첼 리

추가해야 할 한 가지 부정적인 점은 함수가 반환 될 때 스택에 대한 포인터가 더 이상 유효하지 않으므로 함수에서 스택 변수에 대한 포인터를 반환 할 수 없다는 것입니다. 이것은 일반적인 오류이며 스택 변수만으로는 얻을 수없는 주된 이유입니다. 함수가 포인터를 반환해야하는 경우 malloc을 수행하고 메모리 관리를 처리해야합니다.


0

@ Ted Percival :
... malloc ()의 반환 값을 캐스팅 할 필요가 없습니다.

물론 맞습니다. 확인해야 할 K & R 사본이 없지만 항상 사실이라고 생각합니다 .

저는 C에서 암시 적 변환을 많이 좋아하지 않으므로 "마법"을 더 잘 보이게 만들기 위해 캐스트를 사용하는 경향이 있습니다. 가독성에 도움이되는 경우도 있고 그렇지 않은 경우도 있으며, 컴파일러가 자동 버그를 포착하는 경우도 있습니다. 그래도 나는 이것에 대해 어떤 식 으로든 강한 의견을 가지고 있지 않습니다.

컴파일러가 C ++ 스타일 주석을 이해하는 경우 특히 그렇습니다.

그래 .. 날 잡았어. 저는 C보다 C ++에서 더 많은 시간을 보냅니다. 알아 주셔서 감사합니다.


@echristopherson, 감사합니다. 맞아요.하지만이 Q / A는 2008 년 8 월부터 Stack Overflow가 퍼블릭 베타 버전이되기 전이었습니다. 당시 우리는 여전히 사이트가 어떻게 작동해야하는지 파악하고있었습니다. 이 질문 / 답변의 형식이 SO를 사용하는 방법에 대한 모델로 간주되어서는 안됩니다. 감사!
Euro Micelli

아, 지적 해 주셔서 감사합니다. 당시 사이트의 측면이 여전히 유동적이라는 것을 몰랐습니다.
echristopherson 2013 년

0

C에서는 실제로 두 가지 다른 선택이 있습니다. 첫째, 시스템이 메모리를 관리하도록 할 수 있습니다. 또는 혼자서 할 수 있습니다. 일반적으로 가능한 한 오랫동안 전자를 고수하는 것이 좋습니다. 그러나 C의 자동 관리 메모리는 매우 제한적이며 다음과 같은 많은 경우 메모리를 수동으로 관리해야합니다.

ㅏ. 변수가 함수보다 오래 지속되기를 원하고 전역 변수를 원하지 않습니다. 전의:

구조체 쌍 {
   int val;
   구조체 쌍 * next;
}

구조체 쌍 * new_pair (int val) {
   구조체 쌍 * np = malloc (sizeof (struct pair));
   np-> val = val;
   np-> next = NULL;
   return np;
}

비. 동적으로 할당 된 메모리를 원합니다. 가장 일반적인 예는 고정 길이가없는 배열입니다.

int * my_special_array;
my_special_array = malloc (sizeof (int) * number_of_element);
for (i = 0; 나는

씨. 당신은 정말 더러운 일을하고 싶습니다. 예를 들어, 나는 구조체가 많은 종류의 데이터를 표현하기를 원하고 결합을 좋아하지 않습니다 (결합은 너무 지저분 해 보입니다).

구조체 데이터 { int data_type; 긴 data_in_mem; }; struct animal {/ * something * /}; struct person {/ * 다른 것 * /}; struct animal * read_animal (); struct person * read_person (); / * 메인 * / 구조체 데이터 샘플; sampe.data_type = 입력 _ 유형; 스위치 (입력 _ 유형) { 사례 DATA_PERSON : sample.data_in_mem = read_person (); 단절; 사례 DATA_ANIMAL : sample.data_in_mem = read_animal (); 기본: printf ( "오 호! 다시 한 번 경고합니다. OS에 결함이 있다고 판단하겠습니다."); }

긴 값은 모든 것을 담기에 충분합니다. 그것을 해제하는 것을 기억하십시오. 그렇지 않으면 후회할 것입니다. 이것은 C : D에서 재미있게 놀기 위해 제가 가장 좋아하는 트릭 중 하나입니다.

그러나 일반적으로 좋아하는 트릭 (T___T)은 피하고 싶을 것입니다. 너무 자주 사용하면 조만간 OS가 손상됩니다. * alloc과 free를 사용하지 않는 한, 당신은 아직 처녀이고 코드가 여전히 멋져 보인다고 말하는 것이 안전합니다.


"참조, long 값은 모든 것을 담기에 충분합니다."-: / 무슨 말인지, 대부분의 시스템에서 long 값은 int와 정확히 같은 4 바이트입니다. 여기에 포인터를 맞추는 유일한 이유는 long의 크기가 포인터 크기와 동일하기 때문입니다. 하지만 정말로 void *를 사용해야합니다.
Score_Under

-2

확실한. 범위 외부에 존재하는 객체를 생성하는 경우 사용합니다. 여기에 인위적인 예제가 있습니다 (내 구문은 꺼져 있고 C는 녹슬지 만이 예제는 여전히 개념을 설명합니다).

class MyClass
{
   SomeOtherClass *myObject;

   public MyClass()
   {
      //The object is created when the class is constructed
      myObject = (SomeOtherClass*)malloc(sizeof(myObject));
   }

   public ~MyClass()
   {
      //The class is destructed
      //If you don't free the object here, you leak memory
      free(myObject);
   }

   public void SomeMemberFunction()
   {
      //Some use of the object
      myObject->SomeOperation();
   }


};

이 예제에서는 MyClass의 수명 동안 SomeOtherClass 유형의 개체를 사용하고 있습니다. SomeOtherClass 개체는 여러 함수에서 사용되므로 동적으로 메모리를 할당했습니다. SomeOtherClass 개체는 MyClass가 생성 될 때 생성되고 개체 수명 동안 여러 번 사용 된 다음 MyClass가 해제되면 해제됩니다.

분명히 이것이 실제 코드라면 이런 방식으로 myObject를 생성 할 이유 (스택 메모리 소비를 제외하고)가 없을 것입니다. 그러나 이러한 유형의 객체 생성 / 파괴는 많은 객체가 있고 세밀하게 제어하고 싶을 때 유용합니다. 생성되고 파괴 될 때 (예를 들어 애플리케이션이 전체 수명 동안 1GB의 RAM을 차지하지 않도록) 그리고 Windowed 환경에서는 생성하는 객체 (버튼 등)로서 거의 필수입니다. , 특정 함수 (또는 클래스 ') 범위 외부에 있어야합니다.


1
응, C ++이지? 누군가 저에게 전화하는 데 5 개월이 걸린다는 것이 놀랍습니다.
TheSmurf
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.