Activities/공부

[42Seoul] Libft 과제 - 함수 ft_lstnew와 ft_lstadd_front의 차이 이해하기

코드포휴먼 2020. 10. 15. 05:29

prefix로 lst가 붙은 ft함수들은 연결리스트 관련 함수들이다.

연결 리스트는 노드들의 집합이므로 실제로는 노드의 구조체만 정의하면 된다.

따라서 과제에서는 헤더에 아래와 같이 노드의 구조체를 정의하도록 안내된다.

typedef struct	s_list 
{ 
	void	*content;
	struct s_list	*next;
}	t_list;

구조체 노드로 정의한 연결리스트를 나중에 이중포인터로 받아오는데, 처음에는 이해가 되지 않아 확실하게 정리한다.

 

 

먼저 ft_lstnew의 함수 코드는 아래와 같다. ft_lstnew를 이용하여 새 노드를 만들 수 있다.

#include "libft.h"

t_list  *ft_lstnew(void *content)
{
        t_list *new;

        new = (t_list *)malloc(sizeof(t_list));
        if (new == NULL)
                return (NULL);
        new->content = content;
        new->next = NULL;
        return (new);
}

 

 

 

새 노드만 만드는 것이고, 새 노드는 다른 함수에서 연결리스트에 추가하게 될 것이다. 

노드를 추가하는 모습은 다음과 같다.

원본 출처 코딩도장 https://dojang.io/mod/page/view.php?id=646

 

 

ft_lstadd_front의 함수 코드는 아래와 같다. 

맨 앞쪽에 리스트를 만들어 추가하므로 다음 주소값만 담는 것이 아니라 시작 지점도 알아야 한다는 것이 중요하다.

ft_lstadd_front함수는 리턴값이 없다. 따라서 마지막에 *lst = new를 통해서 헤더가 첫 노드를 잡을 수 있게 한다.

이 작업을 위해 이중포인터 파라미터를 갖는다.

첫 번째 매개변수 **lst는 첫 번째 노드의 포인터다. 이중포인터인 것은 *lst에 저장되는 값이 연결된 리스트들의 첫값을 담고 있기 때문이다.

#include "libft.h"

void    ft_lstadd_front(t_list **lst, t_list *new)
{
        if ((lst == NULL) || (new == NULL))
                return ;
        new->next = *lst;
        *lst = new;
}

 

이중포인터로 넘겨받은 **lst가 첫번째 노드를 잡고 있다.

예시로 설명해보자면 연결리스트가 NODE1->NODE2->NODE3로 구성되어있다면

**lst는 NODE1의 주소이며, *lst는 NODE1다.

new->next = *lst; 의 작업을 통해 new라는 리스트 뒤에 *lst에 저장되어있던 NODE1이 연결되었다.

그러나 아직 연결리스트의 시작노드가 new인 것은 아니다.

*lst = new; 의 작업을 통해 연결리스트의 시작노드가 new인 것을 명시하기 위해 *lst에 new를 담는다.

 

 


이중포인터의 활용법을 쉽게 이해하기 위해 LaPiscine 때 작성했던 ft_range와 ft_ultimate_range를 비교해보겠다.

(각각은 C07의 ex01, ex02의 과제였다.)

#include <stdlib.h>

int		*ft_range(int min, int max)
{
	int	i;
	int	*arr;

	if (min >= max)
		return (NULL);
	arr = (int*)malloc(sizeof(int) * (max - min));
	i = 0;
	while (min < max)
	{
		arr[i] = min;
		min++;
		i++;
	}
	return (arr);
}

 

 

ft_range에서는 배열을 반환하지만 ft_ultimate_range에서는 배열을 반환하지 않는다.

따라서 ft_ultimate_range에서도 이중포인터를 받는다. int 배열인 **range를 받는다.

이때 값을 바꿔주기 위해 ft_ultimate_range는 생성한 배열을 매개변수로 받은 *range에 넣어준다.

만약 파라미터가 int *라면 참조를 통해 int값은 바꿔줄 수 있겠으나 배열은 넘겨주지 못한다.

#include <stdlib.h>

