개발 프로젝트/Hive Helsinki [42 Helsinki]

[Hive Helsinki / Piscine] C02

iinana 2024. 3. 11. 00:05
728x90

 Exercise 00: ft_strcpy 

Reproduce the behavior of the function strcpy (man strcpy).
Allowed Function: None

 

  C02에는 전반적으로 string 관련해서 이미 있는 c언어 함수를 다시 만드는 문제가 많이 나왔다. 이 문제는 dest으로 받은 string 공간에 src 문자열을 복사해 넣는 string copy 함수를 만드는 것이다. 

 문제에 명시되어 있는 대로, 터미널에 man strcpy라고 입력하면 strcpy 함수의 manual을 확인할 수 있다. 

man strcpy를 입력하면 나오는 manual

 

프로토타입은 문제에 명시되어 있으므로, 해당 매뉴얼에서 내가 중요하게 봤던 부분은 Description과 사진에는 나와있지 않지만 return value 부분이다. Description에서 내가 뽑은 중요한 포인트는 아래와 같다. 

(1) including the terminating null byte (null character도 복사할 것)
(2) destination string 'dest' must be large enough to receive the copy (dest가 가진 메모리 공간이 충분할 것)
(3) return a pointer to the destination string dest. (dest 포인터를 리턴할 것)

 

특히 두 번째 부분은 manual의 bug 파트에도 자세한 설명이 되어있지만, 그냥 저것만 보고 dest 사이즈가 작아서 발생하는 overflow를 내가 처리할 필요는 없구나 하고 넘어갔다. 

그렇게 만든 ft_strcpy 함수는 다음과 같다. idx를 정의해서 0부터 src[idx]가 null character가 아닐 때까지 순회하며 dest에 character를 하나하나 넣어주고 마지막에 null character를 따로 넣어주며 마무리한다. while loop에서 순회할 때는 src[idx]가 null character이면 while loop를 terminate하게 되므로, null character가 dest에 복사되지 않기 때문에 따로 꼭 넣어줘야 한다. 

그리고 null character는 원래 '\0'로 표기되지만, 그 아스키코드가 0이기 때문에 편의상 0으로 넣어주었고, while loop 안에서도 0은 false로 처리되기 때문에 딸소 src[idx] == 0이나 src[idx] == '\0'와 같은 표기 없이 src[idx]만 조건으로 넣어주면 자동으로 null character에 도달하면 while loop가 멈추게 된다. 

char	*ft_strcpy(char *dest, char *src)
{
	int		idx;

	idx = 0;
	while (src[idx])
	{
		dest[idx] = src[idx];
		idx++;
	}
	dest[idx] = 0;
	return (dest);
}

 

추가적으로, 앞서 언급했듯 C02에서는 원래 있는 함수를 reproduce하는 문제가 많이 나오므로, 코드가 정상적으로 동작하는지 확인하기 위해서 기존의 함수를 많이 사용했다. 테스트 코드의 예시는 아래와 같다.

#include <stdio.h>
#include <string.h>

int main(void)
{
	char c1[10];
	char c2[10] = "apple";
	char c3[10];

	printf("%s %s\n", ft_strcpy(c1, c2), c1);
	printf("%s %s", strcpy(c3, c2), c3);
}

 

C02에 나오는 대부분의 함수들은 대체로 <string.h> 헤더에 포함되어 있다. 터미널에서 매뉴얼을 확인해도 해당 함수가 어떤 헤더에 포함되어 있는지 쉽게 확인이 가능하다. <stdio.h> 헤더는 내가 좀 더 편한 printf() 함수를 사용하기 위해서 포함했다. printf() 사용이 어려운 분들은 C01에서 만들었던 ft_putstr을 가져와서 사용해도 된다.

여기서는 내가 만든 ft_strcpy로는 c1에 c2를 복사해 넣었고, 실제 strcpy로는 c3에 c2를 복사해 넣었다. 이렇게 해서 출력해 보면 아래와 같이 출력된다. 

apple apple
apple apple

 

첫 번째 줄은 내가 만든 함수에서 나온 문자열이고, 두 번째 줄은 strcpy 함수에서 나온 문자열이다. 각 줄의 첫 번째 apple은 함수의 return 값(dest의 포인터)에서 나온 문자열이고, 두 번째 apple은 복사한 destination 문자열에서 나온 문자열이다. 

 

 

 Exercise 01: ft_strncpy 

