strlen 함수 구현하기

윤효준·2024년 7월 21일
0

42 Libft 복습

목록 보기
6/28

strlen 함수의 manual은 다음과 같다!

Synopsis

#include <string.h>

size_t	strlen(const char *s);

size_t란???

  • 부호가 없는 정수 타입이다.

  • 메모리 크기와 객체 크기를 나타내기에 적합한 자료형이다. 그렇기에 배열, 메모리 할당 함수 그리고 문자열 길이를 나타내는 함수에서 자주 사용된다.

  • size_t의 실제 크기와 범위는 시스템의 아키텍처에 따라 달라진다.
    예를 들어 32비트 시스템에서는 size_t가 보통 32비트 길이(unsigned int)이며, 64비트 시스템에서는 size_t가 보통 64비트 길이(unsigned long 또는 unsigned long long)이다.

64비트 시스템에서의 size_t를 더 명확하게 이야기하고자 한다.
size_t는 주로 다음 두 가지 모델 중 하나에 따라 정의된다.

1. LP64 모델(Unix/Linux 시스템)

  • LP64는 Long and Pointer are 64-bit를 의미한다.

2. LLP64 모델(Windows 64비트 시스템)

  • LLP64는 Long Long and Pointer are 64-bit를 의미한다.

따라서 64비트 시스템에서 size_t는 보통 64비트인다. Unix/Linux 시스템에서는 size_tunsigned long과 같은 크기를 가지며, Windows 시스템에서는 size_tunsigned long long과 같은 크기를 가진다.

그러면 unsigned long 또는 unsigned long long으로 표현되는 size_t를 왜 굳이 써야하는가에 대한 의문이 있을 것이다.

가장 큰 이유는 호환성이다.

  • 다른 자료형보다 size_t를 사용하면 시스템의 아키텍처에 따른 크기와 범위 차이를 자동으로 처리할 수 있다.
  • 부호가 없기에 크기나 길이를 나타낼 때 보다 안전하다.
  • 표준 라이브러리 함수들은 size_t를 사용하기에 표준을 준수하기 위해 사용한다.

인수에 사용된 const char *s는 무엇인가???

const char *s는 문자열을 가리키는 포인터이다. 이 포인트는 다음과 같은 이유로 자주 사용된다.

  • const 키워드는 포인터가 가리키는 문자열을 변경할 수 없음을 의미한다. 이는 함수가 입력된 문자열을 변경하지 않을 것이라는 보장을 제공한다. 따라서 사용자가 함수를 사용 시 문자열이 변경되지 않을 것이라는 확신을 가질 수 있다. 또한 컴파일러에게도 데이터를 변경하지 않겠다는 약속을 의미하고 이를 기반으로 컴파일러 또한 최적화를 수행한다.

물론 함수 내부에서 인수를 (char *)로 형 변환을 하여 수정을 할 수 있지만 매우 위험한 방법이고 문자열 상수를 수정하려하는 것은 정의되지 않은 동작이 발생할 수 있다. 역시 컴파일러 또한 const 키워드를 기반으로 한 최적화가 무의미해질 수 있으며, 이는 예기치 않은 동작을 초래한다.

그런데 만약 정말 정말 함수 내부적으로 데이터를 수정하여 사용할 필요하다면 원래 const 데이터는 수정하지 않고 가능한 데이터 구조로 복사(문자열이면 mallocstrcpy 사용) 후 수정하는 방식을 이용하면 된다.

  • 문자열 상수는 변경할 수 없는 데이터이다. 따라서 const char *를 사용하면 문자열 상수를 안전하게 함수로 전달할 수 있다.

const 사용 시 혼동할 수 있는 경우에 대한 설명

  • 가리키는 데이터가 const인 경우

    const char *schar const *s는 동일(const 키위드는 c언어의 구문 분석 규칙에 따라 위치가 앞이든 뒤든 동일한 의미를 가짐)
    포인터가 가리키는 데이터는 변경할 수 없지만, 포인터 자체는 변경할 수 있다.

  • 포인터 자체가 const인 경우

    char *const s
    포인터가 가리키는 데이터는 변경할 수 있지만, 포인터 자체는 변경할 수 없다.

  • 가리키는 데이터와 포인터 자체가 모두 const인 경우

    const char *const s 또는 char const *const s
    포인터 자체도 변경할 수 없고, 가리키는 데이터도 변경할 수 없다.

Description

  • strlen 함수는 string s의 길이를 계산한다.

Return values

  • strlen 함수는 종료 널 문자 앞에 오는 문자 수를 반환한다.

구현

방법1

size_t	ft_strlen(const char *str)
{
	size_t	len;
    
	len = 0;
    while (str[len] != '\0')
		len++;
	return (len);
}

간결하고 직관적이지만, 배열 인덱싱을 사용하여 약간의 오버헤드가 있다.

배열 인덱싱의 작동 방식

str[len]은 컴파일러에 의해 *(str + len)으로 변환된다.
참고로 위와 같은 방식을 사용하기에 str[len]은 len[str]로 사용해도 문제가 없다. 가독성과 의미 전달이 안 될 뿐...!
이는 포인터 str에 인덱스len을 더한 후 해당 주소의 값을 참조하는 것을 의미한다.
따라서 메모리 접근 시 추가적인 덧셈 연산이 필요하다.

포인터의 작동 방식

*str로 포인터가 가리키는 주소값을 바로 참조한다.
컴파일러각 최적화하기 더 쉬운 구조를 가지며, 일반적으로 더 효율적인 기계어 코드를 생성한다.

이러한 이유로 하는 배열 인덱싱을 선호하지 않는다.
그렇기에 내가 42 초반에 작성했던 코드는 방법2와 같다.

방법2

size_t	ft_strlen(const char *str)
{
	size_t	len;
    
	len = 0;
    while (*str != '\0')
	{
    	len++;
        str++;
    }
	return (len);
}

포인터 연산을 사용하여 효율적이지만, 배열 인덱싱에 비해 덜 직관적이다.
포인터를 이동하기에 함수 내부에서 포인터 str 사용할 시 주의가 필요하다.

똑같이 len을 선언해서 하는 방식이지만 개인적으로 str을 옮기고 len을 증가시키는 것이 마음에 들지 않아 고민을 많이 했다.
그 결과 방법3의 방식을 최종적으로 사용했다.

방법3

size_t	ft_strlen(const char *str)
{
	const char	*eos; //end of string

	eos = str;
	while (*eos != '\0')
		eos++;
	return (eos - str);
}

포인터 연산과 포인터 차이를 사용하여 효율적이고 간결하다!

profile
작은 문제를 하나하나 해결하며, 누군가의 하루에 선물이 되는 코드를 작성해 갑니다.

0개의 댓글