2009-03-28 9 views
5

저는 현재 C 기반 응용 프로그램에서 비 반 패턴 방식으로 메모리를 비우는 작업에 조금 노력하고 있습니다. 나는 메모리 관리 아마추어 다.C에서 메모리를 확보하기위한 패턴?

내 주요 문제는 여러 가지 다른 범위의 메모리 구조를 선언하는 것이며 이러한 구조는 다른 함수를 참조하여 전달됩니다. 이러한 함수 중 일부는 오류를 발생시키고 exit() 할 수 있습니다.

exit()를 한 범위에서 사용하면 내 구조를 해제 할 수 있지만 모든 데이터 구조가 해당 범위에 있지는 않습니다.

나는 그것을 psuedo 예외 처리기에서 모두 감쌀 필요가 있고 처리기를 해방해야한다고 느낀다. 그러나 그것은 내가 필요로 할 수도 있고하지 않을 수도있는 모든 것에 대해 알아야하기 때문에 추한 것처럼 보인다. ..

답변

3

래퍼를 malloc으로 생각하고 규칙적인 방법으로 사용하십시오. 링크 된 목록에서 할당 한 메모리를 추적하고 래퍼를 사용하여 메모리를 열어서 해제하십시오. 도 추가 매개 변수와 연결된 목록 구조의 구성원으로 메모리의 이름을 지정할 수 있습니다. 할당 된 메모리가 매우 범위에 의존하는 응용 프로그램에서는 메모리 누수가 발생하며 메모리를 덤프하고 분석하는 좋은 방법이 될 수 있습니다.

업데이트 : 응용 프로그램에서 스레딩은 매우 복잡합니다. 스레딩 문제에 대한 다른 답변을 참조하십시오.

+0

exit()에 대한 일반 호출시 atexit() 함수를 사용하고 링크 된 목록에 할당 된 모든 메모리를 해제하는 함수 (이 경우 전역 변수 여야 함)를 작성할 수도 있습니다. 일반 exit()를 사용하지 않는 것을 기억해야합니다. –

+0

하지만 왜? 운영 체제가 이미 할당 된 메모리 목록을 유지 관리하고 있습니다. 이 기능을 복제하면 약간의 자아 인플레이션 이외의 이점없이 종료 프로세스가 느려질 것입니다. 응용 프로그램을 MSDOS로 이식 할 계획이 아니라면 운영 체제에서 작업을 수행하십시오. – Eclipse

+0

이유는 결국 규율과 funcitonality가 메모리 누수를 추적하는 데 도움이 될 것입니다. 코드를 올바르게 코딩하면 DEFINE 또는 MACRO 정의를 통해 끌 수 있습니다. – ojblass

3

exit()를 호출 할 때 메모리를 확보 할 필요가 없습니다. 프로세스가 종료되면 운영 체제는 관련된 모든 메모리를 해제합니다.

+0

항상 그렇지는 않습니다. 탈출하기 전에 malloc 구조를 해제하는 것이 위생적인 ​​실습으로 간주됩니다. 실제 메모리 누수가 필요할 때도 도움이됩니다. –

+0

좋은 운영 체제에만 해당됩니다. 습관에 빠지기 만하면, 다 끝냈을 때 메모리를 비우는 것이 좋습니다. –

+0

맞아요. 나는 그가 중대한 실패를 논의하고 있다고 추정하고 있습니다. 즉, 프로세스가 빠르게 죽어 가고 죽어 가고있다. – Michael

1

범위/기능간에 공유되는 malloc 된 메모리에 대한 간단한 메모리 관리자를 만들 수 있습니다.

malloc 할 때 등록하고 해제 할 때 등록을 해제하십시오. exit를 호출하기 전에 등록 된 모든 메모리를 해제하는 함수가 있어야합니다.

약간의 오버 헤드가 추가되지만 메모리를 추적하는 데 도움이됩니다. 또한 성가신 메모리 누출을 방지하는 데 도움이 될 수 있습니다.

3

나는이 질문에 적절하게 대답하기를 원한다. 우리는 전체 프로그램 (또는 시스템 또는 어떤 경우이든)의 아키텍처에 대해 알아야 할 필요가있다.

