IT박스

C 메모리 관리

itboxs 2020. 9. 13. 10:15
반응형

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])을 정의해야 할 때 동적 메모리 할당을 사용할 수도 있습니다. 스택에 넣을 수는 없습니다. 흠 ... 스택 오버플로가 발생하기 때문입니다.

Another good example would be an implementation of a data structure, say linked list or binary tree. I don't have a sample code to paste here but you can google it easily.


(I'm writing because I feel the answers so far aren't quite on the mark.)

The reason you have to memory management worth mentioning is when you have a problem / solution that requires you to create complex structures. (If your programs crash if you allocate to much space on the stack at once, that's a bug.) Typically, the first data structure you'll need to learn is some kind of list. Here's a single linked one, off the top of my head:

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

Naturally, you'd like a few other functions, but basically, this is what you need memory management for. I should point out that there are a number tricks that are possible with "manual" memory management, e.g.,

  • Using the fact that malloc is guaranteed (by the language standard) to return a pointer divisible by 4,
  • allocating extra space for some sinister purpose of your own,
  • creating memory pools..

Get a good debugger... Good luck!


@Euro Micelli

One negative to add is that pointers to the stack are no longer valid when the function returns, so you cannot return a pointer to a stack variable from a function. This is a common error and a major reason why you can't get by with just stack variables. If your function needs to return a pointer, then you have to malloc and deal with memory management.


@Ted Percival:
...you don't need to cast malloc()'s return value.

You are correct, of course. I believe that has always been true, although I don't have a copy of K&R to check.

I don't like a lot of the implicit conversions in C, so I tend to use casts to make "magic" more visible. Sometimes it helps readability, sometimes it doesn't, and sometimes it causes a silent bug to be caught by the compiler. Still, I don't have a strong opinion about this, one way or another.

This is especially likely if your compiler understands C++-style comments.

Yeah... you caught me there. I spend a lot more time in C++ than C. Thanks for noticing that.


In C, you actually have two different choices. One, you can let the system manage the memory for you. Alternatively, you can do that by yourself. Generally, you would want to stick to the former as long as possible. However, auto-managed memory in C is extremely limited and you will need to manually manage the memory in many cases, such as:

a. You want the variable to outlive the functions, and you don't want to have global variable. ex:

struct pair{
   int val;
   struct pair *next;
}

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

b. you want to have dynamically allocated memory. Most common example is array without fixed length:

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

c. You want to do something REALLY dirty. For example, I would want a struct to represent many kind of data and I don't like union (union looks soooo messy):

struct data{ int data_type; long data_in_mem; }; struct animal{/*something*/}; struct person{/*some other thing*/}; struct animal* read_animal(); struct person* read_person(); /*In main*/ struct data sample; sampe.data_type = input_type; switch(input_type){ case DATA_PERSON: sample.data_in_mem = read_person(); break; case DATA_ANIMAL: sample.data_in_mem = read_animal(); default: printf("Oh hoh! I warn you, that again and I will seg fault your OS"); }

See, a long value is enough to hold ANYTHING. Just remember to free it, or you WILL regret. This is among my favorite tricks to have fun in C :D.

However, generally, you would want to stay away from your favorite tricks (T___T). You WILL break your OS, sooner or later, if you use them too often. As long as you don't use *alloc and free, it is safe to say that you are still virgin, and that the code still looks nice.


Sure. If you create an object that exists outside of the scope you use it in. Here is a contrived example (bear in mind my syntax will be off; my C is rusty, but this example will still illustrate the concept):

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


};

In this example, I'm using an object of type SomeOtherClass during the lifetime of MyClass. The SomeOtherClass object is used in several functions, so I've dynamically allocated the memory: the SomeOtherClass object is created when MyClass is created, used several times over the life of the object, and then freed once MyClass is freed.

Obviously if this were real code, there would be no reason (aside from possibly stack memory consumption) to create myObject in this way, but this type of object creation/destruction becomes useful when you have a lot of objects, and want to finely control when they are created and destroyed (so that your application doesn't suck up 1GB of RAM for its entire lifetime, for example), and in a Windowed environment, this is pretty much mandatory, as objects that you create (buttons, say), need to exist well outside of any particular function's (or even class') scope.

참고URL : https://stackoverflow.com/questions/24891/c-memory-management

반응형