[04.16/week05]TIL

CHO WanGi·2025년 4월 16일

KRAFTON JUNGLE 8th

목록 보기
30/89

오늘 한줄 요약

진도 빼기에 급급하여 본질을 잃어버림.

새로 배우게 된 것

  • C언어(재귀 vs 반복문)
  • 메모리 Layout
  • Operand Overloading

C언어(재귀 vs 반복문)

  • 문제 요구사항
    연결리스트의 원소들을 역순으로 배치하기

  • 반복문 풀이

void IterativeReverse(ListNode **ptrHead)
{	
	if (*ptrHead == NULL){
		return ;
	}
	// 임시 연결리스트 메모리 할당
	LinkedList *temp_ll = malloc(sizeof(LinkedList));
	temp_ll->head = NULL;
	temp_ll->size = 0;

	ListNode *cur = *ptrHead;

	// 새 리스트에 역순으로 노드 삽입
	while (cur != NULL)
	{
		insertNode(temp_ll, 0, cur->item);
		cur = cur->next;
	}

	// 원본 리스트 노드 해제
	cur = *ptrHead;
	while (cur != NULL)
	{
		ListNode *next = cur->next;
		free(cur);
		cur = next;
	}

	// 헤드 갱신
	*ptrHead = temp_ll->head;

	free(temp_ll);
}

동작 원리는 간단하다.
저장용 임시 연결리스트를 생성하고, 원본 리스트를 순회하며 그 원소를 index 0번(head) 자리에 집어 넣는다.
순회가 끝나면 원본 리스트의 헤드 포인터의 값을 임시 리스트의 헤드로 변경한다.

  • 재귀 풀이
void RecursiveReverse(ListNode **ptrHead)
{
    // base case: 0개 또는 1개 노드일 때는 그대로
    if (*ptrHead == NULL || (*ptrHead)->next == NULL)
        return;

    ListNode *rest = (*ptrHead)->next;

    // 1. 나머지 노드들 재귀적으로 뒤집기
    RecursiveReverse(&rest);

    // 2. 현재 노드를 맨 뒤로 보내기
    (*ptrHead)->next->next = *ptrHead;  // 뒤에 있던 노드가 나를 가리킴
    (*ptrHead)->next = NULL;            // 나는 끝이 됨

    // 3. head 갱신
    *ptrHead = rest;
}

재귀는 현재 노드를 맨 뒤로 계속 보내고, 더이상 보낼 노드가 없을 때 다시 재귀적으로 돌아오는 원리.

딱 보아도 재귀의 코드가 더 간결하다.
또한 반복문 로직은 추가적인 연결리스트 선언 및 초기화로 Malloc 함수를 호출하고
이로 인한 free 함수 호출 로직을 구현해야 했다.
malloc 함수는 상당히 느린 함수 이기 때문에, 로직의 간단함이나, 성능적인 측면에서
이번 문제는 재귀로 푸는 것이 더 좋은 방법이 었구나 느낄 수 있었다.

Memory Layout

출처 : https://responsibility.tistory.com/136

그 유명한 스택 오버플로우는 무한 루프 등의 이유로 스택이 힙의 영역을 침범하는 것을 말한다.
malloc을 통해 동적 메모리를 할당하는 방식은 heap 영역에서 이루어지고, stack영역에는 함수 호출 등이 쌓이게 된다.

Operand Overloading

일단 쉽게 동일한 연산을 진행해도, 피연산자에 타입에 따라 다르게 바뀌는 것을 이야기한다.

  • C언어 CASE
int * a = 0
int b = 0
char ** c = 0

++a;
++b;
++c:

하고 a, b, c를 찍으면 동일하게 1씩 증가시키는 연산이지만
int pointer인 a는 4
int인 b는 1
char Pointer의 pointer인 c는 8이 나온다.

  • Python CASE
a = "123"
b = "123"
print(a + b)

a = 123
b = 123
print(a + b)

동일한 플러스 연산자이지만, 문자열 타입은 문자열 연결로 작동하여 "123123" 이라는 문자열을 출력할 것이고,
정수형 타입은 두 수의 합을 구하는 연산을 수행하여 246 이라는 정수를 출력할 것이다.

  • JS CASE
    이게 그 유명한 banana 밈과 연관이 있는데
("b" + "a" + + "a"+ "a").toLowerCase() 

를 콘솔에 찍으면 "banana"가 나온다.
앞에 "b"+"a"는 문자열 연결로 "ba"가 되지만,

+"a"같은 경우는 동일한 플러스 연산자가 단항연산자로 판단, 숫자로 변환을 시도하게 된다.

숫자가 아닌 값을 변환하기에 Not a Number, NaN이 되고

1) 'ba' + (+'a') + 'a'
2) 'ba' + NaN + 'a'
3) 'baNaN' + 'a'

이런식으로 흘러가서 banana가 나온것.

이제 바나나밈 보면 Operand Overloading 이라고 있어보이게 이야기 할 수 있다.

공부하다가 아쉬운 점

C언어 개념이 먼저긴 한데, 문제를 다 못풀었다는게...
토-일 외박이 크긴 크다... 절대적인 시간도 모자랐고 집중력도 바닥이었다.

profile
제 Velog에 오신 모든 분들이 작더라도 인사이트를 얻어가셨으면 좋겠습니다 :)

0개의 댓글