[c] C의`free`가 해제 할 바이트 수를 사용하지 않는 이유는 무엇입니까?

그냥 확실하게합니다 : 알아요 않습니다 mallocfree일반적으로 OS에서 메모리 청크를 할당하고 응용 프로그램에 메모리의 작은 제비를 소포 자체 관리를 수행하고 할당 된 바이트의 수를 추적 C 라이브러리에서 구현된다 . 이 질문은 어떻게 free does free to free 가 아닙니다 .

오히려 free애초에 이런 식으로 만들어진 이유를 알고 싶습니다 . 저수준 언어이기 때문에 C 프로그래머에게 할당 된 메모리뿐만 아니라 얼마나 많은지를 추적하도록 요청하는 것이 완벽하게 합리적이라고 생각합니다 (사실 저는 일반적으로 바이트 수를 추적하게됩니다. 어쨌든 malloced). 또한 명시 적으로 바이트 free수를 제공하여 일부 성능 최적화를 허용 할 수 있습니다. 예를 들어 서로 다른 할당 크기에 대해 별도의 풀이있는 할당자는 입력 인수를보고 해제 할 풀을 결정할 수 있습니다. 전체적으로 공간 오버 헤드가 적습니다.

그래서, 짧은에, 왜했다 malloc그리고 free그들이 내부적으로 할당 된 바이트 수를 추적하는 데 필요한하고 있다는 등의 생성? 그것은 단지 역사적인 사고입니까?

약간의 수정 : 몇몇 사람들이 “할당 한 금액과 다른 금액을 확보하면 어떨까요”와 같은 포인트를 제공했습니다. 내 상상 한 API는 할당 된 바이트 수를 정확히 해제하기 위해 간단하게 필요할 수 있습니다. 어느 정도 해방하는 것은 단순히 UB 또는 구현 정의 일 수 있습니다. 그래도 다른 가능성에 대한 논의를 중단하고 싶지는 않습니다.



답변

하나의 인수 free(void *)(Unix V7에 도입 됨)는 mfree(void *, size_t)여기서 언급하지 않은 이전의 두 인수에 비해 또 다른 주요 이점이 있습니다. 하나의 인수 는 힙 메모리와 함께 작동하는 다른free 모든 API를 극적으로 단순화 합니다. 예를 들어, 메모리 블록의 크기가 필요한 경우 어떻게 든 하나 (포인터) 대신 두 개의 값 (포인터 + 크기)을 반환해야하며 C는 다중 값 반환을 단일 값 반환보다 훨씬 더 번거롭게 만듭니다. 대신 우리는 또는 다른 것을 써야 할 것 입니다 . (요즘에는 두 번째 옵션이 매우 매력적으로 보입니다. NUL로 끝나는 문자열이 “컴퓨팅 역사상 가장 치명적인 디자인 버그” 라는 것을 알고 있기 때문 입니다.freestrdupchar *strdup(char *)char *strdup(char *, size_t *)struct CharPWithSize { char *val; size_t size}; CharPWithSize strdup(char *), 그러나 그것은 뒤늦은 말입니다. 70 년대에 C의 문자열을 단순하게 처리하는 능력 char *은 실제로 Pascal 및 Algol과 같은 경쟁사에 비해 확실한 이점으로 간주되었습니다 .) 또한 strdup이 문제로 인해 고통을받는 것이 아니라 모든 시스템 또는 사용자 정의에 영향을 미칩니다. 힙 메모리를 할당하는 함수.

초기 유닉스 디자이너들은 매우 영리한 사람들이었고 기본적으로 free더 나은 이유는 여러 가지 가 있습니다 mfree. 질문에 대한 대답은 그들이 이것을 알아 채고 그에 따라 시스템을 설계했기 때문이라고 생각합니다. 그들이 그 결정을 내리는 순간 그들의 머릿속에서 무슨 일이 일어나고 있었는지에 대한 직접적인 기록을 찾을 수 있을지 의심 스럽습니다. 그러나 우리는 상상할 수 있습니다.

두 인수를 사용하여 V6 Unix에서 실행하기 위해 C로 애플리케이션을 작성한다고 가정합니다 mfree. 지금까지 잘 관리했지만 프로그램 이 더 야심 차게 되고 힙 할당 변수를 더 많이 사용해야하므로 이러한 포인터 크기를 추적하는 것이 점점 더 번거로워지고 있습니다 . 그러나 당신은 훌륭한 아이디어를 가지고 있습니다. size_t항상 이러한들을 복사하는 대신 할당 된 메모리 내부에 크기를 직접 숨기는 몇 가지 유틸리티 함수를 작성할 수 있습니다.

