Get_Next_Node 삽질노트 3. 자체 Q&A

Mr.뉴트리아·2020년 10월 24일
0

이 문서는 제 삽질과 해당 프로젝트를 풀기 위한 과정을 기록한 문서입니다. 코딩과 문서를 병행으로 작성하기에, 맞지 않는 지식이 난무할 수 있어 정답 참고용으로는 부적합하다는 것을 알아주세요.

시간이 난다면 칼 뉴포트가 쓴 베스트셀러 "딥 워크"를 읽어보는 것을 추천한다.

프로젝트가 어느정도 진행되었다. 중간점검의 느낌으로, 코드를 짜는 동안 했던 나의 고민과 내가 해결한 방식을 적으려고 한다.

정적 변수는 왜 필요한 것인가?

버퍼의 사이즈가 4라고 가정해보자. 문자열 "abcd\nefg"에서 \n문자를 뽑아내려면 read함수를 2번 호출해야한다. 2번의 호출이면 \n까지 읽어오는 것이 가능하지만 하나의 문제가 발생한다.
버퍼의 사이즈는 해당 파일에서 가져올 문자의 개수이다. 위 경우는 4이니, 파일에서 4문자씩 값을 뜯어온다. 그렇기에 \n 다음에 오는 efg세 문자까지 읽어온다는 것이다. 어찌저찌 efg를 무시하고 abcd\n을 리턴했지만, 사용자가 gnl을 또다시 호출한다면? 이미 read를 두번 호출한 시점에서 파일에 모든 문자를 읽어버렸기 때문에, 문자를 더 이상 읽어올 수가 없다. 그렇기때문에 현재 읽어온 버퍼의 값을 미리 저장하는 임시 버퍼가 필요하다. 이 임시 버퍼는 gnl함수가 여러번 호출되더라도 전에 호출했던 값을 저장하고 있어야 한다. 그래야 위 같은 상황에서 efg를 가지고 있을테니 말이다. 초기화가 한번만 되는 정적(Static) 변수는 우리가 원파는 임시 버퍼의 조건을 제대로 만족한다.

정적 변수의 타입은 무엇으로 해야하는가?

몇가지의 고민과 실패로 인해 버퍼가 가져야 할 조건을 생각할 수 있었다.

1.딥따 큰 상수배열

맨처음 생각했던 아주 무식한 방식이다. 버퍼에서 얼마의 사이즈가 들어올지 모르니 일단
딥따 큰 배열을 선언하고 보자는 방식이었다. 아주 수많은 문제가 있었기에(버퍼의 값이 1이 들 어오는 상황이라던가, 딥따 큰 배열보다 큰 딥따딥따 큰 버퍼가 들어온다던가)
즉, 버퍼는 길이가 가변적이여야 한다.

2.동적 배열

두번째 생각했던 방식이다. read함수의 올바른 결과값은 읽어온 버퍼의 문자열의 개수를 리턴한다. 리턴받은 사이즈로 임시 버퍼를 동적 할당하고, 버퍼의 값을 저장하는 것이다. 이 방식의 문제는 버퍼의 값을 새로 읽어올 때마다 임시버퍼를 해제하고, 다시 사이즈를 할당해주어야한다는 것이다. 만약에 예를 들어보자. 1~10000까지 \n없이 한줄로 쭉이어진 파일이 있다 치자. 이때 버퍼의 사이즈가1이라면, 해당 방식은 이런 로직일 것이다.

1.read함수를 통해 값을 읽어옴. 1읽어왔음.
2.임시 버퍼 사이즈를 1로 동적할당하여 읽어온 값을 저장
3.\n이나 eof를 만나지 못해 다시한번 read를 통해 2를 읽어옴.
4.임시 버퍼의 사이즈가 1이기 때문에, 사이즈를 2로 재할당하고 읽어옴.