Reproduce the behavior of the function strncpy (man strncpy).
Allowed Function: None

 

strncpy는 strcpy와 같지만 인자로 복사할 크기인 n을 입력받아 그만큼만 복사한다. 조건 하나만 추가된 것이지만 주의할 점은 아래와 같다. 

Warning: If there is no null byte among the first n bytes of src, the string placed in dest will not be null-terminated. If the length of src is less than n, strncpy() writes additional null bytes to dest to ensure that a total of n bytes are written.

 

즉, n만큼 복사하는데, 그전에 src 문자열이 끝나지 않으면 dest에 null character는 복사되지 않는다. 그리고 src의 길이가 n보다 짧으면 남은 공간은 null character로 채운다. 

 

몇 가지 사례를 보면, 더 쉽게 이해할 수 있다. 

아래 경우에는 src의 길이는 6(null character 포함)인데 n은 3이므로, dest는 null terminated string이 아니게 된다. 즉 null character가 복사되지 않게 된다. 

ft_strncpy(dest, "apple", 3);

 

다음 경우에는 src의 길이는 6(null character 포함)인데 n은 10이므로, dest에 apple까지 복사된 이후 남은 5개의 공간은 모두 null character가 된다. 

ft_strncpy(dest, "apple", 10);

 

위의 주의사항만 고려하면, 다른 모든 조건은 strcpy와 같다. 따라서 while loop에 idx < n이라는 조건을 하나 더 추가해 주고, 마지막에 null character를 조건 없이 추가해 주던 것과는 달리 idx가 n보다 작은 경우만, 작은 만큼 null character를 추가해 주면 된다. 

char	*ft_strncpy(char *dest, char *src, unsigned int n)
{
	unsigned int		idx;

	idx = 0;
	while (src[idx] && (idx < n))
	{
		dest[idx] = src[idx];
		idx++;
	}
	while (idx < n)
	{
		dest[idx] = 0;
		idx++;
	}
	return (dest);
}

 

 

 Exercise 02: ft_str_is_alpha 

Create a function that returns 1 if the string given as a parameter contains only alphabetical characters, and 0 if it contains any other character. 
Allowed Function: None

 

해당 문제는 string 전체가 alphabet으로 이루어져 있는지 확인하는 함수를 만드는 것이다. 이 문제는 아스키코드에 대한 이해만 있으면 쉽게 풀 수 있다. 'A'의 아스키코드가 65, 'Z'의 아스키코드가 90이고, 'a'의 아스키코드가 97, 'z'의 아스키코드가 122이므로 65와 90 사이이거나 97과 122의 사이에 있으면 알파벳이라는 뜻이다. 그래서 string을 순회하면서, 각각의 문자가 alphabet인지 체크한 후 아닌 문자를 만나는 순간 0을 return하여 alphabet string이 아니라고 리턴하게 된다. 끝까지 alphabet이 아닌 character를 만나지 않으면 while loop가 끝나고 1을 return하게 된다. 나는 각각의 character가 알파벳인지 확인하는 helper function을 만들었지만, 코드 자체가 길지 않기 때문에 굳이 그럴 필요는 없다. 단지 코드를 좀 더 가독성 있게 만들고 싶었다. 그리고 문제 마지막에 str이 빈 문자열이면 그냥 1을 리턴하라는 조건이 있는데, 이대로 실행하게 되면, 문자열이 비어있을 때 while loop 자체가 실행되지 않아 바로 1을 리턴하게 된다.

int	char_is_alpha(char c)
{
	if ((c >= 'A') && (c <= 'Z'))
		return (1);
	if ((c >= 'a') && (c <= 'z'))
		return (1);
	else
		return (0);
}

int	ft_str_is_alpha(char *str)
{
	while (*str != 0)
	{
		if (char_is_alpha(*str) == 0)
			return (0);
		str++;
	}
	return (1);
}

 

 

 Exercise 03: ft_str_is_numeric 

Create a function that returns 1 if the string given as a parameter contains only digits, and 0 if it contains any other character.
Allowed Function: None

 

