malloc () 및 free ()는 어떻게 작동합니까?


276

방법 mallocfree작업 을 알고 싶습니다 .

int main() {
    unsigned char *p = (unsigned char*)malloc(4*sizeof(unsigned char));
    memset(p,0,4);
    strcpy((char*)p,"abcdabcd"); // **deliberately storing 8bytes**
    cout << p;
    free(p); // Obvious Crash, but I need how it works and why crash.
    cout << p;
    return 0;
}

가능한 경우 대답이 메모리 수준에서 깊이 있으면 정말 감사 할 것입니다.


5
실제로 사용되는 컴파일러와 런타임 라이브러리에 의존해서는 안됩니까?
Vilx-

9
그것은 CRT 구현에 달려 있습니다. 그래서 당신은 그것을 일반화 할 수 없습니다.
Naveen

58
strcpy는 8이 아닌 9 바이트를 씁니다. NULL 종결 자 ;-)를 잊지 마십시오.
Evan Teran


2
@ LưuVĩnhPhúc 그건 C ++입니다. 노트cout <<
브레이든 최저

답변:


385

확인 malloc에 ​​대한 답변이 이미 게시되었습니다.

더 흥미로운 부분은 자유어떻게 작동 하는가입니다 (이 방향에서 malloc도 더 잘 이해할 수 있습니다).

많은 malloc / free 구현에서 free는 일반적으로 메모리를 운영 체제로 반환하지 않습니다 (또는 드문 경우에만). 그 이유는 힙에 공백이 생겨서 2GB 또는 4GB의 가상 메모리를 공백으로 마무리하기 때문입니다. 가상 메모리가 완료 되 자마자 큰 어려움에 처하게되므로 피해야합니다. 다른 이유는 OS가 특정 크기와 정렬을 가진 메모리 청크 만 처리 할 수 ​​있기 때문입니다. 구체적으로 말하면 : 일반적으로 OS는 가상 메모리 관리자가 처리 할 수있는 블록 만 처리 할 수 ​​있습니다 (대개 512 바이트의 배수 (예 : 4KB)).

따라서 40 바이트를 OS로 반환하면 작동하지 않습니다. 그래서 무료는 무엇을 하는가?

Free는 메모리 블록을 자체 빈 블록 목록에 넣습니다. 일반적으로 주소 공간에서 인접한 블록을 함께 병합하려고 시도합니다. 사용 가능한 차단 목록은 시작 부분에 관리 데이터가있는 순환 메모리 청크 목록입니다. 이것이 표준 malloc / free로 매우 작은 메모리 요소를 관리하는 것이 효율적이지 않은 이유이기도합니다. 모든 메모리 청크에는 추가 데이터가 필요하며 크기가 작을수록 조각화가 더 많이 발생합니다.

자유 목록은 또한 새로운 메모리 청크가 필요할 때 malloc이 보는 첫 번째 장소입니다. OS에서 새 메모리를 호출하기 전에 스캔됩니다. 필요한 메모리보다 큰 청크가 발견되면 두 부분으로 나뉩니다. 하나는 발신자에게 반환되고 다른 하나는 다시 무료 목록으로 돌아갑니다.

이 표준 동작에 대한 여러 가지 최적화가 있습니다 (예 : 작은 메모리 청크). 그러나 malloc과 free는 매우 보편적이어야하기 때문에 대안을 사용할 수없는 경우 표준 동작은 항상 폴백입니다. 또한 프리리스트를 처리 할 때 최적화가 있습니다 (예 : 크기별로 정렬 된 목록에 청크 저장). 그러나 모든 최적화에는 자체 제한이 있습니다.

왜 코드가 충돌합니까?

그 이유는 4 개의 문자로 된 영역에 9 개의 문자 (후미 널 바이트를 잊지 마십시오)를 쓰면 데이터 청크 뒤에 "다른"메모리에 저장된 관리 데이터를 덮어 쓰게되기 때문입니다 ( 이 데이터는 대부분 메모리 청크의 "앞에"저장되므로). 그러면 free가 청크를 free list에 넣으려고 할 때이 관리 데이터를 건 드리면 덮어 쓴 포인터 위로 넘어 질 수 있습니다. 시스템이 중단됩니다.

