Linked list with C (1)

혀누·2021년 12월 8일
0

우당탕탕C

목록 보기
1/5

C 언어는 F1 자동차와 같다. 엄청나게 빠르지만, 아무나 운전할순 없다.

Intro.

리스트를 선언하면 가상머신으로 메모리를 알아서 할당하고 찾아주는 파이썬과 같은 언어와 달리, 우리의 츤데레 C는 직접 리스트의 원소들을 연결해주어야 한다.

파이썬으로 프로그래밍을 처음 접했던 나로서는 '왜 연결해줘야하지?'와 같은 기본적인 질문부터 생겼다. 그냥 List.append() 하면 되던데..?

이에대한 간단한 답은(물론 간단하지않다.) 컴퓨터 구조상의 특성과 C 가 특히 빠른 이유에서 찾을 수 있었다. 기계와 좀 더 가깝기때문이지!

파이썬에서 리스트를 선언할때 언어 자체에서 알아서 해주던걸 C에서는 내가 해야한다. 다시말해 내가 '특정 변수를 선언하고 이를 이미 생성된 리스트에 넣고싶다.'의 과정에서 마지막 원소 다음에 내가 새로 넣는 이 변수가 올거야! 라는 내용을 컴퓨터가 어떻게 알게되는가? 를 이해해야한다.

Pointer.

혼자 C 를 공부할때 이해가 될듯말듯하며 나를 괴롭혔던 '포인터'개념은 어찌저찌 RB tree 를 짜면서 계속 쓰고, 보다보니 뭔가 이해가 된듯한 착각을 주곤한다. 역시 노이만 선생님은 대단하다.

"수학은 이해하는게 아니라 익숙해지는 것입니다."

아무튼 포인터란 C에서 변수를 선언하는 것과 같은 맥락이다. 예를들어 정수형 변수를 선언하고 그 값으로 3을 집어넣는다면, a라는 그릇에 3이라는 값을 담는다는 의미다.

이걸 포인터에 동일하게 적용하면 3이 담겨있는 a 라는 메모리의 주소값을 포인터 pa라는 그릇에 담는 다는 의미가 된다. 간략한 코드를 보도록하자.

void main() {
	int a;
    	a = 3;
        int *pa;  //포인터형 변수는 *을 붙여서 선언한다.
        pa = &a;  //a 의 주소값을 직접 찾을순없으니 대신 찾아주는 &를 사용한다.
}

코드까지 보고나면 '그래서 그 주소값 가지고 뭘 어떻게 할건데?'라는 생각이 들 수 있다. 다음코드를 보자.

#include <stdio.h>

int main() {

	int a = 3;
    	int *pa = &a;
        printf("%d", *pa);
       
       
      	return 0;

}

자 이 *pa 변수를 출력하면 어떤값이 나올까?
놀랍게도 3이다!

<설명 그림으로 정리>

그런데 뭐하러 이렇게 귀찮게 메모리의 주소값이니 뭐니를 이용하는걸까?
그 진가는 "malloc 함수를 이용한 동적할당"을 배우면 드러나게된다.

Dynamic Memory Allocation.

동적할당에 대해 이야기위해 우선 메모리는 어떻게 구성되어있는지 살펴보자.

여기서 우리는 stack영역과 heap영역에 주목할 것이다. 함수를 선언하고 실행될때 쓰이는 각종 지역변수들과 데이터들은 stack 영역에 쌓이게 된다.

stack 영역은 해당 함수가 종료되면 사용되었던 메모리들을 덮어쓸수있다는 특징이 있다. 이를통해 계속해서 재사용이 가능해지며 메모리 누수를 걱정할 필요가 없다. 하지만 정적할당을 통해 메모리를 할당하기때문에 프로그램을 실행하는 과정에서 늘리거나 줄일수 없다.

