일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
- push swap 설명
- ft_printf
- 42 pipex
- 42 libft
- get_next_line
- 42 so_long
- 와인선별방법
- 와인 고르기
- 파리 피크닉
- 굿노트 스티커
- 지베르니 여름
- 이지젯
- 42
- ecole42
- 포르투갈 여행
- 서울42
- pipex 42
- str함수
- 지베르니
- get next line
- pipex
- so_long
- 알고리즘 기초
- printf
- 지베르니 가을
- gnl
- push swap
- libft
- 지베르니 계절 추천
- 에꼴42
- Today
- Total
뇌 마음 반반저장소
[42_ft_printf] printf 만들기2 (정적 변수와 자료형) 본문
% 뒤에 따라오는 자료형 분류에 따라 다른 자료 출력형을 설정해줘야 한다. 이제 이를 바탕으로 반환해주는 함수를 만들어야 한다...! 그전에 printf를 만들면서 먼저 알았으면 좋았을 것 같은 점을 몇 개 훑고 지나가겠다.
ft_put***_fd 함수를 써서 반환하고 싶었지만 반환 값이 모두 void이기 때문에 마지막에 printf 특성에 따라 인수가 몇 개인지 알려주는 최종 리턴 값을 보내줄 수가 없었다. 그래서 그냥 다시 모두 fd를 제외하고 int로 반환해주는 새로운 함수를 만들었다.
*두고두고 햇갈리는점 :
염두해야 할 것은 모든 자료형을 받아서 그 자료형으로 내보내 주는 게 아! 니! 다!
%로 자료형을 받아오려는 이유는 그 자료형이 어떤 형이고 얼마나 메모리를 차지하는지 계산하기 위함이다.
실제로 함수에서 프린트되는 것은 write함수를 통해 문! 자!로 쓰인다는 점이다...!
-> 이 말인즉슨 우리는 모든 자료형을 문자로 바꿔서 write함수에 입력해 주어야 한다.
그리고 전체 공간을 return하기 위한 문자열의 계산을 잊지 말자.
*알아두면 유용한 정적 변수 (static variable)
printf를 구현하면서 계속 리턴 값을 구해줄 것이다. 그리고 계속 나눠질때까지, 혹은 나머지가 나올때 까지 루프를 돌려준다. 이럴때 몇번이나 값이 증가했는지 루프가 돌면서 계속 플러스를 해준다. 그럴때 프로그램이 종료될 때 까지 값이 유효한, 데이터에 값이 머무는 정적 변수(고정된 변수)를 쓰면 아무리 루프를 돌아도 값이 쌓일 것이다.
👉전역 변수, 지역변수, 정적 함수, 함수들의 저장 영역 더 보기!
자 그럼 예를 들어보잣.
#include<stdio.h>
void static_test()
{
static int i;
i++;
printf("%d\n", i);
}
void main()
{
static_test();
static_test();
static_test();
}
이렇게 함수 안에 static 변수를 선언하면 자동으로 0으로 초기화되면서 아래와 같은 결괏값이 나온다.
$ ./a.out
1
2
3
사실 선언 방법에 따라 저장소가 다르다. 더 알아보기 👇
static int x = 5; //The static variable x is stored in the initialized data segment
static int y; // The static variable y is stored in the BSS segment.
아래와 같이 static 변수를 가장 위해 헤더 부분에 정의를 해줘도 결과는 똑같다 :) 어디에 넣고 쓸지는 상황에 따라 판단할 것! (이렇게 외부에 선언되면 이 함수에서만 이거 쓸 거예요 ~이라는 뜻과 같다!)
#include<stdio.h>
static int i;
void static_test()
{
i++;
printf("%d\n", i);
}
void main()
{
static_test();
static_test();
static_test();
i = 2;
printf("start value 2\n");
static_test();
static_test();
static_test();
i = 3;
printf("start value 3\n");
static_test();
static_test();
static_test();
}
이렇게 상황에 따라서 원하는 곳에서 static 변수를 초기화해서 사용할 수도 있다.
$ ./a.out
1
2
3
start value 2
3
4
5
start value 3
4
5
6
여기서 염두해야 할 것은 static변수는 무조건 상수로만 초기화된다는 점이다. static int i = ft_strlen(s) 뭐 이런 거 안된다. 그리고 static 변수는 매개변수로 받을 수 없다!
루프와 정적 변수와 당신의 수학적 지식은 융합되어 대우주를 이룰 것입니다?
이제 printf를 구현하면서 사용한 함수를 예를 들어보겠다.. 가장 많은 시간을 할애한 부분이다. 어떻게 하면 가장 경제적으로 작문을 하면서 이 static 변수를 잘 활용할까!
예를 들어서 %u를 구현하는 함수를 까 보겠다.
int ft_u(unsigned int u)
{
static int i;
i = 0;
if(u > 9)
{
ft_putten(u / 10);
u %= 10;
}
ft_putchar((u + '0'));
i++;
return(i);
}
그럼 여기서 함수가 어떻게 돌아가고 i값이 어떻게 변하는지 확인해보자.
결과는!
$ ./a.out
365
return value : 3
이렇게만 보면 static 변수를 선언해주는 게 중요할까?라고 생각할 수 있겠지만 static 변수를 쓰지 않으면 {3}에서 {2} 함수로 나갔을 때 다시 i가 0인 기본값으로 돌아간다. 그냥 int로 선언했을 때의 반환 값을 아래에서 확인해 보자.
$ ./a.out
365
return value : 1
그래서 루프를 돌리며 2개 이상의 값을 구하는 수식을 사용한다면 static 변수를 꼭 염두하자!
자, 그러면 본격적으로 다른 자료형들의 변형을 살펴보자.
%c 단일 문자를 인쇄 :
int 형으로 들어간다.
만약 % 다음 문자가 c라면?
그냥 매개변수의 단어를 가져와서 사용하면 된다. ft_putchar로 구현했다.
$ ./main.out
ft_printf returns : A
printf returns : A
%s 일반적인 C 규칙에 정의된 대로 문자열을 인쇄 :
char * 형으로 들어간다.
만약 % 다음 문자가 s라면?
매개변수의 문자열을 차례로 읽어 반환한다. ft_putstr로 구현했다.
$ ./main.out
ft_printf returns : I did my printf!
printf returns : I did my printf!
%p void * 포인터 인수는 16진수 형식으로 인쇄 :
unsigned long long 형으로 들어간다.
만약 % 다음 문자가 p라면?
포인터 값을 구해서 16진수 형식으로 인쇄한다. 일단 16진수가 뭔지 알아보자. 위에는 우리가 흔히 쓰는 10진수이고 아래는 16진수이다.
365를 16진수로 나타내 본다면,
365 나누기 16 = 22이고 나머지는 13이다.
22 나누기 16은 = 1이고 나머지는 6이다.
그러면 가장 나중에 몫으로 나온 1을 먼저 쓰고
첫 나머지 6을 쓰고
그다음 나머지 13(d)을 쓴다.
정답은 16d
그렇다면 2023은 16진수로 어떻게 쓸지 한번 시도해 보자. 정답은 👇
2023 / 16 = 126, 2023 % 16 = 7
126 / 16 = 7, 126 % 16 = 14
정답은 7E7
그렇다면 % p 주소는?
1. 16진수를 나타내는 0x를 꼭 앞에 붙여준다.
2. 16진수로 계산한 주소를 보내준다. (뒤의 % x를 구하는 식을 대입한다.)
$ ./main.out
ft_printf returns : 0x411cb871
printf returns : 0x7ffd411cb871
하지만 왜 위의 결과가 다른데 테스트를 통과하느냐! 메모리의 최솟값과 최댓값 (0x00000000~0xFFFFFFFF) 이러하다. 답은 stackoverflow에서 기다리고 있다... ㅎㅎ
% d 10진수(기준 10)를 인쇄 :
int 형으로 들어간다.
만약 % 다음 문자가 d라면?
int를 차례로 변환해서 넣어주는 ft_putnbr를 사용했다. 이 안에는 ft_itoa로 문자로 바꿔준 다음, ft_strlen으로 숫자를 세고 차례로 반환해준다.
$ ./main.out
ft_printf returns : 365
printf returns : 365
% i 밑줄 10에 정수를 인쇄 :
int 형으로 들어간다.
만약 % 다음 문자가 i라면?
사실 i와 d는 같단다. 그래서 함수 안에서는 같은 카테고리로 분류해서 코드를 작성했다.
% u 부호 없는 10진수(기준 10)를 인쇄 :
unsigned int 형으로 들어간다.
만약 % 다음 문자가 u라면?
이것도 % d와 별 큰 차이는 없지만 unsigned로 '-'를 계산하지 않아도 돼서 짧게 10진수만 표현할 수 있다. 들어오는 자료형이 다르기 때문에 따로 작성해 주어야 한다!
*흥미로운 점 : printf는 %d와 % i를 같은 자료형으로 취급하지만...
scanf에서는 %i는 16진수, 10진수, 8진수 모든 진수를 이해한다..!
%x 숫자를 16진수(기준 16) 소문자 형식으로 인쇄 :
unsigned int 형으로 들어간다.
만약 % 다음 문자가 x라면?
정수가 들어가도 10진수로 위에처럼 계산해서 16진수로 변환된 값이 나와야 한다! 소문자로 16진수를 반환한다.
*코딩이 짧아지는 팁: 아래처럼 hex의 문자열에서 n번째를 프린트하는 방법이 있다..!
hex = "0123456789abcdef";
ft_putchar(hex[n]);
!메인함수!
int main (void)
{
ft_printf("ft_printf returns : %x\n", 2023);
printf("printf returns : %x\n", 2023);
return (0);
}
결과는?
$ ./main.out
ft_printf returns : 7e7
printf returns : 7e7
%X 16진수(기본 16) 대문자 형식으로 숫자를 인쇄 :
unsigned int 형으로 들어간다.
만약 % 다음 문자가 X라면?
대문자로 16진수를 반환한다.
!메인함수!
int main (void)
{
ft_printf("ft_printf returns : %X\n", 2023);
printf("printf returns : %X\n", 2023);
return (0);
}
결과는?
$ ./main.out
ft_printf returns : 7E7
printf returns : 7E7
%% 퍼센트 기호를 인쇄 :
% 가 출력된다.
만약 %다음 문자가 c라면?
그냥 %가 출력되게 한다.
$ ./main.out
ft_printf returns : %
printf returns : %
도움을 주고 싶으신 내용이나
틀린 내용이 있다면 댓글로 남겨주시고,
도움이 되었다면 공감 한 번씩 눌러주세요👍❤️
'42 > ft_printf' 카테고리의 다른 글
[42_ft_printf] printf 만들기1 (feat. 자세한 va 함수 설명) (1) | 2022.12.17 |
---|---|
[42_ft_printf] 시작하며 (0) | 2022.12.16 |