void *my_alloc(size_t size) {
    void *block = malloc(sizeof(size) + size);
    *(size_t *)block = size;
    return (void *) ((size_t *)block + 1);
}
void my_free(void *block) {
    block = (size_t *)block - 1;
    mfree(block, *(size_t *)block);
}

그리고 이러한 새로운 함수를 사용하여 더 많은 코드를 작성할수록 더 멋지게 보입니다. 코드를 더 쉽게 작성할 수있을 뿐만 아니라 코드를 더 빠르게 만듭니다. 두 가지가 자주 함께 사용되지 않습니다! 이들을 size_t사방에 전달하기 전에는 복사를위한 CPU 오버 헤드를 추가했고 레지스터를 더 자주 (특히 추가 함수 인수의 경우) 유출해야하고 메모리 낭비 (중첩 된 함수 호출이 종종 발생하기 때문에) size_t다른 스택 프레임에 저장된 여러 사본 ). 새 시스템에서는 여전히 메모리를 사용하여size_t,하지만 한 번만 해당되며 어디에도 복사되지 않습니다. 이것은 작은 효율성처럼 보일 수 있지만 256KiB의 RAM이있는 하이 엔드 머신에 대해 이야기하고 있음을 명심하십시오.

이것은 당신을 행복하게합니다! 그래서 당신은 다음 유닉스 릴리스에서 작업하는 수염 난 남자들과 멋진 트릭을 공유합니다. 그러나 그것은 그들을 행복하게 만들지 않고 슬프게 만듭니다. 보시다시피, 그들은과 같은 새로운 유틸리티 함수를 추가하는 과정에 strdup있었고 멋진 트릭을 사용하는 사람들이 새로운 함수를 사용할 수 없다는 것을 알고 있습니다. 새로운 함수는 모두 성가신 포인터 + 크기를 사용하기 때문입니다. API. 그리고 그것은 당신도 슬프게 만듭니다 strdup(char *). 시스템 버전을 사용할 수있는 대신 작성하는 모든 프로그램에서 좋은 기능을 직접 다시 작성해야한다는 것을 깨닫기 때문 입니다.

하지만 기다려! 이것은 1977 년이고, 이전 버전과의 호환성은 앞으로 5 년 동안 발명되지 않을 것입니다! 게다가, 아무도 심각한 실제로 사용하지 않고 자사의 오프 색상 이름이 알려지지 않은 “유닉스”일을. K & R의 초판은 현재 출판사로 향하고 있지만 문제가되지 않습니다. 첫 페이지에 “C는 문자열과 같은 복합 객체를 직접 처리하는 작업을 제공하지 않습니다. 힙이 없습니다. … “. 역사의이 시점에서, string.h그리고 malloc벤더 확장은 (!). 따라서 Bearded Man # 1을 제안합니다. 원하는대로 변경할 수 있습니다. 왜 당신의 까다로운 할당자를 공식 할당 자로 선언하지 않습니까?

며칠 후 Bearded Man # 2는 새 API를보고 이전보다 낫다고 말합니다.하지만 여전히 크기를 저장하는 할당 당 전체 단어를 소비하고 있습니다. 그는 이것을 신성 모독의 다음으로 본다. 다른 사람들은 그가 미친 것처럼 그를 쳐다 봅니다. 그날 밤 그는 늦게 머물면서 크기를 전혀 저장하지 않는 새로운 할당자를 발명하지만 대신 포인터 값에 대해 흑 마법 비트 시프트를 수행하여 즉시 유추하고 새 API를 제자리에 유지하면서 교체합니다. 새로운 API는 아무도 스위치를 알아 차리지 못하지만 다음날 아침 컴파일러가 RAM을 10 % 적게 사용한다는 것을 알아 차립니다.

그리고 이제 모두가 행복합니다. 작성하기 쉽고 빠른 코드를 얻고, Bearded Man # 1은 strdup사람들이 실제로 사용할 수 있는 멋진 간단한 코드를 작성 하고 Bearded Man # 2는 자신이 약간의 이익을 얻었음을 확신합니다. -quines엉망으로 만드는 것으로 돌아갑니다 . 그것을 발송하십시오!

아니면 적어도 그렇게되었을 수도 있습니다.


답변

freeC에서 해제 할 바이트 수를 사용하지 않는 이유는 무엇 입니까?”

이 없기 때문에 그것을 위해 필요, 그것은 확실히 감지하지 것이다 어쨌든.

무언가를 할당 할 때 할당 할 바이트 수를 시스템에 알려야합니다 (분명한 이유 때문에).