해당 문제는 앞선 문제와 기본적인 구조는 비슷하고, 단지 alphabet string인지 확인하던 것에서 numeric string인지 확인하는 것으로 바뀐 것이다. 다만 앞선 문제보다 더 간단해진 점은 앞선 문제에서는 lower case alphabet과 upper case alphabet 이 두 구간을 확인했어야 하는데, 이번에는 0부터 9까지의 numeric character 하나의 구간만 확인하면 된다. 

위에서와 마찬가지로 string 내 문자를 하나씩 순회하면서 numeric이 아닌 경우 바로 0을 리턴하고 함수를 중단하면 된다. 그런데 이 문제 코드를 공유하니 왜 여기서는 or(||)을 사용하고 앞선 문제에서는 and(&&)를 사용하냐는 질문을 받았다. 이 문제는 구간 밖에 있는지를 체크하는 거고 앞선 문제에서는 구간 안에 있는지를 체크해서 그런 거라고 설명했다. 하지만 잘 이해하지 못하는 눈치였다... 내가 안 배웠냐고 물어보니까 너는 수학뇌가 있지만 우리는 없다고 했다. 살다 살다 수학뇌 있다는 말은 처음 들었다. 한국 문과로서 자랑스러운 순간이었다...

int	ft_str_is_numeric(char *str)
{
	int		idx;

	idx = 0;
	while (str[idx] != 0)
	{
		if ((str[idx] < '0') || (str[idx] > '9'))
			return (0);
		idx++;
	}
	return (1);
}

 

 

 Exercise 04: ft_str_is_lowercase 

Create a function that returns 1 if the string given as a parameter contains only lowercase alphabetical characters, and 0 if it contains any other character.
Allowed Function: None

 

해당 문제 역시 앞선 문제들과 동일하다. 단지 확인하는 구간만 바뀐 것이다. 

int	ft_str_is_lowercase(char *str)
{
	while (*str != 0)
	{
		if ((*str < 'a') || (*str > 'z'))
			return (0);
		str++;
	}
	return (1);
}

 

 

 Exercise 05: ft_str_is_uppercase 

Create a function that returns 1 if the string given as a parameter contains only uppercase alphabetical characters, and 0 if it contains any other character
Allowed Function: None

 

int	ft_str_is_uppercase(char *str)
{
	while (*str != 0)
	{
		if ((*str < 'A') || (*str > 'Z'))
			return (0);
		str++;
	}
	return (1);
}

 

 

 Exercise 06: ft_str_is_printable 

Create a function that returns 1 if the string given as a parameter contains only printable characters, and 0 if it contains any other character.
Allowed Function: None

 

이 문제도 앞선 문제들과 동일하고, 확인하는 구간만 바뀐 것이다. 하지만 printable 문자에 대해 의견이 분분한데, 구글링 해봤을 때의 결과를 그냥 넣었다. 

int	ft_str_is_printable(char *str)
{
	while (*str != 0)
	{
		if ((*str < 32) || (*str > 126))
			return (0);
		str++;
	}
	return (1);
}

 

 

Exercise 07: ft_strupcase 

Create a function that transforms every letter to uppercase
Allowed Function: None

 

이 문제는 lower case alphabet을 upper case alphabet으로 바꾸는 문제이다.

string을 순회하면서 lower case alphabet인지 확인하고, 그렇다면 upper case로 바꿔준다. lower case에서 upper case로 바꿔줄 때는 둘 간의 차이인 32를 빼주면 된다. 즉, 'A'의 아스키코드가 65이고, 'a'의 아스키코드가 97이므로 둘 간의 차이인 32를 lower case alphabet에서 빼주면 lower case alphabet을 upper case alphabet으로 바꿔줄 수 있다. 

init을 정의해 준 이유는 마지막에 str을 return해야 하기 때문에 초기값을 저장해 두는 목적이다. 따라서 결국 마지막에는 초기값인 init을 return 해준다. (해당 init string에는 upper case로 바꾼 문자열이 반영되어 있다.)

char	*ft_strupcase(char *str)
{
	char	*init;

	init = str;
	while (*str != 0)
	{
		if ((*str >= 'a') && (*str <= 'z'))
			*str -= 32;
		str++;
	}
	return (init);
}

 

 

Exercise 08: ft_strlowcase 

