저는 항상 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"를 할당하고 후행 널 문자는이 끝에 놓일 것입니다.이 부분은 사용을 위해 할당되지 않은 메모리의 일부입니다. 무료이지만 다른 애플리케이션에서 동일하게 사용할 수 있음-이는 메모리 관리의 중요한 부분으로, 실수로 인해 예측할 수없는 (때로는 반복 할 수없는) 결과가 발생합니다.
strcpy()
대신 사용하도록 답변을 편집했습니다 =
. 나는 그것이 Chris BC의 의도라고 생각합니다.
(지금까지의 답변이 정답이 아닌 것 같아서 쓰고 있습니다.)
언급 할 가치가있는 메모리 관리를해야하는 이유는 복잡한 구조를 만들어야하는 문제 / 솔루션이있을 때입니다. (한 번에 스택의 많은 공간에 할당하면 프로그램이 충돌하면 버그입니다.) 일반적으로 배워야 할 첫 번째 데이터 구조는 일종의 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;
}
당연히 다른 몇 가지 기능이 필요하지만 기본적으로 이것이 메모리 관리가 필요한 것입니다. "수동"메모리 관리로 가능한 많은 트릭이 있음을 지적해야합니다. 예 :
좋은 디버거를 구 하세요 ... 행운을 빕니다!
@ Ted Percival :
... malloc ()의 반환 값을 캐스팅 할 필요가 없습니다.
물론 맞습니다. 확인해야 할 K & R 사본이 없지만 항상 사실이라고 생각합니다 .
저는 C에서 암시 적 변환을 많이 좋아하지 않으므로 "마법"을 더 잘 보이게 만들기 위해 캐스트를 사용하는 경향이 있습니다. 가독성에 도움이되는 경우도 있고 그렇지 않은 경우도 있으며, 컴파일러가 자동 버그를 포착하는 경우도 있습니다. 그래도 나는 이것에 대해 어떤 식 으로든 강한 의견을 가지고 있지 않습니다.
컴파일러가 C ++ 스타일 주석을 이해하는 경우 특히 그렇습니다.
그래 .. 날 잡았어. 저는 C보다 C ++에서 더 많은 시간을 보냅니다. 알아 주셔서 감사합니다.
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를 사용하지 않는 한, 당신은 아직 처녀이고 코드가 여전히 멋져 보인다고 말하는 것이 안전합니다.
확실한. 범위 외부에 존재하는 객체를 생성하는 경우 사용합니다. 여기에 인위적인 예제가 있습니다 (내 구문은 꺼져 있고 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 환경에서는 생성하는 객체 (버튼 등)로서 거의 필수입니다. , 특정 함수 (또는 클래스 ') 범위 외부에 있어야합니다.