쉽게 풀어쓴 C언어 Express 제16장 스트림과 파일 입출력 C Express
입출력에 관련된 개념들과 함수들에 대하여 학습한다. 이번 장에서 학습할 내용 입출력에 관련된 개념들과 함수들에 대하여 학습한다. 입출력 형식 스트림의 개념 파일 입출력 입출력 관련 함수
출력 형식 지정자 출력 형식 지정자(format specification) 변환 지정자(conversion specification) 문자, 정수, 실수 등 출력 타입 명시 (변환 문자) 출력 필드의 크기 명시 %[플래그][필드폭][.정밀도]형식문자 정렬 방식, 부호 출력, 공백 문자 출력, 소수점, 8/16진수 접두사 출력 등 명시 소수점 이하 자릿수 명시
출력 형식 지정자 123.456000 1.230000e+001 1.23e+002 1.230000E+001
필드폭과 정밀도
필드폭과 정밀도
형식 플래그(format flag) - + # 플래그 기능 기본 값 space(' ') 왼쪽 정렬 오른쪽 정렬 항상 +/- 부호 출력 (0인 경우에도 + 부호 출력) 음수만 - 부호 출력 오른쪽 정렬 방식의 숫자 출력에서 왼쪽 빈 부분을 공백 문자 대신 0으로 채움 공백으로 채움 space(' ') 0이나 양수 출력에서 부호 대신 공백 출력 공백을 출력하지 않음 # 8진수 출력에서 앞에 0 출력 16진수 출력에서 앞에 0x/0X 출력 0/0x/0X를 출력하지 않음
입력 형식 필드폭을 지정하여 입력 8진수, 16진수 입력 #include <stdio.h> int main( ) { int a, b; printf("5자리 정수 입력: "); scanf("%3d %3d", &a, &b); // 최대 3문자씩 입력 printf("a = %d, b = %d\n", a, b); return 0; } #include <stdio.h> int main() { int d, o, x; sscanf("10 10 10", "%d %o %x", &d, &o, &x); printf("d = %d, o = %d, x = %d\n", d, o, x); return 0; } d = 10, o = 8, x = 16 5자리 정수 입력: 12345 a = 123, b = 45
문자와 문자열 입력 형식 지정자 설명 %c %s %[…] %[^…] char 형으로 문자 입력 공백 문자가 아닌 문자부터 공백 문자가 올 때까지 문자열 입력 %[…] 괄호 안에 속한 문자로 이루어진 문자열 입력 괄호 안에 0-9 같이 범위 표현 허용 예) %[_a-zA-Z] : 밑줄 문자, 영문자 예) %[-+0-9.eE] : 부호(-+), 숫자, 소수점(.), 지수 문자(eE) %[^…] 괄호 안에 속하지 않는 문자로 이루어진 문자열 입력 범위 표현 허용 scan set(문자 집합)
문자 입력과 공백 문자
예제: 문자열 입력 #include <stdio.h> int main(void) { char c, s[80], t[80]; printf("공백으로 분리된 문자열 입력: "); scanf("%s%c%s", s, &c, t); printf("첫번째 문자열=%s\n", s); printf("중간 공백 문자=%c\n", c); printf("두번째 문자열=%s\n", t); return 0; } 공백으로 분리된 문자열 입력: Hello World 첫번째 문자열=Hello 중간 공백 문자= 두번째 문자열=World
예제: 문자 집합 입력 #include <stdio.h> int main(void) { char s[80]; printf("문자열 입력: "); scanf("%[abc]", s); printf("입력된 문자열 = %s\n", s); return 0; } 문자열 입력: accbadae 입력된 문자열 = accba
예제: scanf 반환 값 #include <stdio.h> int main(void) { int x, y, z; if (scanf("%d %d %d", &x, &y, &z) == 3) printf("정수들의 합 = %d\n", x + y + z); else printf("입력 오류\n"); return 0; } 10 20 30 정수들의 합 = 60 10 20 a 입력 오류
scanf 사용시 주의점 입력 값을 저장할 변수의 주소 전달 int i; scanf("%d", &i); // OK scanf("%d", i); // ERROR 배열의 이름은 배열을 가리키는 포인터 char str[80]; scanf("%s", str); // OK scanf("%s", &str); // ERROR 충분한 공간 확보 char str[80]; scanf("%s", str); // 입력된 문자가 80개 이상이면 오류 형식 문자열 끝에 '\n'을 사용하지 말 것 int i; scanf("%d\n", &i); scanf("%d\n", &i); printf("%d\n", i); 12 ↵ ↵ 3 12
스트림의 개념 스트림(stream): 입력과 출력을 바이트의 흐름으로 생각하는 것
스트림과 버퍼 스트림에는 일반적으로 버퍼(buffer)가 사용됨
FILE과 표준 스트림 스트림은 C에서 FILE 구조체로 표현 typedef struct { … } FILE; // <stdio.h> 표준 스트림(standard stream)은 자동으로 생성됨 FILE *stdin, *stdout, *stderr; // <stdio.h> 변수 스트림 기본 연결 장치 stdin 표준 입력 스트림 키보드 stdout 표준 출력 스트림 모니터 화면 stderr 표준 오류 스트림
입출력 함수의 분류 사용하는 스트림에 따른 분류 입출력 형식 사용 여부에 따른 분류 표준 입출력 스트림을 사용하는 함수 스트림을 구체적으로 명시해야 하는 함수 입출력 형식 사용 여부에 따른 분류 입출력 형식을 사용하지 않는 함수: unformatted I/O 입출력 형식을 사용하는 함수: formatted I/O 입출력 형식 표준 스트림 일반 스트림 기능 형식이 없는 입출력 (문자/문자열) getchar( ) fgetc( ), getc( ) 문자 입력 putchar( ) fputc( ), putc( ) 문자 출력 gets( ) fgets( ) 문자열 입력 puts( ) fputs( ) 문자열 출력 형식이 있는 입출력 (정수, 실수, …) scanf( ) fscanf( ) 형식화된 입력 printf( ) fprintf ( ) 형식화된 출력
파일 파일에 저장된 데이터는 프로그램 실행이 끝나도 보존됨 C에서 파일은 일련의 연속된 바이트 모든 파일 데이터는 결국 바이트들로 파일에 저장 파일 데이터에 대한 해석은 프로그래머에게 달려 있음 예: 파일의 4개 바이트 int, float, 픽셀, … 하나의 정수? 하나의 실수? 4개의 문자? 0x36 0x34 0x31 0x0
텍스트 파일(text file) 사람이 읽을 수 있는 텍스트가 들어 있는 파일 예: C 프로그램 소스 파일, 메모장 파일 아스키 코드, 한글 코드, 유니코드 등을 사용하여 저장 라인들로 구분됨: 운영체제에 따라 라인 끝 표지가 다름 ‘W’ ‘O’ ‘R’ ‘L’ ‘D’ ‘\r’ ‘\n’ 윈도우, MS_DOS 유닉스, 리눅스 매킨토시 C언어
이진 파일(binary file) 사람이 직접 읽을 수는 없고 컴퓨터가 처리하기 위한 파일 예: C 프로그램 실행 파일, 사운드 파일, 이미지 파일 이진 데이터를 직접 저장 문자열로 변환하지 않고 입출력 라인들로 구분되지 않음
파일 처리 개요 파일 처리 순서 FILE 구조체를 이용 FILE *: 파일 포인터(file pointer) 파일 열기 (open) 파일 읽기/쓰기 (read/write) 파일 닫기 (close)
파일 열기와 닫기 FILE *fopen(const char *name, const char *mode); 파일 열기 함수 mode: 파일 모드(file mode) 열기 실패의 경우 NULL 포인터 반환 int fclose(FILE *stream); 파일 닫기 함수 반환 값: 성공 0, 실패 EOF FILE *fp; fp = fopen("test.txt", "r"); if (fp == NULL) // if (!fp) printf("test.txt: open fail\n"), exit(1); … fclose(fp); FILE *fp; if (!(fp = fopen("test.txt", "r"))) printf("test.txt: open fail\n"), exit(1); … fclose(fp);
파일 모드 모드 설명 "r" 읽기(read), 파일이 없으면 열기 실패 "w" 쓰기(write), 파일이 없으면 새로 생성, 이미 존재하면 기존 내용 삭제 "a" 추가(append), 파일이 없으면 새로 생성, 이미 존재하면 기존 내용 보존, 항상 파일 끝에 쓰기 "r+" 수정(update: read/write), 파일이 없으면 열기 실패 "w+" 수정, 파일이 없으면 새로 생성, 이미 존재하면 기존 내용 삭제 "a+" 수정, 파일이 없으면 새로 생성, 이미 존재하면 기존 내용 보존, 이진 파일이면 끝에 b 추가 "rb", "wb", "ab", "r+b", "w+b", "a+b" 텍스트 파일이면 끝에 t 추가 또는 생략 "r", "w", "a", "r+", "w+", "a+", "rt", "wt", "at", "r+t", "w+t", "a+t" 수정 모드에서 읽기쓰기, 쓰기읽기 전환 시, 반드시 fflush(), fseek(), fsetpos(), rewind() 중 하나를 먼저 호출해야 함
파일 삭제 int remove(const char *path); 파일 삭제 함수 반환 값: 성공 0, 실패 0이 아닌 값 파일 포인터를 사용하지 않는 함수 if (remove("test.txt")) printf("test.txt: 삭제 실패\n");
크게 텍스트 입출력과 이진 데이터 입출력으로 구분할 수 있습니다. 파일 입출력 함수 설명 입력 함수 출력 함수 문자 입출력 fgetc( ), getc( ) fputc( ), putc( ) 문자열 입출력 fgets() fputs( ) 형식화된 입출력 fscanf( ) fprintf( ) 이진 데이터 입출력 fread( ) fwrite( ) 크게 텍스트 입출력과 이진 데이터 입출력으로 구분할 수 있습니다.
문자 입출력 int fgetc(FILE *fp); int getc(FILE *fp); 문자 입력 fgetc 함수; getc 일반적으로 매크로 반환 값: 성공 입력된 문자, 실패(파일 끝, 에러) EOF #define getchar( ) getc(stdin) int fputc(int c, FILE *fp); int putc(int c, FILE *fp); 문자 출력 fputc 함수; putc 일반적으로 매크로 반환 값: 성공 출력된 문자(c), 실패(에러) EOF #define putchar(c) putc((c), stdout)
문자 입출력 #include <stdio.h> int main(void) { FILE *fp; #include <stdio.h> int main(void) { FILE *fp; fp = fopen("sample.txt", "w"); if (fp == NULL) return printf("파일 열기 실패\n"), 1; else printf("파일 열기 성공\n"); putc('a', fp); putc('b', fp); putc('c', fp); // fputc('a', fp); fputc('b', fp); fputc('c', fp); fclose(fp); return 0; } sample.txt abc 파일 열기 성공
문자 입출력 #include <stdio.h> int main(void) { FILE *fp; int c; #include <stdio.h> int main(void) { FILE *fp; int c; if (!(fp = fopen("sample.txt", "r"))) return printf("파일 열기 실패\n"), 1; while ((c = getc(fp)) != EOF) // c = fgetc(fp) putchar(c); fclose(fp); return 0; } sample.txt abc abc
문자열 입출력 char *fgets(char *s, int n, FILE *fp); s에 한 라인의 문자열 입력 최대 n-1 문자만 입력 안전함, 공백 문자들도 입력, '\n'도 저장 반환 값: 성공 s, 실패(파일 끝, 에러) NULL char *gets(char *s): stdin에서 입력 무조건 한 라인 입력 위험함 '\n'을 읽어들이지만 저장하지는 않음 int fputs(const char *s, FILE *fp); 문자열 s 출력 반환 값: 성공 음수가 아닌 값, 실패(에러) EOF int puts(const char *s): stdout에 출력 s를 출력한 후 '\n' 출력
문자열 입출력 char s[100]; fputs(fgets(s, 6, stdin), stdout); s ab cd\0 ab cde\n ab cd char s[100]; fputs(fgets(s, 10, stdin), stdout); char s[100]; puts(gets(s)); s s ab cde\n\0 ab cde\0 ab cde\n ab cde\n
문자열 입출력 #include <stdio.h> #include <stdlib.h> int main(void) { FILE *fp1, *fp2; char file1[100], file2[100]; char buffer[100]; printf("원본 파일: "); scanf("%s", file1); printf("복사 파일: "); scanf("%s", file2); if (!(fp1 = fopen(file1, "r"))) { fprintf(stderr,"%s: 열기 실패\n", file1); exit(1); } if (!(fp2 = fopen(file2, "w"))) { fprintf(stderr,"%s: 열기 실패\n", file2); while (fgets(buffer, 100, fp1) != NULL) fputs(buffer, fp2); fclose(fp1); fclose(fp2); return 0; } 원본 파일: a.txt 복사 파일: b.txt a.txt C 프로그래밍 C Programming b.txt C 프로그래밍 C Programming
문자열 입출력 proverb.txt #include <stdio.h> #include <stdlib.h> #include <string.h> int main(void) { char *fname = "proverb.txt"; FILE *fp; int line_num = 0; char line[256], word[256]; printf("탐색할 단어: "); scanf("%s", word); if (!(fp = fopen(fname, "r"))) fprintf(stderr,"%s: 열기 실패\n", fname), exit(1); while (fgets(line, 256, fp)) { line_num++; if (strstr(line, word)) printf("%s:%d -- %s 발견\n", fname, line_num, word); } fclose(fp); return 0; A house divided against itself cannot stand. A good man is hard to find. A house is not a home. A friend in need is a friend indeed. 탐색할 단어: house proverb.txt:1 -- house 발견 proverb.txt:3 -- house 발견
형식화된 입출력 printf("...", …): fprintf(stdout, "...", …) int fprintf(FILE *fp, const char *format, ...); int fscanf(FILE *fp, const char *format, ...); printf("...", …): fprintf(stdout, "...", …) scanf("...", …): fscanf(stdin, "...", …) FILE *fp; int i = 123; float f = 1.23F; if ((fp = fopen("sample.txt", "w"))) { fprintf(fp, "%d %.2f\n", i, f); fclose(fp); } FILE *fp; int i; float f; if ((fp = fopen("sample.txt", "r"))) { fscanf(fp, "%d %f", &i, &f); printf("%d %.2f", i, f); fclose(fp); } sample.txt 123 1.23 123 1.23
예제 #include <stdio.h> typedef struct { int number; char name[20]; float score; } Student; int main(void) { FILE *fp; char fname[100]; int count = 0; float total = 0.0F; Student s; printf("성적 파일: "); scanf("%s", fname); if (!(fp = fopen(fname, "w"))) return fprintf(stderr,"%s: 열기 실패\n", fname), 1; while (1) { printf("학번, 이름, 성적 입력: "); if (scanf("%d %s %f", &s.number, s.name, &s.score) != 3) break; fprintf(fp, "%d %s %g\n", s.number, s.name, s.score); } fclose(fp); if (!(fp = fopen(fname, "r"))) while (fscanf(fp, "%d %s %f", &s.number, s.name, &s.score) == 3) total += s.score, count++; printf("평균 = %g\n", total / count); return 0; 성적 파일: score.txt 학번, 이름, 성적 입력: 1 KIM 90.2 학번, 이름, 성적 입력: 2 PARK 30.5 학번, 이름, 성적 입력: 3 MIN 56.8 학번, 이름, 성적 입력: ^Z 평균 = 59.1667 1 KIM 90.2 2 PARK 30.5 3 MIN 56.8 score.txt
버퍼링(buffering) fopen()을 사용하여 파일을 열면 자동으로 버퍼(buffer) 할당 버퍼는 입출력 데이터의 임시 저장 장소로 이용되는 메모리 블록 디스크 장치는 블록 단위로 입출력할 경우 효율적으로 동작 블록의 크기는 일반적으로 512바이트의 배수 예: 512, 1024, 4096, 8192 flush: 버퍼의 내용을 비우는 동작 fclose(): flush 버퍼 반납 buffered I/O: 버퍼를 이용하는 입출력 File Buffer Program 물리적 입출력 (Physical I/O) 논리적 입출력 (Logical I/O)
fflush, feof, ferror int fflush(FILE *fp); 출력 스트림의 경우, 버퍼의 내용을 실제 파일에 저장하고 버퍼를 비움 입력 스트림에 대한 동작은 OS에 따라 다름 반환 값: 성공 0, 실패 EOF int feof(FILE *fp); 현재 파일 끝 상태이면 참(0이 아닌 값) 반환 int ferror(FILE *fp); 현재 파일이 에러 상태이면 참(0이 아닌 값) 반환
이진 데이터 파일 메모리의 내부 데이터 표현 형식 그대로 저장
이진 데이터 출력 size_t fwrite(const void *buffer, size_t size, size_t count, FILE *fp); buffer에서 크기가 size인 항목을 count 개수만큼 출력 반환 값: 출력된 항목 개수 반환 값이 count가 아니면 에러 FILE *fp; int a[ ] = { 10, 20, 30, 40, 50 }; size_t size = sizeof a[0]; size_t n = sizeof a / sizeof a[0]; if ((fp = fopen("data.bin", "wb"))) { if (fwrite(a, size, n, fp) != n) fprintf(stderr, "출력 실패\n"); fclose(fp); } int a[5] = { 10, 20, 30, 40, 50 }; if (fwrite(a, sizeof a, 1, fp) != 1) // error if (fwrite(a, sizeof a[0], 5, fp) != 5) // error
이진 데이터 입력 size_t fread(void *buffer, size_t size, size_t count, FILE *fp); buffer에 크기가 size인 항목을 최대 count 개수만큼 입력 반환 값: 입력된 항목 개수 반환 값이 count보다 작으면 파일 끝이나 에러 #define SIZE 100 FILE *fp; int a[SIZE]; size_t n; if ((fp = fopen("data.bin", "rb"))) { n = fread(a, sizeof a[0], SIZE, fp); if (n < SIZE) fprintf(stderr, "파일 끝 또는 입력 실패\n"); fclose(fp); }
예제: 파일 복사 #include <stdio.h> #include <stdlib.h> #define USAGE "Usage: fcopy from to\n" #define BUF_SIZE 1024 int main(int argc, char *argv[ ]) { FILE *fpi, *fpo; char buf[BUF_SIZE]; size_t n; if (argc != 3) fprintf(stderr, "%s", USAGE), exit(1); if (!(fpi = fopen(argv[1], "rb"))) fprintf(stderr, "%s: fail to open\n", argv[1]), exit(1); if (!(fpo = fopen(argv[2], "wb"))) fprintf(stderr, "%s: fail to open\n", argv[2]), exit(1); while ((n = fread(buf, 1, BUF_SIZE, fpi)) > 0) if (fwrite(buf, 1, n, fpo) != n) break; if (ferror(fpi)) fprintf(stderr, "%s: fail to read\n", argv[1]), exit(1); if (ferror(fpo)) fprintf(stderr, "%s: fail to write\n", argv[2]), exit(1); fprintf(stderr, "copied: %s => %s\n", argv[1], argv[2]); fclose(fpi); fclose(fpo); return 0; } C> fcopy fcopy.exe fcopy2.exe copied: fcopy.exe => fcopy2.exe
예제: 파일 추가 // 첫 번째 파일을 두 번째 파일 끝에 추가 #include <stdio.h> #include <stdlib.h> #define BUF_SIZE 1024 int main(void) { FILE *fp1, *fp2; char file1[100], file2[100]; char buffer[BUF_SIZE]; int n; printf("입력 파일: "); scanf("%s", file1); printf("추가할 파일: "); scanf("%s", file2); if (!(fp1 = fopen(file1, "rb"))) fprintf(stderr,"입력 파일 열기 실패\n"), exit(1); if (!(fp2 = fopen(file2, "ab"))) fprintf(stderr,"추가할 파일 열기 실패\n"), exit(1); while ((n = fread(buffer, 1, BUF_SIZE, fp1)) > 0) fwrite(buffer, 1, n, fp2); fclose(fp1); fclose(fp2); return 0; } 입력 파일: a.dat 추가할 파일: b.dat
예제: 이진 데이터 입출력 #include <stdio.h> #include <stdlib.h> #define SIZE 3 typedef struct { int number; char name[20]; double gpa; } Student; int main(void) { Student table[SIZE] = { { 1, "Kim", 3.99 }, { 2, "Min", 2.68 }, { 3, "Lee", 4.01 } }; Student s; FILE *fp; int i; if (!(fp = fopen("student.dat", "wb"))) fprintf(stderr,"출력 파일 열기 실패\n"), exit(1); if (fwrite(table, sizeof(Student), SIZE, fp) != SIZE) fprintf(stderr,"파일 출력 실패\n"), exit(1); fclose(fp); if (!(fp = fopen("student.dat", "rb"))) fprintf(stderr,"입력 파일 열기 실패\n"), exit(1); for (i = 0; i < SIZE; i++) { if (fread(&s, sizeof s, 1, fp) != 1) fprintf(stderr,"파일 입력 실패\n"), exit(1); printf("%d, %s, %g\n", s.number, s.name, s.gpa); } fclose(fp); return 0; 1, Kim, 3.99 2, Min, 2.68 3, Lee, 4.01
예제: Hexa Dump // 16진수로 파일 내용 출력 #include <stdio.h> #include <stdlib.h> #include <ctype.h> int main(void) { FILE *fp; char fname[100]; unsigned char buffer[16]; int address = 0; int i, bytes; printf("파일 이름: "); scanf("%s", fname); if (!(fp = fopen(fname, "rb"))) fprintf(stderr,"%s: 열기 실패\n", fname), exit(1); while ((bytes = fread(buffer, 1, 16, fp)) > 0) { printf("%08X: ", address); address += bytes; for (i = 0; i < bytes; i++) printf("%02X ", buffer[i]); for ( ; i < 16; i++) printf(" "); for (i = 0; i < bytes; i++) putchar(isprint(buffer[i]) ? buffer[i] : '.'); putchar('\n'); } fclose(fp); return 0; in.html <html> <body> <pre> <h1>Build Log</h1> 파일 이름: in.html 00000000: 3C 68 74 6D 6C 3E 0D 0A 3C 62 6F 64 79 3E 0D 0A <html>..<body>.. 00000010: 3C 70 72 65 3E 0D 0A 3C 68 31 3E 42 75 69 6C 64 <pre>..<h1>Build 00000020: 20 4C 6F 67 3C 2F 68 31 3E 0D 0A Log</h1>..
임의 접근 순차 접근(sequential access): 파일의 처음부터 차례대로 입출력 임의 접근(random access): 파일의 임의 위치에서 직접 입출력 직접 접근(direct access) 파일 위치(file position) 표시자 바이트 단위로 현재 입출력 위치 정보 유지 첫 번째 바이트: 0번째 바이트 파일 위치를 이동시키면 임의 접근 가능 순차 접근 임의 접근 파일 위치 표시자 1 2 3 4 5 6 7
임의 접근 관련 함수 int fseek(FILE *fp, long offset, int origin); origin 기준으로 offset만큼 떨어진 바이트로 파일 위치 변경 offset이 음수: 파일 시작 쪽으로 이동 offset이 양수: 파일 끝 쪽으로 이동 반환 값: 성공 0, 실패 0이 아닌 값 origin 값 기준 위치 SEEK_SET 파일 시작 SEEK_CUR 1 현재 위치 SEEK_END 2 파일 끝 fseek(fp, 0L, SEEK_SET); // 파일 시작으로 이동 (SEEK_SET이면 offset은 절대 위치) fseek(fp, 100L, SEEK_SET); // 100번째 바이트로 이동 fseek(fp, 0L, SEEK_END); // 파일 끝으로 이동 fseek(fp, -20L, SEEK_END); // 파일 끝에서 시작 쪽으로 20바이트 이동 fseek(fp, 50L, SEEK_CUR); // 현재 위치에서 끝 쪽으로 50바이트 이동 fseek(fp, sizeof(struct element), SEEK_CUR); // 구조체 크기만큼 파일 위치 증가 long ftell(FILE *fp); void rewind(FILE *fp); 현재 파일 위치 반환 에러 -1 반환 파일 위치를 0으로 변경 파일 시작 위치로 이동
예제 #include <stdio.h> #include <stdlib.h> #define SIZE 1000 int main(void) { FILE *fp; int table[SIZE], data, n; long pos; for (n = 0; n < SIZE; n++) table[n] = n * n; fp = fopen("sample.dat", "wb"); fwrite(table, sizeof table, 1, fp); fclose(fp); fp = fopen("sample.dat", "rb"); while (1) { printf("데이터 위치(0 ~ %d): ", SIZE - 1); scanf("%d", &n); if (n < 0 || n >= SIZE) break; pos = (long)(n * sizeof(int)); if (fseek(fp, pos, SEEK_SET)) fprintf(stderr, "fseek 실패\n"), exit(1); fread(&data, sizeof(int), 1, fp); printf("%d: %d\n", n, data); } fclose(fp); return 0; 데이터 위치(0 ~ 999): 3 3: 9 데이터 위치(0 ~ 999): 9 9: 81 데이터 위치(0 ~ 999): -1