이것은 다소 우아한 행동입니다. 또한 어딘가에 런 어웨이 포인터가 memory-free-list의 데이터를 덮어 쓰고 시스템이 즉시 충돌하지는 않지만 나중에 일부 서브 루틴이 발생하는 상황을 보았습니다. 중간 정도의 복잡한 시스템에서도 이러한 문제는 실제로 디버깅하기가 매우 어려울 수 있습니다! 내가 관여했던 한 경우, 메모리 덤프에 표시된 것과 완전히 다른 위치에 있었기 때문에 충돌의 원인을 찾는 데 며칠이 걸렸습니다. 시한 폭탄과 같습니다. 다음 "무료"또는 "멀록"이 충돌하지만 그 이유를 모릅니다.

이것들은 최악의 C / C ++ 문제 중 하나이며 포인터가 그렇게 문제가 될 수있는 한 가지 이유입니다.


63
Soooo 많은 사람들이 free ()가 메모리를 OS로 반환하지 않을 수 있다는 것을 깨닫지 못합니다. 그들을 계몽시켜 주셔서 감사합니다.
Artelius

Artelius : 반대로, 새로운 의지는 항상됩니까?
Guillaume07

3
@ Guillaume07 나는 당신이 새로운 것이 아니라 삭제를 의미한다고 가정합니다. 아닙니다. (필요하지는 않습니다). 거의 같은 것을 삭제하고 해제하십시오. 다음은 각각 MSVC2013에서 호출하는 코드입니다. goo.gl/3O2Kyu
Yay295

1
delete는 항상 소멸자를 호출하지만 메모리 자체는 나중에 할당하기 위해 사용 가능 목록으로 이동할 수 있습니다. 구현에 따라 malloc이 사용하는 것과 동일한 프리리스트 일 수도 있습니다.
David C.

1
@Juergen 그러나 free ()가 malloc에서 할당 된 메모리 양을 포함하는 여분의 바이트를 읽으면 4를 얻습니다. 그런 다음 충돌이 어떻게 발생했는지 또는 free ()가 관리 데이터를 어떻게 터치합니까?
정의되지 않은 동작

56

aluser 가이 포럼 에서 말한 것처럼 스레드 :

프로세스에는 주소 x에서 주소 y까지 힙이라는 메모리 영역이 있습니다. 모든 malloc의 데이터는이 영역에 있습니다. malloc ()은 힙에서 사용 가능한 공간의 모든 청크에 대한 일부 데이터 구조를 유지합니다. malloc을 호출하면 목록을 통해 충분히 큰 청크를 찾고 포인터를 반환하고 더 이상 비어 있지 않다는 사실을 기록합니다. 동일한 포인터로 free ()를 호출하면 free ()는 해당 청크의 크기를 찾아서 free chunks () 목록에 다시 추가합니다. malloc ()을 호출하고 힙에서 충분히 큰 청크를 찾을 수 없으면 brk () syscall을 사용하여 힙을 증가시킵니다. 즉 주소 y를 늘리고 이전 y와 새 y 사이의 모든 주소를 유효한 메모리 여야합니다. brk ()는 syscall이어야합니다.

malloc ()은 시스템 / 컴파일러에 따라 다르므로 구체적인 대답을하기가 어렵습니다. 그러나 기본적으로 할당 된 메모리를 추적하고 사용 방법에 따라 무료 통화가 실패하거나 성공 할 수 있습니다.

malloc() and free() don't work the same way on every O/S.


1
이것이 정의되지 않은 행동이라고 불리는 이유입니다. 한 번의 구현으로 유효하지 않은 쓰기 후 무료로 전화하면 악마가 코에서 날아갈 수 있습니다. 당신은 몰라요
Braden Best

