Memory 관리 방법

보다 효율적인 Memory 관리 방법이 필요하기에 진행함

연속 메모리 할당

  • 가장 일반적인 방법으로, 동일한 타입의 데이터들을 연속된 메모리 상에 할당시키는 방법
  • Vector와 Array가 대표적인 예이다.
  • 다른 타입 간의 메모리 관계를 구축할 수 없다는 단점이 존재
  • 내부 단편화 : 없음
  • 외부 단편화 : 있음

페이징 기법 ( Paging )

  • 고정된 크기의 메모리 블락을 할당시키는 방법
  • 일정한 크기의 메모리 블락을 제공해주고, 해당 메모리 블락 안에서 객체들을 생성하는 방법
  • 내부 단편화 : 해당 메모리 블락을 전부 사용하지 못할 경우, 내부 단편화 발생 가능
  • 외부 단편화 : 없음

세그먼테이션 기법 ( Segmentation )

  • 서로 다른 크기의 메모리 블락을 할당하는 방법
  • 요구되거나 혹은 필요로 되어지는 메모리 블락만큼 할당시키는 방법
  • 내부 단편화 : 요구되어지는 전체 블락을 사용하기에 내부 단편화 발생하지 않음
  • 외부 단편화 : 해당 메모리 사용이 끝나고 해제되었을 때, 해당 메모리 블락을 다시 알맞은 크기로 사용 못할 가능성이 다분함

현재 Memory Project 내부 메모리 관리 방법

페이징 기법 ( Paging )

  • 해당 타입에 대해서 일정한 크기의 메모리 블락을 제공함
  • 해당 메모리 블락이 가득 찼을 시, 새로운 메모리 블락을 생성
    • 이때, 연속된 메모리 블락의 위치가 아닌 임의의 위치로 메모리 블락이 생성
  • 또한, 서로 다른 타입의 메모리 블락 크기가 일정치 않을 수 있음
    • 이로 인한, 외부 단편화 발생 가능

개선 방안

L1 캐시 메모리만큼 할당

  • 결국엔 메모리 블락만큼 할당해서 사용한 이유가 캐시 히트 확률을 높이기 위함임
  • 제공 되어질 메모리 블락의 최대치에 한계를 두어야 함
  • L1 캐시 메모리 사이즈를 넘어갈 시에는, 랜덤된 위치에 할당되어질 수 있음

메모리 주소 직접 관리

  • 자체적으로 메모리 주소를 관리해줘야 함
  • 서로 다른 객체들의 할당이 이루어졌을 때, 해당 타입의 메모리 블락을 할당하려고 하는데 이미 그곳에 다른 객체의 메모리 블락이 위치할 수도 있음
  • 그럴 경우, 직접 주소들을 확인할 수 있는 내부 구성이 필요함
  • 또한 이 과정에서 오버헤드가 많이 발생할 것으로 보임
  • 직접 주소를 관리하게 된다면
    • 해당 메모리 블락이 가득 찼을 시, 다음을 검사함
      • 현재 해당 연속 메모리 블락의 사이즈가 L1 캐시 사이즈를 넘었는지 확인
      • 다음 연속 메모리 블락을 생성할 위치가 다른 메모리가 사용 중이지 않거나 유효한지 확인
    • 위의 검사를 통과한 후에 해당 타입의 메모리 블락을 생성하여서 사용

Memory Pool과 연속 Memory Pool 객체가 필요

  • 메모리 풀과 그러한 메모리 풀들을 묶어서 타입별로 관리하는 객체가 필요함
  • 현재 Memory Pool 객체는 존재하지만, 해당 Memory Pool 내에서 연장할 수 있는 기능을 추가해야 함
  • 또한, 해당 Memory Pool들을 묶어서 타입 별로 관리할 수 있는 객체가 필요
  • 그러한 객체를 Memory Manager에서 최종 관리하는 방식으로 진행 필요

최종안