Create a function that transforms every letter to lowercase
Allowed Function: None

 

이 문제는 위 문제와 동일한 구조이다. 

char	*ft_strlowcase(char *str)
{
	char	*init;

	init = str;
	while (*str != 0)
	{
		if ((*str >= 'A') && (*str <= 'Z'))
			*str += 32;
		str++;
	}
	return (init);
}

 

 

Exercise 09: ft_strcapitalize 

Create a function that capitalizes the first letter of each word and transforms all other letters to lowercase.
Allowed Function: None

 

 이 문제는 위에서 익힌 것들을 활용하는 문제이다. 문장에서 단어의 첫 글자를 대문자로 바꾸고, 아니라면 소문자로 바꿔야 하는 문제이다. 처음에 단어의 첫 글자가 아니라면 소문자로 바꿔야 하는지 몰라서 패스를 못했던 기억이 있다. 또 숫자 뒤에 나오는 알파벳은 첫 글자로 볼 수 없지만, 특수 문자 뒤에 나오는 알파벳은 첫 글자로 보아 대문자로 바꿔야 한다. 문제에 나온 예시는 다음과 같다. 

원래 문장: salut, comment tu vas ? 42mots quarante-deux; cinquante+et+un
바뀐 문장: Salut, Comment Tu Vas ? 42mots Quarante-Deux; Cinquante+Et+Un

 

 따라서 나는 is_a_or_d() 함수를 통해, 해당 문자가 알파벳이거나 숫자인지 체크했다. 알파벳이거나 숫자가 아닌 문자 뒤에 나온 소문자 알파벳을 모두 대문자로 바꿔주기 위함이다. 또 is_lowcase() 함수를 통해 소문자 알파벳인지를, is_upcase() 함수를 통해 대문자 알파벳인지를 체크했다.

 메인으로 사용하는 함수, 즉 문제에서 요구한 함수인  ft_strcapitalize()에서는 우선 문장의 가장 첫 번째 문자가 소문자 알파벳인지 확인하여 그렇다면 대문자로 바꿔준다. 뒤에 나오는 코드에서, 소문자의 앞글자가 특수문자인지를 체크하기 때문에, 아무런 조건 없이 대문자로 바꾸어줘야 하는 문장의 가장 첫 글자를 먼저 대문자로 바꿔주고 시작한다. 따라서 뒤에 나오는 while문에서는 index가 1일 때부터 시작한다. while문 안에서는 만약 현재 문자가 대문자이고 바로 앞 문자가 알파벳이거나 숫자라면 현재 문자를 소문자로 바꿔주고, 현재 문자가 소문자이고 바로 앞 문자가 알파벳이나 숫자가 아니라면 현재 문자를 대문자로 바꿔주는 과정을 거친다. 

int	is_a_or_d(char c)
{
	if ((c >= 'A') && (c <= 'Z'))
		return (1);
	if ((c >= 'a') && (c <= 'z'))
		return (1);
	if ((c >= '0') && (c <= '9'))
		return (1);
	return (0);
}

int	is_lowcase(char c)
{
	if ((c >= 'a') && (c <= 'z'))
		return (1);
	else
		return (0);
}

int	is_upcase(char c)
{
	if ((c >= 'A') && (c <= 'Z'))
		return (1);
	else
		return (0);
}

char	*ft_strcapitalize(char *str)
{
	int		idx;

	if (is_lowcase(str[0]) == 1)
		str[0] -= 32;
	idx = 1;
	while (str[idx] != 0)
	{
		if (is_upcase(str[idx]) && is_a_or_d(str[idx - 1]))
			str[idx] += 32;
		else if (is_lowcase(str[idx]) && !(is_a_or_d(str[idx - 1])))
			str[idx] -= 32;
		idx++;
	}
	return (str);
}

 

 

 

Exercise 10: ft_strlcpy 

