대표적으로 했던 삽질들을 모아보았다.
리스트를 만들 때 매개변수 노드인 head를 이중 포인터로 받지 않았음.
왜 이중포인터로 받아야 하는가? : 매개변수는 값을 그대로 가져오는 것이 아니라, 값을 복사한 변수를 가져오는 것임. head 노드의 가리키는 곳을 변경하여야 하기 때문에 head 그 자체의 주소가 필요했음 그렇기 때문에 이중 포인터로 매개변수를 받아야 함.
huge 테스트를 해보면 알 수 있다. 끔찍한 시간이 걸림. 이를 해결하기 위해 노드에 tail이라는 노드 포인터를 추가하였다.
tail 노드는 head노드만 가지고 있으며, 해당 리스트의 끝 노드를 가리키고 있다.
만약 헤드 노드가 지워지면(해당 프로젝트에서 다른 노드가 지워질 일은 없다. 무조건)다음 노드에게 tail의 주소를 물려주는 식으로 코드를 작성함.
이거에 진짜 많은 시간이 날라감(일주일 정도 날라간듯... 멍청한 내 머리야!!!)
gnl에서 1은 한줄이 정상적으로 출력됨, 0는 파일을 다 읽었음 -1은 예외사항인데, 이 0을 어떻게 판별하는가가 많이 난감했다. 이때 여러 삽질들을 했었음
이런 파일을 예시로 들어보겠다.
생각한 지 하루만에 고친 방식. 버퍼사이즈가 1일때만 정상적으로 작동한다.
이 방식때문에 진짜 몇날 며칠을 고생했다. 실제로 작성한 코드를 보자.
ret는 read함수의 return value이다. 당연히 ret가 0보다 크다는 것은 읽어올 값이 더 존재한다는 것이기에 1을 리턴한다. 중요한 것은 else부분인데, 만약 ret가 0이고(-1은 자체적으로 예외처리로 빠지니 나올 값은 0밖에 없다) 리스트의 사이즈가 0보다 크다는 것은, 서브 버퍼에 아직 출력할 값이 남아있다는 것이다. 그러므로 1을 반환, 리스트의 사이즈도 1보다 작다면, 이는 리스트에 아무것도 남지 않았다는 것이기에 0을 반환한다.
해당 테스트 결과에서는 실제로 정상적인 값을 출력하였다. 한번 테스트 케이스를 바꿔보자.
어???왜 빈줄 하나가 더 생기는거지? gnl로직으로는 B때 return value가 0이 나와야한다.
이유는 썩 어렵지 않았다. 코드 상 마지막에 무조건 0이 출력될 수 있는 구조로 짜여져 있었다.
실제로 return값을 판별하는 gnl_return 함수가 리스트를 날리는 gnl_clear함수보다 위에 있다. 정상적인 방법으로는
문자열 읽기->문자열복사->한줄읽은데까지 문자열 삭제->마지막 라인인지 검사 이 순서여야 하는데,
문자열 읽기->문자열복사->마지막 라인인지 검사-> 한줄읽은데까지 문자열 삭제 이 순서로 처리되어 있어 아무것도 없는 빈 한줄이 추가된다는 것이다.
그러면 저 함수 선언 위치를 바꾸면 되지 않을까? 라고 생각했지만 오산이었다, 이미 리스트 삭제가 진행된 뒤라, 마지막 라인보다 한줄 적게 출력되었다.
마지막 라인을 검출하는 방식을 너무 복잡하고 생각하고있었다, read의 리턴값이 몇이고... 리스트의 남은 값이 몇이고....솔직히 다 떠나서 불변하는 진리가 있었는데 말이다.
"\n이 검출되지 않으면 그 라인이 끝"
그 생각으로 코드를 수정하였다. 리스트를 지우되, \n을 검출하면 1을 리턴하는 방식으로말이다.
int gnl_clear(t_list** head)
{
int i;
int size;
int ret;
i = 0;
ret = 0;
if (head && *head)
{
size = gnl_lst_size(*head);
while (i < size)
{
if ((*head)->content == '\n')
{
gnl_remove_head(head);
ret = 1;
break;
}
gnl_remove_head(head);
i++;
}
}
return (ret);
}
그랬더니...
WA!! 잘된다!!! 유레카!!!
이 쉬운걸 여태 생각을 못했다니... 지금 와서 생각하면 지난 며칠이 너무 바보같았다.진짜...
이제 진짜로 메모리 누수 검사를 진행할 수 있겠다... 신난다!!!