
천천히 하니까 할 수 있자나...!

RED⇒ 루트를 블랙으로 변경

부모와 할아버지의 색 교환 + 할아버지 기준 오른쪽 회전

부모 기준 왼쪽 회전 + CASE3 방식

REDRED부모와 부모 형제 노드 색 변경 + 할아버지 색변경 ⇒ 할아버지 노드에서 다시 확인 시작

일단 삽입되었을 때, RB TREE의 속성을 깨는 경우를 분기처리 하면 다음 과 같다.
결국 RB트리의 삽입 함수는 삽입 위치 탐색 -> 삽입 -> RB트리 속성에 맞게 재조정
의 단계로 진행된다.
new_node->parent == t->nil이면new_node->color = RBTREE_BLACK삽입 방향 == 부모 방향일 때 (예: 부모가 왼쪽 자식이고, 삽입된 노드도 왼쪽 자식)부모와 조부모의 색 교환
color_t temp = parent->color;
parent->color = grand_parent->color;
grand_parent->color = temp; ```
회전 ⇒ 조부모노드 기준**
left_rotateright_rotate삽입 방향 != 부모 방향일 때left_rotateright_rotate삽입 노드 기준 부모&삼촌과 조부모 색깔 변경
parent->color = RBTREE_BLACK;
uncle->color = RBTREE_BLACK;
grand_parent->color = RBTREE_RED;
조부모 노드에서 다시 확인
Fix-up이제 분기처리에 맞게 구조체의 포인터를 사용해서 구현하면 된다.
void rbtree_fix_up(rbtree *t, node_t *z){
node_t *uncle;
node_t *parent = z->parent; // 새 노드의 부모
node_t *grand_parent = parent->parent; // 새 노드의 조부모
// int is_uncle_right = 0;
int is_straight_left = 0;
int is_straight_right = 0;
// 예외처리
if (parent == t->nil || grand_parent == t->nil) return;
// 1. 루트 노드 속성 위반
if (t->root == z){
z->color = RBTREE_BLACK; // 루트노드로 들어간 z를 새 노드로 변경
return;
}
// 0. 삼촌 노드 방향 Tracking
if (grand_parent->left == parent) {
uncle = grand_parent->right;
} else {
uncle = grand_parent->left;
}
// 삽입 방향 체크
if((parent->left == z) && (grand_parent->left == parent)){
is_straight_left = 1;
}
if((parent->right == z) && (grand_parent->right == parent)){
is_straight_right = 1;
}
// 새노드 z의 부모 색깔 확인
if(parent->color == RBTREE_BLACK){ // 부모가 껌정이면 굳이 재조정 필요 없으니 return
return;
}
// CASE 1(부모 RED + 삼촌 RED)
if(uncle != t->nil && uncle->color == RBTREE_RED){
// parent & uncle와 grand_parent의 색 변경
parent->color = RBTREE_BLACK;
uncle->color = RBTREE_BLACK;
grand_parent->color = RBTREE_RED;
// 조부모 노드에서 다시 확인
rbtree_fix_up(t, grand_parent);
} else{ // (부모 RED + 삼촌 BLACK)
// CASE 3 (일직선)
if(is_straight_left){ // 왼쪽 일직선
// 1. 부모와 조부모 색 교환
color_t temp = parent->color;
parent->color = grand_parent->color;
grand_parent->color = temp;
// 2. 회전
right_rotate(t, grand_parent);
} else if(is_straight_right){
// 1. 부모와 조부모 색 교환
color_t temp = parent->color;
parent->color = grand_parent->color;
grand_parent->color = temp;
left_rotate(t, grand_parent);
} else {
// CASE 2 (꺾인 선)
if(parent->left == z){
// 왼쪽 삽입인 경우
right_rotate(t, parent);
z = z->left;
}
else
{ // 오른쪽 삽입
left_rotate(t, parent);
z = z->right;
}
// CASE 3 적용
parent = z->parent;
grand_parent = parent->parent;
color_t temp = parent->color;
parent->color = grand_parent->color;
grand_parent->color = temp;
if (z == parent->left) {
right_rotate(t, grand_parent);
} else {
left_rotate(t, grand_parent);
}
}
}
t->root->color = RBTREE_BLACK;
}
GNU 컴파일러의 모음으로 GNU Compiler Collection의 약자이다.
결국 컴파일 도구인데,
컴파일 과정은 알다 시피 전처리 -> 컴파일 -> 어셈블 -> 링크 단계로 구성된다.
GCC컴파일러 역시 위 4단계를 다 거치기 때문에 옵션을 통해 조절이 가능하다
-o [파일명] [*.c]gcc -o result.out main.c ⇒ main.c 의 실행파일이 result.out에 저장-E-S*.s가 생성-c[파일명][*. c]gcc -c ft_isalnum.cD [매크로상수명]=[값] :gcc -D BUFFER_SIZE=42 : BUFFER_SIZE 라는 매크로 상수의 값을 42로 설정한다.이번에 rbtree lab을 진행하면서 헤더파일과 여러개의 c 파일을 실행 파일로 만들어야 했다.
물론 MAKEFILE 이 제공되었지만, 이게 없었다면 어떻게 해야할까...? 라는 생각이 들었다.
gcc -c rbtree.c
gcc -c driver.c
gcc -c test-rbtree.c
gcc -o test.out rbtree.o driver.o test-rbtree.o
특히나 이 방식은 특정 파일이 수정되었다면, 다시 컴파일 해주어야 한다.
이걸 간편하게 만들어주는 것이 바로 Makefile
(make clean)
make test

hello.o 와 app.out 만 다시 빌드void delete_rbtree(rbtree *t) {
// TODO: reclaim the tree nodes's memory
// 재귀적으로 진행?, 루트에서 양쪽 자식 확인후 있다면 재귀적으로 free?
if (t == NULL) return;
node_t *root = t->root;
if (root != t->nil) {
if (root->left != t->nil) { // 왼쪽 서브트리 있다면
t->root = root->left; // heap-use-after-free
delete_rbtree(t);
}
if (root->right != t->nil) { // 오른쪽 서브트리 있다면
t->root = root->right;
delete_rbtree(t);
}
free(root);
}
// 마지막 루트 기준 nil과 트리 해제
if (t->root == root) { // 가장 처음 들어온 루트일 때만
free(t->nil);
free(t);
}
}
그 MacOS는 valgrind 지원이 종료되어, Clang/GCC 내장 기능인 AddressSanitizer를 사용했다.
clang -fsanitize=address -g -o rbtree test.c rbtree.c
./rbtree
결과값은
==7546==ERROR: AddressSanitizer: heap-use-after-free on address 0x6020000000f8 at pc 0x000100e0a1c8 bp 0x00016eff6ab0 sp 0x00016eff6aa8
READ of size 8 at 0x6020000000f8 thread T0
#0 0x000100e0a1c4 in delete_rbtree rbtree.c:73
#1 0x000100e0a158 in delete_rbtree rbtree.c:71
#2 0x000100e09310 in main test.c:59
#3 0x000199ae4270 (<unknown module>)
문제 상황
heap-use-after-free
free로 메모리할당 받은 것을 해제를 했는데, 이후에 그 메모리를 읽으려고 하고 있다.
원인
t->root = root->left; , t->root = root->right;
지금 코드를 잘 보면,
root를 가리키던 트리를 root->left 또는 root->right로 갱신한 후, 다시 재귀적으로 delete_rbtree(t) 호출한다.
이 상태에서 마지막에 free(root)를 하게 되면 이미 헤제된 메모리를 재귀 도중 다시 접근하기 때문에 발생
void delete_node(rbtree *t, node_t *node) {
if (node == t->nil) return;
delete_node(t, node->left);
delete_node(t, node->right);
free(node);
}
void delete_rbtree(rbtree *t) {
if (t == NULL) return;
delete_node(t, t->root);
free(t->nil);
free(t);
}위 코드를 테스트 돌릴때 처음에 레드 노드 다음에 레드 노드가 오는 그런 결과가 등장
알고보니 CASE 3에서 회전을 조부모 노드를 넘겨준게 아니라 부모 노드를 넘겨주어 잘못된 결과가 등장
이를 조부모를 수정해서 완성했다.