이제 우리가 원하는 동적할당을 하게되는데, 동적할당은 Heap 영역에 하게 된다. 바로 이 부분에서! malloc함수가 역할을 한다. 왜냐하면 방금전에 선언한 함수로 접근하는 메모리 영역은 Stack 이었으니까 말이다. (=>내가 선언하는 함수로는 heap에 접근할 수가 없다.) 또한 Heap영역은 프로그램이 종료되기 전까지 메모리가 유지되는 영역이기때문에 추후 언급하게될 free() 함수를 해서 할당을 풀어줘야 다시 사용할 수 있게 된다.

malloc 함수는 Heap영역에 동적으로 메모리를 할당하고 그 메모리의 헤더 주소값을 리턴해주는 함수다. 아래는 사용 예시 코드이다.

int main() {

    int *new_pt;
    new_pt = malloc(sizeof(int));
	
    return 0;
}

위 코드에서 sizeof 부분은 내가 할당하고싶은 메모리의 크기를 나타낸다. int이므로 4바이트를 할당한다고 생각하면 되겠다.

여기까지 왔다면 이런 의문이 생길수 있다. '음... 물론 heap영역에 할당한다는 차이점이 있지만 이게 왜 "동적" 할당인거지? array[20] 이런 식으로 선언하는 정적 할당이랑 다른게 뭔데?'

이름 그대로에서 느낄 수 있듯이, 정적 할당은 컴파일 과정에서 딱! 박아놓고 시작한다. 나 프로그램 진행되는 동안 이만큼만 쓸거야! 하고 말이다.

하지만 동적할당은 프로그램의 실행과정에서 다양한 상황과 크기의 메모리 할당을 할 수 있게된다. (on runtime) 경우에 따라 이만큼을 할당할수도, 저만큼을 할당할수도 있는 것이다.

Struct.

뭔가 순서가 뒤바뀐것 같지만, 'struct'는 쉽게말해 우리에게 익숙한 파이썬의 Class 의 초기버전(?)이라고 생각하면 마음이 한결 편하다. 여러 변수형들을 조합해서 새로운 타입의 변수를 만들어내주는 친구다. 바로 예시 코드로 보자.

struct Node {

    int key;
    int value;

};
// 혹은 typedef 를 사용하여 이후에 더 짧게 선언할 수 있다. 
typedef struct Node {

	int key;
    	int value;

}node;

node 라는 구조체는 int형 자료가 두개 조합된 형태이다. 여기서 구조체의 크기를 계산할 수 있게되는데 현재 int 두개이므로 8바이트의 크기를 가진다. 앞서 얘기한 새로운 타입의 변수라고 한 이유는 아래 코드를 보면 알 수 있다.

int first_node;
struct Node first_node;
// typedef 사용시
node first_node;

int 형 변수를 선언하듯이 struct Node 형 변수를 선언한 셈이다.
그리고 양심이 찔리긴 하지만 파이썬이나 자바의 class 처럼 구성요소를 이루는 값을 집어넣을 수 있다. (객체지향프로그래밍이 안되는 C에서 자꾸 class라고 이야기하는게 마음이 불편하지만 어쩔 수 없습니다 :> )

그렇다면 struct안의 특정 변수에 값을 집어넣고 싶을땐 어떻게하면 될까? 파이썬처럼 하면되나?


typedef struct Node {
	
    int key;
    int data;


}Node;

int main() {

	
    Node new_node;
    new_node.key = 1;
    new_node.data = 30;
    return 0;

}

이러면 에러난다.

c에서 사용하는 struct의 성분을 접근하는 기호는 '->' 이다.

typedef struct Node {
	
    int key;
    int data;


}Node;

int main() {

	
    Node new_node;
    new_node->key = 1;
    new_node->data = 30;
    return 0;

}

이렇게 써야한다!

쓰다보니 길어져서 linked list 는 아직 시작도 못했지만 일단 마무리하고 다음 포스트에서 다루는 걸로....
profile
개발자(물리)

0개의 댓글