본문 바로가기

CS/CS Book

[컴퓨터 시스템 딥다이브] ch02 C프로그래밍 심화 part02(2.7~2.9)

💡이 글은 컴퓨터 시스템 딥다이브(수잰 J. 매슈스, 한빛미디어 2024)를 읽고 적은 글입니다.

 

구조체

구조체는 서로 다른 데이터 타입을 하나로 묶을 수 있다.

typedef struct student
{
	size_t   age;
 	char     name20
}              t_student;

구조체 내부 필드에 동적할당하여 쓰는 경우

코드에서 head를 반복문 안에서 아래와 같이 쓰는 것을 보고 저렇게 하면 처음 head에 넣은 값(여기서는 10)이 head가 앞에 있지 않을 텐데 어떻게 되는 거지 하고 궁금하여 코드를 적어보았다.

temp->data = i;
temp->next = head;
head = temp;

#include <stdio.h>
#include <stdlib.h>

typedef struct node
{
        int          data;
        struct node *next;
}  t_node;

void printNode(t_node *node);

void printList(t_node *node)
{
        t_node *temp = node;

        printf("In printList()\\n");

        while (temp)
        {
                printNode(temp);
                temp = temp->next;
        }
}

void    printNode(t_node *node)
{
        printf("node->data is %d\\n", node->data);
};

int main()
{
        t_node  *head;
        t_node  *temp;

        head = NULL;
        head = (t_node *)malloc(sizeof(t_node));

        if (head == NULL)
        {
                printf("Error malloc\\n");
                exit(1);
        }
        head->data = 10;
        head->next = NULL;

        for (int i = 0; i < 2; i++)
        {
                temp = (t_node *)malloc(sizeof(t_node));
                if (temp == NULL)
                {
                        printf("Error malloc\\n");
                        exit(1);
                }
                temp->data = i;
                temp->next = head;
                head = temp;
        }

        printList(head);
        //while(1);
        return 0;
}

혹시 메모리를 잃어서 leak이 날 수도 있지 않을까 했지만 잘못 생각하였었다.

stack처럼 마지막에 들어온 값이 가장 앞에 위치하게되고 이전에 할당된 node들은 뒤로 밀리는 식이다.

  1. 노드 1개만 있는 경우 (head에 동적할당된 노드)
    • 10
  2. 노드 2개가 있는 경우
    • 0 → 10
  3. 노드 3개가 들어온 경우
    • 1 → 0 → 10

leak check

  • gcc -fsanitize=leak node_test.c -o node_test

node_test.c

#include <stdio.h>
#include <stdlib.h>

typedef struct node 
{
	int			data;
	struct node *next;
}  t_node;

void	freeList(t_node *node);
void	printNode(t_node *node);
void	printList(t_node *node);

// for leack check
void	check_leak(void);

void	freeList(t_node *node) 
{
	t_node	*cur_node;
	t_node	*next_node;

	cur_node = node;
	while(cur_node)
	{
		next_node = cur_node->next;
		cur_node->next = NULL;
		free(cur_node);
		cur_node = next_node;
	}
}

void	printNode(t_node *node)
{
	printf("node->data is %d\\n", node->data);
};

void	printList(t_node *node)
{
	t_node *temp = node;

	printf("In printList()\\n");

	while (temp) 
	{
		printNode(temp);
		temp = temp->next;
	}
}

void   check_leak(void)
{
		system("leaks a.out");
}

int main()
{
	t_node	*head;
	t_node	*temp;

	head = NULL;
	head = (t_node *)malloc(sizeof(t_node));

	if (head == NULL)
	{
		printf("Error malloc\\n");
		exit(1);
	}
	head->data = 10;
	head->next = NULL;

	for (int i = 0; i < 2; i++)
	{
		temp = (t_node *)malloc(sizeof(t_node));
		if (temp == NULL)
		{
			printf("Error malloc\\n");
			exit(1);
		}
		temp->data = i;
		temp->next = head;
		head = temp;
	}

	printList(head);
	// freeList(head);
	atexit(check_leak);
	// while(1);
	return 0;
}

before free

after free

다행히 freeList()가 잘 동작하고 있다.

그 외(컴파일 과정 위주)

이번 장은 구조체 심화, IO함수, 일부 C 고급 기능들(switch, void * 등 등)에 대해 소개된다.

설명의 빌드업이 좋다. 구조체 필드의 경우 배열, 포인터가 들어가 있는 부분만 호출된 함수에서 값을 바꾼 것이 호출한 쪽에서도 적용되는 것을 이해하기 쉬운 단계부터 설명해 주며 연결리스트를 추상화한 모습이 친절하여 이해하기 좋았다.

C언어 배운지 얼마 안 되었을 때 이 책의 C언어 설명 부분을 참고하면 이해하기 좋았을 거 같다는 생각이 들었다.

컴파일 과정과 각 과정에 대한 명령어가 같이 제공되어 테스트하며 이해하기 좋았다

컴파일 과정과 순서

  • 프리컴파일 → 컴파일 → 어셈블리 → 링크 편집
  • 프리컴파일
    • gcc -E node_test.c
    • 전처리기 지시문 확장
  • 컴파일
    • gcc -S node_test.c
    • C 프로그램 소스코드를 기계 어셈블리 코드로 변환
    • node_test.c → node_test.s
  • 어셈블리
    • gcc -c node_test.c
    • objdump -d node_test.c
    • 어셈블리 코드를 재배치 가능한 2진 객체 코드(node_test.o)로 변환한다.
  • 링크 편집
    • gcc node_test.c
    • ./a.out
    • objdump -d a.out
    • 마지막으로 실행 가능한 단일 파일(a.out) 생성

컴파일 과정 관련 명령어 및 결과

프리컴파일 과정을 볼 수 있는 명령어

  • gcc -E [program_name].c > out
    • 전처리 결과를 out 파일에 저장!
  • gcc -E 옵션에 대한 설명

  • 전처리기 지시문이 확장된 모습
    • 90줄이던 소스코드가 프리컴파일러 단계를 거치니 1900여 줄이나 나오게 된다.

컴파일, 기계 어셈블리 코드 보기

  • gcc -S node_test.c

어셈블리

  • gcc -c node_test.c
  • objdump -d node_test.o > node_test_objdump_out

링크 편집

  • gcc node_test.c
  • ./a.out
  • objdump -d a.out

욕심으로는 어셈블리 코드도 다 넣고 싶었지만 너무 길어져서 결과의 시작과 끝 부분 캡처로 간략하게 대체하여 본다.

 

컴파일 과정명령어와 함께 알려주어서 처음으로 각 과정별로 어떤 결과가 나오는지 실습해 볼 수 있어 유용했다.

그 외에 컴파일 관련 오류 케이스들과 “나만의 라이브러리 만들어보기”를 하며 “라이브러리 2진 형태로 만들기”라든지 여러 팁들이 꾸준히 나와서 잘 모르던 과정들도 배우고 명령어도 알 수 있어 이번에도 좋은 책이라 생각 든다.