들어가며.
이 글은 42서울의 라피신 과정중 가장 고민했던 함수였던 strlcat에 대한 설명이고, 특히나 '반환값'에 대한 고찰이 담겨있습니다.
strlcat에 대한 설명은 man strlcat 이나 다른 블로그에도 잘 설명이 되어있습니다. 참고하시길 바랄게요.
중간중간 이해하기 어려워도 끝까지 정독하신 후 또 다시 읽어보시고 궁금한 점이나 틀린 부분은 댓글남겨주시면 감사하겠습니다.
왜 이 글을 쓰는지?
strlcat을 짜는 것은 어렵지 않았습니다.
하지만 제가 가장 어려웠던 것은 strlcat의 반환값을 왜 그렇게 짜야하는지, 반환값이 의미하는 바가 무엇인지가 가장 궁금했습니다.
그리고 공식문서와 동료들과의 토론, 제 고찰이 담긴 결론을 냈습니다.
이 글은 이 결론을 설명하는 글입니다.
strlcat
말 그대로 두 string을 연결한 길이를 반환하는 함수입니다.
하지만 strlcat은 항상 두 string이 연결된 정확한길이를 반환하지 않습니다.
* (strncat은 strlcat과 달리 origin에서 n만큼의 길이만 떼어내 dest에 이어붙입니다. 그리고 이어붙인 문자열 자체를 반환합니다.)
반환값의 정의
자 그럼, man strlcat에서 반환값에 대한 정의를 확실하게 확인해봅시다.
해석) "strlcat의 반환값은 dst에 str를 더한 길이를 의미합니다. 좀 혼란스러울수 있겠지만, '잘림방지'를 위해 간단히 수행되었습니다."
여기서 "잘림방지"에 주목해야합니다. 이 키워드를 머리속에 가지고 있고, strlcat의 구현코드를 한번 보시죠.
특히, 아래 return부분 코드에 주목해주세요.
아래는 제가 실제로 통과한 strlcat의 코드입니다.
unsigned int ft_strlcat(char *dest, char *src, unsigned int size)
{
unsigned int p_dest;
unsigned int p_src;
unsigned int dest_len;
unsigned int src_len;
dest_len = ft_strlen(dest);
src_len = ft_strlen(src);
p_dest = dest_len;
p_src = 0;
while (src[p_src] && (p_src + dest_len + 1) < size)
{
dest[p_dest] = src[p_src];
p_dest++;
p_src++;
}
dest[p_dest] = '\0';
if (dest_len < size)
{
return src_len + dest_len;
}
else
{
return src_len + size;
}
}
(피신과정이시면 제 코드를 그대로 사용하는 것 보다 이해한 뒤 본인의 손으로 직접만들어보세요.)
- dest_len이 size(세번째 매개변수)보다 작을때 (src_len + dest_len) 을 반환
- dest_len이 size(세번째 매개변수)보다 클때 (src_len + size)을 반환
의문점
의문점이 있었습니다.
- 공식문서에 나온 반환값의 의미는 dest_len + src_len 이니까 이 값을 그대로 반환 하면 될텐데 왜 저렇게 구분지어 반환을 해야할까?
- 특히 왜 src_len에 size를 더해야할까?
size
이 의문을 해결하기 위해서는 "size(세번째 매개변수)"의 의미를 알고있어야합니다.
두개의 string과 함께 전달되는 세번째 매개변수 size는 두 문자열이 합쳐질 공간의 크기를 의미합니다.
개발자는 strlcat를 이용할때 두 문자열과 함께 문자열이 합쳐질 공간의 길이를 미리 정해놓고 값을 넘겨주는 것이죠.
상황별 예시
상황1)
그런데 만약 문자열 dest의 길이가 size보다 크다면 어떻게 될까요?
쉽게 풀어쓴다면, strlcat은 정해진 길이의 공간(size)에 dest + src 를 해야하는 함수인데 이 공간이 dest조차도 담기지 않는 공간이면 어떻게 될까요?
"아무런 수행을 하지 않습니다."
상황2)
만약 문자열 dest의 길이가 size보다 작다면 어떻게 될까요? (dest_len < size)
size에 dest는 무조건 다 들어갈 수 밖에 없고 이제 dest와 size의 차이만큼 중 '\0'자리(1개)를 뺀 만큼, 즉 (size-dest-1) 만큼의 src의 글자가 dest에 붙을 것입니다.
dest의 길이가 size보다 1만큼 더 작다면 그 자리는 '\0'자리이기 때문에 아무런 수행을 하지 않을 것이고,
2이상부터는 src의 문자열중 한개의 문자라도 dest뒤에 붙을 것입니다.
여기서, "한개의 문자라도 dest에 붙는다는 것"이 중요합니다.
dest_len < size 인 순간, dest는 src의 문자가 붙기 때문에(차이가 1인 경우는 편의상 제외하고) dest는 "오염"되기 시작합니다.
개발자에게 있어 문자열이 오염되어있는지, 아무런 수행을 하지 않았는지는 매우 "중요한 일"입니다.
그리고 공교롭게도 return을 구분할때 if (dest_len < size) 라는 조건이 들어가죠.
반환조건
다시한번 리턴값의 대한 공식문서의 설명을 보겠습니다.
해석) "strlcat의 반환값은 dst에 str를 더한 길이를 의미합니다. 좀 혼란스러울수 있겠지만, '잘림방지'를 위해 간단히 수행되었습니다."
여기서의 "잘림"는 제가 말한 dest의 "오염"을 의미합니다.
다시한번 반환조건을 보겠습니다.
... 생략
if (dest_len < size)
{
return src_len + dest_len;
}
else
{
return src_len + size;
}
... 생략
- dest_len < size : dest는 다 들어가있고 src의 문자가 하나라도 옮겨지게 될 상황에서는 (src_len + dest_len) (실제 두 문자열이 잘림없이 정상적으로 합쳐지게 될 때의 길이)를 반환합니다.
- dest_len > size : 아무런 수행도 하지 않는 상황에서는 src_len + size를 반환합니다.
- dest_len == size : 어차피 dest_len과 size가 같기때문에 위 두 조건 어디에 속해있어도 값이 같습니다. 그래서 src_len + size를 반환해도 무방합니다.
반환값들의 의미
그렇다면 이제 이 반환값들의 "의미" 즉, 이 함수를 만든사람은 개발자가 이 반환값들을 어떻게 사용하라고 이렇게 정의를 해 놓은 것일까요?
(여기서부터는 제 개인적인 고찰이 담겨있습니다. 이것보다 더 좋은 결론을 찾을 수 없었습니다.)
개발자는 이제 아래의 코드처럼 이용하면 dest에 아무런 수행을 하지 않았는지, 아니면 한문자라도 옮겨갔는지 확인이 가능합니다.
...생략
if (ft_strlcat(dst, src, 10) <= ft_strlen(src) + ft_strlen(dst))
{
// 아무런 수행을 하지 않음.
// dst가 오염되어있지 않음.
}
else
{
// dst가 오염됨을 확인가능.
}
...생략
ft_strlcat에 넘겨주는 인자만으로도 dest가 어떤 상황인지 쉽게 확인이 가능하고 개발자들은 이에따른 예외처리도 쉽게 가능합니다.
아래의 조건이 가능한 이유는
ft_strlcat(dst, src, 10) <= ft_strlen(src) + ft_strlen(dst)
dest_len > size 일때 src_len + size를 반환한다면, 무조건 (src_len + size) < (src_len + dest_len)일 수 밖에 없기 때문입니다.
strlcat 함수 제작자는 strlcat에 넘겨주는 3개의 인자를 이용해 오염됨을 쉽게 판단하기 위해서는 이와같은 반환값조건을 선택해야했을 것입니다.
마치며.
"반환값들의 의미" 파트 전까지는 팩트를 기반으로 작성되었습니다. 하지만 반환값들의 의미 파트에는 제 생각이 들어가 있습니다.
"잘림방지"를 위해 반환값을 설정해놓았다는 공식문서의 내용을 바탕으로 개발자가 이 반환값을 어떻게 사용하면 좋을 지에 대해 고찰해 보았습니다.
다소 난해한 부분이 있을 수 있습니다. 한번 더 정독해보시고 궁금한 점이나 틀린 부분은 댓글로 남겨주세요.