36

malloc / free의 한 구현은 다음을 수행합니다.

  1. sbrk () (Unix 호출)를 통해 OS에서 메모리 블록을 확보하십시오.
  2. 크기, 권한, 다음 및 이전 블록의 위치와 같은 정보를 사용하여 해당 메모리 블록 주위에 머리글과 바닥 글을 만듭니다.
  3. malloc에 ​​대한 호출이 들어 오면 적절한 크기의 블록을 가리키는 목록이 참조됩니다.
  4. 그런 다음이 블록이 반환되고 이에 따라 머리글과 바닥 글이 업데이트됩니다.

25

메모리 보호에는 페이지 세분성이 있으며 커널 상호 작용이 필요합니다.

예제 코드는 본질적으로 예제 프로그램이 트랩되지 않는 이유를 묻습니다. 메모리 보호는 커널 기능이며 전체 페이지에만 적용되는 반면 메모리 할당자는 라이브러리 기능이며 강제로 .. 종종 페이지보다 훨씬 작은 크기의 블록.

메모리는 프로그램에서 페이지 단위로만 제거 할 수 있으며 심지어 관찰되지 않을 수도 있습니다.

calloc (3)과 malloc (3)은 필요한 경우 커널과 상호 작용하여 메모리를 얻습니다. 그러나 대부분의 free (3) 구현은 메모리를 커널 1로 반환하지 않으며 , 해제 된 블록을 재사용하기 위해 calloc ()과 malloc ()이 나중에 참조 할 수있는 빈 목록에 추가합니다.

free ()가 시스템에 메모리를 반환하려고하더라도 커널이 실제로 영역을 보호하도록하려면 적어도 하나의 연속 메모리 페이지가 필요하므로 작은 블록을 해제하면 보호 변경이 발생합니다. 페이지 의 마지막 작은 블록

그래서 당신의 블록은 무료 목록에 앉아 있습니다. 여전히 할당 된 것처럼 거의 항상 메모리와 주변 메모리에 액세스 할 수 있습니다. C는 머신 코드로 직접 컴파일되며 특별한 디버깅 배열이 없으면로드 및 저장에 대한 상태 점검이 없습니다. 이제 사용 가능한 블록에 액세스하려고하면 라이브러리 구현 자에 부당한 요구를하지 않기 위해 표준에 의해 동작이 정의되지 않습니다. 할당 된 블록 외부에서 해제 된 메모리 나 메모리에 액세스하려고하면 잘못 될 수있는 여러 가지가 있습니다.

  • 때때로 할당자는 별도의 메모리 블록을 유지 관리하고 때로는 블록 바로 앞이나 뒤에 할당 한 헤더를 사용하지만 ( "바닥 글"이라고 생각합니다) 사용 가능한 목록을 유지하기 위해 블록 내에서 메모리를 사용하려고 할 수도 있습니다. 서로 연결되어 있습니다. 그렇다면 블록을 읽을 수는 있지만 내용이 변경 될 수 있으며 블록에 쓰면 할당자가 잘못 작동하거나 충돌 할 수 있습니다.
  • 당연히 블록은 나중에 할당 될 수 있으며 코드 또는 라이브러리 루틴으로 덮어 쓰거나 calloc ()로 0을 덮어 쓸 수 있습니다.
  • 블록이 재 할당되면 크기도 변경 될 수 있으며,이 경우 더 많은 링크 또는 초기화가 다양한 위치에 작성됩니다.
  • 분명히 당신은 프로그램의 커널 알려진 세그먼트 중 하나의 경계를 넘어서 범위를 벗어난 것을 참조 할 수 있으며,이 경우 트랩 할 것입니다.

작동 이론

