Loading the catalog…
Loading the catalog…
[시나리오] 정수 편집 버퍼 EditBuffer. 내용이 커지면 eb_grow() 가 realloc 으로 버퍼를 키운다. "실행 취소(undo)"를 위해 eb_snapshot() 이 현재 상태를 undo[] 에 저장한다. [기대 동작] 스냅샷을 찍고 값을 많이 추가한 뒤, 정리(eb_free)에서 누수 없이 해제하고 정상 종료. [증상] eb_snapshot() 이 저장하는 것은 "그 시점의 data 포인터(원시 주소)"다. 이후 eb_grow() 가 realloc 으로 버퍼를 옮기면(주소 변경), 저장해 둔 스냅샷 포인터는 '이미 해제된 옛 블록'을 가리키게 된다(댕글링). 정리 시 eb_free() 는 현재 data 를 해제한 뒤 undo[] 의 옛 포인터들도 free 하는데, 그 블록들은 realloc 이 이미 해제한 것이라 → double free / invalid pointer 로 glibc abort(SIGABRT). #include <stdio.h> #include <stdlib.h> #include <string.h> #define MAX_UNDO 8 typedef struct { int *data; size_t len, cap; int *clipboard; int *undo[MAX_UNDO]; int undo_n; } EditBuffer; static void eb_init(EditBuffer *e) { e->cap = 4; e->len = 0; e->undo_n = 0; e->data = malloc(e->cap * sizeof(int)); if (!e->data) { perror("malloc"); exit(1); } /* data 바로 뒤에 놓이는 별도 할당. data 가 힙 맨 끝(top)이 아니게 되어 이후 realloc 이 제자리 확장 대신 '이동'을 택하게 만든다(→ 옛 블록 해제). */ e->clipboard = malloc(e->cap * sizeof(int)); if (!e->clipboard) { perror("malloc"); exit(1); } } static void eb_snapshot(EditBuffer *e) { if (e->undo_n < MAX_UNDO) e->undo[e->undo_n++] = e->data; } static void eb_grow(EditBuffer *e, size_t need) { size_t nc = e->cap; while (nc < need) nc *= 2; int *p = realloc(e->data, nc * sizeof(int)); if (!p) { perror("realloc"); free(e->data); exit(1); } e->data = p; e->cap = nc; } static void eb_push(EditBuffer *e, int v) { if (e->len == e->cap) eb_grow(e, e->len + 1); e->data[e->len++] = v; } static void eb_free(EditBuffer *e) { free(e->data); free(e->clipboard); for (int i = 0; i < e->undo_n; i++) { free(e->undo[i]); } e->undo_n = 0; e->data = NULL; } int main(void) { EditBuffer e; eb_init(&e); for (int i = 0; i < 3; i++) eb_push(&e, i); eb_snapshot(&e); for (int i = 0; i < 4000; i++) eb_push(&e, i); printf("len=%zu cap=%zu head=%d tail=%d\n", e.len, e.cap, e.data[0], e.data[e.len - 1]); eb_free(&e); printf("done\n"); return 0; } 이곳에 버그가 있습니다. 이를 gdb를 활용하여 디버깅을 해야하는게 이번 문제입니다. (make파일을 사용하여 gdb를 실행하였습니다.) 저는 우선 시나리오와 기대동작을 보았습니다. [시나리오] 정수 편집 버퍼 EditBuffer. 내용이 커지면 eb_grow() 가 realloc 으로 버퍼를 키운다. "실행 취소(undo)"를 위해 eb_snapshot() 이 현재 상태를 undo[] 에 저장한다. [기대 동작] 스냅샷을 찍고 값을 많이 추가한 뒤, 정리(eb_free)에서 누수 없이 해제하고 정상 종료. 그 후, EditBuffer 구조체의 데이터들을 살피고, gdb로 실행하여 오류가 나는 곳을 포착하였습니다. 입력 \> make gdb NAME=10_realloc_dangling \> run 출력 \>Program received signal SIGABRT, Aborted. ... \>0x0000555555555483 in eb_free (e=0x7fffffffe050) at challenges/10_realloc_dangling/bug.c:85 위 에러를 보고 eb_free() 함수 중 버퍼의 undo 포인터에 접근할 때 오류가 나는 것을 확인하였습니다. 이를 해결하기 위해 undo에 값을 넣는 방식을 먼저 찾아보았습니다. eb_snapshot() 함수에서 undo에 data의 주소를 넣고있었습니다. static void eb_snapshot(EditBuffer *e) { if (e->undo_n < MAX_UNDO) e->undo[e->undo_n++] = e->data; } 현재로선 이상하지 않은 코드입니다. 다만, data의 주소가 변경된다면 댕글링 포인터가 됩니다. 이를 확인하기 위해 각 함수에서 printf로 data의 주소를 출력해 봤습니다. 그 결과, eb_grow()를 실행한 뒤 data의 주소가 바뀌는 것을 확인했습니다. eb_init()에는 data가 힙의 맨 끝(top)이 아니게 되어 이후 realloc이 제자리 확장 대신 이동을 택하게 만든다 는 주석이 있었고, 실제로 eb_grow()에서 data에 realloc을 사용하고 있었습니다. 요약하자면, undo에 data의 주소를 저장하였지만, data를 realloc하는 과정에서 확장이 아닌, 이동을 하여 기존 data주소를 갖고 있던 undo는 댕글링 포인터가 되어, free할 때 오류가 발생하게 되었습니다. 이를 해결하기 위해 저는 undo에 data의 주소를 담는 것이 아닌, data를 복사하여 새로 할당받아두어 undo에 저장하는 방식으로 해결하였습니다. if (e->undo_n < MAX_UNDO) e->undo[e->undo_n++] = e->data; -> if (e->undo_n < MAX_UNDO) { int* newData = malloc(sizeof e->data); newData = memcpy(newData, e->data, sizeof e->data); e->undo[e->undo_n++] = newData; } malloc으로 데이터를 복사할 공간을 생성. memcpy로 data를 새 메모리에 복사. undo에 새 메모리의 주소를 저장. 아래는 최종 코드입니다. #include <stdio.h> #include <stdlib.h> #include <string.h> #define MAX_UNDO 8 typedef struct { int *data; size_t len, cap; int *clipboard; int *undo[MAX_UNDO]; int undo_n; } EditBuffer; static void eb_init(EditBuffer *e) { e->cap = 4; e->len = 0; e->undo_n = 0; e->data = malloc(e->cap * sizeof(int)); if (!e->data) { perror("malloc"); exit(1); } /* data 바로 뒤에 놓이는 별도 할당. data 가 힙 맨 끝(top)이 아니게 되어 이후 realloc 이 제자리 확장 대신 '이동'을 택하게 만든다(→ 옛 블록 해제). */ e->clipboard = malloc(e->cap * sizeof(int)); if (!e->clipboard) { perror("malloc"); exit(1); } } static void eb_snapshot(EditBuffer *e) { if (e->undo_n < MAX_UNDO) { int* newData = malloc(sizeof e->data); newData = memcpy(newData, e->data, sizeof e->data); e->undo[e->undo_n++] = newData; } } static void eb_grow(EditBuffer *e, size_t need) { size_t nc = e->cap; while (nc < need) nc *= 2; int *p = realloc(e->data, nc * sizeof(int)); if (!p) { perror("realloc"); free(e->data); exit(1); } e->data = p; e->cap = nc; } static void eb_push(EditBuffer *e, int v) { if (e->len == e->cap) { eb_grow(e, e->len + 1); } e->data[e->len++] = v; } static void eb_free(EditBuffer *e) { free(e->data); free(e->clipboard); for (int i = 0; i < e->undo_n; i++) { free(e->undo[i]); } e->undo_n = 0; e->data = NULL; } int main(void) { EditBuffer e; eb_init(&e); for (int i = 0; i < 3; i++) eb_push(&e, i); eb_snapshot(&e); for (int i = 0; i < 4000; i++) eb_push(&e, i); printf("len=%zu cap=%zu head=%d tail=%d\n", e.len, e.cap, e.data[0], e.data[e.len - 1]); eb_free(&e); printf("done\n"); return 0; }
What RADAR observed and classified to build this opportunity. It is what the source published, not a verification that the offer is still active.
[W5] C언어로 디버깅 하기 10.realloc_dangling. [시나리오] 정수 편집 버퍼 EditBuffer. 내용이 커지면 eb_grow() 가 realloc 으로 버퍼를 키운다. "실행 취소(undo)"를 위해 eb_snapshot() 이 현재 상태를 undo[] 에 저장한다. [기대 동작] 스냅샷을 찍고 값을 많이 추가한 뒤, 정리(eb_free)에서 누수 없이 해제하고 정상 종료. [증상] eb_snapshot() 이 저장하는 것은 "그 시점의 data 포인터(원시 주소)"다. 이후 eb_grow() 가 realloc 으로 버퍼를 옮기면(주소 변경), 저장해 둔 스냅샷 포인터는 '이미 해제된 옛 블록'을 가리키게 된다(댕글링). 정리 시 eb_free() 는 현재 data 를 해제한 뒤…
Open source