ft_atoi

ft_atoi

“atoi에 대하여”

<목차>

1. MY CODE

2. MAN ATOI

3. MAN STRTOL

4. 0-OVERFLOW-UNDERFLOW

convert ASCII string to integer

(1) MY CODE

#include "libft.h"

static int	is_space(char c)
{
	if (c == 9 || c == 10 || c == 11
		|| c == 12 || c == 13 || c == 32)
		return (1);
	else
		return (0);
}

int	ft_atoi(const char *str)
{
	long long	cnt;
	long long	is_minus;

	cnt = 0;
	is_minus = 1;
	while (*str)
	{
		if (is_space(*str))
			str++;
		else
			break ;
	}
	if (*str == '-' || *str == '+')
	{
		if (*str == '-')
			is_minus *= -1;
		str++;
	}
	while (ft_isdigit(*str))
	{
		cnt *= 10;
		cnt += *str - '0';
		str++;
	}
	return (is_minus * cnt);
}

(2) MAN ATOI

  • 라피신을 진행하면서 뿐만 아니라, Libft를 만들면서도 듣는 충고가 man을 열심히 보라는 것이다. 그때 당시에는 귀담아듣지 않았는데 지금 생각해보면 Libft를 만들 때만큼은 좀 봐두는 게 좋지 않았나 싶다.
SYNOPSIS

 #include <stdlib.h>

  int     atoi(const char *str);

DESCRIPTION
 - The atoi() function converts the initial portion of the string pointed to by str
 to int representation.

	-> It is equivalent to:

		(int) strtol(str, (char **)NULL, 10);
  • 딱 봐도 “It is equivalent to ~” 부분이 제일 중요하다. 맞다, atoi()는 libc에 의해 내부적으로 strtol()을 돌려 결과를 반환하는 방식을 사용한다. 그렇다면 strtol()의 MAN을 보지 않을 수가 없다.

(3) MAN STRTOL

NAME
 strtol -- convert a string value to a long (strtoimax, strtoll, strtoq의 대한 설명은 제외)

SYNOPSIS

 #include <stdlib.h>

	long     strtol(const char *restrict str, char **restrict endptr, int base);