따라서 예제에서 전반적인 이론으로 거꾸로 작업하면 malloc (3)은 필요할 때 커널에서 메모리를 가져옵니다 (일반적으로 페이지 단위). 이 페이지들은 프로그램에 따라 분할되거나 통합됩니다. Malloc과 무료 협력하여 디렉토리를 유지 관리합니다. 그들은 큰 블록을 제공 할 수 있도록 가능한 경우 인접한 자유 블록을 통합합니다. 디렉토리는 해제 된 블록에서 메모리를 사용하여 링크 된 목록을 형성하는 것을 포함하거나 포함하지 않을 수 있습니다. (대안은 좀 더 공유 메모리와 페이징 친화적이며, 디렉토리에 특별히 메모리를 할당하는 것이 포함됩니다.) Malloc과 free는 특별하고 선택적 디버깅 코드가 컴파일 될 때 개별 블록에 대한 액세스를 강제 할 수있는 능력이 거의 없습니다. 프로그램.


1. free () 구현이 메모리를 시스템에 반환하려고 시도하는 경우가 거의 없다는 사실은 구현자가 느슨해 졌기 때문일 필요는 없습니다. 커널과의 상호 작용은 단순히 라이브러리 코드를 실행하는 것보다 훨씬 느리며 이점은 적습니다. 대부분의 프로그램은 정상 상태이거나 메모리 사용량이 증가하므로 반환 가능한 메모리를 찾는 힙을 분석하는 데 소요되는 시간이 완전히 낭비됩니다. 다른 이유는 내부 조각화로 페이지 정렬 블록이 존재하지 않을 가능성이 있으며, 블록을 반환하면 블록이 양쪽으로 조각화 될 가능성이 있습니다. 마지막으로, 많은 양의 메모리를 반환하는 일부 프로그램은 malloc ()을 무시하고 페이지를 할당하고 해제 할 가능성이 높습니다.


좋은 대답입니다. 동적 스토리지 할당 : 윌슨 (Wilson) 등의 설문 조사 및 비판적 검토에서 헤더 필드 및 자유 목록과 같이 할당자가 사용하는 내부 메커니즘에 대한 심층적 인 검토가 권장됩니다.
Goaler444

23

이론적으로 malloc은이 응용 프로그램의 운영 체제에서 메모리를 가져옵니다. 그러나 4 바이트 만 원할 수 있고 OS는 페이지 (일반적으로 4k)에서 작동해야하기 때문에 malloc은 그 이상을 수행합니다. 페이지를 가져 와서 자체 정보를 저장하므로 해당 페이지에서 할당하고 해제 한 내용을 추적 할 수 있습니다.

예를 들어, malloc은 4 바이트를 할당 할 때 4 바이트에 대한 포인터를 제공합니다. malloc은 4 바이트 이전 8-12 바이트의 메모리 를 사용하여 할당 한 모든 메모리 체인을 만드는 것을 알지 못할 수도 있습니다 . free를 호출하면 포인터를 가져 와서 데이터가있는 위치로 백업하고 작동합니다.

메모리를 비우면 malloc은 해당 메모리 블록을 체인에서 꺼내고 해당 메모리를 운영 체제에 반환하거나 반환하지 않을 수 있습니다. 그렇다면 OS가 해당 위치에 액세스 할 수있는 권한을 빼앗기 때문에 해당 메모리에 액세스하는 것보다 실패 할 수 있습니다. malloc이 메모리를 유지하면 (해당 페이지에 다른 것들이 할당되었거나 일부 최적화가 있기 때문에) 액세스가 작동합니다. 여전히 잘못되었지만 작동 할 수 있습니다.

면책 조항 : 내가 설명한 것은 malloc의 일반적인 구현이지만 결코 유일하게 가능한 것은 아닙니다.


12

strcpy 행은 NUL 종료 자로 인해 8이 아닌 9 바이트를 저장하려고합니다. 정의되지 않은 동작을 호출합니다.

