3단원 — 입출력
프로그램이 외부와 대화하는 가장 기본적인 두 가지 도구를 익힙니다. `printf`는 **출력**, `scanf`는 **입력** 입니다.
이 강의에서 배우는 것
- 1`printf`의 서식 지정자를 자료형에 맞게 사용한다.
- 2`scanf`로 정수, 실수, 문자열을 입력받는다.
- 3너비/정밀도 같은 형식 옵션으로 출력을 정돈한다.
- 4`scanf`의 흔한 오류(`&` 누락, `%lf` vs `%f`)를 피한다.
왜 입출력 형식이 까다로울까?
`printf`/`scanf`는 **가변 인자 함수**입니다. 컴파일러가 인자의 자료형을 일일이 검사하지 못하므로, 서식 문자열(`"%d %f"`)을 보고 "메모리에서 값을 어떻게 꺼내야 하는지" 결정합니다. 서식과 실제 자료형이 어긋나면 **쓰레기 값** 또는 **세그멘테이션 폴트**가 납니다.
이 단원은 사실상 "**서식 지정자 표를 외우는 단원**" 입니다.
핵심 개념
1) 주요 서식 지정자
| 지정자 | `printf` 자료형 | `scanf` 자료형 | 예 |
|---|---|---|---|
| `%d` | `int` | `int *` | `printf("%d", 42)` |
| `%u` | `unsigned int` | `unsigned int *` | |
| `%f` | `double` (자동 승격) | `float *` | `printf("%.2f", 3.14)` |
| `%lf` | `double` | `double *` | `scanf("%lf", &x)` |
| `%c` | `char`(int로 승격) | `char *` | `printf("%c", 'A')` |
| `%s` | `char *` | `char *` | `scanf("%9s", buf)` |
| `%x`, `%o` | 16진/8진 정수 | `printf("%x", 255)` → `ff` | |
| `%zu` | `size_t` | `printf("%zu", sizeof(int))` |
`printf`에서 `float` 인자는 호출 규약상 `double`로 자동 승격되어 `%f`로 받습니다. `scanf`에서는 그렇지 않으므로 **반드시** `float *`는 `%f`, `double *`는 `%lf`.
2) 너비와 정밀도
printf("[%5d]\n", 42); // [ 42] 너비 5
printf("[%-5d]\n", 42); // [42 ] 왼쪽 정렬
printf("[%05d]\n", 42); // [00042] 0 채움
printf("[%8.3f]\n", 3.14); // [ 3.140] 너비 8, 소수 3자리3) `scanf` 사용법
int n;
scanf("%d", &n); // & 로 변수의 주소를 전달여러 값:
int a, b;
scanf("%d %d", &a, &b); // 공백/탭/개행 구분**주의**: `scanf("%d", n);` 처럼 `&`를 빼먹으면 컴파일은 통과하더라도 실행 시 **세그멘테이션 폴트**.
4) 문자열 입력은 `fgets`가 안전
`scanf("%s", ...)`는 공백을 만나면 멈추고 버퍼 크기를 검사하지 않아 **버퍼 오버플로** 위험이 있습니다. 한 줄 입력은 `fgets`가 권장입니다.
char buf[64];
fgets(buf, sizeof(buf), stdin); // 줄바꿈 포함
buf[strcspn(buf, "\n")] = '\0'; // 줄바꿈 제거예제로 보기
예제 1 — `ex01_printf.c` : 다양한 서식 지정자
**실행 결과**
10진: 255
16진: ff
8진: 377
문자: A (코드 65)
문자열: Hello
실수 기본: 3.141592
실수 두자리: 3.14
지수표기: 3.141592e+00핵심: 같은 값(255)도 진수 지정자에 따라 다르게 출력됩니다.
예제 2 — `ex02_scanf_int.c` : 정수 한 개 입력
int n;
scanf("%d", &n);
printf("입력값: %d\n두 배: %d\n", n, n * 2);**입력**: `7`
**실행 결과**
정수를 입력하세요: 입력값: 7
두 배: 14핵심: `&n`의 `&`를 빼면 컴파일은 되지만 실행이 깨집니다.
예제 3 — `ex03_scanf_multi.c` : 여러 값 한 번에 입력
**입력**: `17 5`
**실행 결과**
두 정수를 공백으로 구분해 입력: 합: 22
차: 12
곱: 85
몫: 3핵심: 입력은 **공백, 탭, 개행** 어떤 것으로 구분해도 됩니다.
예제 4 — `ex04_format.c` : 너비/정밀도
**실행 결과**
[ 42]
[42 ]
[00042]
[ 3.1416]
[3.1416 ]핵심: 표 형태 출력에서 컬럼을 맞출 때 자주 씁니다.
다른 시각으로 보기 — `printf`는 "양식 채우기"
서식: "값은 %d, 비율은 %.2f%%\n"
인자: 42 0.75
│ │
▼ ▼
출력: "값은 42, 비율은 0.75%"서식의 `%d`/`%.2f`는 빈 칸이고, 뒤의 인자가 그 자리에 차례로 채워집니다. `%%`는 화면에 `%` 한 글자를 출력하기 위한 이스케이프.
자주 하는 실수
- **`scanf`에 `&` 누락**: `scanf("%d", n)` → 충돌. 반드시 `&n`.
- **`%f` vs `%lf`**: `double` 입력에는 `%lf`, `float` 입력에는 `%f`.
- **버퍼 오버런**: `char buf[8]; scanf("%s", buf);` 에 9자 입력 → 메모리 손상.
→ `scanf("%7s", buf)` 처럼 너비를 제한하거나 `fgets` 사용.
- **개행이 남음**: `scanf("%d", ...)` 후 `fgets`를 부르면 빈 줄이 읽힙니다.
→ `scanf` 뒤에 `getchar()` 한 번으로 흡수하거나, 처음부터 `fgets` + `sscanf` 패턴 사용.
- **`%d`에 `double` 전달**: 출력값이 의미 없음(자료형 불일치).
정리
- `printf`/`scanf`는 서식 지정자가 핵심이며 자료형과 짝을 맞춰야 한다.
- `scanf`는 입력 변수의 **주소**(`&x`)를 전달한다.
- `float`는 `%f`, `double`은 `printf`는 `%f` / `scanf`는 `%lf`.
- 문자열 한 줄 입력은 `fgets`가 안전하다.
- 너비/정밀도(`%5d`, `%.2f`)로 깔끔한 표 출력을 만들 수 있다.
직접 해 보기
cd src
gcc -std=c11 -Wall -o ex01 ex01_printf.c && ./ex01
gcc -std=c11 -Wall -o ex02 ex02_scanf_int.c && echo 7 | ./ex02
gcc -std=c11 -Wall -o ex03 ex03_scanf_multi.c && echo "17 5" | ./ex03
gcc -std=c11 -Wall -o ex04 ex04_format.c && ./ex04응용:
- `ex02`에서 `&n`의 `&`를 지우고 컴파일·실행. 결과를 관찰하세요.
- `ex03`에서 `b = 0`을 입력했을 때의 출력을 확인.
💻 예제 (examples)
실제로 컴파일·실행해 결과를 확인할 수 있는 예제입니다.
#include <stdio.h>
int main(void) {
int n = 255;
double x = 3.141592;
printf("10진: %d\n", n);
printf("16진: %x\n", n);
printf("8진: %o\n", n);
printf("문자: %c (코드 %d)\n", 'A', 'A');
printf("문자열: %s\n", "Hello");
printf("실수 기본: %f\n", x);
printf("실수 두자리: %.2f\n", x);
printf("지수표기: %e\n", x);
return 0;
}
10진: 255
16진: ff
8진: 377
문자: A (코드 65)
문자열: Hello
실수 기본: 3.141592
실수 두자리: 3.14
지수표기: 3.141592e+00#include <stdio.h>
int main(void) {
int n;
printf("정수를 입력하세요: ");
if (scanf("%d", &n) != 1) {
printf("입력 오류\n");
return 1;
}
printf("입력값: %d\n", n);
printf("두 배: %d\n", n * 2);
return 0;
}
정수를 입력하세요: 입력값: 7
두 배: 14#include <stdio.h>
int main(void) {
int a, b;
printf("두 정수를 공백으로 구분해 입력: ");
if (scanf("%d %d", &a, &b) != 2) {
printf("입력 오류\n");
return 1;
}
printf("합: %d\n", a + b);
printf("차: %d\n", a - b);
printf("곱: %d\n", a * b);
if (b != 0)
printf("몫: %d\n", a / b);
return 0;
}
두 정수를 공백으로 구분해 입력: 합: 22
차: 12
곱: 85
몫: 3#include <stdio.h>
int main(void) {
int n = 42;
double pi = 3.14159265;
printf("[%5d]\n", n);
printf("[%-5d]\n", n);
printf("[%05d]\n", n);
printf("[%10.4f]\n", pi);
printf("[%-10.4f]\n", pi);
return 0;
}
[ 42]
[42 ]
[00042]
[ 3.1416]
[3.1416 ]📝 과제 (exercises)
직접 풀어보고, 막힐 때 정답을 펼쳐 비교해보세요.
문제 1 (hw01.c)
목표: 사용자에게 이름과 나이를 입력받아 다음 형식으로 출력하세요.
- 파일명: hw01.c
이름을 입력: 홍길동
나이를 입력: 20
홍길동님은 20세입니다.▶정답 코드 펼치기 / 접기
#include <stdio.h>
int main(void) {
char name[64];
int age;
printf("이름을 입력: ");
scanf("%63s", name);
printf("나이를 입력: ");
scanf("%d", &age);
printf("%s님은 %d세입니다.\n", name, age);
return 0;
}
이름을 입력: 홍길동
나이를 입력: 20
홍길동님은 20세입니다.문제 2 (hw02.c)
목표: 세 정수 a, b, c를 입력받아 평균을 소수점 둘째 자리까지 출력하세요.
- 파일명: hw02.c
세 정수 입력: 10 20 33
평균: 21.00▶정답 코드 펼치기 / 접기
#include <stdio.h>
int main(void) {
int a, b, c;
printf("세 정수 입력: ");
scanf("%d %d %d", &a, &b, &c);
double avg = (a + b + c) / 3.0;
printf("평균: %.2f\n", avg);
return 0;
}
세 정수 입력: 10 20 33
평균: 21.00문제 3 (hw03.c)
목표: 초 단위 시간(`int seconds`)을 입력받아 `시:분:초` 형식으로 출력하세요. 예: `3725` → `1:2:5`
- 파일명: hw03.c
▶정답 코드 펼치기 / 접기
#include <stdio.h>
int main(void) {
int seconds;
printf("초 단위 시간 입력: ");
scanf("%d", &seconds);
int h = seconds / 3600;
int m = (seconds % 3600) / 60;
int s = seconds % 60;
printf("%d:%d:%d\n", h, m, s);
return 0;
}