버퍼 사이즈를 이렇게 할당, 재할당하는 모습은 그리 좋아보이지는 않는다. 그렇다고 한번에 할당을 크게 해주면 첫번째 상황에서 나온 문제와 직면할 수 있기도 하고. 또 하나의 문제 임시버퍼의 저장 이후 처리가 귀찮다는 것이다.
"abcd\nefgh\nijk"라는 문자열이 있고, 버퍼의 사이즈가 100이라 치자. gnl호출 시 한번에 read함수 호출에 문자열 전체가 임시 버퍼에 저장될 것이다. gnl을 처음 실행할 때는 "abcd"
두번째 실행때는 "efgh", 세번째 실행때는 "ijk"가 출력되어야 하는데, 이때 임시 버퍼에서 "어디에서부터 어디까지 출력되어야 하는지"를 정하는 것이 여간 귀찮은 일이 아니다. int형 정적변수를 사용해서 인덱스를 관리하는것은 보너스 점수를 받지 않겠다는것이고, 한줄 읽은 값을 제외하고 재할당하는 방식은 맨처음 생각했던 문제와 크게 다를 바가 없다
즉, 저장과 다음 호출의 처리가 간편해야 한다는 것이다.

그렇게 생각하여 나온 것이 바로 아래 해결책이다.

3.리스트 활용.

리스트는 길이도 가변적인 뿐더러, 문자 하나하나가 노드이기에 문자를 잘라내는 처리도 간편하다.
아래는 리스트를 활용한 대략적인 코드 로직이다.

int		get_next_line(int fd, char **line)
{	
	static t_list		*g_buffer = 0;
	char			buffer[BUFFER_SIZE];
	int 			ret;

	while(1)
	{
		if ((ret = read(fd,buffer,BUFFER_SIZE)) <= 0)
		{
			//버퍼에 남은 문자열이 있을 때
			if(gnl_lst_size(g_buffer) > 0)
			{
            			//매개변수의 1일 경우, 노드의 헤드에서부터 \n까지 잘라냄.
				*line = gnl_strdup(g_buffer,'\n',gnl_lst_find(g_buffer,'\n'));
				
				gnl_clear(&g_buffer,1);
				return (1);
			}
            		//매개변수가 0일 경우 그냥 리스트 클리어 함수
			gnl_clear(&g_buffer,0);
			return (0);
		}
        	//임시 버퍼에 버퍼에 담긴 값을 저장함.
		gnl_backup(&g_buffer,buffer,ret);
		if (gnl_lst_find(g_buffer,'\n') != -1)
		{
        		//임시 버퍼에 담긴 값을 *line에 복사하여 넘겨준다(\n전까지만)
			*line = gnl_strdup(g_buffer,'\n',gnl_lst_find(g_buffer,'\n'));
			gnl_clear(&g_buffer,1);
			return (1);
		}
	}
}

리스트 길이가 커질수록 노드를 추가에 드는 시간이 많이 늘어나기 때문에,
환형 리스트로 변경할 예정이다.

대략적인 코드는 이런데, 아직 갈길이 멀긴 멀다. 더 나은 방법이 있나 생각도 해야하고,
예외처리도 해야하고, 다른 fd가 들어왔을 때의 상황도 생각해야하고, norm도 처리해야한다.
그래도 오늘 이정도 발전이 있었다는 걸로 만족해야지.

끝으로, 프랭크 시나트라의 my way 가사를 출력해보았다.
노래와 같이 들어봤으면 한다
Frank Sinatra - My Way

profile
뉴트리아는 가시쥐과에 속하는 설치류의 일종이다. 오랫동안 뉴트리아과의 유일종으로 분류했지만, 현재는 가시쥐과에 포함시킨다. 늪너구리, 해리서 또는 코이푸라고도 한다. 뉴트리아는 스페인어로 수달을 의미하고, 출생지 남미에서는 이 종류를 코이푸라고 부른다.

0개의 댓글