반응형
※ 본 자료는 위 책을 읽고 개인적으로 정리한 포스트임을 알려드립니다.
# 동적 메모리 관리
- 포인터의 강력한 기능의 대부분은 동적으로 할당된 메모리를 추적할 수 있는 포인터의 능력에서 기인한다.
- 동적 메모리 관리는 복잡한 데이터 구조를 조작하는 일을 포함한 많은 일의 핵심이다.
- C 프로그램은 런타임 시스템(runtime system) 안에서 실행된다.
- 런타임 시스템 : 운영체제에서 제공되는 환경
- 많은 프로그램 기능들과 함께 스택(stack), 힙(heap)을 지원
- 메모리 관리 : 프로그램의 핵심
- 자동 관리 : 자동 변수의 메모리 할당
- 정적 변수와 전역 변수의 경우는 프로그램 시작 시 0으로 초기화되는 애플리케이션의 다른 데이터 세그먼트에 위치함
- 메모리를 할당하고 해제하는 능력
- 메모리를 더 효율적이며 유연하게 관리함
- 동적 메모리 할당 능력은 연결 리스트와 큐 강튼 가변 요소를 사용하는 데이터 구조를 구현할 때 도움
- C99 표준 : 가변 길이 배열(Variable Length Array)이 도입
- 이 배열의 크기는 컴파일 될 때가 아니라 실행 될때 결정됨
- 동적 메모리 관리 (dynamic memory management)
- 힙(Heap)에서 메모리를 할당하는 동적 메모리 관리를 지원한다.
- 동적 메모리 할당은 할당, 해제 함수를 사용하여 수동으로 처리
# 동적 메모리 할당
- stdlib.h 헤더 파일에서 관련 함수를 찾을 수 있음
- malloc류 함수로 메모리 할당
- 애플리케이션에서 할당된 메모리 사용
- free 함수를 이용해 할당된 메모리를 해제
- 실제 사용
1 2 3 4 5 | int *pi = (int*) malloc(sizeof(int)); *pi = 5; printf("*pi: %d\n", *pi); free(pi); |
- 메모리 할당에 성공하면 할당된 메모리의 포인터를 반환
- 실패하면 널 포인터를 반환
- sizeof 연산자를 사용하면 애플리케이션에 대한 이식성을 높일 수 있음
# 메모리 누수
- 상황
- 메모리의 주소를 잃어버리는 경우
- free 함수가 호출되어야 하는 상황에 호출되지 않는 경우
- 결론
- 메모리의 양 감소
- OOM 오류 발생(Out of Memory)
- 프로그램 종료
- 숨겨진 메모리 누수
- 메모리를 해제해야할 때 해제하지 못하는 경우 발생
- 구조체를 먼저 해제 했을 때 구조체 안에 동적으로 할당했던 포인터를 해제 하지 않은 것
# 메모리 주소 손실
- pi 포인터를 해제 하지 않고 재사용할 때 이전에 할당된 메모리의 주소는 손실
1 2 3 | int *pi = (int*) malloc(sizeof(int)); *pi = 5; pi = (int*) malloc(sizeof(int)); |
- 포인터의 증가를 통해 포인터의 시작위치를 잃어버리는 것
1 2 3 4 5 6 | char *name = (char*) malloc (strlen("Susan")+1); strcpy(name, "Susan"); while(*name != 0){ printf("%c", *name); name++; } |
# 동적 메모리 할당 함수
- stdlib.h
- malloc : 힙에서 메모리 할당
- realloc : 기존 할당된 메모리의 크기 변경
- calloc : 힙에서 메모리 할당 그리고 0으로 설정
- free : 할당된 메모리를 힙으로 반환
- malloc 함수 사용
- 할당된 메모리에 대한 void 포인터를 반환
- 할당할 수 없을 때 malloc 함수는 NULL을 반환
- void* malloc(size_t);
- 0을 전달하면 함수 행동 구현에 따라 NULL 포인터 또는 0바이트가 할당된 영역에 대한 포인터를 반환할 수도 있음
- 단계
- 힙 영역에서 메모리가 할당된다.
- 메모리는 수정되거나 초기화되지 않는다.
- 첫 번째 시작 주소가 반환된다.
- 캐스팅은 필요한가?
- 명시적 캐스팅은 malloc 함수의 사용 의도를 명확히 함
- 명시적 캐스팅을 필요로 하는 C++ 또는 초기 C 컴파일러와 호환 가능한 코드를 생성
- 메모리 할당 실패
- 할당에 실패한 포인터는 일반적으로 가비지 값을 포함
- 런타임 예외(runtime exception)가 발생
# 정적 포인터 및 전역포인터에 malloc 사용
1 2 | static int *pi; pi = malloc(sizeof(int)); |
# calloc 함수 사용하기
1 | void *calloc(size_t numElements, size_t elementSize); |
- calloc 함수는 메모리 할당과 동시에 초기화를 한다.
- numElements와 elementSize 인자 값에 의해 결정되는 크기만큼의 메모리를 할당하며, 할다된 메모리의 첫 바이트를 가리키는 포인터를 반환
- 두 값중 하나라도 0이면, 널 포인터가 반환된다.
- calloc 함수는 메모리를 할당할 수 없을 때 널 포인터를 반환하고, 전역 변수 errno가 ENOMEM(out of memory)으로 설정된다.
- 대체
- calloc함수 대신에 malloc 함수와 memset 함수를 같이 사용하면 calloc 함수를 사용하는 것과 같은 결과를 얻을 수 있다.
- calloc 함수의 실행이 malloc 함수보다 시간이 오래 걸린다.
1 2 | int *pi = malloc(5*sizeof(int)); memset(pi, 0, 5* sizeof(int)); |
# realloc 함수 사용
- 포인터에 할당된 메모리의 크기를 늘리거나 줄이는 일이 필요할 때 사용
- 가변 배열 구현에 유용
- void * realloc(void *ptr, size_t size);
- realloc 함수는 호출 시 두 개의 인자를 취하며, 재할당된 메모리 영역의 포인터를 반환한다.
- 첫번째 인자는 기존 할당된 메모리에 대한 포인터
- 두번째 인자는 요청할 메모리의 크기
- 재할당된 메모리의 크기는 기존 메모리의 크기와 다르며 반환 값은 재할당된 메모리의 포인터
# alloca 함수와 가변 길이 배열
- alloca 함수(마이크로소프트 운영체제의 malloca 함수와 동일)는 함수 내부에서 사용할 목적으로 스택 프레임 안의 메모리를 할당
- 함수(스택 프레임을 소유한)가 반환하면 alloca에 의해 할당된 메모리는 자동으로 해제됨.
# free 함수로 메모리 반환하기
- 동적 메모리 할당에서 프로그래머는 할당된 메모리가 더는 필요치 않을 경우, 다른 용도에 사용될 수 있도록 free 함수를 사용하여 메모리를 해제할 수 있다.
- free 함수의 프로토타입 : void free(void *ptr);
# 해제된 포인터에 NULL 할당
- 포인터는 메모리가 해제된 이후에도 문제를 유발할 수 있음
- 명시적으로 NULL을 할당
- 이미 해제된 포인터의 사용은 런타임 예외를 발생
1 2 3 4 | int *pi = (int*) malloc(sizeof(int)); ... free(pi); pi = NULL; |
- 이 기법은 댕글링 포인터 같은 문제를 해결하기 위한 것
# 이중 해제
- 이중 해제(Double Free)란 메모리 해제를 두 번 시도하는 것을 말함
1 2 3 4 5 | int *pi = (int*) malloc(sizeof(int)); *pit = 5; free(pi); ... free(pi); |
# 에일리어싱(aliasing)
- 두포인터가 같은 메모리 위치를 참조하는 것
1 2 3 4 5 6 | p1 = (int*) malloc(sizeof(int)); int *p2 = p1; free(p1); ... free(p2); |
- 여기서 힙 관리자는 메모리가 해제될 때 메모리가 이미 해제되었는지 판단하지 못하고, 두 번 해제 한다.
- 두 번 해제하는지도 판단하지 못한다.
# 힙 메모리와 시스템 메모리
- free 함수가 호출되었다고해서 힙 관리자가 반드시 해제된 메모리를 운영체제로 반환하는 것이 아니고, 애플리케이션 내에서 해당 메모리를 다시 사용할 수 있게 할 뿐, 프로그램이 메모리를 할당한 후 다시 해제한다고 해도 일반적으로 운영체제 측면에서는 해제된 메모리가 애플리케이션의 메모리 사용량에 반영되지 않는다.
# 프로그램 종료시 메모리 해제
- 운영체제는 메모리를 포함한 애플리케이션 리소스의 관리 책임이 있음.
- 사용된 메모리를 다른 애플리케이션이 재할당할 수 있도록함
- 수동적으로 사용 종료된 메모리를 해제하는 습관은 좋다.
- 진단 도구로 메모리 누수를 쉽게 찾을 수 있다.
- 메모리 반환은 프로그램의 책임
- 보장
- 메모리 해제의 중요성에 비해 구현하기가 꽤 까다롭다.
- 구현에 다로 시간을 써야 하며, 복잡한 구조체의 경우 구현이 더 복잫바다.
- 애플리케이션의 크기가 늘어난다.
- 애플리케이션의 실행 시간이 길어진다.
- 메모리 해제 코드에서 새로운 오류가 발생하기도 한다.
# 댕글링 포인터
- 포인터가 여전히 해제된 메모리 영역을 가리키고 있다면, 이러한 포인터를 댕글링 포인터(Dangling Pointers)라고 한다.
- 너무 빠른 해제(premature free)라고 불림.
- 발생하는 문제
- 메모리 접근 시 예측 불가능한 동작
- 메모리 접근 불가 시 세그멘테이션 오류(Segmentation fault)
- 잠재적인 보안 위협
- 결과
- 메모리 해제 후, 해제된 메모리에 접근
- 함수 호출에서 자동 변수를 가리키는 포인터의 반환
- 예제 1
1 2 3 4 5 | int *pi = (int*) malloc(sizeof(int)); *pi = 5; printf("*pi: %d\n", *pi); free(pi); *pi = 10; |
- 예제 2
1 2 3 4 5 6 7 8 9 10 | int *p1 = (int*) malloc(sizeof(int)); *p1 = 5; ... int *p2; p2 = p1; ... free(p1); ... *p2 = 10; // 댕글링 포인터 |
- 댕글링 포인터 다루기
- 포인터가 원인인 문제들의 디버깅은 때로 해결하기 어려울 때가 있음.
- 해결
- 메모리 해제 후 포인터를 NULL로 설정
- 다수의 포인터 복사본이 존재할 경우 복사본 중에 단 하나의 포인터에만 영향을 미치기 때문에 문제가 남아 있을 수 있다.
- free함수를 대체할 새로운 함수 작성
- 몇몇 런타임 시스템이나 디버깅 시스템은 해제된 메모리를 특별한 값을 덮어씀
- ex) 0xDEADBEEF, 0xCC, 0xCD, 0xDD
- 댕글링 포인터를 발견하기 위해 서드파티 도구를 이용
- 메모리 누수 탐지
- 할당된 메모리를 덮어쓰는 문제와 메모리 누수를 해결하기 위한 기술 : 마이크로소프트가 제공
- 힙의 무결성 검사
- 메모리 누수 검사
- 힙 메모리가 부족한 상황 재현
- 기술 정보 : http://bit.ly/12SftWV Microsoft Developer Network
- Mudflap 라이브러리 http://bit.ly/YilPI1
# 동적 메모리 할당 기술
- 힙 관리자 종류
- OpenBSD의 malloc
- Hoard의 malloc
- 구글의 TCMalloc
- GNU C 라이브러리의 dlmalloc (http://dmalloc.com/)
- 이 라이브러리는 디버깅을 위한 기능이나 메모리 누수 시에 이를 추적하는 기능을 제공
- 메모리 할당과 해제 기술의 구현 방식은 컴파일러마다 다를 수 있다.
- 대부분의 힙 관리자 (malloc)은 메모리를 할당하기 위해 데이터 세그먼트를 사용하며, 이 방식은 힙 메모리 단편화(fragmentation)나 프로그램 스택과 충돌이 일어나기도 한다.
- 관리해야할 이슈
- 프로세스 또는 스레드 단위로 할당할지 결정
- 힙 보안 취약점 이슈
- 가비지 컬렉션
- 문제
- 성능 이슈
- 메모리 참조 범위 제한
- 스레드 문제
- 깔끔한 메모리 해제(memory gracefully)
- 해결
- 자동 메모리 해제 기술 : 가비지 컬렉션(garbage collection)
- 쓰는 이유
- 메모리 해제 시점에 대한 고민으로 부터 프로그래머를 자유롭게 한다.
- 프로그래머는 애플리케이션 자체의 문제에만 집중할 수 있다.
- 대안
- 수동 메모리 관리의 대안
- BDW 가비지 컬렉터(Boehm-Demers-Weiser garbage collector)
# 리소스 획득 즉시 초기화 (RAII, Resource Acquisition Is Initialization)
- C++언어에서 리소스 할당과 해제
- 예외 상황에서 유용
- 비표준 확장 기능임 : GNU C 컴파일러 지원
- GNU 확장 : RAII_VARIABLE 매크로 사용
- 변수 타입
- 변수가 생성될 때 호출할 함수
- 변수가 범위를 벗어날 때 호출할 함수
- 선언 문
1 2 3 | #define RAII_VARIABLE(vartype, varname, initval, dtor) \ void _dtor _ ## varname (vartype * v) {dtor(*v); } \ vartype varname __attribute__(cleanup(_dtor_ ## varname))) = (initval) |
- 예외 처리기 사용
- 예외 처리기(Exception Handler)를 사용하여 다른 방법으로 메모리 해제 작업 가능
- C표준 아님
- try 블록 으로 예외가 일어나는 문장을 감싸고 finally 블록에 반드시 free가 되도록 한다.
반응형
'Programming > C/C++' 카테고리의 다른 글
C/GCC/최적화 및 디버깅 옵션 추가 (0) | 2018.03.14 |
---|---|
C/Pointer/C포인터의 이해와 활용 - 3 (0) | 2018.03.13 |
C/Pointer/C포인터의 이해와 활용 - 1 (0) | 2018.03.12 |
C/Symbolic Execution/KLEE Reference (0) | 2018.02.06 |
C/Symbolic Execution/KLEE Tutorials 1-2 Anaylsis (0) | 2018.01.17 |