F.R.I.D.A.Y.

printf 이진수 출력(사용자 지정 서식지정자 구현) 본문

DEV/C C++

printf 이진수 출력(사용자 지정 서식지정자 구현)

F.R.I.D.A.Y. 2021. 9. 14. 01:58
반응형

 사용자 지정 서식지정자를 구현해 printf 이진수 출력하기

 


 printf는 C언어의 스탠다드 라이브러리에서 출력을 담당하는 함수로 서식 지정자를 가지고 있다.

 

서식지정자

 printf는 기본적으로 바이너리 출력을 지원하지 않는다. 즉, 이진수 출력을 지원하기 위해서는 따로 함수를 만들어 별도로 작성을 하거나, 직접 printf의 모든 기능을 구현하면서 이진수 출력에 대한 코드를 넣어서 라이브러리를 새로 만들어야한다.

 

 이진수 출력의 문제나, 혹은 직접 커스텀 서식지정자를 만들고 싶을 때도 동일하게 발생하는 문제다.

 

이진수 출력용 서식지정자 정하기

 제공되는 서식지정자에는 이진수 형식으로 출력을 지원하는 서식지정자를 제공하지 않으므로, 새로운 함수를 만들어 기존 printf 함수와 혼용해 코드를 작성하면 그 길이를 상당히 단축시킬 수 있다.

 

 일단, 이진수 출력용 서식지정자는 %b로 결정했다. %b는 제공되는 서식지정자에도 존재하지 않는 정해지지 않은 지정자일 뿐만 아니라, 이진수를 뜻하는 binary의 첫 글자를 따온 것이기 때문에 보다 명확하게 사용할 수 있을 것이다.

 

로직 구현하기

 일단 printf의 기본 구조를 파악해야한다. printf는 서식지정자와 함께 뒤이어 가변인자를 이용해 출력할 값을 받아온다. 따라서 서식지정자를 만났을 때, printf로 어떻게 넘길 것인가가 중요하다.

 

모아서 전달

 만일 아래의 출력이 존재한다고 할 때, 우리는 %b를 기점으로 이전 문자열과 이후 문자열을 나누어서 처리하는 방식을 이용할 수 있다.

printf("Happy %d year! %d is decimal -> binary is: %b\n %d year is next year.\n", 2021, 2021, 2021, 2022);

 

 이 코드를 구현하고자 하는 %b까지 처리해 출력을 하면 아래와 같은 방식의 문자열이 출력될 것이다.

 

 %b 문자열을 기준으로 좌측에는 두 개의 가변인자가, 오른쪽에는 하나의 가변인자 출력이 필요하다. 그러나, 찾아본 결과 가변 인자를 이용해 가변인자로 보낼 때 사이에 범위를 지정해 넘기는 방식은 찾아볼 수 없었다.[# 정확히 하면, 한 두 페이지 찾아보고 다른 방식이 더 쉽겠다는 결론이 있었다. 성능적인 부분은 전혀 고려하지 않았다.] 때문에 다른 두 번째 방식을 고려할 때가 된 것이다.

 

하나씩 전달

 가변인자를 범위를 지정해 한번에 넘기는 것은 힘들지만, 하나씩 넘긴다면 속도는 느리더라도 충분히 쉽게 만들 수 있다. <모아서 전달>문단의 코드를 다시 한번 보자.

printf("Happy %d year! %d is decimal -> binary is: %b\n %d year is next year.\n", 2021, 2021, 2021, 2022);

 위 코드를 서식지정자 기준으로 자르면 아래와 같다.

  • Happy %d
  •  year! %d
  •  is decimal -> binary is: %b
  • \n %d
  •  year is next year.\n

 총 다섯 개의 문자열로 나눌 수 있다. 이렇게 나누어서 처리를 하게 되면 가변인자를 처리해서 하나씩 넘길 수 있게되고, 이는 printf가 제공하는 문법을 그대로 차용하면서 사용자 지정 서식지정자를 구현할 수 있게 된다.

 


구현된 코드

 가변인자를 printf에 <하나씩 전달>하는 방식으로 구현한 코드는 다음과 같다.

#include <stdarg.h> // va_list, va_start, va_arg, va_end 사용

int binPrintf(const char* args, ...) {
	const char* binEsc = "%b";
	size_t len = 0; while (*(args + len++));
	char* tempStr = malloc(sizeof(char) * (len + 1));
	if (tempStr == NULL) return -1;

	size_t bfOffset = 0;

	size_t va_count = 0;

	va_list list;

	va_start(list, args);

	for (size_t i = 0; i < len - 1; ++i) {
		tempStr[i - bfOffset] = args[i];
		
		if (args[i] == '%') {
			if (args[i + 1] == 'b') {
				tempStr[i - bfOffset] = 0;
				printf(tempStr);
				int temp = va_arg(list, int);
				for (int k = 31; k >= 0; --k) {
					putc('0' + ((temp >> k) & 0x01), stdout);
					if (!(k % 4) && k) {
						putc(' ', stdout);
					}
				}
				++i;

			}
			else {
				// 모든 서식 지정자에 대해 예외 처리를 진행하려면 해당 스코프 내부를 수정할 것

				tempStr[i - bfOffset + 1] = args[i + 1];	// 남은 서식지정자 가져오기
				tempStr[i - bfOffset + 2] = 0;				// 널문자
				++i;
				printf(tempStr, va_arg(list, void*));

			}
			bfOffset = i +1;
		}

	}
	tempStr[len - 1 - bfOffset] = 0;
	printf(tempStr);

	va_end(list);

	free(tempStr);

	return 0;
}

 

 코드를 보면 알겠지만, 해당 코드는 이런 식의 구현도 가능하다는 것을 알리고자 %와 문자 하나를 포함해 총 두 글자용 서식지정자만 처리할 수 있다. 때문에 %lld %lf와 같이 두 글자가 넘어가는 서식지정자는 제대로 된 결과를 출력할 수 없다.

 

 메모리를 매번 할당받고 해제하는 과정은 상당한 오버헤드를 가진다. 때문에 인자로 받는 문자열의 최대 길이만큼을 처음에 할당받고, 해당 메모리를 재사용하는 방식으로 일회만 할당, 일회만 해제하는 방식으로 성능을 조금이라도 높혔다. 이 과정에서 재사용에 필요한 몇 가지 변수가 더 필요하기는 했지만, 오버헤드를 줄일 수 있다는 점에서 몇 바이트 더 깎아먹는 것은 괜찮은 일이 아닐까 싶다.

 


. 번외

 또 생각해보면 <모아서 전달>하는 방식을 이용하면 기존 서식지정자에 대한 처리를 할 필요가 없는데, 이것도 한 번 찾아봐야겠다. 깊이 들어가면 이 방식이 더 나을 듯

 

# index

728x90
반응형

'DEV > C C++' 카테고리의 다른 글

함수 포인터 예시  (0) 2021.12.19
C++ 레퍼런스 타입  (0) 2021.10.07
다중 모니터 위치 파악하기  (0) 2021.07.16
PC 카카오톡 AD 제거  (0) 2021.06.25
회전 사각형을 외접하는 사각형  (0) 2021.05.26
Comments