programing

C 메모리 관리

goodsources 2022. 8. 7. 16:53
반응형

C 메모리 관리

C에서는 기억력을 어떻게 관리하는지 잘 살펴야 한다고 항상 들었어요.그리고 아직 C를 배우기 시작했지만, 지금까지 메모리 관리 관련 활동을 전혀 할 필요가 없었습니다.저는 항상 변수를 풀고 온갖 추한 일들을 해야 한다고 상상했어요.하지만 이것은 사실이 아닌 것 같다.

"메모리 관리"를 수행해야 하는 경우의 예를 코드 예시와 함께 보여 주실 수 있습니까?

변수를 메모리에 저장할 수 있는 두 곳이 있습니다.다음과 같은 변수를 만드는 경우:

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

변수는 "스택"에 생성됩니다.스택 변수는 범위를 벗어나면(즉, 코드가 더 이상 스택 변수에 도달할 수 없는 경우) 자동으로 해제됩니다.'자동' 변수라고 할 수도 있지만, 이제는 유행이 지났다.

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

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

컴파일 시 어레이의 크기를 모르거나 대규모 어레이 또는 구조가 필요한 경우 "플랜 B"가 필요합니다.

플랜 B는 "Heap"이라고 불립니다.일반적으로 운영 체제에서 허용하는 크기만큼 변수를 만들 수 있지만 직접 생성해야 합니다.이전 게시물에서는 다음과 같은 다른 방법이 있지만 한 가지 방법을 보여 주었습니다.

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 및 같은 언어 및 환경이 있습니다.다른 어프로치를 사용하는 C# 등의 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");
...

이 예가 도움이 되길 바랍니다!

C에서 포인터의 역할을 고려하는 가장 간결한 답변 방법이라고 생각합니다.포인터는 가볍지만 강력한 메커니즘으로, 자신의 발을 쏘는 엄청난 능력을 희생하면서 당신에게 엄청난 자유를 줍니다.

C에서 당신이 소유한 기억을 가리키는 포인터는 당신 혼자만의 책임이다.포인터를 포기하지 않는 한 조직적이고 절제된 접근법이 필요하며, 효과적인 C를 쓰는 것이 어렵습니다.

지금까지 투고된 답변은 자동(스택) 및 힙 변수 할당에 집중되어 있습니다.스택 할당을 사용하면 자동으로 관리되고 편리한 메모리가 되지만 상황에 따라서는(대용량 버퍼, 재귀 알고리즘) 스택 오버플로라는 끔찍한 문제가 발생할 수 있습니다.스택에 할당할 수 있는 메모리의 양을 정확하게 파악하는 것은 시스템에 따라 크게 다릅니다.내장된 시나리오에 따라서는 수십 바이트가 제한될 수 있으며 데스크톱 시나리오에 따라서는 안전하게 메가바이트를 사용할 수 있습니다.

히프 할당은 언어 고유의 할당이 적습니다.기본적으로 반환할 준비가 될 때까지 특정 크기의 메모리 블록에 대한 소유권을 부여하는 라이브러리 호출 세트입니다.단순하게 들리지만 프로그래머의 깊은 슬픔과 관련이 있다.문제는 단순합니다(같은 메모리를 2회 비우는 것, 또는 전혀 비우지 않는 것, 충분한 메모리 할당이 없는 것 등).그러나 회피와 디버깅은 어렵습니다.엄격한 규율된 접근은 실제로 절대적으로 필수적이지만 물론 언어가 실제로 그것을 의무화하지는 않는다.

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

메모리를 할당하고 해방하는 방법에 대한 훌륭한 답변이 여기 있습니다.C를 사용하는 것의 보다 어려운 점은 할당한 메모리밖에 없다는 것입니다.이것이 올바르게 행해지지 않으면 이 사이트의 사촌이 되는 버퍼 오버플로우입니다.사용되고 있는 메모리를 덮어쓰고 있을 가능성이 있습니다.다른 어플리케이션에서 매우 예측 불가능한 결과를 얻을 수 있습니다.

예:

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

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

myString = "abcde";

프로그램에 할당한 5바이트에 "abcde"를 할당합니다.이 끝에는 뒤에 늘 문자가 붙습니다.사용에 할당되지 않은 메모리의 일부이며 빈 메모리는 비어 있을 수 있지만 다른 응용 프로그램에 의해 동일하게 사용될 수 있습니다.이것은 메모리 관리의 중요한 부분입니다.실수로 인해 오류가 발생할 수 있습니다.예측할 수 없는(때로는 재현할 수 없는) 결과를 초래합니다.

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

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

 // Do something with the string...

 free(string); // Free the allocated memory

주의할 점은 포인터를 항상 NULL로 초기화하는 입니다.초기화되지 않은 포인터는 포인터 오류를 사일런트하게 진행할 수 있는 의사랜덤의 유효한 메모리주소를 포함할 수 있기 때문입니다.포인터를 NULL로 초기화함으로써 포인터를 초기화하지 않고 이 포인터를 사용하는지 여부를 항상 파악할 수 있습니다.그 이유는 운영 체제가 가상 주소 0x00000000을 일반 보호 예외에 "배선"하여 늘 포인터 사용을 트랩하기 때문입니다.

또한 int[10000]와 같이 대규모 어레이를 정의해야 할 경우 동적 메모리 할당을 사용할 수도 있습니다.그냥 쌓아둘 순 없어 왜냐면, 음...스택 오버플로가 발생합니다.