무료 통화는 중단되거나 중단되지 않을 수 있습니다. 할당의 4 바이트 "후"메모리는 C 또는 C ++ 구현에서 다른 용도로 사용될 수 있습니다. 그것이 다른 것에 사용된다면, 그 모든 것에 낙서를하면 "다른 것"이 잘못 될 수 있지만, 다른 것에 사용되지 않으면, 당신은 그것을 피할 수 있습니다. "그것으로 도망 치다"는 소리는 좋지만 실제로는 나쁘다. 왜냐하면 코드가 제대로 실행되는 것처럼 보이지만 차후에는 도망 치지 않을 수 있기 때문이다.

디버깅 스타일 메모리 할당자를 사용하면 특별한 가드 값이 작성되었으며 해당 값과 패닉이없는 경우이를 무료로 확인할 수 있습니다.

그렇지 않으면 다음 5 바이트에 아직 할당되지 않은 다른 메모리 블록에 속하는 링크 노드의 일부가 포함되어있을 수 있습니다. 블록을 해제하면 사용 가능한 블록 목록에 블록을 추가하는 것이 좋을 수 있으며 목록 노드에서 무언가를 작성했기 때문에 해당 작업이 잘못된 값을 가진 포인터를 역 참조하여 충돌을 일으킬 수 있습니다.

그것은 모두 메모리 할당 자에 달려 있습니다-다른 구현은 다른 메커니즘을 사용합니다.


12

malloc () 및 free () 작동 방식은 사용 된 런타임 라이브러리에 따라 다릅니다. 일반적으로 malloc ()은 운영 체제에서 힙 (메모리 블록)을 할당합니다. malloc ()에 대한 각 요청은이 메모리의 작은 청크를 할당하여 호출자에게 포인터를 리턴합니다. 메모리 할당 루틴은 힙에서 사용 된 여유 메모리를 추적 할 수 있도록 할당 된 메모리 블록에 대한 추가 정보를 저장해야합니다. 이 정보는 종종 malloc ()에 의해 포인터가 리턴되기 직전에 몇 바이트에 저장되며 메모리 블록의 링크 된 목록이 될 수 있습니다.

malloc ()에 의해 할당 된 메모리 블록을 지나서 쓰면 다음 블록의 예약 유지 정보 중 일부를 파괴 할 가능성이 높습니다.

너무 많은 문자를 버퍼에 복사 할 때 프로그램이 중단 될 수도 있습니다. 여분의 문자가 힙 외부에있는 경우 존재하지 않는 메모리에 쓰려고 할 때 액세스 위반이 발생할 수 있습니다.


6

이것은 malloc과 관련이 없으며 무료입니다. 문자열을 복사 한 후 프로그램에 정의되지 않은 동작이 나타납니다.이 시점에서 또는 이후에 충돌 할 수 있습니다. malloc을 사용하지 않고 사용하지 않고 스택에 또는 정적으로 char 배열을 할당 한 경우에도 마찬가지입니다.


5

malloc 및 free는 구현에 따라 다릅니다. 일반적인 구현에는 사용 가능한 메모리를 사용 가능한 메모리 블록의 링크 된 목록 인 "사용 가능한 목록"으로 분할하는 것이 포함됩니다. 많은 구현에서 인위적으로 작은 객체와 큰 객체로 나눕니다. 사용 가능한 블록은 메모리 블록의 크기와 다음 블록의 위치 등에 대한 정보로 시작합니다.

malloc을 사용하면 빈 목록에서 블록을 가져옵니다. 해제하면 블록이 해제 목록으로 돌아갑니다. 포인터 끝을 덮어 쓸 때 사용 가능한 목록의 블록 헤더에 쓸 수 있습니다. 메모리를 비우면 free ()는 다음 블록을 보려고 시도하고 아마도 버스 오류를 일으키는 포인터에 부딪 히게됩니다.


4

그것은 메모리 할당 기 구현과 OS에 달려 있습니다.