그러나 이미 개체를 할당 한 경우 다시 가져 오는 메모리 영역의 크기가 결정됩니다. 암묵적입니다. 그것은의 메모리를 하나 개의 연속 블록. 당신은 그것의 일부를 할당 해제 할 수 없습니다 ( realloc()그것이 어쨌든하고있는 일이 아닙니다), 당신은 단지 전체를 할당 해제 할 수 있습니다 . 당신은 “X 바이트를 할당 해제”할 수 없습니다-당신은 당신이 얻은 메모리 블록을 해제하거나 해제 malloc()하지 않습니다.

이제 해제하려면 메모리 관리자 시스템에 “여기에 포인터가 있고, free()가리키는 블록 이 있습니다.”라고 말할 수 있습니다 . -메모리 관리자는 암시 적으로 크기를 알고 있거나 크기가 필요하지 않을 수도 있기 때문에이를 수행하는 방법을 알게됩니다 .

예를 들어, 대부분의 일반적인 구현은 malloc()사용 가능하고 할당 된 메모리 블록에 대한 포인터의 링크 된 목록을 유지합니다. 에 포인터를 전달 free()하면 “할당 된”목록에서 해당 포인터를 검색하고 해당 노드의 연결을 해제 한 다음 “사용 가능한”목록에 첨부합니다. 영역 크기도 필요하지 않았습니다. 잠재적으로 해당 블록을 재사용하려고 할 때만 해당 정보가 필요합니다.


답변

C는 C ++처럼 “추상적”이 아닐 수 있지만 여전히 어셈블리에 대한 추상화를위한 것입니다. 이를 위해 가장 낮은 수준의 세부 정보가 방정식에서 제외됩니다. 이렇게하면 대부분의 경우 모든 C 프로그램을 이식 할 수 없게 만드는 정렬 및 패딩에 집중할 필요가 없습니다.

요컨대, 이것이 추상화 작성의 전체 요점입니다 .


답변

사실, 고대의 유닉스 커널 메모리 할당에, mfree()했다 size인수를. malloc()사용 mfree()가능한 블록 주소 및 크기에 대한 정보를 포함하는 두 개의 어레이 (코어 메모리 용 하나, 스왑 용 하나)를 유지했습니다.

Unix V6까지 사용자 공간 할당자가 없었습니다 (프로그램은를 사용합니다 sbrk()). Unix V6에서 iolib는 할당 자 alloc(size)free()크기 인수를 사용하지 않는 호출을 포함했습니다 . 각 메모리 블록 앞에는 크기와 다음 블록에 대한 포인터가 있습니다. 포인터는 사용 가능한 블록에서만 사용되었으며 사용 가능한 목록을 탐색 할 때 사용 중 블록 메모리로 재사용되었습니다.

유닉스 32V에서와 유닉스 V7에서, 이것은 새로운 투입했습니다 malloc()free()구현, free()테이크하지 않았다 size인수를. 구현은 순환 목록이었고, 각 청크 앞에는 다음 청크에 대한 포인터와 “사용 중”(할당 된) 비트가 포함 된 단어가옵니다. 따라서 malloc()/free()명시적인 크기도 추적하지 않았습니다.


답변

freeC에서 해제 할 바이트 수를 사용하지 않는 이유는 무엇 입니까?

그럴 필요가 없기 때문입니다. 이 정보는 malloc / free가 수행 한 내부 관리에서 이미 사용할 수 있습니다.

다음은 두 가지 고려 사항입니다 (이 결정에 기여했을 수도 있고 아닐 수도 있음).

  • 함수가 필요하지 않은 매개 변수를받을 것으로 예상하는 이유는 무엇입니까?

    (이는 동적 메모리에 의존하는 거의 모든 클라이언트 코드를 복잡하게 만들고 애플리케이션에 완전히 불필요한 중복성을 추가합니다). 포인터 할당을 추적하는 것은 이미 어려운 문제입니다. 관련 크기와 함께 메모리 할당을 추적하면 클라이언트 코드가 불필요하게 복잡해집니다.

  • 이 경우 변경된 free기능은 무엇을 합니까?

    void * p = malloc(20);
    free(p, 25); // (1) wrong size provided by client code
    free(NULL, 10); // (2) generic argument mismatch
    

    해제 되지 않겠습니까 (메모리 누수 원인?)? 두 번째 매개 변수를 무시 하시겠습니까? exit를 호출하여 애플리케이션을 중지 하시겠습니까? 이를 구현하면 응용 프로그램에 추가 실패 지점이 추가 될 것입니다. 필요하지 않은 기능에 대해 (필요한 경우 “응용 프로그램 수준에서 솔루션 구현”아래의 마지막 요점을 참조하십시오).

