C07의 경우 malloc, 즉 메모리 할당과 관련된 내용으로 이루어진다. 메모리 할당은 주로 배열을 사용하고 싶지만, 배열을 선언하는 시점에 배열의 크기가 확정되어 있지 않을 때 사용한다. 관련된 자세한 내용은 아래 글을 참조하시기 바란다. 나는 C언어 공부할 때 해당 교안을 위주로 공부했는데, 잘 정리되어 있어서 완독하고 나면 피신에 나오는 문제들의 난이도가 크게 어렵지 않게 느껴진다.
씹어먹는 C 언어 - <20 - 1. 동동동 메모리 동적할당(Dynamic Memory Allocation)>
모두의 코드 씹어먹는 C 언어 - <20 - 1. 동동동 메모리 동적할당(Dynamic Memory Allocation)> 작성일 : 2010-08-03 이 글은 94347 번 읽혔습니다. 안녕하세요. 여러분. 정말 멀리 달려 온 것 같네요. 벌써 제 20
modoocode.com
Exercise 00: ft_strdup
Reproduce the behavior of the function strdup (man strdup).
Allowed Function: malloc
첫 번째 문제는 strdup 함수를 재구현하는 문제이다. strdup 함수는 인자로 받은 src를 복사한 새로운 문자열을 리턴하는 함수이다. 이전에 strcpy가 인자로 주어진 destination 배열에 source를 복사했다면, strdup을 malloc을 활용하여 새로운 문자열을 만들어 해당 문자열에 source를 복사해야 한다.
우선 메모리를 할당해야 하므로, src 배열의 길이를 재고, 그 길이만큼의 메모리 공간을 리턴할 배열에 할당해주어야 한다. 나는 리턴할 배열을 copy 변수로 선언하고, src 배열의 길이 + 1만큼의 크기를 copy 배열에 할당해 준다. +1을 하는 것은 null character를 위한 공간까지 할당해 주는 것이다.
메모리 할당 이후에도 copy 배열이 NULL이라면, 메모리 공간이 충분하지 못하다는 등의 이유로 메모리 할당에 실패했다는 뜻이므로, 다시 NULL을 리턴해준다.
그리고 메모리가 할당되어 있는 copy 배열에 src 배열을 복사해주고, 이를 리턴하면 함수가 마무리된다.
#include <stdlib.h>
char *ft_strdup(char *src)
{
char *copy;
int i;
i = 0;
while (src[i])
i++;
copy = (char *)malloc(sizeof(char) * (i + 1));
if (!copy)
return (0);
i = 0;
while (src[i])
{
copy[i] = src[i];
i++;
}
copy[i] = 0;
return (copy);
}
/*
#include <stdio.h>
#include <string.h>
int main(void)
{
printf("%s, %s\n", ft_strdup(""), strdup(""));
printf("%s, %s\n", ft_strdup("hello 42"), strdup("hello 42"));
printf("%s, %s\n", ft_strdup("B7z"), strdup("B7z"));
printf("%s, %s\n", ft_strdup("dil4bhq73CZDHSPNiaDvISEwi"),
strdup("dil4bhq73CZDHSPNiaDvISEwi"));
printf("%s, %s\n", ft_strdup("2XkqldbJAM55uaiaYncUp8CvTC"),
strdup("2XkqldbJAM55uaiaYncUp8CvTC"));
printf("%s, %s\n", ft_strdup("utWXd03IJVeLVqdrnqM7j"),
strdup("utWXd03IJVeLVqdrnqM7j"));
printf("%s, %s\n", ft_strdup("dpJ"), strdup("dpJ"));
}
*/
Exercise 01: ft_range
Create a function ft_range which returns an array ofints. This int array should contain all values between min and max. Min included - max excluded.
Allowed Function: malloc
두 번째 문제 역시 첫 번째 문제와 같이 malloc을 사용하여 동적할당을 하는 문제이다. 해당 문제는 주어진 min부터 max까지의 값들을 차례로 새로 생성한 배열에 넣어 리턴하는 문제이다. 우선 min이 max보다 크거나 같으면 배열에 들어갈 수 있는 수가 없다는 뜻이므로 비정상적인 입력으로 인식하여 NULL을 리턴해준다. 그 후 (max - min)만큼, 즉 들어갈 숫자의 개수만큼의 크기를 가진 배열을 선언해 주고, malloc check을 한다. 이후 min에서 max까지 순회하며 생성한 배열에 값을 넣어주고, 해당 ary를 리턴해주면 마무리된다.
#include <stdlib.h>
int *ft_range(int min, int max)
{
int *ary;
int i;
if (min >= max)
return (0);
ary = (int *)malloc((max - min) * sizeof(int));
if (!ary)
return (0);
i = 0;
while ((min + i) < max)
{
ary[i] = min + i;
i++;
}
return (ary);
}
Exercise 02: ft_ultimate_range
Create a function ft_ultimate_range which allocates and assigns an array of ints. This int array should contain all values between min and max. Min included - max excluded. The size of range should be returned (or -1 on error). If the value of min is greater or equal to max’s value, range will point on NULL and it should return 0.
Allowed Function: malloc
세 번째 문제는 앞선 ex01과 동일하지만, 주어진 투 포인터 배열에 메모리를 할당하고 값을 넣어주어야 한다. 그리고 리턴하는 값은 할당된 배열이 아닌 배열의 크기가 리턴되어야 한다. 그래서 모든 다른 과정은 같지만 새로운 배열을 선언해주지 않고 *range에 메모리를 할당하여 값을 넣는다는 점, i를 리턴한다는 점이 다르다. 또한, min이 max보다 크거나 같을 경우는 배열의 크기가 0이라는 의미로 0을 리턴하지만, malloc check에서 메모리 할당에 실패한 경우는 -1을 리턴하여 배열 생성 과정에서 문제가 있음을 알린다.
#include <stdlib.h>
int ft_ultimate_range(int **range, int min, int max)
{
int i;
*range = (int *)malloc((max - min) * sizeof(int));
if (!range)
return (-1);
if (min >= max)
{
*range = 0;
return (0);
}
i = 0;
while ((min + i) < max)
{
(*range)[i] = min + i;
i++;
}
return (i);
}
Exercise 03: ft_strjoin
Write a function that will concatenate all the strings pointed by strs separated by sep. size is the number of strings in strssize is the number of strings in strs. if size is 0, you must return an empty string that you can free().
Allowed Function: malloc
네 번째 문제는 인자로 주어진 여러 개의 문자열을 주어진 seperator와 함께 하나의 문자열로 합치는 함수를 만드는 것이다. 주어진 인자 중 size는 strs 투포인터 배열 내 문자열의 개수이다. 다시 말해 strs 투포인터 배열에는 size 개의 문자열이 들어있다. sep은 주어진 separator 문자열로, strs 내 문자열들을 이을 때 그 사이에 sep 문자열을 넣어줘야 한다.
우선 하나의 문자열로 만들어 주기 위해서는, 그 하나의 문자열을 선언해주고 크기에 맞게 메모리를 할당해주어야 한다. 따라서 해당 함수는 최종 결과물의 길이인 len을 구하는 것으로 시작한다. 첫째로 sep의 길이를 파악해서 문자열 개수만큼 곱한 값을 len에 저장해 준다. 예를 들어 문자열이 3개라면 아래와 같이 sep은 두 번, 즉 size-1번 들어간다.
문자열1 + sep + 문자열2 + sep + 문자열3
여유 있게 메모리를 주기 위해서, 그리고 혹시 모를 예외 상황을 위해서 size개가 필요하다고 가정하고 메모리를 할당해주었다. 그리고 나서는 size개의 문자열을 순회하며 크기를 파악하여 각각의 크기를 모두 len 변수에 더해준다.
이렇게 len 변수가 완성되었다면, 이를 이용해서 배열에 메모리를 할당해 준다. +1은 null character를 추가해 주기 위함이다. malloc check를 해주고, 첫 번째 요소(index 0)를 0, 즉 null character로 채워준다. 그 이유는 이다음에 문자열을 붙여 넣을 때 ft_strcat() 함수를 사용하기 때문이다. ft_strcat() 함수는 dest 문자열의 끝에서부터 src 문자열을 이어서 붙여 넣기 시작한다. 여기서 dest 문자열의 끝이란 null character를 의미한다. 따라서 주어지는 dest 문자열의 끝에 null character가 없다면 무한 반복되고 결국 overflow가 발생할 수 있다. 따라서 dest로 문자열을 넣기 전에 우선 가장 처음부터 이어 붙어야 하므로 index 0을 null character로 설정해 주고 시작하는 것이다.
이후 while 문을 사용하여, res 문자열에 strs 배열 내 i번째 문자열을 이어 붙여 주고, 해당 문자열이 마지막 문자열이 아닐 경우 sep 문자열까지 붙여 넣어주면 된다. 이 결괏값을 리턴하면 함수가 마무리된다.
#include <stdlib.h>
int ft_strlen(char *str)
{
int len;
len = 0;
while (str[len])
len++;
return (len);
}
void ft_strcat(char *des, char *src)
{
while (*des)
des++;
while (*src)
{
*des = *src;
src++;
des++;
}
*des = 0;
}
char *ft_strjoin(int size, char **strs, char *sep)
{
int len;
char *res;
int i;
i = 0;
len = ft_strlen(sep) * size;
while (i < size)
len += ft_strlen(strs[i++]);
res = (char *)malloc(sizeof(int) * (len + 1));
if (!res)
return (0);
res[0] = 0;
i = 0;
while (i < size)
{
ft_strcat(res, strs[i]);
if (i != (size - 1))
ft_strcat(res, sep);
i++;
}
return (res);
}
/*
#include <stdio.h>
int main(int argc, char **argv)
{
printf("%s", ft_strjoin(argc, argv, ", "));
}
*/
Exercise 04: ft_convert_base
Create a function that returns the result of the conversion of the string nbr from a base base_from to a base base_to.
nbr, base_from, base_to may be not writable.
nbr will follow the same rules as ft_atoi_base (from an other module). Beware of ’+’, ’-’ and whitespaces.
The number represented by nbr must fit inside an int.
If a base is wrong, NULL should be returned.
The returned number must be prefix only by a single and uniq ’-’ if necessary, no whitespaces, no ’+’.
Allowed Function: malloc
해당 문제부터 복잡해지기 시작하는데, 이 문제는 base_from에서 base_to로 진법을 변환하는 문제이다. 함수에 nbr, base_from, base_to가 인자로 주어지고, nbr은 base_from에 표현된 수들로 이루어진 숫자이다. 즉, base_from의 길이가 8이면 8진법으로 나타난 수인 것이다. 하지만 그 내용이 01234567이라면 우리가 흔히 아는 8진법으로 표현되는 것이지만 abcdefgh로 주어지면 해당 8개의 문자들로 표현된 8진법 숫자가 된다.
아래 예를 보면, base_from에 0부터 9까지의 10개의 숫자로 구성되어 있으니 34는 10진법으로 표기된 숫자이다. 이를 0부터 F까지 16개의 숫자로 구성된 base_to에 따라 16진수로 표기하는 것이 목표이고, 따라서 결과가 22가 된다.
nbr : 34
base_from : 0123456789
base_to: 0123456789ABCDEF
▶result : 22
다른 예를 들어보겠다. 아래 예는 위 예와 같이 base_from은 10개의 문자, base_to는 16개의 문자로 되어있지만, 우리가 아는 10진법과 16진법 체계에서 사용되는 숫자가 아닌 다른 문자들로 구성되어 있다. 따라서 이에 맞춰서 숫자를 변환해 주면 되는 것이다.
nbr : rt
base_from : qwertyuiop
base_to: asdfghjklzxcvbnm
▶result : dd
그리고 문제에서 요구하는 특정한 nbr의 형식도 존재한다. 나는 nbr이 총 세 파트로 이루어진다고 이해했는데, 바로 공백 파트, 부호 파트 그리고 숫자 파트이다. 만약 각 파트에 올바른 문자가 위치하지 않으면 그 즉시 함수가 중단된다. 순서는 작성한 그대로 공백 파트 -> 부호 파트 -> 숫자 파트인데, 만약 숫자 파트 중간에 공백이나 부호 혹은 다른 종류의 문자가 나온다면 그 즉시 중단된다. 만약 부호 파트에 공백이 있다면 숫자파트까지 가지 못하고 0을 리턴하게 된다. 공백 파트나 부호 파트는 생략될 수도 있다. 이를 바탕으로 한 몇 가지 예시는 아래와 같다.
[예시 1] \t ----+1234
[결과 1] 1234
[예시 2] +- -+1234
[결과 2] 0
[예시 3] 123 4
[결과 3] 123
[예시 4] \t \n---++++-+-1234
[결과 4] -1234
이러한 문제 이해를 바탕으로 코드를 보겠다. 코드가 긴 관계로 부분 부분 잘라서 설명하고 마지막에 전체 코드를 첨부하려 한다. 기본적인 방식은 공백파트와 부호파트를 넘겨 숫자파트에 도달하면 nbr을 10진수로 변환해 주고, 이를 다시 base_to를 기반으로 한 수로 변환하여 리턴하는 방식이다.
첫 번째로 볼 함수는 문제에서 요구한 ft_convert_base() 함수이다. 앞선 문제 설명에서 언급했듯, base_from을 기반으로 한 nbr이 제공되고, 이를 base_to 기반의 수로 바꾸어서 리턴하면 된다. fsize 변수에 check_condition() 함수로 base 조건을 확인함과 동시에 base_from의 길이를 세서 저장한다. tsize에는 같은 과정을 base_to에 한다. base의 길이가 1보다 작거나 같아서는 안되고, 이후 더 자세히 설명하겠지만 check_conditions() 함수에서 인자가 base 조건을 충족하지 않는다면, 0을 리턴한다. 따라서 fsize나 tsize가 1보다 작거나 같은지 확인하는 것만으로 base 조건을 충족하는지를 확인할 수 있다.
그 이후, 가장 처음에 white sapce가 나올 수 있다는 조건이 있지만, white space는 필요한 정보는 아니므로 그다음으로 넘겨준다. 그 이후 check_sign() 함수를 이용하여, 양수인지 음수인지 확인하면서, 부호 파트 역시 넘긴다.
이 과정을 모두 지나고 나서, 정상적인 입력이라면 숫자 파트가 나와야 한다. 이 이후 숫자 파트가 나오지 않는다면 형식이 지켜지지 않았다는 뜻이므로 0을 리턴해야 한다. 따라서 find_num() 함수를 통해 숫자가 맞는지 확인함과 동시에 10진수 숫자로 변환하여 dec 변수에 10진수를 저장해 준다. 그리고 이 10진수와 앞서 얻었던 sign 정보를 이용하여 ft_itoa_base() 함수를 통해 주어진 base_to를 기반으로 한 숫자로 변환해 리턴해준다.
char *ft_convert_base(char *nbr, char *base_from, char *base_to)
{
long long int fsize;
int tsize;
int sign;
long long int dec;
fsize = check_conditions(base_from);
tsize = check_conditions(base_to);
if ((fsize <= 1) || (tsize <= 1))
return (0);
while (*nbr && is_white_space(*nbr))
nbr++;
sign = check_sign(&nbr);
dec = 0;
while (*nbr && find_num(*nbr, base_from) != -1)
dec = dec * fsize + find_num(*nbr++, base_from);
return (ft_itoa_base(dec, tsize, base_to, sign));
}
그럼 base의 조건을 확인하면서 base의 길이를 세는 check_conditions() 함수부터 보겠다. 우리가 확인해야 하는 base의 조건은 다음과 같다.
1. base는 길이가 0이거나 1인 문자열이어서는 안 된다.
2. base는 같은 문자를 두 번 포함해서는 안된다.
3. base는 +나 -, 혹은 white space를 포함해서는 안된다.
해당 함수에서는 2번과 3번을 확인하면서 문자열 길이를 세서 리턴한다. 만약 조건을 충족하지 않으면 0을 리턴하는데, 따라서 이 함수 결괏값이 1보다 큰지를 확인하면 1번 조건을 충족하는지와 함수 실행결과 2&3번 조건을 충족하는지를 모두 확인할 수 있게 되는 것이다.
이렇게 조건을 확인하도록 할 코드는 아래와 같다. 우선 리턴할 결괏값인 size를 0으로 초기화해 주고, while문이 한 번 돌 때마다 1을 더해주며 문자열의 길이를 잰다.
c변수에 현재 순회 중인 위치의 문자를 넣어주고, 이 c에 대해서 3번 조건을 충족하는지를 확인해 주고, 충족하지 않는다면 그 즉시 0을 리턴한다. 그리고 현재 순회 중인 문자 바로 직전까지의 문자를 다시 순회하며 중복되는 문자가 있는지 확인하고, 중복을 확인하는 즉시 마찬가지로 0을 리턴해준다.
무사히 순회를 마쳤다면 size를 리턴하면서 함수를 마무리한다.
int check_conditions(char *base)
{
int size;
char c;
int i;
size = 0;
while (base[size])
{
c = base[size];
if (is_white_space(c))
return (0);
if ((c == '+') || (c == '-'))
return (0);
i = 0;
while (base[i] && (i < size))
{
if (c == base[i])
return (0);
i++;
}
size++;
}
return (size);
}
다음은 특정한 문자가 white space인지를 확인해 주는 is_white_space() 함수이다. white space는 '\t', '\n', '\v', '\f', '\r', ' '의 여섯 가지 종류의 공백을 포함한다. 만약 인자로 주어진 문자가 여섯 종류의 공백 중 하나라면 1을 아니라면 0을 리턴해준다.
int is_white_space(char c)
{
if ((c == '\t') || (c == '\n') || (c == '\v'))
return (1);
if ((c == '\f') || (c == '\r') || (c == ' '))
return (1);
return (0);
}
다음은 음수인지, 양수인지 숫자의 부호를 확인하면서, 문자열에서 부호 파트를 넘겨주는 check_sign() 함수이다. 이 함수가 문자열 nbr을 투포인터로 받는 이유는 이 함수를 벗어난 후에도 nbr이 가리키고 있는 위치가 check_sign() 함수가 이미 순회한 부분이 아니라 그 뒷부분이 되기를 원하기 때문이다.
해당 문제에서 부호는 '-' 문자가 부호 파트에 홀수개 있는지 짝수개 있는지에 따라 결정된다. -부호가 홀수개 있으면 결과적으로 그 수는 음수가 되고, 짝수개 있으면 결과적으로 그 수는 양수가 된다. 따라서 이 함수에서는 '-' 문자의 개수를 세서 리턴하는 방식으로 부호를 확인시켜 준다.
nbr 문자열에서 순회 중인 문자가 + 혹은 - 부호일 때까지 순회하면서 - 부호의 개수만 세서 이를 리턴한다.
int check_sign(char **nbr)
{
int sign;
sign = 0;
while (**nbr && ((**nbr == '+') || (**nbr == '-')))
{
if (**nbr == '-')
sign++;
++(*nbr);
}
return (sign);
}
다음은 nbr을 10진수로 변환하는 find_num() 함수이다. 정확히 말하면 nbr 문자열 전체를 한 번에 10진수로 변환해 주는 것은 아니고, 현재 순회 중인 문자 하나를 10진수로 변환한다. 이를 ft_convert_base() 함수에서 결과로 받아, 이제껏 변환시켜서 저장해 온 dec 변수에 base_from의 크기를 곱해준 값에 현재 순회 중인 문자를 변환한 값을 더해주는 방식으로 nbr의 숫자 파트를 순회하며 10진수로 변환하게 되는 것이다. 결론적으로 해당 함수에서 하는 역할은 입력받은 문자 하나를 10진수로 변환하여 int type으로 리턴하는 것이다.
해당 함수에서는 입력받은 base(base_from이 될 것이다.)를 순회하며 현재 입력받은 문자와 동일한 문자를 찾는다. 만약 찾았다면 해당 문자의 index를 리턴하고 끝까지 찾지 못한다면 -1을 리턴하여 숫자 파트가 잘못되었음을 알린다.
int find_num(char c, char *base)
{
int i;
i = 0;
while (base[i])
{
if (c == base[i])
return (i);
i++;
}
return (-1);
}
마지막으로 앞서 변환한 10진수를 다시 base_to를 기반으로 한 숫자로 변환하는 ft_itoa_base() 함수이다. 인자로 받은 n은 10진수로 표현된 수이고, bsize는 입력받은 base의 크기를, base는 변환해야 할 base를, 마지막으로 sign은 앞서 세어두었던 - 부호의 개수를 가지고 있다.
우선 새로운 문자열을 리턴해야 하므로, 메모리 할당을 위해 필요한 길이를 측정해줘야 한다. 따라서 len 변수를 0으로 초기화하고 temp에 n을 담아 temp를 bsize로 몇 번 나눌 수 있는지를 확인하면서 len을 증가시켜 준다. 그리고 만약 n이 0일 경우 len도 0으로 남아있을 테지만, 사실 0이라는 숫자를 담을 한 자리가 필요하기 때문에 len을 증가시켜주고, 부호가 음수가 되어야 하는 경우도 부호를 위한 자리가 추가로 필요하므로 len을 증가시켜준다. 이렇게 측정한 길이를 바탕으로 res 배열에 메모리를 할당해 주고, malloc check까지 해준다.
이렇게 배열에 메모리 할당하는 과정이 끝났다면, 본격적으로 숫자를 담는 배열을 만들어준다. 만약 음수라면 첫 번째 자리에 '-' 문자를 넣어준다. 그리고 마지막 자리에는 0을 넣어준다. 그리고 끝자리부터 len이 0보다 클 때까지 하나씩 감소시키며 res 배열에 알맞은 문자를 넣어준다. 여기서 알맞은 문자는 base 배열 내 n을 bsize로 나눈 나머지의 인덱스에 위치한 문자일 것이다.
마지막으로 이렇게 만든 배열을 리턴해주면 함수가 마무리된다. ft_convert_base() 함수에서는 이 값의 리턴값을 그대로 리턴한다.
char *ft_itoa_base(long long int n, int bsize, char *base, int sign)
{
int len;
char *res;
long long int temp;
len = 0;
temp = n;
while (temp > 0)
{
len++;
temp /= bsize;
}
if (n == 0 || (sign % 2 && n != 0))
len++;
res = (char *)malloc(sizeof(char) * (len + 1));
if (!res)
return (0);
if (sign % 2 && n != 0)
res[0] = '-';
res[len--] = 0;
while (len > 0 || (len == 0 && res[len] != '-'))
{
res[len--] = base[n % bsize];
n /= bsize;
}
return (res);
}
다음은 위 문제를 위해 만든 두 개의 파일의 최종본이다.
// ft_convert_base.c
#include <stdlib.h>
int is_white_space(char c);
int check_conditions(char *base);
int find_num(char c, char *base)
{
int i;
i = 0;
while (base[i])
{
if (c == base[i])
return (i);
i++;
}
return (-1);
}
char *ft_itoa_base(long long int n, int bsize, char *base, int sign)
{
int len;
char *res;
long long int temp;
len = 0;
temp = n;
while (temp > 0)
{
len++;
temp /= bsize;
}
if (n == 0 || (sign % 2 && n != 0))
len++;
res = (char *)malloc(len + 1);
if (!res)
return (0);
if (sign % 2 && n != 0)
res[0] = '-';
res[len--] = 0;
while (len > 0 || (len == 0 && res[len] != '-'))
{
res[len--] = base[n % bsize];
n /= bsize;
}
return (res);
}
int check_sign(char **nbr)
{
int sign;
sign = 0;
while (**nbr && ((**nbr == '+') || (**nbr == '-')))
{
if (**nbr == '-')
sign++;
++(*nbr);
}
return (sign);
}
char *ft_convert_base(char *nbr, char *base_from, char *base_to)
{
long long int fsize;
int tsize;
int sign;
long long int dec;
fsize = check_conditions(base_from);
tsize = check_conditions(base_to);
if ((fsize <= 1) || (tsize <= 1))
return (0);
while (*nbr && is_white_space(*nbr))
nbr++;
sign = check_sign(&nbr);
dec = 0;
while (*nbr && find_num(*nbr, base_from) != -1)
dec = dec * fsize + find_num(*nbr++, base_from);
return (ft_itoa_base(dec, tsize, base_to, sign));
}
/*
#include <stdio.h>
int main(int argc, char **argv)
{
if (argc != 4)
return (0);
printf("%s", ft_convert_base(argv[1], argv[2], argv[3]));
}
*/
// ft_conver_base2.c
#include <stdlib.h>
int find_num(char c, char *base);
char *ft_itoa_base(long long int n, int bsize, char *base, int sign);
char *ft_convert_base(char *nbr, char *base_from, char *base_to);
int is_white_space(char c)
{
if ((c == '\t') || (c == '\n') || (c == '\v'))
return (1);
if ((c == '\f') || (c == '\r') || (c == ' '))
return (1);
return (0);
}
int check_conditions(char *base)
{
int size;
char c;
int i;
size = 0;
while (base[size])
{
c = base[size];
if (is_white_space(c))
return (0);
if ((c == '+') || (c == '-'))
return (0);
i = 0;
while (base[i] && (i < size))
{
if (c == base[i])
return (0);
i++;
}
size++;
}
return (size);
}
Exercise 05: ft_split
Create a function that splits a string of character depending on another string of characters.
You’ll have to use each character from the string charset as a separator.
The function returns an array where each element of the array contains the address of a string wrapped between two separators. The last element of that array should equal to 0 to indicate the end of the array.
There cannot be any empty strings in your array. Get your own conclusions accordingly.
The string given as argument won’t be modifiable.
Allowed Function: malloc
앞서 ex03였던 ft_strjoin이 여러 개의 문자열을 하나로 합치는 문제였다면, 해당 문제는 하나의 문자열을 여러 개로 나누는 문제이다. 입력받은 str을 charset을 separator로 활용하여 나누는 것이다. 이렇게 나눈 문자열들을 포함한 배열을 리턴하는 것이 목표이다.
내가 이 문제를 위해 선택한 방식을 간략히 먼저 설명하면, 문자열 str을 순회하며 만약 charset에 포함되는 문자라면 넘기고, charset에 포함된 문자가 아니라면 해당 문자를 시작으로 다음 charset이 나오기 전까지 하나의 문자열을 만든 후 다음으로 넘기는 것이다. 즉, 첫 번째로 charset이 아닌 문자가 시작점이 되어 문자열을 하나 만들고, 다시 그다음 처음으로 charset이 아닌 문자가 시작점이 되어 다음 문자열을 만드는 것을 반복한다.
이를 바탕으로 코드를 보겠다. 우선 프로토타입으로 주어진 함수인 ft_split()을 먼저 보겠다. 우선 malloc으로 함수를 시작한다. 여러 개의 문자열을 담을 문자열로 구성된 배열을 리턴해야 하므로, 투포인터 res 변수를 선언하여 여기에 char* type 사이즈를 str의 길이 + 1개 할당해 준다. 이는 해당 배열이 가질 수 있는 최대값을 할당해준 것이다. str이 한 글자 한 글자 분리되어 저장되는 경우 최대 str의 길이만큼의 문자열을 가질 수 있기 때문이다. (중간중간 charset이 있어줘야 해서 이론적으로는 길이 만큼이 될 수 없지만, 간단한 코드와 혹시 모를 경우를 위해 넉넉히 잡아주었다.) +1은 문제에서 요구한 대로, 배열의 마지막 요소를 0으로 주어 배열이 끝났음을 표기해줘야 하므로 이를 위한 자리이다. 이후 malloc check를 해주고, 만약 str 문자열이 빈 문자열이라면 0을 넣어 리턴해준다.
이후 str문자열을 순회하면서 is_charset() 함수로 charset에 해당하는 문자인지 확인해 준다. 만약 해당하는 문자라면 그냥 다음으로 넘겨주고, 그렇지 않다면 res 배열의 현재 위치(i)에 create_string() 함수로 다음 charset 전까지의 문자열을 만들어 저장해 주고, 그 문자열의 길이만큼을 str에 더해줘, 만든 문자열 다음부터 다시 while문 안에서 순회할 수 있도록 해준다.
char **ft_split(char *str, char *charset)
{
char **res;
int i;
res = (char **)malloc((ft_strlen(str) + 1) * sizeof(char *));
if (!res)
return (0);
if (!(*str))
{
res[0] = 0;
return (res);
}
i = 0;
while (*str)
{
if (!is_charset(*str, charset))
{
res[i] = create_string(str, charset);
str += ft_strlen(res[i++]);
}
else
str++;
}
res[i] = 0;
return (res);
}
다음은 현재 순회 중인 문자가 charset, 즉 separator인지 확인하는 is_charset() 함수이다. 이 함수는 입력받은 문자 c가 charset 문자열에 포함된 문자라면 1을 아니라면 0을 리턴한다.
만약 charset이 빈 문자열이라면 c는 무조건 charset에 포함된 문자가 아니므로 0을 리턴해준다. 그리고 while문으로 charset 문자열을 순회하며 만약 현재 순회 중인 charset 내 문자가 c와 동일하면 바로 1을 리턴해준다. charset을 끝까지 순회했는데 c와 일치하는 문자가 없었다면, c가 charset에 포함된 문자가 아니라는, 즉 separator가 아니라는 뜻이므로 0을 리턴해준다.
int is_charset(char c, char *charset)
{
if (!(*charset))
return (0);
while (*charset)
{
if (c == *charset)
return (1);
charset++;
}
return (0);
}
다음은 charset이 아닌 문자를 만났을 때, 그다음 charset 전까지를 하나의 문자열로 만들어주는 create_string() 함수이다. 먼저 새로운 문자열 하나를 만들어야 하므로 메모리 할당을 위해 길이를 파악해야 한다. 따라서 while문으로 다음 charset을 만나거나 문자열이 끝나기 전까지의 길이를 len변수에 저장해 준다.
그리고 res 배열에 len + 1만큼의 메모리를 할당해 준다. 이 배열에 len만큼 str에서 res로 복사해 주고, 마지막에 null character를 넣어준 후 리턴해준다.
char *create_string(char *str, char *charset)
{
int len;
char *res;
int i;
len = 0;
while (str[len] && !(is_charset(str[len], charset)))
len++;
res = (char *)malloc((len + 1) * sizeof(char));
if (res == NULL)
return (NULL);
i = 0;
while (i < len)
{
res[i] = str[i];
i++;
}
res[i] = 0;
return (res);
}
아래 코드는 ft_split 문제를 위한 전체 코드이다.
#include <stdlib.h>
int is_charset(char c, char *charset)
{
if (!(*charset))
return (0);
while (*charset)
{
if (c == *charset)
return (1);
charset++;
}
return (0);
}
int ft_strlen(char *str)
{
int len;
len = 0;
while (str[len])
len++;
return (len);
}
char *create_string(char *str, char *charset)
{
int len;
char *res;
int i;
len = 0;
while (str[len] && !(is_charset(str[len], charset)))
len++;
res = (char *)malloc((len + 1) * sizeof(char));
if (res == NULL)
return (NULL);
i = 0;
while (i < len)
{
res[i] = str[i];
i++;
}
res[i] = 0;
return (res);
}
char **ft_split(char *str, char *charset)
{
char **res;
int i;
res = (char **)malloc((ft_strlen(str) + 1) * sizeof(char *));
if (!res)
return (0);
if (!(*str))
{
res[0] = 0;
return (res);
}
i = 0;
while (*str)
{
if (!is_charset(*str, charset))
{
res[i] = create_string(str, charset);
str += ft_strlen(res[i++]);
}
else
str++;
}
res[i] = 0;
return (res);
}
'개발 프로젝트 > Hive Helsinki [42 Helsinki]' 카테고리의 다른 글
[Hive Helsinki / Piscine] C08 (1) | 2024.05.01 |
---|---|
[Hive Helsinki / Piscine] Rush02 (0) | 2024.04.30 |
[Hive Helsinki / Piscine] C06 (1) | 2024.04.19 |
[Hive Helsinki / Piscine] Rush01 (2) | 2024.04.18 |
[Hive Helsinki / 42cursus] restrict pointer와 size_t type (0) | 2024.04.16 |