Reproduce the behavior of the function strlcpy (man strlcpy)
Allowed Function: None

 

 이 문제는 앞서 나왔던, strcpy나 strncpy와 상당히 유사하다. destination에 source 문자열을 copy한다는 것이 같기 때문이다. 특히 strncp와는 복사할 크기가 따로 주어진다는 점에서 더 유사하다. 하지만 다른 점이 있다면, strncpy의 경우 인자로 주어진 n이 soucre에서 몇 개의 글자를 가져올 것인가에 대한 것이라면 strlcpy에서 인자로 주어지는 size는 복사한 후 destination 문자열의 크기에 대한 정보라는 것이다. 굉장히 말장난 같아 보이지만 strncat과 strlcat의 차이를 보면 더 선명하게 그 의미를 이해할 수 있다. 하지만 여기서는 copy 함수를 다루고 있으므로, 구분하기 가장 쉬운 기준을 말하자면 null값을 크기에 포함하느냐 아니냐이다. strncpy에서 인자로 주어지는 크기는 null값을 포함하지 않지만 strlcpy에서의 크기는 null값까지 포함한 크기이다. 예를 들어 "abc"라는 문자열을 복사하면서 크기가 3으로 주어진다면, strncpy의 경우 "abc" 전체가 복사되지만, strlcpy는 "ab"까지만 복사되는 것이다. "ab"까지만 복사해야 최종 크기가 null 문자를 포함하여 3이 된다.

 따라서 코드로 이를 구현해 보면, src 문자열의 크기를 파악해 주고, 복사할 사이즈가 0이면 src 문자열의 크기를 리턴(매뉴얼에 따라 구현)해준다. 0이 아니라면 0부터 null 문자를 위한 사이즈 하나를 남겨주고 copy 해준다. 마지막에 null 문자를 넣어주고 src의 사이즈를 리턴해주면 끝난다. 

 여기서 idx와 len을 모두 unsigned로 지정해 준 이유는 모두 음수가 될 수 없는 값이기도 하고, 특히 idx같은 경우는 인자로 주어진 size와 비교해줘야 하는데, 플래그 없이 실행시켰을 때는 에러가 발생하지 않지만 시스템에서 체크할 때 사용하는 플래그( -Wall -Wextra -Werror)와 함께 실행시키면 에러가 발생할 수 있다. 따라서 시스템에서 통과하기 위해서는 unsigned로 type을 모두 맞춰주어야 한다. 

unsigned int	ft_strlcpy(char *dest, char *src, unsigned int size)
{
	unsigned int		idx;
	unsigned int		len;

	len = 0;
	while (src[len])
		len++;
	if (size == 0)
		return (len);
	idx = 0;
	while (src[idx] && (idx < (size - 1)))
	{
		dest[idx] = src[idx];
		idx++;
	}
	dest[idx] = 0;
	return (len);
}

 

 

 

Exercise 11: ft_putstr_non_printable 

Create a function that displays a string of characters onscreen. If this string contains characters that aren’t printable, they’ll have to be displayed in the shape of hexadecimals (lowercase), preceeded by a "backslash".
Allowed Function: write

 

 이 문제는 문장에서 non printable character를 찾아서 해당 문자의 아스키코드로 변경하는 함수를 만들어야 한다. 그런데 그냥 아스키코드로 변경하는 것이 아니라, 소문자 알파벳으로 표현된 16진수 아스키코드로 바꿔야 하고, 그 앞에는 \가 나와야 한다. 문제에서 주어진 예시는 다음과 같다. 