답변은 다음과 같습니다. 당신이 사용할 수있는 많은 전략이 있습니다.

현대 데스크탑 또는 서버 운영 체제에서 다른 사람이 지적한 바대로 exit()을 사용할 수 있으며 프로그램에서 할당 한 메모리는 걱정하지 않아도됩니다.

이 전략은 예를 들어 exit()이 모든 것을 정리하지 못하는 임베디드 운영 체제에서 개발하는 경우에 변경됩니다. 일반적으로 내가 보는 것은 오류로 인해 개별 기능이 반환 될 때 자신이 할당 한 모든 것을 정리해야한다는 것입니다. 예를 들어 10 개의 함수를 호출 한 후에는 exit() 호출이 표시되지 않습니다. 각 함수는 리턴 할 때 차례대로 오류를 표시하고 각 함수는 자체적으로 정리합니다. 원래 main() 함수 (사용자가 직접 호출하면 main()이라고 할 수 있음)는 오류를 감지하고 할당 된 메모리를 정리 한 다음 적절한 조치를 취합니다.

범위 안에 범위가있는 경우 로켓 과학이 아닙니다.다중 스레드 실행 및 공유 데이터 구조가있는 경우 어려워집니다. 그런 다음 가비지 컬렉터가 필요하거나 구조의 마지막 사용자가 참조를 계산하고 메모리를 확보 할 수있는 방법이 필요할 수 있습니다. 예를 들어, BSD 네트워킹 스택의 소스를 보면, 확장 된 기간 동안 "활성"상태로 유지되어야하고 다른 시스템과 공유해야하는 일부 구조에서는 refcnt (참조 카운트) 값을 사용한다는 것을 알 수 있습니다 사용자. (이것은 기본적으로 가비지 컬렉터가 수행하는 것입니다.)

0

매우 간단하게 참조 계산 구현이 없으므로 객체를 생성하고 전달할 때 참조 카운트 된 숫자를 증가 및 감소시킵니다 둘 이상의 스레드가있는 경우 원자 단위).

그런 식으로 객체가 더 이상 사용되지 않을 때 (0 참조) 안전하게 삭제하거나 참조 횟수 감소 호출에서 자동으로 삭제할 수 있습니다.

1

마이클의 조언은 당연한 것입니다. 종료하는 경우 시스템에서 어쨌든 그것을 회수하기 때문에 메모리를 확보하는 것에 대해 걱정할 필요가 없습니다.

하나의 예외는 공유 메모리 세그먼트 (적어도 System V 공유 메모리 아래)입니다. 이러한 세그먼트는 세그먼트를 생성하는 프로그램보다 오래 지속될 수 있습니다.

지금까지 언급되지 않은 옵션 중 하나는 표준 malloc() 위에 구축 된 경기장 기반 메모리 할당 체계를 사용하는 것입니다. 전체 응용 프로그램이 단일 영역을 사용하는 경우 클린업 코드가 해당 영역을 해제 할 수 있으며 모든 응용 프로그램이 한 번에 해제됩니다. (APR - Apache Portable Runtime - 내가 생각하는 수영장 기능을 제공하며 David Hanson의 "C Interfaces and Implementations"는 경기장 기반의 메모리 할당 시스템을 제공하며 원하는 경우 사용할 수 있도록 작성했습니다.) 이것을 "가난한 사람의 쓰레기 수거"라고 생각할 수 있습니다.

일반적인 메모리 규율으로 메모리를 동적으로 할당 할 때마다 어떤 코드에서 메모리를 해제하고 언제 릴리스 할 수 있는지 이해해야합니다. 몇 가지 표준 패턴이 있습니다. 가장 간단한 방법은 "이 함수에 할당되고이 함수가 반환하기 전에 해제"입니다. 이렇게하면 메모리를 크게 통제 할 수 있습니다 (메모리 할당이 포함 된 루프에서 너무 많은 반복을 실행하지 않는 경우). 그리고 현재 함수와 호출하는 함수에서 사용할 수 있도록 범위를 지정합니다. 분명히, 호출하는 함수가 데이터 포인터를 다람쥐처럼 (캐시하지 않고) 나중에 메모리를 해제하고 다시 사용한 후에 다시 사용하려고 시도한다는 것은 합리적으로 확신 할 수 있어야합니다.