DESCRIPTION
 - The strtol() function converts the string in str to a long value. (중략) The string may 
 begin with an arbitrary amount of whitespace (as determined by isspace(3)) followed by a 
 single optional "+" or "-" sign. If base is zero or 16, the string may then include a 
 "0x" prefix, and the number will be read in base 16; otherwise, a zero base is taken 
 as 10 (decimal) unless the next character is "0", in which case it is taken as 8 (octal).
 
 - The remainder of the string is converted to a long in the obvious manner, stopping 
 at the first character which is not a valid digit in the given base. (In bases above 10, 
 the letter `A' in either upper or lower case represents 10, `B' represents 11, 
 and so forth, with `Z' representing 35.)

 - If endptr is not NULL, strtol() stores the address of the first invalid character 
 in *endptr. If there were no digits at all, however, strtol() stores the original
 value of str in *endptr. (Thus, if *str is not "\0" but **endptr is "\0" on return, 
 the entire string was valid.)

RETURN VALUE
 - The strtol() functions return the result of the conversion, unless the value would 
 underflow or overflow.  If no conversion could be performed, 0 is returned and the 
 global variable errno is set to EINVAL (the last feature is not portable across 
 all platforms).  If an overflow or underflow occurs, errno is set to ERANGE and 
 the function return value is clamped according to the following table.

           Function       underflow     overflow
           strtol()       LONG_MIN      LONG_MAX
  • 봐야 할 포인트가 한두가지가 아니어서 사실상 man 전체를 긁어왔다(그래도 나름 선별해서 가져왔다).
  • const char* str가 변환할 문자열, 그리고 char** endptr은 변환 작업 후 위치할 포인터의 포인터, 그리고 base는 진법을 의미한다.
  • 먼저 str의 앞부터 존재할 수 있는 임의의 공백들을 조사한다. 공백 문자들은 man isspace(3)에 정의된 것들을 의미한다. 살펴보자면
MAN ISSPACE(3)

SYNOPSIS
 #include <ctype.h>

DESCRIPTION
 The isspace() function tests for the white-space characters.  For any
     locale, this includes the following standard characters:

   -> "\t"   "\n"    "\v"    "\f"    "\r"    " "
  • 이 여섯 가지 문자들을 의미한다.
    • “\t”는 수평 탭(Horizontal Tab, 9, HT)으로 우리에게 잘 알려져 있는 바로 그 Tab이다.
    • “\n”은 개행(Line Feed, 10, LF)이다. 줄을 내린 후 커서를 맨 왼쪽으로 끌어 당긴다. 타자기 시절에는 줄을 내리는 것만이 LF이고 왼쪽으로 당기는 것은 CR이어서 온전히 한 줄을 내리기 위해서는 CRLF을 했어야 했지만, 현대에 와서는 LF만으로 줄을 내릴 수 있게 되었다.
    • “\v”는 수직 탭(Vertical Tab, 11, VT)으로 바로 밑으로 커서를 내린다.
    • “\f”는 폼 피드(Form Feed, 12, FF)로, 프린터가 용지의 한 페이지 길이에 해당하는 만큼 페이지를 넘기거나 다음 페이지의 제일 첫 부분으로 넘기게 하는 특수 문자이다.
    • “\r”는 캐리지 리턴(Carrige Return, 13, CR)로 커서를 맨 왼쪽으로 끌어당긴다.
    • ” “는 공백(Space, 32, SP(C))이다.
  • 사용 결과는 대략 다음과 같다.
#include <stdio.h>

int main()
{
	printf("abc\ndef\n");
	printf("abc\fdef\n");
	printf("abc\vdef\n");
	printf("abc\rdef\n");
}
output:
         abc
         def
         abc
            def
         abc
            def
         def (커서가 당겨져서 abc가 없어져버림)
  • 임의의 수로 들어온 공백들을 체크하고 난 이후에는 부호를 확인한다. “+”이면 양수, “-“이면 음수이다.
  • 부호를 확인한 이후 본격적으로 숫자를 체크한다. atoi의 경우 십진수로 변환하므로 “0x”, “0”과 같은 접두사를 확인할 필요는 없다.
  • 각 문자를 순회하면서 ASCII값을 확인하여 숫자가 아니거나, 문자열의 끝까지 읽게 되면 멈춘다. 탐색을 멈춘 지점이 endptr이 된다. “+123abc”과 같은 문자열을 읽었을 경우 반환값은 123, (char **) endptr은 a의 주소를 가리키는 포인터가 되고( *endptr을 출력하면 “abc”가 출력됨), +123”을 읽었을 때는 마찬가지로 반환값은 123이지만 endptr은 “\0”이 된다( *endptr를 출력해도 아무런 값이 출력되지 않는다).
  • 문자열에 숫자가 존재하지 않는다면, *endptr에 온전한 문자열이 담기게 된다(이는 매개변수 str이 “\0”이 아닌데, **endptr이 “\0”이라면 문자열의 모든 값이 숫자라는 것을 의미하는 것이기도 하다).

(4) 0? OVERFLOW? UNDERFLOW?

  • strtol()의 경우 man에 적혀있는 바와 같이 변환에 실패하면 0, underflow가 발생하면 LONG_MIN, overflow가 발생하면 LONG_MAX를 반환한다. atoi()는 어떨까?
  • 여러가지 man 홈페이지를 살펴봐도 정형화된 atoi의 overflow, underflow 처리는 존재하지 않는다. overflow일 때 -1을 반환하고, underflow일 때 0을 반환하는 등의 처리도, 결국 여러가지 처리법 중의 하나이다.
  • 그 말은 즉슨 ft_atoi()에 별다른 예외처리를 할 필요가 없다는 것이다. 이 문제로 꽤나 골머리를 썩혔던 것으로 기억하는데, 결국 싱거웠던 고민에 불과했다…

<번외>

  • *endptr이 어떤 값을 가리키는지는 사실 Libft에서는 중요한 부분은 아닌데, 내 경우에는 나중에 C++에서 파싱할 때 이 부분을 다시 보게 되었다. 아까 input으로 “123abc”가 들어왔을 때 *endptr이 a의 주소를 가리킨다고 말한 바 있는데, 이 endptr을 확인해서 input string이 온전한 숫자인지 확인하는 것이다. 보통 endptr가 “\0”이 아니면 예외 처리를 하는 방식을 사용한다(float를 판별하려면 “f”인지도 확인해본다). 꽤나 강력한 방법이라고 생각하는데 나는 C를 하면서는 한번도 이 방법을 써본적이 없다. 어딘가에서는 사용해볼 만한 방법이 아니었나 생각한다.