본문 바로가기
Activities/공부

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

by 코드포휴먼 2020. 10. 15.

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

 

댓글