순수 C로 짠 Garbage Collector
글쓴이: denmark114 / 작성시간: 목, 2014/09/25 - 1:59오후
동적할당된 메모리를 링크드 리스트에 싸그리 모아서 GC_FREE 라는 매크로로 필요할 때 한번에 해제하는 방식입니다. 용도나 스코프별로 컬렉터를 여러개 선언해서 각자 적절한 곳에서 GC_FREE 해줄수도 있구요, 작은 프로그램에서는 아래 예시 프로그램에서처럼 그냥 컬렉터 하나에 다 모았다가 프로그램 종료시에 한번에 전부 해제해 줄수도 있습니다. valgrind로 다양하게 테스트해본 결과 메모리 누수는 발생하지 않았고 잘 작동했습니다만, 혹시 제가 발견 못한 버그가 있다면 수정해주시면 감사하겠습니다. 소스는 헤더파일 2개가 전부구요 압축파일로도 첨부해놨습니다. 아래는 코드입니다.
linked_list.h
struct linked_list { void *elem; struct linked_list *next; }; #define ITERATOR(LIST_NAME) _iter_##LIST_NAME #define DECLARE_LIST(NAME) struct linked_list *NAME, *ITERATOR(NAME) #define INITIALIZE_LIST(NAME) \ NAME = (struct linked_list *)malloc(sizeof(struct linked_list)); \ NAME->elem = NULL; \ NAME->next = NULL; \ ITERATOR(NAME) = NAME #define PUSH(TYPE, X, LIST) \ do { \ ITERATOR(LIST) = ITERATOR(LIST)->next = (struct linked_list *)malloc(sizeof(struct linked_list)); \ ITERATOR(LIST)->elem = malloc(sizeof(TYPE)); \ *(TYPE *)ITERATOR(LIST)->elem = X; \ ITERATOR(LIST)->next = NULL; \ } while (0) #define DESTROY_LIST(LIST) \ do { \ struct linked_list *l; \ ITERATOR(LIST) = LIST; \ do { \ l = ITERATOR(LIST)->next; \ free(ITERATOR(LIST)->elem); \ free(ITERATOR(LIST)); \ } while ((ITERATOR(LIST) = l) != NULL); \ } while (0)
garbage_collector.h
#include "linked_list.h" #define DECLARE_GC(NAME) \ DECLARE_LIST(NAME); \ static void _throw_away_##NAME() { \ ITERATOR(NAME) = NAME; \ do { \ free(ITERATOR(NAME)->elem == NULL ? NULL : *(void **)ITERATOR(NAME)->elem); \ } while ((ITERATOR(NAME) = ITERATOR(NAME)->next) != NULL); \ DESTROY_LIST(NAME); \ } #define INITIALIZE_GC(NAME) INITIALIZE_LIST(NAME) #define GC_ALLOC(TYPE, TARGET, LEN, GC_NAME) \ do { \ TARGET = (TYPE *)malloc((LEN) * sizeof(TYPE)); \ PUSH(void *, TARGET, GC_NAME); \ } while (0) #define GC_FREE(NAME) _throw_away_##NAME()
테스트 및 예시 프로그램
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <limits.h> #include <time.h> #include "garbage_collector.h" struct vector { int *row; int len; }; void run_test(unsigned num_tests); char *rand_str(unsigned max_len); char *concat(const char *str_1, const char *str_2); struct vector rand_vector(unsigned len); struct vector add_vector(const struct vector v_1, const struct vector v_2); void print_vector(struct vector v); DECLARE_GC(gc); enum { STR_LEN_MAX = 20, VECTOR_LEN = 8, NUM_TESTS = 1000 }; int main() { INITIALIZE_GC(gc); srand(time(NULL)); run_test(NUM_TESTS); GC_FREE(gc); return 0; } void run_test(unsigned num_tests) { char *str[2]; struct vector v[2]; int i; for (i = 0; i < num_tests; i++) { printf("TEST %d\n\n", i + 1); str[0] = rand_str(STR_LEN_MAX); str[1] = rand_str(STR_LEN_MAX); printf("%s + %s = %s\n\n", str[0], str[1], concat(str[0], str[1])); v[0] = rand_vector(VECTOR_LEN); v[1] = rand_vector(VECTOR_LEN); printf(" "); print_vector(v[0]); printf("\n+ "); print_vector(v[1]); printf("\n= "); print_vector(add_vector(v[0], v[1])); puts("\n\n"); } } char *rand_str(unsigned max_len) { char *str; int len; int i; len = rand() % max_len + 1; GC_ALLOC(char, str, len + 1, gc); for (i = 0; i < len; i++) { str[i] = rand() % 26 + 'a'; } str[i] = '\0'; return str; } char *concat(const char *str_1, const char *str_2) { char *str; int str_1_len, len; int i; str_1_len = strlen(str_1); len = str_1_len + strlen(str_2); GC_ALLOC(char, str, len + 1, gc); for (i = 0; i < str_1_len; i++) { str[i] = str_1[i]; } for (; i < len; i++) { str[i] = str_2[i - str_1_len]; } str[i] = '\0'; return str; } struct vector rand_vector(unsigned len) { struct vector v; int i; GC_ALLOC(int, v.row, len, gc); for (i = 0; i < len; i++) { v.row[i] = rand() % SHRT_MAX; } v.len = len; return v; } struct vector add_vector(const struct vector v_1, const struct vector v_2) { struct vector v; int i; if (v_1.len != v_2.len) { puts("different vector length"); GC_FREE(gc); exit(EXIT_SUCCESS); } GC_ALLOC(int, v.row, v_1.len, gc); for (i = 0; i < v_1.len; i++) { v.row[i] = v_1.row[i] + v_2.row[i]; } v.len = v_1.len; return v; } void print_vector(struct vector v) { int i; putchar('['); for (i = 0; i < v.len; i++) { printf("%d%s", v.row[i], i == v.len - 1 ? "" : ", "); } putchar(']'); }
File attachments:
첨부 | 파일 크기 |
---|---|
gc.zip | 1.75 KB |
Forums:
DevC++ 로 프로젝트를 만들어 봤습니다.
ㅇ_ㅇ;; 물론. 내용은 C 로 동일합니다.
이와는 별개로. 윈도우7과 DevC++ 문제일 수 있는데요.
윈도우7 에서는 DevC++ 컴파일 후. 일정시간동안 .exe 파일에 생성 및 실행. 삭제가 되지 않는 현상이 있었습니다.
----------------------------------------------------------------------------
젊음'은 모든것을 가능하게 만든다.
매일 1억명이 사용하는 프로그램을 함께 만들어보고 싶습니다.
정규 근로 시간을 지키는. 야근 없는 회사와 거래합니다.
각 분야별. 좋은 책'이나 사이트' 블로그' 링크 소개 받습니다. shintx@naver.com
ㅇ_ㅇ;;
DevC++ 이라면 윈도우용 gcc를 이용할텐데 제 윈도우7 노트북에서는 gcc로 컴파일 시 이상이 없습니다.. 신기하네요 ㅎ
혹시 DevC++ 오리지날 이용하시나요? 그런 경우라면 gcc 구버전이 들어가 있어서 그럴수도 있을까요..? Orwell DevC++ (http://orwelldevcpp.blogspot.nl/) 라고 최근까지 활발하게 업데이트 되고 있는 버전도 있습니다.
Bloodshed Dev-C++
사용하고 있습니다.
----------------------------------------------------------------------------
젊음'은 모든것을 가능하게 만든다.
매일 1억명이 사용하는 프로그램을 함께 만들어보고 싶습니다.
정규 근로 시간을 지키는. 야근 없는 회사와 거래합니다.
각 분야별. 좋은 책'이나 사이트' 블로그' 링크 소개 받습니다. shintx@naver.com
garbage_collector.h 수정 및 최적화
본래 garbage_collector.h 에서 불러오던 linked_list.h 의 리스트는 보이드 포인터 할당과 타입 캐스팅을 이용하여 어떤 타입의 자료든 무작정 쑤셔넣을수 있습니다만, 가비지 컬렉터 전용으로 쓰려니 void * 를 저장하기 위해 매번 void ** 를 할당하는 비효율적인 코드가 되었더군요.. 결국 이 링크드리스트를 가비지컬렉터 전용으로 뜯어고쳐서 garbage_collector.h 에 하나로 합쳐보았습니다. 불필요한 메모리 할당도 없앴고요. 사용법도 이전처럼 DECLARE_GC, INITIALIZE_GC 나눌 필요 없이 전역에 MAKE_GC 한번만 해주면 더 간편히 이용할 수 있습니다.
garbage_collector.h
//편집, 코드 한줄 빼먹어서 추가하고 원본첨부
동적 할당된 메모리를 일괄로 해제할 수 있는 기능으로
동적 할당된 메모리를 일괄로 해제할 수 있는 기능으로 보이네요.
작고 크기에 원하는 기능을 잘 수행하도록 만들었네요.
아파치의 apr 라이브러리의 memory pool 기능을 살펴보세요.
좀 더 진보된 기능을 살펴보실 수 있을꺼예요.
최종 목적이 Garbage Collector 구현일지는 모르지만,
현재 구현물은 Garbage Collector 하고는 개념이 좀 다릅니다.
내 블로그: http://unipro.tistory.com
^^
최근에 자바를 배우면서 객체를 자유자재로 동적할당하고 리턴하고 하는점이 너무 맘에들어 C로 비슷하게 따라해보려다 나온 발상입니다. C에서 문자열 합치기나 벡터연산을 저런식으로 했다간 원래 큰일나겠죠^^.
근본적으로 가비지 컬렉터가 될 수 없다는건 만들고나서, 이 글 올리고 나서, 인터넷 뒤져보고 생각해보니 이해하고 있습니다. 무엇보다 자동으로 객체의 쓸모없어짐을 추적하는 기능이 없네요.
시스템을 최대한 건들지 않고 포터블하게 가비지컬렉팅을 구현할 방법을 찾아보던중에 reference counting 이란게 있더라고요. c++에서 shared_ptr를 통해서 사용할 수 있다는데, 문제는 저걸 c로 구현하려고 하니 c++에서처럼 스코프를 벗어나는걸 자동으로 추적할수도 없고, 연산자 오버로딩도 불가능해서 문제가 되는것 같습니다. 매크로를 써서 메모리 할당, 값 대입, 디레퍼런싱, 닫는 중괄호까지 다 재정의하는 방법도 생각해봤습니다만 이건 아니겠죠^^.
결국 진짜 garbage collector를 만드려면 아주아주 얇은 VM을 만들어서, 마치 자바 머신에서 가비지컬렉팅만 떼어논것처럼?, 그 위에서 돌아가는 프로세스의 객체를 실시간으로 추적하는 방식이 있겠다고 결론은 지었는데, 이건 뭔가 바로 나올만한건 아닌거 같습니다 ㅎ.
boehm-gc 라는 것이 있습니다.
boehm-gc 라는 것이 있습니다.
실시간으로 추적하는 방법은 너무 비싸서
실시간으로 추적하는 방법은 너무 비싸서 사용하지않습니다.
보통 주기적으로 gc를 실행하거나, 일정 사용량을 넘어가면 한다던지, 여러가지 복잡한 알고리즘들이 있습니다.
대부분의 gc는 비쌉니다. 그래서, 왠만하면, 불필요한 memory object 스캔횟수를 줄일려고 노력합니다.
결국, 대부분의 VM들은 C/C++로 짜여졌습니다. process를 만들어서, 추적하지는않고,
대부분, 트랩을 걸어놓거나, 특정 키워드 실행때마다 gc를 동작시킵니다.
가장흔히 쓰이는 알고리즘은 generational GC입니다.
좀더 자세한 내용은 다음의 문서를 참조하세요.
http://en.wikipedia.org/wiki/Tracing_garbage_collection
댓글 달기