Part 04 입출력과 전처리 ©우균, 창병모 이 슬라이드는 부산대학교 우균이 작성하였습니다. 오류나 수정할 사항 있으면 woogyun@pusan.ac.kr로 연락 주세요.
이 장의 내용 전처리기 지시자 표준입출력 버퍼 입출력 방향 재지정
4.1 전처리기 지시자
전처리기 지시자 전처리기(preprocessor) 전처리기 지시자(preprocessor directive) 컴파일러가 프로그램을 번역하기 '전'에 소스 프로그램을 '처리'하는 프로그램 전처리기 지시자(preprocessor directive) 전처리기에게 특정 작업을 지시하는 유사명령어 실제로 실행 시간에 수행되는 명령어가 아님 #으로 시작함 중요한 전처리기 지시자 #include: 다른 파일의 내용을 현재 파일에 포함시킴 #define: 특정 단어를 다른 문자열로 바꿈
#include 파일 포함 지시자 헤더파일(header file) 다른 파일에 포함시킬 목적으로 작성된 파일 인클루드 파일(include file)이라고도 함
헤더파일 종류 <표준 헤더파일> "일반 헤더파일" 시스템의 특별한 위치에 존재 소스파일과 같은 위치에서 찾을 수 있는 파일
변수 값 출력 프로그램 표준 헤더파일 이 부분을 별도의 헤더파일로 만들자
직접 작성한 헤더파일 #include 예 일반 헤더파일이므로 defineX.h는 include.c와 같은 디렉터리에 있어야 함
#define 다른 문자열로 대치될 단어(매크로; macro)를 정의함 전처리기가 매크로 MESSAGE를 "Have a nice day !"로 바꾸어 준다.
매크로 함수 함수 형태의 매크로 실제로 함수는 아니지만 인수를 받는 형태임 문자열 리터럴을 나란히 두면 접합된다. DUP("Hello? ")에서 MESSAGE는 "Hello? "이므로 DUP("Hello? ")는 다음과 같이 바뀐다. "Hello? " "Hello? "
매크로 정의가 길 때 백슬래시(backslash)는 매크로 정의가 다음 줄에 계속됨을 의미함
매크로 인수 이름의 문자열 매크로 인수 x의 이름에 해당하는 문자열은 #x로 나타냄
전처리기 지시자 기능 요약 파일 포함: 표준 헤더파일, 일반 헤더파일 매크로: 매크로 상수, 매크로 변수
4.2 표준입출력
표준입출력 표준입출력(standard input/output) 표준입출력 라이브러리 거의 모든 컴퓨터가 기본적으로 수행하는 입출력 입출력 모두 문자열로 구성되어 있다고 간주함 표준입력은 통상 키보드, 표준출력은 통상 모니터를 나타냄 표준입출력 라이브러리 C 언어는 표준입출력을 다루는 명령어를 제공하지 않고 함수를 제공하고 있음 함수의 인터페이스(interface)를 표준에서 정의하고 있을 뿐, 함수를 구현하는 방법은 시스템마다 다를 수 있음 이러한 표준입출력 함수는 라이브러리로 제공됨
표준 라이브러리 라이브러리(library) 표준 라이브러리(standard library) 미리 작성해 둔 프로그램 표준 라이브러리(standard library) 컴퓨터 시스템이 달라도 같은 기능(same functionality)을 같은 방식(same interface)으로 사용할 수 있도록 작성해 둔 라이브러리 표준입출력 라이브러리(standard input/output library) 표준입출력을 다루는 프로그램(함수) 집합
printf: 형식에 맞는 출력 %d 2 정수 i "2" 문자열 %f 3.14 실수 f "3.140000" %c '5' "5" printf 포맷 스트링은 데이터 변환을 지시함
printf 포맷 스트링 자료형 변환 규칙을 규정하는 문자열 printf의 첫 번째 인수로 사용됨
printf 출력 폭 지정 포맷 스트링에서 % 다음에 양의 정수를 기입하여 출력 폭 지정 실행결과: i = 2 c = 5 10칸
printf 정밀도 지정 포맷 스트링에서 %와 f 사이에 (출력폭이 지정된 경우, 출력폭 다음에) ".양수" 형태로 정밀도 지정 실행결과: pi = 3.141593 pi = 3.14 pi = 3.141592653590 10칸
scanf: 형식에 맞는 입력 %d 27 정수 n "27" 문자열 scanf 포맷 스트링은 문자열을 데이터로 변환함 주소연산자 &를 사용한 점에 주의!
scanf에서 주의할 점 주소 연산자(address-of operator) & 프롬프트(prompt) scanf("%d", &n); 표준입력에서 변환한 값을 저장할 장소를 알아야 하기 때문에 변수 n의 주소(&n)을 두 번째 인수로 사용함 주소 연산자를 누락시키면 실행시간 오류 프롬프트(prompt) 입력할 내용을 설명해 주는 안내문 프롬프트가 없으면 “프로그램이 죽은” 것으로 오해할 수 있음 앞 슬라이드의 프로그램은 프롬프트가 없다!
scanf: 프롬프트를 사용한 버전 바로 이것이 프롬프트! 입력할 내용을 설명해 준다.
scanf 포맷 스트링 TIP: double 타입으로 입력 받아야 할 경우에는 %f 대신 %lf를 사용한다.
getchar, putchar: 문자 입출력 표준입력 putchar 'A' 문자 .....A 표준출력 대문자로 변환하는 함수 toupper() 실행결과: 소문자를 입력하세요. a a의 대문자는 A입니다. 안내문구(prompt) putchar가 출력한 문자들
gets, puts: 줄 단위 입출력 C99에서는 const int로 배열 크기 선언이 가능함 실행결과: 사용자가 입력한 \n puts가 출력한 \n line에는 여기까지만 저장됨(\n은 제외됨)
gets, puts 주의점 줄바꿈문자 처리 이전 예에서 gets 처리 후 line[256] 상황 gets는 \n을 떼고 \0을 붙여 준다. puts는 \n을 문자열 출력 끝에 자동으로 붙여 준다. 주의: fgets, fputs는 다르게 동작한다. 이전 예에서 gets 처리 후 line[256] 상황 버퍼 오버런(buffer overrun) 버퍼(임시저장소)의 범위를 넘어섬 gets와 puts는 버퍼 오버런의 위험이 있음 fgets, fputs를 사용하는 것이 권장됨.
4.3 버퍼
입출력 버퍼 버퍼(buffer)란? 버퍼가 필요한 이유 버퍼의 본래 뜻: 완충장치 컴퓨터 분야에서는 ‘임시 저장소’라는 뜻으로 쓰임 버퍼가 필요한 이유 데이터를 생성하는 프로세스와 데이터의 처리하는 프로세스 두 프로세스를 생각해 보자. 데이터 생성 속도와 데이터 처리 속도가 다를 경우 이전에 생성된 데이터를 보관해야 한다. 이를 보관하는 장소를 ‘버퍼’라고 한다.
입력 버퍼 실행결과: 정수 n과 문자 c를 입력해 주세요. 250 a n = 250 c = a는 어디로?
scanf가 공백을 건너뛰는 습성 포맷 스트링 %c의 특이함 %s로도 공백을 건너뛰어 문자를 읽을 수 있다 포맷 스트링에 공백이 하나라도 있으면 여러 공백문자에 대응함 scanf("%d%c", …) ≠ scanf("%d %c", …) 포맷 스트링 %c를 제외하고는 모두 공백을 건너뜀 scanf("%d%d", …) ≡ scanf("%d %d", …) %s로도 공백을 건너뛰어 문자를 읽을 수 있다 %s로 입력을 받되 입력 폭을 1로 지정 char c; scanf("%1s", &c); // scanf(" %c", &c);와 같음 %s로 공백을 건너뛰지 않으려면? 불가능함. gets를 사용하세요!
버퍼 관련 용어 버퍼 오버런(buffer overrun) 플러싱(flushing) 버퍼 오버런에 주의해야 할 입출력 함수 버퍼에 저장할 수 있는 한계를 넘어 데이터가 저장되는 상황 다른 공간의 데이터를 무심코 삭제할 수 있으므로 매우 위험함 플러싱(flushing) 버퍼의 내용을 모두 처리하고 버퍼를 비우는 것 버퍼 오버런에 주의해야 할 입출력 함수 gets와 puts는 버퍼 오버런을 유발할 수 있음 fgets, fputs를 사용하는 것이 좋음
4.4 입출력 방향 재지정
입출력 방향 재지정 입력 재지정(input redirection) 출력 재지정(output redirection) 합쳐서… 표준입력 대신 파일로부터 입력 받음 program.exe < inputFile.txt 출력 재지정(output redirection) 표준출력 대신 파일로 내용을 저장 program.exe > outputFile.txt 합쳐서… program.exe < inputFile.txt > outputFile.txt program.exe > outputFile.txt < inputFile.txt 화살표 기억법: <를 로, >를 로 생각하면 기억하기 쉽다.
입출력 방향 재지정 예 입력파일 input.txt 수행 명령어 출력 파일 output.txt
Key Point
Key Point 1 #include는 헤더 파일을 포함시키고 싶을 때 사용하는 전처리기 지시자다. 현재 디렉터리의 헤더파일 이름은 큰 따옴표( " )로 감싸서 나타내고 표준 헤더파일은 꺾은 괄호(<, >)로 감싸서 나타낸다. 전처리기는 #define 상수를 해당 정의대로 확장시켜 준다. #define으로 정의된 상수를 매크로라고 한다. 전처리기는 #define 상수로 정의된 내용이 실제로 ‘상수’인지 아닌지는 신경 쓰지 않는다. 문자열 리터럴을 두 개 나란히 나열하면 자동으로 문자열 리터럴들을 접합해 준다. #define을 이용하여 매크로 함수를 정의할 수 있다. 매크로 함수란 인수를 받는 매크로를 말한다. 매크로 정의가 길어서 한 라인에 다 표시하기 힘들 경우에는 백슬래시를 이용하여 여러 줄에 표시할 수 있다.
Key Point 2 매크로 함수를 정의할 때, 문자열 내에서는 매크로 인수가 확장되지 않는다. 매크로 인수 x 앞에 #을 붙여 #x와 같이 쓰면, 매크로 인수 x로 주어진 내용을 문자열로 변환하라는 의미이다. C 언어는 매우 단순한 형태의 입출력 기능만을 가정한다. 이를 표준입출력이라고 하는데 표준입출력이란 간단히 말해서 문자열 수준(텍스트 수준)의 입출력이다. C 언어는 다양한 플랫폼에 대해 동일한 서비스를 제공하기 위해 라이브러리 함수를 사용한다. 플랫폼에 상관없이 공통적으로 제공되는 라이브러리를 표준라이브러리라고 한다. 다양한 자료형의 자료를 표준출력에 출력할 경우에는 printf를 사용하는 것이 편리하다. printf는 포맷 스트링을 이용하여 자료형 변환 형식을 지정한다. 대표적인 포맷 스트링으로는 %d, %f, %c 등이 있다. 각각 정수형, 부동소수형, 문자형 자료를 문자열로 변환한다.
Key Point 3 출력되는 자료의 폭을 지정하기 위해서 포맷 스트링의 % 다음에 숫자 n을 기입할 수 있다. 또 출력되는 자료의 정확도를 명시하기 위해 .p 형태의 숫자를 기입할 수 있다. %10.2f는 전체 출력 폭을 10으로, 소수점 이하 자릿수를 2로 하여 출력하라는 뜻이다. 사용자가 입력해야 하는 내용에 대해 설명하는 문구를 출력하는 것이 좋다. 이러한 설명 문구를 프롬프트라고 한다. 다양한 자료형의 자료를 표준입력으로부터 입력받을 경우에는 scanf를 사용하는 것이 편리하다. scanf의 포맷 스트링도 printf의 포맷 스트링과 유사하다. getchar는 문자 하나를 입력받아 리턴하고 putchar는 문자 하나를 출력한다. 줄단위 입출력 함수 gets는 개행문자까지 읽지만 실제로는 개행문자 대신 널문자를 저장한다. 반면 puts는 널문자 전까지 출력하고 널문자 대신 개행문자를 출력한다.
Key Point 4 데이터를 처리하기 전에 임시로 저장해 두는 공간을 버퍼라고 한다. 입력 데이터를 저장하기 위한 버퍼를 입력버퍼라고 하고 출력 데이터를 저장하기 위한 버퍼를 출력버퍼라고 한다. 버퍼 오버런이란 버퍼에 저장할 수 있는 한계를 넘어 데이터가 저장되는 상황을 말한다. 파일 infile로 표준입력을 재지정하려면 명령줄에 <infile을 쓰고, 파일 outfile로 표준출력을 재지정하려면 명령줄에 >outfile을 쓴다.
요약(1/2) 전처리기 지시자 표준입출력 표준입출력 라이브러리 #include: 다른 파일을 포함시킴 #define: 매크로(다른 문자열로 바뀔 문구)를 정의함 표준입출력 키보드(std. in)와 모니터(std. out)를 추상화함 문자 단위 입출력을 수행함 표준입출력 라이브러리 형식에 따른 입출력: printf(), scanf() 문자 단위 입출력: getchar(), putchar() 줄 단위 입출력: gets(), puts()
요약(2/2) 버퍼 입출력 방향 재지정 매크로 인수 활용 입출력 속도 차이를 메워주기 위한 임시 저장소 버퍼오버런: 저장 범위를 넘어 다른 영역에 침범하는 현상 버퍼오버런을 방지하기 위해 gets, puts보다 fgets, fputs를 사용하자. 입출력 방향 재지정 표준입출력 대신 파일을 사용하도록 지정함 매크로 인수 활용 #x: 매크로 인수 x에 해당하는 문자열 x ## y: 매크로 인수 이름 x와 y를 접합함
주의사항 요약 사용자로부터 입력 받을 때에는 안내문구(prompt)를 사용하는 것이 좋음 scanf의 인수로는 변수의 주소를 주어야 함 int x; scanf("%d", x); // 잘못됨 scanf("%d", &x); // 올바름 scanf로 double형 데이터를 읽으려면 "%lf"를 이용함 scanf 포맷 스트링 "%c"는 공백을 건너뛰지 않음 " %c"를 이용하거나 "%1s"를 이용함
프로그래밍 실습
▶ 프로그래밍 실습 1 세 정수 a, b, c를 입력 받은 후 a와 b의 합에서 c의 거듭제곱을 빼는 프로그램을 작성하라. 거듭제곱은 c * c로 계산하면 된다. 단 다음과 같은 내용의 헤더파일 myLang.h를 만들고 이를 다음과 같이 #include하여 사용하라. 여러분의 프로그램에는 int, main, scanf, printf, return 등을 사용해서는 안 된다. 대신 myLang.h에 정의된 #define 상수를 이용하여 작성해야 한다.
▶ 프로그래밍 실습 2 텍스트 내용을 간단히 암호화 하는 방법 중 ‘k-증가’라는 알고리즘이 있다. 이는 각 문자를 k문자 뒤의 문자로 변경하는 알고리즘이다. 이 경우 문자들의 배열은 원형 형태로 배열되어 있다고 가정한다. 즉 문자 z 다음에는 a가 나타나고 문자 Z 다음에는 A가 나타난다고 가정한다. 정수 k와 문자 하나를 입력받은 후, k-증가 알고리즘대로 해당 문자를 암호화 하는 프로그램을 작성하라. 예: 2 Z B 6 i o 9 p y