예를 들어, 윈도우에서 프로세스는 페이지 이상의 RAM을 요청할 수 있습니다. 그런 다음 OS는 해당 페이지를 프로세스에 할당합니다. 그러나 이것은 응용 프로그램에 할당 된 메모리가 아닙니다. CRT 메모리 할당자는 메모리를 연속 "사용 가능한"블록으로 표시합니다. 그런 다음 CRT 메모리 할당자는 사용 가능한 블록 목록을 실행하여 사용할 수있는 가장 작은 블록을 찾습니다. 그런 다음 필요한만큼의 블록을 가져 와서 "할당 된"목록에 추가합니다. 실제 메모리 할당의 헤드에 첨부되는 것은 헤더입니다. 이 헤더에는 다양한 정보가 포함됩니다 (예를 들어, 링크 된 목록을 구성하기 위해 다음 및 이전에 할당 된 블록을 포함 할 수 있습니다. 대부분의 경우 할당 크기를 포함합니다).

그런 다음 Free는 헤더를 제거하고 사용 가능한 메모리 목록에 다시 추가합니다. 주변 여유 블록과 함께 더 큰 블록을 형성하면 더 큰 블록을 제공하기 위해 함께 추가됩니다. 이제 전체 페이지가 해제되면 할당자는 해당 페이지를 OS로 반환합니다.

간단한 문제는 아닙니다. OS 할당 자 부분은 완전히 제어 할 수 없습니다. Doug Lea의 Malloc (DLMalloc)과 같은 내용을 읽고 상당히 빠른 할당자가 어떻게 작동하는지 이해하는 것이 좋습니다.

편집 : 할당보다 큰 값을 쓰면 다음 메모리 헤더를 덮어 썼기 때문에 충돌이 발생합니다. 이 방법을 사용하면 해제 할 때 정확히 무엇이고 다음 블록으로 병합하는 방법에 대해 매우 혼란스러워집니다. 이것은 항상 무료로 즉시 충돌을 일으킬 수 있습니다. 나중에 충돌이 발생할 수 있습니다. 일반적으로 메모리 덮어 쓰기를 피하십시오!


3

귀하가 소유하지 않은 메모리를 사용했기 때문에 프로그램이 충돌합니다. 운이 좋으면 문제가 오랫동안 숨겨져 나중에 다시 와서 물릴 수 있습니다.

malloc / free 구현이 진행되는 한, 모든 책이 주제에 집중되어 있습니다. 기본적으로 할당자는 OS에서 더 큰 메모리 덩어리를 가져 와서 관리합니다. 할당자가 해결해야하는 몇 가지 문제는 다음과 같습니다.

  • 새로운 기억을 얻는 방법
  • 그것을 저장하는 방법-(목록 또는 다른 구조, 크기가 다른 메모리 청크에 대한 여러 목록 등)
  • 사용자가 현재 사용 가능한 것보다 많은 메모리를 요청하는 경우 수행 할 작업 (OS에서 더 많은 메모리를 요청하고 기존 블록 중 일부에 참여하는 방법, 정확하게 참여하는 방법, ...)
  • 사용자가 메모리를 비울 때 수행 할 작업
  • 디버그 할당자는 요청한 더 큰 청크를 제공하고 바이트 패턴을 채울 수 있습니다. 메모리를 비울 때 할당자는 블록 외부에 기록되었는지 확인할 수 있습니다 (어쩌면 귀하의 경우에 발생합니다) ...

2

실제 동작이 다른 컴파일러 / 런타임마다 다르기 때문에 말하기가 어렵습니다. 디버그 / 릴리스 빌드조차도 동작이 다릅니다. VS2005의 디버그 빌드는 할당 사이에 마커를 삽입하여 메모리 손상을 감지하므로 충돌 대신 free ()로 선언됩니다.


1

그것은 단순히 주변에 프로그램 브레이크 포인터를 이동하는 것을 깨닫게하는 것도 중요 brk하고 sbrk실제로하지 않습니다 할당 메모리를, 그냥 주소 공간을 설정합니다. 예를 들어 Linux에서 주소 범위에 액세스하면 실제 실제 페이지가 메모리를 "백업"하여 페이지 오류가 발생하고 결국 커널이 페이지 할당자를 호출하여 백업 페이지를 가져옵니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.