graph TB
A(Send Message for Creating Instance) --> B[MemoryManager]
B --> C{ Check the MemoryAddress existed }
C -- Yes --> D{ Check the MemoryPool }
C -- No --> E[ Create MemoryAddress for Instance ]
E --> D
D -- No Full --> F[ Create Instance ] --> DONE( DONE )
D -- Full --> G{ Check the adding MemoryPool' Size  }
G -- Bigger than L1 Cache --> H{ Check the adding address is valid }
G -- Smaller than L1 Cache --> I
H -- Alreay used --> J( Find new address ) 
J ----> H
H -- No used --> K( Create new MemoryPool ) --> I
I( Input address for MemoryPool ) --> F
  • MemoryManager : MemoryAddress를 관리, 인스턴스 생성 및 소멸 명령을 유저에게 받는 객체
  • MemoryAddress : 각 타입별로 MemoryPool들을 관리하는 역할
  • MemoryPool : 각 타입별로 지정된 크기만큼 메모리 블락을 할당 받고 연장 및 축소가 가능한 객체

# 자동변수 (Automatic Variables)

$@ : 현재 Target 이름
$* : 확장자가 없는 현재의 TARGET
$% : 대상의 이름 (해당 규칙 대상이 archive인 경우)
$< : 현재 Target이 의존하는 대상들 중 첫번째 파일
$? : 현재 Target이 의존하는 대상들 중 변경된 것들의 목록
$^ : 현재 Target이 의존하는 대상들의 전체 목록
$+ : $^와 비슷하지만, 중복된 파일 이름들까지 모두 포함

# Makefile 함수 설명

1. $(subst from, to, text)
-> subst : substitution의 약자
-> 이 함수가 하는 일은 text라는 문자열 중에서 from에 해당하는
    문자열을 to에 해당하는 문자열로 대체
-> EX
    -> $(subst ame, AmE, game jame)
    -> gAmE jAmE

2. $(wildcard pattern)
-> pattern : 일반적 파일 명이나, 경로가 포함된 파일 명칭 패턴
-> 특정 파일 명칭 패턴을 통해 원하는 종류의 파일들을 불러오는데 사용
-> 이때 사용한는 특수문자 : *
-> EX
    -> $(wildcard ../target/*.c)
    -> ../target/main.c   ../target/source.c

3. $(patsubst pattern, replacement, text)
-> subst와 유사
-> subst와 다른점은 from, to와 같이 특정 문자열을 대체하는 것이 아닌,
    특정 패턴을 대체한다는 것과 공백 문자로 각 단어를 구분한다는 점
-> 이때 사용되는 특수문자 : % (wildcard의 *와 동일)
-> EX
    -> $(patsubst %.c, %.o, x.cc.c bar.c)
    -> x.cc.o bar.o

4. $(notdir names... )
-> names에 입력된 파일명들 중에서 경로라고 판단되는 부분 제거
-> EX
    -> $(notdir forge/target.c name.c pms/fiya.mp3)
    -> target.c name.c pms/fiya.mp3

# 자동 prerequisite 생성
컴파일 시 -MD 옵션을 주면, d 확장자를 가진 파일이 생성된다.
이 파일에는 자동적으로 목적파일과 컴파일한 소스파일을 타켓으로 삼는
의존파일들을 담은 목록을 담고 있다.

# G++ 옵션
1. -MF  
-> 오브젝트 파일의 종속성을 검사하여 내용을 .dep 파일에 기록하고
    .cpp를 컴파일하여 .o 파일을 생성한다.

2. -MD
-> 오브젝트 파일의 종속성을 검사하여 내용을 .d 파일에 기록하고,
    .cpp를 컴파일하여 .o 파일을 생성한다.

 

# C++ 컴파일러로 설정
CXX = g++

# C++ 컴파일러 옵션
CXXFLAGS = -Wall

# 생성하고자 하는 실행 파일 이름
TARGET = example.exe

# 디렉토리 설정
SRC_DIR = ./src
OBJ_DIR = ./obj
DEP_DIR = ./dep
BIN_DIR = ./bin
HED_DIR = include/

# 디렉토리 안에 파일들 "경로/제목.형식자"들을 불러오기
SRCS = $(wildcard $(SRC_DIR)/*.cpp)
OBJS = $(subst $(SRC_DIR), $(OBJ_DIR), $(SRCS:.cpp=.o))
DEPS = $(OBJS:.o=.d)

# Target 설정
all : $(BIN_DIR)/$(TARGET)

# Object file 생성
$(OBJ_DIR)/%.o : $(SRC_DIR)/%.cpp
	$(info Step A : Complie Source file for Object file)
	$(info $< Compling...)
	$(CXX) $(CXXFLAGS) -I$(HED_DIR) -c $< -o $@ -MD
	$(info $< Complied...)

# Linking & Target file 생성
$(BIN_DIR)/$(TARGET) : $(OBJS)
	$(info Step B : Link Object files to Targer file)
	$(info -----   [$(TARGET)]   -----)
	$(info ----- Start Linking...-----)
	$(CXX) $(CXXFLAGS) $(LDFLAGS) $^ -o $@
	$(info -----     Linked...   -----)

.PHONY : name
name :
	echo SRCS is $(SRCS)
	echo OBJS is $(OBJS)
	echo DEPS is $(DEPS)

# Dependency files 생성
-include $(DEPS)
$(info Step C : Include Dependency files)

[ 동적할당 ]

- 메모리 영역의 종류

  >> 스택 : 지역 변수

  >> 데이터 : 전역, 정적, 외부(데이터) 변수

  >> ROM : 코드

  >> 힙 : 동적할당

 

- 특징

  >> 런타임 중에 대응 가능

  >> 사용자가 직접 관리해야함 ( 직접 해제 필요)

 

// 동적할당 설명
#include <stdio.h>

int main(){
// 메모리 영역
// 1. 스택 : 지역 변수
// 2. 데이터 : 전역, 정적, 외부(데이터) 변수
// 3. ROM : 코드
// 4. 힙 : 동적 할당

	int* pInt = (int*)malloc(100);
    	// malloc(N) : N개의 Byte에 대하 주소를 받음
        // 힙 영역에 N Byte 입력
        // malloc 는 Byte에 대한 주소값만 제시함
        // 이 주소값을 어떤 자료형으로 해석할 지는 미지수
        // (int*)로 하여서 주어진 주소값을 int값으로 볼 것이라고 제시
        
    free(pInt)
    	// Byte 할당 해제
	return 0;
}

 

[ 가변 배열 ]

일반적인 배열

  >> 메모리의 크기 고정

 

가변 배열

  >> 메모리의 크기가 고정되어있음

  >> 동적으로 메모리 크기를 변경시켜서 크기가 변할 수 있는 배열로 존재할 수 있게 해줌

 

// main.cpp
#include <stdio.h>
	// printf, scanf_s 등등을 사용하기 위한 라이브러리
#include <cstdlib>
	// free : 메모리 해제를 사용하기 위한

// 가변배열 구조체 선언
typedef struct _tabArr{
    int*    pInt;
    int     iCount;
    int     iMaxCount;
}_tArr;

// 가변배열 관련 함수 선언
void Init(_tArr* _pArr){
    _pArr->pInt = (int*)malloc(sizeof(int)*2);
    _pArr->iCount = 0;
    _pArr->iMaxCount = 2;
}	
	// 초기화 함수

void Release(_tArr* _pArr){
    free(_pArr->pInt);
    _pArr->iCount = 0;
    _pArr->iMaxCount = 0;
}	
	// 해제 함수

void Resize(_tArr* _pArr){
    _pArr->iMaxCount *=2;
    int* _pNew = (int*)malloc(_pArr->iMaxCount*sizeof(int));
    for(int I=0; I < _pArr->iCount ; ++I){
        _pNew[I] = _pArr->pInt[I];
    }
    free(_pArr->pInt);
    _pArr->pInt = _pNew;
    printf("Size UP : %d\n",_pArr->iMaxCount);
}
	// 크기 변경 함수

void PushBack(_tArr* _pArr, int Data){
    if (_pArr->iCount >= _pArr->iMaxCount){
        Resize(_pArr);
    }
    _pArr->pInt[_pArr->iCount++] = Data;
}
	// 데이터 입력 함수

int main(){
    _tArr S={};

    Init(&S);
    	// _tArr*의 타입으로 들어가야하기에, &(Reference)를 사용
    for (int i =0; i<10 ; i++){
        printf("%d\n",i);
        PushBack(&S,i);
        	// _tArr*의 타입으로 들어가야하기에, &(Reference)를 사용
    }
    for (int i=0; i<S.iCount; i++){
        printf("%d\n",S.pInt[i]);
        	// _tArr->pInt로 접근
    }
    Release(&S);
    	// _tArr*의 타입으로 들어가야하기에, &(Reference)를 사용
    return 0;
}

'C++ > 기본 문법' 카테고리의 다른 글

void 포인터  (0) 2022.06.30
포인터 && const  (0) 2022.06.30
배열 && 구조체  (0) 2022.06.30

[ void ] : 아무런 타입 없음

 

[ void 포인터 ] : 원본의 자료형이 정해지지 않은 포인터

- 특징

   >> 원본의 자료형이 정해지지 않음

   >> 어떠한 타입의 변수 주소를 다 저장할 수 있음

   >> 어떠한 타입으로 해석할 지가 정해져 있지 않기에, 역참조 불가능

   >> 어떠한 타입으로 주소 연산을 할 지가 정해져 있지 않기에, 주소 연산 불가능

 

#include <stdio.h>

int main(){
	void* pVoid = nullptr;
    
    { 
    	int a = 0;
        float b = 0.0f;
        double c = 0.;
        long long d = 0;
        
        pVoid = &a;
        pVoid = &b;
        pVoid = &c;
        pVoid = &d;
        	// 가능
            // 모든 타입의 변수 주소를 저장할 수 있음
        
        *pVoid; 
        	// 불가능 - error
            // 어떤 타입으로 해석할 지가 안 정해져 있음
            // 역참조 불가능
        
        pVoid + 1;
        	// 불가능 - error
            // 어떤 타입으로 변수 주소를 연산할 지가 안 정해져 있음
            // 주소 연산 불가능
    }
	return 0;
}

 

'C++ > 기본 문법' 카테고리의 다른 글

동적할당 && 가변배열  (0) 2022.07.01
포인터 && const  (0) 2022.06.30
배열 && 구조체  (0) 2022.06.30

+ Recent posts