1. 스트림(Stream)
- 입력 또는 출력 장치로부터 데이터가 한 방향으로 흐르는 개념이다.
- 스트림에는 버퍼(buffer)가 포함되어 있으며, 이는 CPU와 입출력 장치 간의 속도 차이를 보완하기 위해 사용된다.
⭕ 주요 표준 스트림
stdin 표준 입력 스트림: 주로 키보드 입력에서 사용되며, scanf() 함수와 연결된다.
stdout 표준 출력 스트림: 주로 모니터 출력에서 사용되며, printf() 함수와 연결된다.
stderr 표준 에러 출력 스트림: 오류 메시지를 출력할 때 사용된다.
✅ 각 함수(printf, scanf 등)를 사용하면 자동으로 스트림이 생성된다.
2. 파일 입출력
1) 표준 입출력 라이브러리(Standard I/O Library)
- 보조 기억 장치(예: HDD, SSD)에 저장된 파일을 단위로 데이터를 읽고 쓸 수 있다.
- C 언어에서는 텍스트 파일과 바이너리 파일 모두를 처리할 수 있으며, 파일 형식에 따라 사용해야 할 함수가 달라진다.
2) 바이너리와 문자열의 차이
| 구분 | 바이너리(Binary) | 문자열(Text) |
| 데이터 인식 | 숫자 자체로 인식 | 아스키코드로 인식 |
| 크기 판단 기준 | 메모리 크기 (예: sizeof) | 널 문자('\0') 기준 |
| 예시 | sizeof(char[8]) → 8 | strlen(char[8]) → 문자열 길이만큼 |
✅ 바이너리 속성이 적용된 파일을 바이너리 파일, 문자열 속성이 적용된 파일을 텍스트 파일이라 한다.
3) 파일 포인터와 FILE 구조체
- 파일 입출력을 위해서는 FILE형 구조체 포인터를 사용해야 한다.
- 이 포인터는 실제 파일과 프로그램을 연결하는 다리 역할을 하며, stdio.h에 다음과 같이 정의되어 있다:
typedef struct {
short level;
unsigned flags;
char fd;
unsigned char hold;
short bsize;
unsigned char *buffer;
unsigned char *curp;
unsigned istemp;
short token;
} FILE;
4) 파일 열기와 닫기
- 파일을 열 때는 fopen_s() 함수를 사용하며, 반환타입은 errno_t(typedef로 생성)이다.
errno_t err = fopen_s(&파일포인터, "파일명", "파일모드");
⭕ 파일 열기 모드 요약표:
| 모드 | 설명 |
| "r" | 읽기 전용, 파일 없으면 오류 |
| "r+" | 읽기/쓰기, 파일 없으면 오류 |
| "w" | 쓰기 전용, 기존 파일이면 내용 삭제 |
| "w+" | 읽기/쓰기, 기존 파일이면 내용 삭제 |
| "a" | 추가 모드, 파일 없으면 새로 생성 |
| "a+" | 추가 + 읽기, 파일 없으면 새로 생성 |
| "b" | 바이너리 모드 (⚠️ 생략 불가) |
| "t" | 텍스트 모드 (생략 가능) |
✅ 파일을 연 후에는 반드시 fclose()로 닫아야 한다. 그렇지 않으면 버퍼에 남은 데이터가 저장되지 않을 수 있다.
5) 문자 입출력 함수
문자 읽기
- fgetc(FILE *stream)
· 파일에서 문자 하나를 읽어 아스키 코드 값(정수)로 반환한다.
· EOF 또는 오류 발생 시 -1을 반환. - getc(FILE *stream)
· fgetc()와 동일한 기능을 하는 매크로 함수이다.
문자 출력
- fputc(int ch, FILE *stream)
· 하나의 문자를 파일에 출력한다. 성공 시 해당 문자의 아스키 코드 반환, 실패 시 EOF 반환. - putc(int ch, FILE *stream)
· fputc()의 매크로 버전으로, 효율이 더 좋을 수 있다.
6) 문자열 입출력 함수
- fgets(char *str, int size, FILE *stream)
· 파일에서 한 줄을 읽어 문자열로 저장한다.
· 줄바꿈 문자를 포함하며, 널 문자 \0로 종료된다. - fputs(const char *str, FILE *stream)
· 문자열 전체를 파일에 출력한다.
· 자동으로 줄바꿈이 되지 않기 때문에, 줄바꿈이 필요할 경우 \n을 포함시켜야 한다.
💻 예제 코드 - 문자열 입출력
#include <stdio.h>
int main() {
FILE *fp;
fopen_s(&fp, "str.txt", "w");
if (fp) {
fputs("첫 번째 줄\n", fp);
fputs("두 번째 줄", fp); // 줄바꿈 없음
fclose(fp);
}
fopen_s(&fp, "str.txt", "r");
char buffer[100];
while (fgets(buffer, sizeof(buffer), fp)) {
printf("읽은 줄: %s", buffer);
}
fclose(fp);
return 0;
}
7) 텍스트 파일 형식 입출력
- fprintf(FILE *stream, const char *format, ...)
· 형식 문자열을 사용하여 텍스트 파일에 출력. - fscanf_s(FILE *stream, const char *format, ...)
· 형식 문자열에 따라 텍스트 파일에서 입력.
· 공백 기준으로 분리되며 문자열 입력 시 버퍼 크기를 반드시 지정해야 한다.
💻 예제 코드
#include <stdio.h>
int main() {
FILE *fp;
fopen_s(&fp, "data.txt", "w");
fprintf(fp, "%d %s\n", 42, "Hello");
fclose(fp);
int num;
char word[20];
fopen_s(&fp, "data.txt", "r");
fscanf_s(fp, "%d %s", &num, word, (unsigned)_countof(word));
printf("읽은 데이터: %d, %s\n", num, word);
fclose(fp);
return 0;
}
8) 바이너리 파일 입출력
· 바이너리 속성 파일은 데이터를 숫자로만 판단하기에 직접 데이터 크기를 선언해야한다.
· 실제 데이터 크기는 ‘저장할 데이터 크기’ * ‘반복 횟수’이다.
- fwrite(const void *ptr, size_t size, size_t count, FILE *stream)
· ptr이 가리키는 데이터 블록을 count만큼 파일에 이진 형식으로 저장. - fread(void *ptr, size_t size, size_t count, FILE *stream)
· 파일로부터 count개의 데이터 블록을 읽어 ptr에 저장.
💻 예제 코드
#include <stdio.h>
int main() {
FILE *fp;
int data[3] = {100, 200, 300};
fopen_s(&fp, "bin.dat", "wb");
fwrite(data, sizeof(int), 3, fp);
fclose(fp);
int result[3];
fopen_s(&fp, "bin.dat", "rb");
fread(result, sizeof(int), 3, fp);
fclose(fp);
for (int i = 0; i < 3; i++) {
printf("읽은 값: %d\n", result[i]);
}
return 0;
}
9) 파일 포인터 접근 함수
- 지금까지 했던 방식은 앞에서 부터 하나씩 접근하는 순차 접근(sequential access)이다.
- 입의 접근(random access)는 임의의 값에 따라 접근하는 방식이다.
- fseek(FILE *stream, long offset, int origin)
· 파일 포인터를 offset만큼 이동시킨다.
· 기준값(origin): - ftell(FILE *stream)
· 현재 파일 포인터 위치를 바이트 단위로 반환. - rewind(FILE *stream)
· 파일 포인터를 파일 시작 위치로 초기화. - feof(FILE *stream)
· 파일의 끝(EOF)에 도달했는지 여부를 확인.
· 루프에서 파일 읽기를 반복할 때 자주 사용된다.
💻 예제 코드
#include <stdio.h>
int main() {
FILE *fp;
int nums[] = {1, 2, 3, 4, 5};
fopen_s(&fp, "offset.dat", "wb");
fwrite(nums, sizeof(int), 5, fp);
fclose(fp);
fopen_s(&fp, "offset.dat", "rb");
fseek(fp, sizeof(int) * 2, SEEK_SET); // 세 번째 정수 위치
int val;
fread(&val, sizeof(int), 1, fp);
printf("3번째 값: %d\n", val);
rewind(fp);
while (!feof(fp)) {
fread(&val, sizeof(int), 1, fp);
if (!feof(fp)) // 마지막 EOF 중복 출력 방지
printf("값: %d\n", val);
}
fclose(fp);
return 0;
}
✅ 마무리 요약
- 스트림은 입력/출력 흐름을 나타내며, 버퍼를 이용해 효율적으로 데이터를 전달한다.
- FILE * 포인터는 파일과 프로그램을 연결하는 매개체이다.
- 문자와 문자열 입출력은 각각 fgetc/fputc, fgets/fputs 함수를 이용한다.
- fprintf, fscanf_s는 텍스트 형식의 데이터를 파일로 읽고 쓸 때 유용하다.
- fread, fwrite는 바이너리 데이터를 다룰 때 반드시 사용된다.
- fseek, ftell, rewind 등으로 파일 내부를 자유롭게 탐색할 수 있다.
- feof()는 파일의 끝을 만났는지를 판단할 때 사용한다.
'Language > C' 카테고리의 다른 글
| 📌 Chapter 16 - 동적 메모리와 연결 리스트 - 연결 리스트 (0) | 2025.06.23 |
|---|---|
| 📌 Chapter 16 - 동적 메모리와 연결 리스트 - 동적 할당 (1) | 2025.06.23 |
| 📌 Chapter 14 - 구조체 - 함수, 중첩, 공용, 비트필드 (1) | 2025.06.20 |
| 📌 Chapter 14 - 구조체 - 배열 (0) | 2025.06.20 |
| 📌 Chapter 14 - 구조체 (1) | 2025.06.20 |