다음 표준 패턴은 fopen()fclose()으로 예시된다. 호출 코드에서 사용할 수있는 메모리에 대한 포인터를 할당하고 프로그램이 끝날 때 해제 할 수있는 함수가 있습니다. 그러나 이것은 종종 첫 번째 경우와 매우 유사합니다. fopen()이라는 함수에서도 fclose()을 호출하는 것이 좋습니다.

나머지 '패턴'의 대부분은 다소 ad hoc입니다.

1

사람들은 실수로 코드를 종료 (또는 중단)하는 경우 메모리를 확보 할 필요가 없다는 것을 이미 지적했습니다. 그러나 경우에 따라, 내가 개발 한 패턴이 있으며, 오류가 발생했을 때 자원을 만들고 찢어 버리기 위해 많이 사용합니다.참고 : 실제 코드를 작성하지 않고 여기에 패턴을 보여주고 있습니다.

int foo_create(foo_t *foo_out) { 
    int res; 
    foo_t foo; 
    bar_t bar; 
    baz_t baz; 
    res = bar_create(&bar); 
    if (res != 0) 
     goto fail_bar; 
    res = baz_create(&baz); 
    if (res != 0) 
     goto fail_baz; 
    foo = malloc(sizeof(foo_s)); 
    if (foo == NULL) 
     goto fail_alloc; 
    foo->bar = bar; 
    foo->baz = baz; 
    etc. etc. you get the idea 
    *foo_out = foo; 
    return 0; /* meaning OK */ 

    /* tear down stuff */ 
fail_alloc: 
    baz_destroy(baz); 
fail_baz: 
    bar_destroy(bar); 
fail_bar: 
    return res; /* propagate error code */ 
} 

"나는 고토를 사용하기 때문에 나쁘다"라는 말을 몇 가지 가지게 될 것입니다. 그러나 일관되게 적용하면 코드를보다 명확하고 단순하며 유지 보수하기가 쉬워 지도록 goto를 체계적이고 체계적으로 사용합니다. 코드가 없으면 코드를 통해 문서화 된 간단한 경로를 얻을 수 없습니다.

실제 사용중인 상용 코드에서 이것을 보려면, 말하자면 arena.c from the MPS (동시에 메모리 관리 시스템)을보십시오.

이것은 가난한 사람의 시도 ... 마무리 처리기의 일종이며 소멸자와 비슷한 것을 제공합니다.

저는 이제 greybeard처럼 들릴 것입니다. 그러나 다른 사람들의 C 코드에서 수년 동안 일하면서 명확한 오류 경로가없는 것은 종종 매우 심각한 문제입니다. 특히 네트워크 코드 및 기타 신뢰할 수없는 상황에서 그렇습니다. 그 (것)들을 소개해서 때때로 저에게 상담 수입의 조금을 만들었다.

귀하의 질문에 대해 말할만한 다른 것들이 많이 있습니다. 유용 할 때를 대비하여이 패턴으로 남겨 두겠습니다.

+0

goto를 사용하기 때문에 좋지 않습니다. 죄송합니다. 저항 할 수 없습니다 : D –

+0

'baz'와'bar'를'NULL'으로 초기화하고 destroy 함수가'NULL' 매개 변수를 우아하게 처리하도록 ('free'와 같이)하면, 단 하나의'fail :'레이블 만 사용할 수있어 좀 더 혼잡하지 않게됩니다. –

+0

제안 해 주셔서 감사합니다! 꽤 많은 것을 위해 NULL을 사용하는 것을 피하는 내 높은 신뢰성 프로젝트의 코딩 규칙입니다. 주위에 NULL이 있으면 모든 종류의 오류가 발생합니다. 우리는 특히 당신이 취해야 할 특별한 행동을 나타 내기 위해 NULL을 사용하는 것을 피합니다. 하지만 NULL에 대해 쓸 수있는 전체 에세이 에세이가 있습니다. P – rptb1