printf 출력 형식의 구조
%[flag][width][.정밀도(precision)][길이(length)]서식지정자(specifier)
구현해야할 것
[Mandatory]
- 다음의 conversion을 구현. %c %s %p %d %i %u %x %X %%
- 다음 flag들의 동작을 구현. - 0 . *
[Bonus]
- %n %f %e %g 중 하나 이상의 conversion 구현
- l ll h hh 중 하나 이상의 flag 구현
- + (공백) # flag 구현.
공부해야할 것
(몰랐던 것만 정리했다. 티스토리 표 디자인 진심 구리네)
서식문자(specifier)
%i | 부호가 있는 10진 정수. %d 는 다음과 같은 차이가 있다. →https://hashcode.co.kr/questions/934/형식지정자-i-d-차이 |
%u | 부호가 없는 10진 정수 |
%x | 부호가 없는 16진법 정수 (소문자 사용) |
%X | 부호가 없는 16진법 정수 (대문자 사용) |
%% | stdout에 %를 문자 그대로 출력 |
%n | 아무것도 출력하지 않는다. 그 대신, 인자로 부호 있는 int형을 가리키는 포인터를 전달해야 되는데, 여기에 현재까지 쓰여진 문자 수가 저장된다. |
%g | %f와 %e중 더 짧은 것을 사용 |
플래그(flag)
- | 왼쪽 정렬 |
0 | 수들을 오른쪽으로 정렬하되 빈 칸을 삽입하는 대신에 0 삽입. (문자열에 대해선 undefined behavior. -와 같이 쓰일경우 - 가 우선순위 높음) |
* | 폭(width) : 너비를 인자로 받는다. width 값이 음수면 - flag 가 있는 것 처럼 왼쪽 정렬된다. |
예시 printf("iNum : %*d\n", 5, iNum); printf("fNum : %*f\n", 30, fNum); printf("결과 : %+*s\\n", 14, "문자열 자르기"); //결과 iNum : 65 fNum : -26.230000 결과 : 문자열 자르기 |
|
(공백) | 앞에 부호가 붙지 않는다면 한 칸을 출력한다. (다시말해 123은 " 123" 으로 출력되고 -123은 "-123"으로 출련된다.) |
# | o, x, X 서식 문자들과 사용되면 출력되는 값 앞에 각각 0, 0x, 0X 가 붙게 된다. (출력값이 0일 땐 제외). e, E, f 서식 문자들과 사용되면 소수점 아래 수들이 없음에도 불구하고 강제적으로 소수점을 붙이도록 한다. 원래 소수점 아래 수들이 없다면 소수점을 붙이지 않는다. g 와 G 서식 문자들과 사용되면 e 와 E 일때와 동일한 작업을 하지만 소수들의 뒷부분에 붙는 0 들 (123.1200 등) 은 제거되지 않는다. |
+ | 출력 결과값이 양수인 경우라도 + 기호를 앞에 붙여서 출력하도록 한다. (물론 음수면 자동적으로 - 가 붙는다). 기본적으로 지정하지 않았을 경우 음수에만 앞에 - 가 붙는다. |
길이(length)
ll | long long int, unsigned long long int |
h | short int, unsigned short int |
hh | signed char, unsigned char |
+ ) restrict 포인터
printf 메뉴얼을 보니 함수 원형에 restrict * 가 붙어있어서 호기심에 공부했다.
//printf 함수원형
int printf(const char * restrict format, ...);
↓ 이런 함수가 있다고 하자
//increase 함수
void increase(int *a, int *b, int *x)
{
*a += *x;
*b += *x;
}
어셈블리 뜯어보는 방법
↓
$ gcc -g -std=c99 -O3 -c increase.c
$ objdump -S increase.o
항목 |
설명 |
-g |
gdb에게 제공하는 정보를 바이너리에 삽입한다. |
-std=c99 |
(C언어와 관련된 옵션) c99 표준 지정 |
-O3 |
가장 높은 레벨의 최적화. 모든 함수를 인라인 함수와 같이 취급한다. |
-c(파일명.c) |
오브젝트 파일을 생성한다. |
objdump |
목적파일의 내용을 덤프하는 것. 덤프는 짐을 내린다는 뜻인데, 기억 장치의 내용을 일부 또는 전부를 출력한 복사본을 의미 |
-s |
section 별로 나눠서 내용를 보여줌 |
결과
0000000000000000 _increase:
; {
0: 55 pushq %rbp
1: 48 89 e5 movq %rsp, %rbp
; *a += *x;
4: 8b 02 movl (%rdx), %eax //x를 역참조하여 가져온 값을 eax에 저장
6: 01 07 addl %eax, (%rdi) //eax의 값만큼 a를 역참조하여 값을 증가시킴. rdi에 eax만큼 더한다.
; *b += *x;
8: 8b 02 movl (%rdx), %eax //x를 역참조하여 가져온 값을 eax에 저장
a: 01 06 addl %eax, (%rsi) //eax의 값만큼 b를 역참조하여 값을 증가시킴
; }
c: 5d popq %rbp
d: c3 retq
함수 호출 규약에 의하면, 매개변수는 레지스터를 통해 전달되고, 다음의 순서로 레지스터를 이용한다.
rdi 첫번째 매개변수
rsi 두번째 매개변수
rdx 세번째 매개변수
본론으로 돌아와서, restrict 가 필요한 이유는 뭘까?
int a = 1;
increase(&a, &a, &a);
이렇게 호출하면 매개변수 a, b, x는 같은 메모리 공간을 가리키게 된다. 이런 포인터를 alias라고 부르는데, 이런 경우 잘못된 처리를 되돌리는 작업이 복잡하다.
그래서 alias가 아닌 상황에서, 어셈블리를 더 최적화할 수 있기 위해 restrict 포인터라는 것이 존재한다.
void increase(int *restrict a, int *restrict b, int *restrict x)
{
*a += *x;
*b += *x;
}
결과
0000000000000000 _increase:
; {
0: 55 pushq %rbp
1: 48 89 e5 movq %rsp, %rbp
; *a += *x;
4: 8b 02 movl (%rdx), %eax
6: 01 07 addl %eax, (%rdi)
; *b += *x;
8: 01 06 addl %eax, (%rsi)
; }
a: 5d popq %rbp
b: c3 retq
*b += *x 의 어셈블리 명령이 1줄 줄어들었다. 매개변수 x의 위치가 a, b와 다르다는 것이 확실하기 때문에 x의 위치를 다시 역참조하하는 movl (%rdx), %eax가 필요없어진 것이다.
그러니까 restrict 포인터는 컴파일러에게 최적화를 하라고 알려주는 키워드이다. restrict가 있다고 무조건 메모리가 다른 공간을 가리킨다고 보장하는게 아니므로, 사용할 때 주의하여야한다.
참고
https://www.cypress.com/file/54441/download
https://dojang.io/mod/page/view.php?id=760
'공부기록 > 42 Seoul' 카테고리의 다른 글
[ft_printf] 03 libft.a 를 다른 프로젝트에서 사용할 수 있게 하기 (0) | 2020.07.18 |
---|---|
[ft_printf] 02 가변인자(variable argument) 에 대하여 (0) | 2020.07.17 |
get_next_line Valgrind 캡쳐 (0) | 2020.06.07 |
norminette 메시지 (0) | 2020.04.06 |
42 SEOUL La Piscine 후기 _ 4주차 _ 끝 (13) | 2020.03.25 |