int		ft_ultimate_range(int **range, int min, int max)
{
	int	i;
	int	*arr;
	
	if (min >= max)
		return (NULL);
	arr = (int*)malloc(sizeof(int) * (max - min));
    if (!arr)
        return (-1);
	i = 0;
	while (min < max)
	{
		arr[i] = min;
		min++;
		i++;
	}
	*range = arr;
	return (i);
}

 

 

Moulinette의 메인함수에서 함수 사용시에 int*타입의 range를 함수에 매개변수로 &range 같은 식으로 넘겨줄 것이다.

아래의 실행 예를 본다면 새로 만든 정수형 포인터 변수 d를 함수에 넘겨주기 위해 &d를 사용했다.

#include <stdio.h>

int main(void)
{
	int a = 5;
	int b = 9;
	
	// ft_range
	int *c = ft_range(a, b);
	for (int i=0;i<4;i++)
		printf("%d ", c[i]);
	
	printf("\n");
	// ft_ultimate_range
	int *d; 
	printf("%d\n", ft_ultimate_range(&d, a, b));
	for (int i=0;i<4;i++)
		printf("%d ", d[i]);
    
	// printf("%ls\n", ft_range(a, b));   // 아무것도 안 나옴
	// printf("%ls\n", d);   // 아무것도 안 나옴
	return (0);
}


// Console
5 6 7 8
4
5 6 7 8

 

위의 메인함수에서 만약 ft_ultimate_range 함수에 &d가 아닌 d로 넘겨준다면 아래와 같은 오류가 발생한다.

ft_ultimate_range를 정의할 때 파라미터로 이중포인터를 받는다고 정의했기 때문이다.

/workspace/C_Test/src/main.c: In function ‘main’:
/workspace/C_Test/src/main.c:58:35: warning: passing argument 1 of ‘ft_ultimate_range’ from incompatible pointer type [-Wincompatible-pointer-types]
   58 |  printf("%d\n", ft_ultimate_range(d, a, b));
      |                                   ^
      |                                   |
      |                                   int *
/workspace/C_Test/src/main.c:24:30: note: expected ‘int **’ but argument is of type ‘int *’
   24 | int  ft_ultimate_range(int **range, int min, int max)
      |                        ~~~~~~^~~~~
5 6 7 8
세그멘테이션 오류 (core dumped)

 


참고로 함수 매개변수로 포인터변수를 받을 때엔 넘겨줄 때도 주소값을 넘겨줘야 한다.

예시를 들기 위해 LaPiscine 때 작성했던 ft_swap을 들고 왔다.

(C01의 ex02의 과제였다.)

void	ft_swap(int *a, int *b)
{
	int temp;

	temp = *a;
	*a = *b;
	*b = temp;
}

ft_swap은 void형 함수임에도 불구하고 함수를 실행하면 a와 b가 호출한 함수 단에서도 바뀌어 있다.
이유는 포인터변수로 받아와 직접 대입하여 값을 바꿔줬기 때문이다.

 

 

아래 메인함수에서 호출은 ft_swap(&a, &b)로 했다. 

#include <stdio.h>

int main(void)
{
	int a = 5;
	int b = 7;
	
	printf("%d\n", a);   // 5
	printf("%d\n", b);   // 7
	
	/* warning: format ‘%p’ expects argument of type ‘void *’, but argument 2 has type ‘int’ */
	// printf("%p\n", a);
	// printf("%p\n", b);
	
	/* warning: format ‘%s’ expects argument of type ‘char *’, but argument 2 has type ‘int *’ */
	// printf("%ls\n", &a);
	// printf("%ls\n", &b);
	
	printf("%p\n", &a);   // 0x7fff5f63c940
	printf("%p\n", &b);   // 0x7fff5f63c944
	
	/* warning: passing argument 1 of ‘swap’ makes pointer from integer without a cast */
	// swap(a, b);
	swap(&a, &b);
	printf("%d\n", a);   // 7
	printf("%d\n", b);   // 5
	
	return (0);
}

 


출처

연결 리스트

코딩도장 "74.1 연결 리스트 구조체 만들고 사용하기", dojang.io/mod/page/view.php?id=645

코딩도장 "74.2 노드 추가 함수 만들기", dojang.io/mod/page/view.php?id=646