원래 문장: Coucou\ntu vas bien ?
바뀐 문장: Coucou\0atu vas bien ?

 

 여기서는 non printable character인 '\n' (new line character)가 '0a'로 바뀐 것을 볼 수 있다. 이런 식으로 printable 범위 밖의 문자들을 변경하는 것이다. 

 나는 helper function으로 return_hex() 함수를 만들어 10진수 아스키코드를 16진수로 바꾸는 것을 구현했다. 다만 해당 함수는 하나의 문자만 바꿀 수 있으므로, 전체 숫자에서 각각의 자리를 처리하여 넣는 것은 메인으로 작동하는 function의 몫으로 남겨뒀다. 더 많은 자릿수가 있으면 아예 전체 숫자를 16진수로 바꾸는 함수를 만들었겠지만, 아스키코드의 최댓값이라고 해봐야 16진수로는 두 자릿수이기 때문에 간단히 한자리만 16진수 문자로 바꾸는 helper function을 만들었다. 이 문제에서 보통 helper function으로 printable인지 확인하는 함수를 많이 쓰는데, 코드가 많이 복잡하지 않아서 따로 빼지 않고 그냥 작성했다.

 다음 문제에서 작성을 요구한 ft_pustr_non_printable() 함수를 보면, 우선 char pointer 타입으로 받은 str을 unsigned char poiner 타입으로 바꾼다. 사실 왜 바꿨는지 정확히 기억은 안 나지만, 아마 당시 클러스터 내에서 음수 아스키코드로 표현되는 문자(예를 들면 핀란드어 알파벳)도 처리해야 한다는 의견이 한번 제시된 후에 평가할 때 그것까지 꼼꼼히 체크하는 평가자들이 있어서 그랬던 것 같다. 그래서 unsigned로 바꾼 문자열이 끝날 때까지 while문 내에서 non printable이라면 16진수 아스키코드로 바꿔서 출력하고, printable이라면 그냥 출력해 주는 과정을 반복한다.

 16진수 아스키코드로 바꾸는 과정은 다음과 같다. 우선 문제에서 제시한 대로 back slash를 추가하고, 현재 문자에서 16을 나눈 값을 return_hex() 함수에 넣어서 16진수 중 십의자릿수를 만들어 출력한다. 다음 현재 문자에서 16을 나눈 나머지 값을 return_hex() 함수에 넣어 16진수 중 일의 자릿수를 만들어 출력한다. 

#include <unistd.h>

char	return_hex(char n)
{
	if (n <= 9)
		return (n + '0');
	else
		return (n + 87);
}

void	ft_putstr_non_printable(char *str)
{
	char			c;
	unsigned char	*temp;

	temp = (unsigned char *)str;
	while (*temp != 0)
	{
		if ((*temp < 32) || (*temp > 126))
		{
			write(1, "\\", 1);
			c = return_hex(*temp / 16);
			write(1, &c, 1);
			c = return_hex(*temp % 16);
			write(1, &c, 1);
		}
		else
			write(1, temp, 1);
		temp++;
	}
}

 

 

 

Exercise 12: ft_print_memory 

Create a function that displays the memory area onscreen.
The display of this memory area should be split into three "columns" separated by a space :
◦ The hexadecimal address of the first line’s first character followed by a ’:’.
◦ The content in hexadecimal with a space each 2 characters and should be padded with spaces if needed (see the example below).
◦ The content in printable characters.
If a character is non-printable, it’ll be replaced by a dot.
Each line should handle sixteen characters.
If size equals to 0, nothing should be displayed.