그보다는 애초에 왜 무료가 이런 식으로 만들어 졌는지 알고 싶습니다.

이것이 “적절한”방법이기 때문입니다. API는 작업을 수행하는 데 필요한 인수를 필요로하며 그 이상은 안됩니다 .

또한 해제 할 바이트 수를 명시 적으로 제공하면 일부 성능 최적화를 허용 할 수 있습니다. 예를 들어 서로 다른 할당 크기에 대해 별도의 풀을 가진 할당자는 입력 인수를보고 해제 할 풀을 결정할 수 있습니다. 전체적으로 공간 오버 헤드가 적습니다.

이를 구현하는 적절한 방법은 다음과 같습니다.

  • (시스템 수준에서) malloc 구현 내에서-라이브러리 구현자가 수신 된 크기를 기반으로 내부적으로 다양한 전략을 사용하기 위해 malloc을 작성하는 것을 막을 수는 없습니다.

  • (애플리케이션 수준에서) malloc을 래핑하고 자체 API 내에서 무료로 사용하고 대신이를 사용합니다 (애플리케이션에서 필요할 수있는 모든 곳).


답변

떠오르는 다섯 가지 이유 :

  1. 편리합니다. 프로그래머의 전체 부하를 제거하고 오류를 추적하기 매우 어려운 클래스를 방지합니다.

  2. 블록의 일부를 해제 할 수있는 가능성을 열어줍니다. 그러나 메모리 관리자는 일반적으로 추적 정보를 원하기 때문에 이것이 무엇을 의미하는지 명확하지 않습니다.

  3. Orbit의 Lightness Races는 패딩과 정렬에 관한 것입니다. 메모리 관리의 특성상 할당 된 실제 크기가 요청한 크기와 상당히 다를 수 있습니다. 즉, 할당 된 실제 크기를 반환하려면 free크기와 위치 malloc를 변경해야합니다.

  4. 어쨌든 크기를 전달하는 것이 실질적인 이점이 있는지는 분명하지 않습니다. 일반적인 메모리 관리자는 크기를 포함하여 각 메모리 청크에 대해 4-16 바이트의 헤더를 가지고 있습니다. 이 청크 헤더는 할당 및 할당되지 않은 메모리에 공통적 일 수 있으며 인접 청크가 해제되면 함께 축소 될 수 있습니다. 호출자가 사용 가능한 메모리를 저장하도록 만드는 경우 할당 된 메모리에 별도의 크기 필드가 없어서 청크 당 4 바이트를 확보 할 수 있지만 호출자가 어딘가에 저장해야하기 때문에 해당 크기 필드는 얻을 수 없습니다. 그러나 이제 정보는 어쨌든 운영 효율성이 떨어질 가능성이있는 헤더 청크에 예측 가능하게 위치하지 않고 메모리에 흩어져 있습니다.

  5. 이 경우에도 이었다 더 효율적이 근본적으로 가능성이 프로그램은 시간이 해제 많은 양의 메모리를 소비하는 것 어쨌든 그래서 장점은 작은 것입니다.

덧붙여서, 다른 크기 항목에 대한 별도의 할당 자에 대한 아이디어는이 정보없이 쉽게 구현됩니다 (할당이 발생한 위치를 확인하기 위해 주소를 사용할 수 있음). 이것은 일상적으로 C ++로 수행됩니다.

나중에 추가

우스꽝스럽게도 또 다른 대답은 std :: allocatorfree이런 식으로 작동 할 수있는 증거로 제시 했지만 실제로는 왜 free이런 식으로 작동하지 않는지에 대한 좋은 예가 됩니다. malloc/ freedo와 std :: allocator가 수행하는 작업 에는 두 가지 주요 차이점 이 있습니다. 첫째, mallocfree사용자에 직면 해있다 – 그들은 작업은 일반적으로 프로그래머를 설계하고 – 반면 std::allocator표준 라이브러리에 전문 메모리 할당을 제공하도록 설계되었습니다. 이것은 첫 번째 요점이 중요하지 않거나 중요하지 않을 때의 좋은 예를 제공합니다. 라이브러리이기 때문에 추적 크기의 복잡성을 처리하는 어려움은 어쨌든 사용자에게 숨겨져 있습니다.