다른 좋은 예로는 링크드 리스트나 바이너리 트리와 같은 데이터 구조의 구현이 있습니다.여기에 붙일 샘플코드는 없지만 구글에서 쉽게 찾을 수 있습니다.

(지금까지의 답변이 정확하지 않다고 생각되어 글을 씁니다.)

메모리 관리를 언급할 가치가 있는 이유는 복잡한 구조를 작성해야 하는 문제나 솔루션이 있을 때 입니다.(한 번에 스택의 많은 공간에 할당하면 프로그램이 크래쉬하는 것은 버그입니다.)일반적으로 가장 먼저 배워야 할 데이터 구조는 일종의 목록입니다.여기 제 머릿속에 바로 연결된 하나의 것이 있습니다.

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로 나누어진 포인터를 반환하는 것이 보증된다는 사실을 이용하여
  • 당신 자신의 사악한 목적을 위해 여분의 공간을 할당하고
  • 메모리생성 중..

좋은 디버거 가져오기...행운을 빕니다.

물론이죠. 사용하는 범위 밖에 있는 개체를 만드는 경우.다음으로 의도된 예를 제시하겠습니다(구문은 꺼집니다.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 객체는 여러 함수로 사용되기 때문에 메모리를 동적으로 할당했습니다.MyClass 객체는 MyClass 생성 시 생성되며 객체의 수명 동안 여러 번 사용되며 MyClass가 해제되면 해방됩니다.

물론 이것이 실제 코드라면 myObject를 (스택메모리 소비량을 제외하고) 이런 유형의 오브젝트 작성/파괴는 오브젝트가 많고 오브젝트가 생성 및 파괴될 때(애플리케이션이 RAM을 1GB까지 흡수하지 않도록) 세밀하게 제어해야 할 때 유용합니다.예를 들어 전체 수명) 및 Windowed 환경에서는 사용자가 만드는 개체(버튼 등)가 특정 함수(또는 클래스)의 범위를 훨씬 벗어나야 하기 때문에 이는 거의 필수적입니다.

@유로 미셀리

추가되는 부정적인 점 중 하나는 함수가 반환될 때 스택에 대한 포인터가 더 이상 유효하지 않기 때문에 함수에서 스택 변수로 포인터를 반환할 수 없다는 것입니다.이는 일반적인 오류이며 스택 변수만으로 해결할 수 없는 주요 원인입니다.함수에서 포인터를 반환해야 할 경우 malloc 및 메모리 관리를 처리해야 합니다.

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

물론 당신이 옳습니다.K&R 사본을 확인할 필요는 없지만, 그것은 항상 사실이라고 생각합니다.

저는 C의 암묵적인 변환을 별로 좋아하지 않기 때문에 "매직"을 더 잘 보이게 하기 위해 캐스팅을 사용하는 경향이 있습니다.때론 아니기도 하죠. 때때로, 그리고 때로는 조용한 버그는 컴파일러에 의해 잡히기 때문에 가독성을 돕는다.여전히, 어떻게든 난 이것에 대해 강한 의견을 가지고 있지 않다.

만약 당신의 컴파일러C++-style 발언 이해하고 이것은 특히 가능성이 있다.

그래, 거기서 날 잡았잖아나는 C보다 C++에서 더 많은 시간을 보낸다.알아줘서 고마워요.

C에서는, 실제로는 2개의 다른 선택지가 있습니다.첫째, 시스템에서 메모리를 관리하도록 할 수 있습니다.아니면 혼자서 할 수도 있어요.일반적으로는 가능한 한 전자에 집착하는 것이 좋습니다.단, C의 자동 관리 메모리는 매우 한정되어 있기 때문에 많은 경우 다음과 같이 수동으로 메모리를 관리해야 합니다.

a. 변수가 함수보다 오래 지속되도록 하고 전역 변수를 사용하지 않도록 해야 합니다.예:

구조쌍{내부 값구조 쌍 *다음;}
structure pair*new_interval(int val){structure pair*np = malloc(size of(size of(spair)));np->val = val;np->next = NULL;반환 np;}

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

int *my_special_array;my_special_array = malloc(size of(int) * number_of_array);
(i=0;i)의 경우

c. 당신은 정말 더러운 것을 하고 싶다.예를 들어, 저는 많은 종류의 데이터를 나타내는 구조를 원합니다만, 유니언은 마음에 들지 않습니다(유니온은 매우 지저분해 보입니다).

구조 데이터{int data_type;긴 data_in_mem; }; 구조동물 {/*something*/};구조자{/*다른 것*/}; structure animal*read_animal();struct person*read_person(); /*메인*/구조 데이터 샘플sampe.data_type = input_type;스위치(input_type){케이스 DATA_PERN:sample.data_in_mem = read_person();부서지다케이스 데이터_동물:sample.data_in_mem = read_animal();디폴트:printf("오호!경고합니다.다시 한 번 말씀드리면 OS에 장애가 발생하게 됩니다.";}

가치가 길면 뭐든 다 가질 수 있어. 그걸 풀어주는 것만 기억해. 그렇지 않으면 후회하게 될 거야.이것은 제가 씨디에서 재미있게 놀기 위해 가장 좋아하는 요령 중 하나입니다.

단, 일반적으로 좋아하는 트릭(T__T)은 피하는 것이 좋습니다.OS를 너무 자주 사용하면 조만간 OS가 망가질 것입니다.*alloc and free를 사용하지 않는 한, 당신은 아직 처녀이며, 코드는 여전히 멋있다고 해도 무방합니다.

언급URL : https://stackoverflow.com/questions/24891/c-memory-management

반응형