Allowed Function: write

 

 이 문제는 앞서 배운 걸 모두 종합해 활용하는 문제이다. 한국에서는 보통 과제를 다들 100%까지 하지만 여기는 다들 패스만 받는 분위기라 아무도 도전하지 않았지만 난 했다... 문제가 많이 어렵다기보다는 조건이 많아 까다롭고 실수하기 쉬우며 무엇보다 구현하기 귀찮다. 아무튼 결국 해내긴 했지만, 굳이 도전하기를 추천하지는 않는다. 

 문제에 대해서 설명하자면, 한 줄에 16 글자씩 주어진 문자열에서 뽑아내서 출력하는 건데, 당연히 그냥 출력하는 건 아니고, 해당 문자를 3가지 다른 형태로 출력해야 한다. 첫 번째는 첫 글자의 주소값인데, 16진수로 출력해야 한다. 조건에 명시되어있지는 않지만 예시를 보면 16자리로 표현한다. 두 번째는 문자의 아스키코드인데, 이것 역시 16진수로 출력해야 하며, 두 글자씩 붙여서 출력한다. 마지막은 문자 그 자체를 출력하는 것인데, non-printable 문자의 경우 '.'으로 바꾸어 출력해야 한다. 이 세 가지 형태를 space로 나누어 출력하고, 첫 번째 형태인 주소값 다음에는 ':'을 추가한다.

 코드를 보면, print_address() 함수로 첫 번째 형태인 주소값을 출력하고, print_ascii() 함수로 두 번째 형태인 아스키코드를 출력하며, print_char() 함수로 문자 그  자체를 출력한다. 

 우선 print_address() 함수에서는 10진수 주소값을 인자로 받아, 16의 16 제곱(pow 변수)부터 16의 0 제곱까지 나눠가며 한 자리씩 16진수로 출력한다. 각 자릿수를 16진수로 출력하는 방식은 앞선 Exercise11과 유사하다.

 다음으로 print_ascii()에서는 문자열과 길이를 인자로 입력받아 길이만큼 아스키코드를 출력하고, 만약 길이가 16보다 작은 경우는 16까지 공백을 출력한다. (마지막 줄은 글자가 16 미만일 수 있기 때문에 형태를 맞춰주기 위해서는 마지막에 공백을 추가해야 한다.) 16진수 아스키코드로 바꾸는 방식은 앞선 Exercise11과 동일하다. 다만 문자 2개의 아스키코드를 출력한 후 공백을 출력해줘야 한다. 따라서 문자가 16개 미만이어서 끝에 공백을 추가하는 경우 이 점을 고려해서 공백 4개(한 문자의 아스키코드 두 자리 * 2) 출력 후 공백 하나를 추가해 주고 다시 4개를 출력하는 과정을 거쳐야 한다. 

 print_char() 함수는 비교적 간단한데, 문자열을 순회하면서 non-printable이라면 '.'을 출력하고, 아니라면 문자 자체를 출력하면 된다. 여기서는 문자가 16개 미만이라도 공백을 추가할 필요가 없는데, 공백을 추가하지 않아도 마지막 요소이기 때문에 형태가 무너지지 않기 때문이다. 

 마지막으로 문제에서 제시한 ft_print_memory() 함수에서는 우선 void pointer 형태로 제시된 addr 변수를 character pointer type으로 바꿔주어야 한다. 또 만약 size가 0일 경우 아무것도 출력해서는 안된다고 문제에 명시되어 있기 때문에 바로 함수를 종료시켜 준다. 그리고 while문에서는 size만큼의 문자열을 모두 출력해줘야 하는데 문자를 16개씩 출력해야 하므로, size-i, 즉 남은 문자의 개수가 16 이상인 경우에는 n을 16이라고 지정하고 16개의 문자를 출력해 주면 되고, 남은 문자의 개수가 16 미만이라면 그만큼을 n으로 지정해 준다. 그 후, 주소/아스키/문자열을 차례로 공백으로 나누어 출력해 주고, 마지막에 new line character를 더해주면 한 줄을 출력하는 과정이 완성된다. i에 n을 더해줌으로써 다음 문자로 이동한다. 

#include <unistd.h>

char	return_hex(int n)
{
	if (n <= 9)
		return (n + '0');
	else
		return (n + 87);
}

void	print_address(unsigned long long int add)
{
	long long unsigned int		pow;
	int							count;
	char						c;

	pow = 1;
	count = 0;
	while (count < 15)
	{
		pow *= 16;
		count++;
	}
	while (pow > 0)
	{
		c = return_hex(add / pow);
		write(1, &c, 1);
		add = add % pow;
		pow /= 16;
	}
}

void	print_ascii(unsigned char *str, int len)
{
	int				i;
	int				num;
	unsigned char	c;

	i = 0;
	while (i < len)
	{
		num = (unsigned int)str[i];
		c = return_hex(num / 16);
		write(1, &c, 1);
		c = return_hex(num % 16);
		write(1, &c, 1);
		if ((i % 2) == 1)
			write(1, " ", 1);
		i++;
	}
	while (i < 16)
	{
		write(1, "  ", 2);
		if ((i % 2) == 1)
			write(1, " ", 1);
		i++;
	}
}

void	print_char(unsigned char *str, int len)
{
	int		i;

	i = 0;
	while (i < len)
	{
		if ((str[i] < 32) || (str[i] > 126))
			write(1, ".", 1);
		else
			write(1, &str[i], 1);
		i++;
	}
}

void	*ft_print_memory(void *addr, unsigned int size)
{
	unsigned char	*str;
	unsigned int	i;
	unsigned int	n;

	if (size == 0)
		return (addr);
	str = (unsigned char *)addr;
	i = 0;
	while (i < size)
	{
		if ((size - i) >= 16)
			n = 16;
		else
			n = size - i;
		print_address((unsigned long long)str[i]);
		write(1, ": ", 2);
		print_ascii(&str[i], n);
		write(1, " ", 1);
		print_char(&str[i], n);
		write(1, "\n", 1);
		i += n;
	}
	return (addr);
}
728x90