Born to Code
42서울 본과정
토글이 없어 보기 힘듭니다.
노션과 깃 페이지를 남겨두니 참고하세요.
보너스 안함
개발 전 기록사항
굳이 헤더를 여러개 만들 필요가 있을까? 우선 하나만 만들어보자.
파일 하나에 전부 구현하고, 파일을 나누어 보자.
버퍼관리를 하지 말라는 것은 문자열 관리 없이 바로 출력하라는 것일까?
%를 만나기 전까지는 출력하고 %
를 만나면 처리하면 되려나?
저번에 멘토님이 말씀주신 LRparser
에 대해 알아보자 (안함)
반환값은 출력한 문자열의 길이, \n포함, 에러시 아무것도 출력안하고 0반환
플래그 중복 가능 여러개 중복 가능
width든 뭐든 %와 타입 사이에 플래그는 옮
width에서 int범위를 초과하면 오버플로우가 아니라 그냥 출력이안됨
기능구현목록
출력할 문자열 (string)과 데이터의 문자열 (data_str)은 서로 다름.
%
를 만나지 않는다면 계속 출력.%
를 만났다면, flag
, width
, precision
순으로 파싱.type
을 만났다면, 해당 출력 함수 호출.FLAG_STAR
)가 세워져 있다면, 가변인자에서 문자를 받음.FLAG_STAR
)가 세워져 있지 않고 포인터의 문자가 digit
이라면, 숫자 파싱.width
가 세워져 있지 않다면 0
..
을 가리키고 있다면, 플래그프리시전 (FLAG_PREC
)을 세우고, precision
을 0으로 초기화. (.
만 나오고 숫자가 안나올 수 있기 때문)*
을 가리키고 있다면, 가변인자에서 숫자를 받아와서 저장, 플래스스타프리시전 (FLAG_STAR_PREC
)을 세움.*
이 아니면서 숫자라면, 숫자 파싱.-1
.-
, 0
, *
이 그만 나올때까지 포인터 이동.flag
에 비트로 저장.width
값을 비교하여 1 or width 값만큼 출력길이 지정.0
혹은 ' '
값 저장.0
추가.-
추가.null
일 경우 문자열을 널로 채움.width
를 비교하여 출력할 문자열 할당.0
혹은 ' '
값 저장.-
로 바꾸고 가변인자 문자열의 -
를 0
으로 바꿈.)print_c
와 같으나, 가변인자를 받지 않고 %
를 넣음.null
일 경우 2칸 할당 뒤 숫자 0
, 아닐 경우 16칸을 할당하고 데이터를 16진수로 변환하여 넣음.+2
만큼 저장.width
의 길이와 출력문자열의 길이를 비교하여 문자열 할당.0
혹은 ' '
값 저장.0
과 x
를 넣음.null
일 경우 (null)
문자열, 아닐경우 그대로 저장.width
값과 문자열의 길이를 비교하여 출력문자열의 길이 할당.print_int
와 같으나 마이너스 부분만 삭제.print_p
와 같으나 0x
부분만 삭제. 대문자소문자는 put_number_base
에서 처리피신때부터 악명높았던 printf가 왜 그만한 평을 받고 있는지 잘 알게 되었다.
각 타입의 출력형식 뿐 아니라 플래그의 적용이나 옵션의 유무도 달랐다.
%
를 만나기 전까진 한글자씩 출력.%
를 만나면, 플래그 길이 정밀도 순으로 파싱 후 타입 확인.%
부터 플래그, 길이, 정밀도 순으로 파싱을 하고 각 값을 저장한다.# define FLAG_MINUS 1
# define FLAG_ZERO 2
# define FLAG_STAR 4
# define FLAG_STAR_PREC 8
# define FLAG_PREC 16
/*
typedef struct s_options
{
unsigned char flag;
int width;
int precision;
char type;
} t_options;
*/
unsigned char flag;
flag = 0; // 0000 0000
while (**p == '-' || **p == '0' || **p == '*')
{
if (**p == '-')
flag |= FLAG_MINUS; // => flag = flag | FLAG_MINUS
else if (**p == '0')
flag |= FLAG_ZERO; // 0000 0010
else if (**p == '*')
flag |= FLAG_STAR; // 0000 0100
(*p)++;
}
/*
* `|` 는 OR 연산으로 flag | FLAG_MINUS 의 값은 0000 0001이 됨.
*/
if (op.flag & FLAG_ZERO)
string[i++] = '0';
/*
* '&' 는 AND연산으로 flag & FLAG_ZERO 의 반환값은 FLAG_ZERO, 즉 양수임
* 만약 flag가 0000 0000 이라면(오른쪽 두번째 숫자가 0일 경우), 반환값은 0임.
*/
단 x(적용되지 않음)의 경우, undefinded behavior이지 결과값은 플래그가 적용된 상태로 나옴.(클러스터 맥 기준)
플래그가 두개 동시에 나오는 경우, 0 플래그는 제외하고 -플래그만 사용한다.
또한, 플래그가 아래 예시처럼 여러개가 나올 경우에도 작동된다.
물론 위 경우처럼 -
과 0
이 동시에 나올 경우, undefined behavior이다.
하나만 여러개 나올 경우 경고조차 발생하지 않는다.
플래그는 어느 위치에서나 적용될 수 있지만, 경고 메세지가 뜬다.
(%--10.3--s 의 경우, invalid conversion specifier '-')
따라서, 이번에 작성한 ft_printf 코드는 undefinded behavior에 관하여 출력내용을 따라하기도, 에러로 판단하고 출력하지 않기도 한다.
while (**p == '-' || **p == '0' || **p == '*')
{
if (**p == '-')
flag |= FLAG_MINUS;
else if (**p == '0')
flag |= FLAG_ZERO;
else if (**p == '*')
flag |= FLAG_STAR;
(*p)++;
}
%
에서부터 type
까지의 부분에 대하여 출력될 총 문자열의 길이를 뜻한다.데이터의 길이
(가변인자로 받았고, 가공되어 출력 전의 상태의 데이터)보다 값이 크다면 width
만큼, 아닐 경우 데이터의 길이
만큼 출력된다.*
이 나오면 가변인자에서, 나오지 않았다면 숫자가 있을 경우 파싱하여 값을 저장한다.*
과 숫자가 같이 올 수 없다.-
플래그 + 숫자로 간주한다.if (*flag & FLAG_STAR)
{
width = va_arg(ap, int);
if (width < 0)
{
*flag |= FLAG_MINUS;
width *= -1;
}
}
else if (ft_isdigit(**p))
width = ft_printf_atoi(p);
.
을 사용.*
을 이용하여 가변인자에서 불러올 수 있다..
뒤에 숫자 없이 바로 type
이 올 수 있다. (이때, 0으로 간주 ex)%.d)if (**p == '.')
{
(*p)++;
*flag |= FLAG_PREC;
precision = 0;
if (**p == '*')
{
(*p)++;
precision = va_arg(ap, int);
*flag |= FLAG_STAR_PREC;
}
else if (ft_isdigit(**p))
precision = ft_printf_atoi(p);
}
코드를 넣으면 글이 너무 길어져 삭제하였습니다.
코드가 필요하신 분은 노션을 참고해주세요.
구현방법은 정말 여러가지입니다. 참고용으로만 보시길 바랍니다.
데이터 문자열과 출력문자열은 서로 다릅니다.
가변인자란?
...
을 사용하여 가변인자임을 알린다.int ft_printf(const char *str, ...)
헤더 : <stdarg.h>
사용 :
va_list
변수를 만든다.
해당 변수는 각 가변인자의 시작 주소를 가리키는 포인터이다.
va_start
함수를 이용하여,
해당 함수는 가변인자를 가져올 수 있도록 포인터를 설정(초기화)하는 함수이다.
- va_start(ap, str)
에서 ap는 가변인자(va_list) 변수, 그 뒤 고정인자를 넣는다.
이때 가변인자는 고정인자의 뒤에 위치하게 된다.
int ft_printf(const char *str, ...)
{
va_list ap;
int count;
va_start(ap, str);
count = myprintf(str, ap);
va_end(ap);
return (count);
}
va_arg
함수를 이용하여 가변인자를 가져온다.
va_arg
(va_list, 자료형)은 va_list
가 가리키는 값을 자료형의 크기
만큼 리턴한 후, va_list를 자료형 크기만큼 뒤로 옮긴다
.
va_end
함수를 사용하여 va_list를 초기화
한다.
<출처>
출력된 길이를 반환한다.
오류 발생 시 -1을 리턴한다.
코드 삭제함.
data
의 크기를 int
로 받아야 한다.
이에 관련한 설명은 아래 링크로 대신한다.
char type in va_arg stack_overflow
width
의 길이가 1보다 길면, 출력할 길이를 width만큼 확보한다.0플래그
가 적용될 사항일 경우 0
, 아닐 경우 빈칸
을, 확보한 string에 넣는다.-플래그
유무에 따라 문자를 앞 혹은 뒤에 넣고 출력한다.코드 삭제함.
가변인자를 char *
자료형으로 받아온다.
저장되는 데이터는 문자열의 주소값이다.
data
가 null(0)
일 경우 문자열은 (null)
이 된다. 이때, 괄호가 포함된다.
정밀도가 음수이거나 문자열의 길이보다 길
경우 문자열을 그대로 두고, 양수이면서 문자열의 길이보다 짧을
경우 문자열을 정밀도의 길이만큼만 자른다.
if (op.precision < 0 || op.precision > ft_strlen(data))
pro_data = ft_strndup(data, ft_strlen(data));
else
pro_data = ft_strndup(data, op.precision);
width
이 현재 가공된 데이터(pro_data)
보다 길 경우 width만큼, 짧을 경우
가공된 데이터 길이만큼 확보한다.0플래그
가 적용될 사항일 경우 0
, 아닐 경우 빈칸
을, 확보한 string에 넣는다.-플래그
유무에 따라 문자열를 앞 혹은 뒤에 넣고 출력한다.코드 삭제함.
data를 int형
으로 받아온다.
data를 itoa함수
를 사용하여 문자열로 바꾼다. 이때 음수는 고려하지 않는다.
(문자열에 '-' 안넣음)
정밀도가 문자열의 길이보다 길다면 그 차만큼 앞에 0을 추가
한다.
data가 음수면 앞에 -
를 추가한다.
정밀도가 0
이면서 data가 null
이면 문자열을 널값이 들어있는 1칸짜리
로 바꾼다.
width값이 데이터 문자열의 길이보다 길다면 width, 아니라면 데이터문자열 길이만큼 출력할 문자열의 길이를 확보한다.
0플래그
가 적용될 사항일 경우 0
, 아닐 경우 빈칸
을, 확보한 string에 넣는다.
-플래그
유무에 따라 문자열를 앞 혹은 뒤에 넣는다.
-플래그가 적용되지 않으면서 때 데이터가 음수
라면, 출력할 문자열의 맨 앞을 -로 바꾸고 중간에 들어있는 -를 0으로 치환
한다.
출력
코드 삭제함.
integer 출력방법에서 -와 관련된 부분을 삭제
하고 출력.
주소값 출력이지만 가변인자로 일반변수를 넣으면, 값을 16진수로 변환하여 출력한다.
코드 삭제함.
data
를 void *
형으로 받아오고 unsigned long
으로 캐스팅한다.
data가 null
이라면 1칸
, 아닐 경우 16칸
의 데이터 문자열의 길이를 확보한다.
data가 null
이라면 문자 0
, 아니라면 put_number_base 함수로 16진수로 변환한 값
을 데이터 문자열에 저장한다.
data가 null
이면서 정밀도가 0
인 경우 문자열을 널값이 들어간 1칸
짜리로 바꾼다.
정밀도가 문자열의 길이보다 길다면 그 차만큼 앞에 0
을 추가한다.
문자열의 길이 + 2
만큼 출력할 문자열의 길이를 확보한다.
문자열의 길이 + 2보다 width길이가 길다면 그만큼 더 확보
한다.
0플래그
가 적용될 사항일 경우 0
, 아닐 경우 빈칸
을, 확보한 string에 넣는다.
-플래그
유무에 따라 문자열를 앞 혹은 뒤에 넣는다.
이때, -플래그
의 적용을 받는다면 출력할 문자열의 앞 2칸
을 확보한다. (2칸 뒤에 데이터를 넣는다.)
데이터문자열이 들어간 위치 앞 2칸을 0x
로 채운다.
출력
코드 삭제함.
%p
에서 출력할 문자열 2칸 더 확보
, 0x 추가
부분을 제외하고 작성한다.
코드 삭제함.
%c
에서 가변인자를 받아오는 부분
과 문자를 출력할 문자열에 삽입하는 과정을 %로 바꾸면
된다.
jaeseokim
님의 블로그와 깃을 참고하여 작성하였다.warning
리눅스 토르발즈의 printf를 참고하는 도중, 실제 printf에서 가변인자의 개수나 경고를 발생시키는 주체 등에 관한 궁금증이 생겼다.
결론은, printf에서 경고 등은 컴파일에서 발생시키는 것으로 현재 나의 실력으로는 구현을 하거나 더 알아보기 힘들다고 판단했다.
가변인자의 끝
실제 printf에서 가변인자의 개수를 덜 주거나 더 주는 경우에 관하여, 가변인자의 개수를 파악하거나 알 수 있는 방안이 있는지 궁금했다.
결론은, 사실상 불가능 한 것으로 봐야할 것이다.
기타 참고