둘째, std :: allocator는 항상 동일한 크기의 항목으로 작동 합니다. 즉, 원래 전달 된 요소 수를 사용하여 여유 공간의 양을 결정할 수 있습니다. 이것이 free그 자체 와 다른 이유 는 예시입니다. 에서 std::allocator그들은 항상 정렬 요구 사항의 같은 종류 그래서 할당 할 항목 같은, 알려진, 크기 및 항목의 항상 같은 종류의 항상. 이것은 할당자가 처음에 이러한 항목의 배열을 단순히 할당하고 필요에 따라 처리하도록 특수화 될 수 있음을 의미합니다. 당신이 할 수없는 free대신 가끔 발신자가 *을 요청함으로써보다 큰 블록을 반환하는 것이 훨씬 더 효율적입니다, 반환에 대한 최선의 크기가 요구 크기가 있음을 보장 할 수있는 방법이 없기 때문에 하나사용자 또는 관리자는 실제로 부여 된 정확한 크기 를 추적해야합니다 . 이러한 종류의 구현 세부 정보를 사용자에게 전달하는 것은 호출자에게 도움이되지 않는 불필요한 골칫거리입니다.

-* 여전히이 점을 이해하기 어려운 사람이 있다면 다음을 고려하십시오. 일반적인 메모리 할당자는 메모리 블록의 시작 부분에 소량의 추적 정보를 추가 한 다음 여기에서 포인터 오프셋을 반환합니다. 여기에 저장된 정보에는 일반적으로 다음 사용 가능한 블록에 대한 포인터가 포함됩니다. 헤더가 4 바이트 길이 (실제로 대부분의 실제 라이브러리보다 작음)이고 크기를 포함하지 않는다고 가정하고 사용자가 16 바이트 블록을 요청하면 20 바이트의 여유 블록이 있다고 가정 해 보겠습니다. 시스템은 16 바이트 블록을 반환하지만 매번 시간을 낭비 할 수없는 4 바이트 조각을 남깁니다.malloc호출됩니다. 대신 관리자가 단순히 20 바이트 블록을 반환하면 이러한 지저분한 조각이 쌓이지 않고 사용 가능한 메모리를 더 깔끔하게 할당 할 수 있습니다. 그러나 시스템이 크기 자체를 추적하지 않고이를 올바르게 수행하려면 사용자가 모든 단일 할당에 대해 무료로 다시 전달하려면 실제로 할당 된 메모리 양을 추적해야합니다 . 동일한 인수가 원하는 경계와 일치하지 않는 유형 / 할당에 대한 패딩에 적용됩니다. 따라서, 대부분에서 필요로하는 free크기를 취할하는 중입니다 (A)는 실제로 할당 된 크기 또는 (b)와 일치하는 통과 크기에 의존 할 수없는 메모리 할당 이후 완전히 쓸모 무의미하게 추적 작업을 수행하기 위해 사용자가 필요 현실 현명한 메모리 관리자가 쉽게 처리 할 수있는 크기입니다.


답변

나는 이것이 당신이 바라는 것이 아니라 그럴듯하게 올바른 유일한 것이라고 믿기 때문에 이것을 대답으로 게시하고 있습니다.

원래는 편리하다고 여겨졌지만 그 이후로는 개선 할 수 없었습니다.
그것에 대한 설득력있는 이유가 없을 것입니다. (그러나 이것이 틀렸다면 기꺼이 삭제하겠습니다.)

가능하다면 이점 이있을 것입니다 . 미리 알고있는 크기를 가진 하나의 큰 메모리 조각을 할당 한 다음, 작은 메모리 덩어리를 반복적으로 할당하고 해제하는 대신 한 번에 조금씩 해제 할 수 있습니다. 현재 이와 같은 작업은 불가능합니다.


받는 많은 (많은 1 크기를 통과하는 것은 너무 말도 생각하는 당신!)

std::allocator<T>::deallocate방법에 대한 C ++의 디자인 결정을 참조해도 될까요?

void deallocate(pointer p, size_type n);

이 가리키는 영역의 모든 물체는 이 호출 전에 파괴되어야합니다. 이 메모리를 얻기 위해 전달 된 값과 일치해야 합니다.n Tp
nallocate

이 디자인 결정을 분석 하는 데 다소 “흥미로운” 시간이 있을 것 입니다.


operator delete경우 2013 년 N3778 제안 ( “C ++ 크기 할당 해제”) 도이 문제를 해결하기위한 것으로 밝혀졌습니다 .


1 원래 질문 아래의 설명을보고 매개 변수 부족을 정당화 하기 위해 “요구 된 크기는 free호출에 완전히 쓸모가 없습니다” 와 같은 성급한 주장을 한 사람이 몇 명인지 확인하십시오 size.