ft_itoa

ft_itoa

“ft_itoa에 대하여”

<목차>

1. MY CODES

2. 정수를 문자열로 변환

ft_itoa — 정수를 문자열로 바꿔보자

(1) MY CODES

#include "libft.h"

static size_t	ft_len(long long *num)
{
	int				len;
	long long		tmp;

	len = 0;
	if (*num <= 0)
	{
		len += 1;
		*num *= -1;
	}
	tmp = *num;
	while (tmp)
	{
		len += 1;
		tmp /= 10;
	}
	return (len);
}

char	*ft_itoa(int n)
{
	size_t		len;
	char		*ascii;
	long long	num;

	num = (long long) n;
	len = ft_len(&num);
	ascii = (char *) malloc(sizeof(char) * (len + 1));
	if (ascii == NULL)
		return (NULL);
	ascii[len] = '\0';
	ascii[0] = '-';
	while (1)
	{
		ascii[--len] = num % 10 + '0';
		num /= 10;
		if (num == 0)
			break ;
	}
	return (ascii);
}
  • 정수를 매개 변수로 받아서 자릿수를 구하고, 그 만큼의 문자열을 할당한 뒤 while문을 돌면서 채워 넣는다.

(2) 정수를 문자열로 변환

  • ft_atoi의 역이다. 정수를 매개 변수로 받아 문자열로 변환한 뒤 반환한다. 몇 가지 이야기하고 싶은 부분이 있다.

(1) long long num

num = (long long) n;
  • 피신 코드나 libft 코드에 보면 이런 식의 라인이 종종 나타나는데, 이는 정수를 문자열로 변환하거나, 문자열을 정수로 변환하는 등의 함수에서 오버플로우 처리 부분에서 등장하는 패턴 같은 것이다. 왜 이런 식으로 사용하는 것일까? 우선은 아래 코드를 보자.
# include <stdio.h>

void	check(int a)
{
	printf("%d\n", a);
	long long num = (long long) a;
	printf("%lld\n", num);
}

int	main()
{
	long long a = 2147483650;
	check(a);
}
  • main에서 a를 2147483650으로 선언했다. 그런데 함수 check()는 정수를 매개 변수로 받는다. long long이었던 a가 정수로 casting되면서 오버플로우가 발생하게 될 것이다.

  • 함수 check에서 a를 받아서 새로 선언한 long long num에 복사했다. 그 과정에서 a를 다시 long long으로 변환했다. 그리고 a와 num을 출력했다.

output:
-2147483646
-2147483646
  • 둘다 동일한 값을 출력한다. 이는 long long이 int로 바뀌면서 원래의 값에서 변경된 부분이 irreversible하다는 것을 보여준다.
num = (long long) n;
  • 일단 여기까지 이 라인은 변수를 tmp로 저장하는 방식 이상의 의미를 가진다고 보기 어렵다. 여기까지는 그렇다. 다음으로 넘어가보자.

(2) ft_len

  • ft_len은 매개변수로 long long num의 포인터를 받아, num의 자릿수를 세는 함수이다. 단도직입적으로 말하자면, ft_len의 매개변수로 long long을 주지 않으면 -2147483647을 매개변수로 받았을 때 예외가 발생하게 된다. 그래서 long long을 사용한 것이다. num = (long long) n; 도 그 사전 작업을 하는 과정에서 나온 코드이다. 어떤 식으로 예외가 발생하는 것일까?
static size_t	ft_len(long long *num)
{
	...
	if (*num <= 0)
	{
		len += 1;
		*num *= -1; // <= -2147483648에 -1을 곱하는 순간 오버플로우 발생
	}
	...
}
  • 주석으로 써놓은 것과 같이 *num이 -2147483648이면 -을 지우기 위해 -1을 곱하게 되는데, 그 순간 오버플로우가 발생하면서 값이 꼬여버린다. 그것을 방지하기 위해 *num은 long long으로, 2147483648의 값을 바꾸어서는 안된다.
long long tmp = *num;
  • 여담으로, num의 포인터를 보내서 num을 수정하는 것이 곧바로 반영되도록 했는데, 갑자기 tmp를 새로 선언해서 거기다가 num의 값을 복사하고 tmp를 나누면서 자릿수를 세기 시작한다. 이럴거면 애초에 num을 바로 보내고 곧바로 num을 나누면서 자릿수를 세는게 나았을 것 같다…

(3) 정수를 문자열로 변환

  • 자릿수를 구했으면 그 자릿수에 1을 더한 만큼의 메모리를 할당한다(Null Terminating). 그리고 0번째 인덱스를 ‘-‘로 고정한다. 나도 어디서 주워들은 방법인데, 음수면 자연스럽게 1번째 인덱스까지만 채워지고, 양수면 ‘-‘가 숫자로 메워진다. 깔끔하기 그지없다.
  • num을 나누면서 문자열을 채워넣는다. 정수를 아스키 번호로 바꾸는 것이기 때문에 ‘0’(48)을 더하면서 채워야 한다. 종종 0 ~ 9와 ‘0’ ~ ‘9’을 헷갈리는 경우가 있다. ‘0’은 48이고, ‘9’은 57이다.