728x90
파일 열기 및 닫기
파일 열기
- 파일에 접근하려면 먼저 파일을 열어야 한다 (File Open).
- 파일을 연다는 것은 파일에 저장된 데이터를 읽고, 쓰기 위한 준비를 한다는 의미를 갖는다.
- 파일의 입출력은 스트림(Stream)으로 처리되는데, 이를 위해서는 파일의 데이터를 임시로 저장하는 내부 버퍼가 필요하며, 파일의 현재 위치(File Position)를 초기화해야 하는데 이러한 일련의 준비 과정이 파일 오픈이다.
파일을 오픈할 때는 fopen_s 함수를 사용한다.
- 첫 번째 파라미터로 입출력에 필요한 FILE 포인터를 전달한다.
- 두 번째 파라미터로 오픈할 파일의 이름(경로 및 확장자 포함)을 전달한다.
- 세 번째 파라미터로 파일을 어떻게 열 것인지를 지정하는 모드를 전달한다.
int main()
{
FILE* file = nullptr;
fopen_s(&file, "../Test.txt", "rt");
}
반환 값
- fopen_s 함수는 지정한 파일을 지정한 모드로 열고 입출력에 필요한 FILE 구조체를 생성한 후 그 포인터를 첫 번째 파라미터로 전달된 변수에 설정한다.
- fopen_s 함수 오류가 발생했을 때 errno_t 타입의 오류 객체를 반환한다.
int main()
{
FILE* file = nullptr;
errno_t ret = fopen_s(&file, "../Test.txt", "rt");
if (file == nullptr)
{
std::cout << "Failed to open map file\n";
return;
}
}
파일 닫기
- fopen_s로 파일 열기에 성공했으면, 입출력 함수를 활용해 파일 내부 데이터에 접근할 수 있다.
- 파일을 모두 사용한 후에는 fclose 함수를 사용해 닫아야 한다.
int main()
{
FILE* file = nullptr;
fopen_s(&file, "../Test.txt", "rt");
fclose(file);
}
모드 및 파일 형태
모드
- 모드는 파일을 어떤 방식으로 열 것인지를 지정하는 데 사용한다.
| 모드 | 설명 |
| r | 읽기 전용으로 파일을 연다. 이 모드로 파일을 열면 읽기만 가능하다. 파일을 찾지 못하면 오류가 반환(리턴)된다. |
| w | 데이터를 쓸 목적으로 파일을 열 때 사용한다. 이 모드로 연 파일은 쓰기만 가능하기 때문에 읽지는 못한다. 파일이 없는 경우 새로 생성하며 파일이 있는 경우에는 기존 파일을 덮어쓴다. (참고로, 윈도우는 쓰기 전용 속성이 없지만, 스트림은 쓰기 전용 상태로 열 수 있다) |
| a | 데이터를 추가할 목적으로 파일을 열 때 사용한다. 즉, 파일 끝에 데이터를 추가한다. 이 모드로 파일을 열면, 파일을 연 직후에 파일 포인터(File Position)가 파일의 끝 위치로 이동한다. 파일이 없는 경우 새로 생성한다. |
| r+ | 읽고 쓰기가 가능하도록 파일을 연다. 파일이 없을 경우 에러를 반환한다. |
| w+ | 읽고 쓰기가 가능하도록 파일을 연다. 파일이 없는 경우 생성한다. |
| a+ | 읽기와 추가가 가능하도록 파일을 연다. 파일이 없는 경우 생성한다. |
파일 형태
- fopen_s의 mode에는 오픈 모드 외에도 파일의 형태를 지정하는 플래그를 추가로 지정할 수 있다.
- 열려는 파일을 텍스트 파일로 열려면 t를 붙이고, 바이너리(이진) 파일로 열려면 b를 붙인다.
- 텍스트 파일 모드로 파일을 열면 다음의 두 가지 변환을 진행한다.
- 개행 코드를 의미하는 CR/LF 조합은 LF로 변환해 읽히며 LF를 기록하면 CR/LF가 출력된다.
이렇게 변환하는 이유는 C 문자열 출력 함수들이 개행을 위해 LF(\n)을 사용하기 때문이다. - 파일의 끝을 나타내는 Ctrl+Z(0x1A)는 EOF(-1)로 변환되어 읽힌다.
단 “a+” 모드로 열었을 때는 데이터를 끝 부분에 추가할 수 있도록 Ctrl+Z를 제거한다.
- 개행 코드를 의미하는 CR/LF 조합은 LF로 변환해 읽히며 LF를 기록하면 CR/LF가 출력된다.
- 오픈 모드와 파일 형태가 mode 파라미터에 같이 전달되는데, 오픈 모드가 먼저 오고 파일 형태가 나중에 오는 형식으로 써야 한다. 단 +는 파일 형태 다음에 와도 상관없다.
| 모드 | 설명 |
| rt | 텍스트 파일을 읽기 전용으로 읽는다. |
| wb | 이진 파일을 쓰기 전용으로 연다. |
| r+b, rb+ | 이진 파일을 읽기, 쓰기가 가능하도록 연다. |
CRLF와 LF의 차이
Windows 운영체제는 기본적으로 텍스트 파일에서 줄 바꿈을 CRLF로 표시한다.
Unix, Linux, MacOS 등의 운영체제에서는 텍스트 파일에서 줄 바꿈을 LF로 표시한다.

VSCode에서는 텍스트 파일을 열면, 이를 변경할 수도 있다.

CRLF는 줄 바꿈 표현을 캐리지 리턴(\r)과 라인 피드(\n)를 함께 사용하여 표시한다.
aaaa\r\n
bbbb\r\n
cccc
LF는 줄 바꿈 표현을 라인 피드(\n)만 사용하여 표시한다.
aaaa\n
bbbb\n
cccc
캐리지 리턴(\r): 커서를 현재 줄의 맨 앞으로 이동시키는 동작
라인 피드(\n): 커서를 다음 줄로 이동시키는 동작
즉, CRLF는 LF보다 한 줄의 끝을 나타내는 데 더 많은 바이트를 사용한다.
파일의 임의 접근
- 파일 스트림은 다음에 입출력을 진행할 파일의 위치를 항상 기억하고 있으며, 이 위치를 FP(File Position)라고 한다.
- 처음 파일을 열면 FP가 파일의 첫 부분을 가리키며 스트림에서 내용을 읽거나 쓰면 액세스 한 만큼 FP가 자동으로 뒤로 이동한다.
- 이런 방식으로 파일의 처음부터 순차적으로 액세스 하는 방식을 순차 접근(Sequencial Access)라고 한다.
- 반면, 원하는 위치로 한 번에 이동하면서 읽는 방법을 임의 접근(Random Access)라고 한다.
- 다음에 액세스 할 위치를 옮기고 싶을 때는 FP를 먼저 이동시킨 다음, 액세스 함수를 호출하면 된다.
fseek
- 첫 번째 파라미터는 대상 스트림이며, 두 번째 파라미터 offset은 FP를 어디로 옮길지를 지정한다. 세 번째 파라미터 origin은 어느 위치를 기준으로 FP를 옮길 것인지를 지정한다.
- origin에 사용할 수 있는 값은 3가지가 있다.
- SEEK_SET: 스트림 처음 위치 기준
- SEEK_CUR: 현재 위치 기준
- SEEK_END: 스트림의 끝 위치 기준
- SEEK_END일 경우 offset은 음수여야 하며, SEEK_CUR는 방향에 따라 양수, 음수 모두 가능하다.
// FP를 파일의 맨 첫 위치에서 0칸 이동한 위치로 옮김
fseek(file, 0, SEEK_SET);
// FP를 파일의 현재 위치에서 5칸 앞으로 이동한 위치로 옮김
fseek(file, 5, SEEK_CUR);
// FP를 파일의 마지막 위치에서 0칸 이동한 위치로 옮김
fseek(file, 0, SEEK_END);
ftell / rewind
ftell 함수는 FP의 현재 위치를 반환하는 함수이다.
rewind는 FP를 파일의 맨 처음 위치로 되돌리는 함수이다.
일반적으로 이들은 파일 크기를 구하는 상황에서 많이 사용된다.
- fseek 함수를 통해 FP를 파일의 맨 끝으로 이동시킨다.
- ftell 함수를 통해 현재 파일의 위치를 반환받는다.
- 파일의 가장 마지막 위치를 받으므로, 이는 파일의 크기가 된다.
- rewind 함수를 통해 다시 FP를 맨 처음 위치로 되돌린다.
- 이는 fseek(file, 0, SEEK_SET)와 동일하다
fseek(file, 0, SEEK_END);
size_t fileSize = ftell(file);
rewind(file);
파일 액세스
파일 읽고 쓰기
- 파일을 열었으면, 파일 안의 내용을 읽고 쓸 수 있다.
- 간단한 포맷인 문자열부터 입출력을 해보자. 이 때는 다음의 두 함수를 사용한다.
- 읽고 쓸 대상 스트림을 전달하기 위해 FILE 타입의 구조체 포인터를 파라미터로 받는 점이 동일하다.
char* fgets(char* string, int n, FILE* stream); // 문자열 읽기 함수.
int fputs(const char* string, FILE* stream); // 문자열 쓰기 함수.
파일에서 문자열 쓰기
- 아무것도 없는 텍스트 파일을 열어 문자열을 써보자.
- 파일에 문자열을 쓸 때는 fputs 함수를 사용한다.
#include <iostream>
int main()
{
const char* msg = "C 표준 라이브러리 함수로 생성한 텍스트 파일\n";
FILE* file = nullptr;
fopen_s(&file, "Test.txt", "wt");
if (file)
{
fputs(msg, file);
fclose(file);
}
}
- 코드를 실행하면, Main.cpp 파일이 있는 위치에 Test.txt 파일이 생성된 것을 볼 수 있다.
- Test.txt 파일을 열어보면, 코드에서 추가했던 문자열이 그대로 저장된 것을 확인할 수 있다.


파일에서 문자열 읽기
- 앞에서 생성한 Test.txt 텍스트 파일을 읽어보자.
- 파일에서 문자열을 읽을 때는 fgets 함수를 사용한다.
- fgets로 한 줄씩 읽어 화면에 출력하는데 이 과정을 파일의 끝까지 반복한다.
- 읽기에 사용하는 buffer는 256 바이트의 크기로 선언했고, fgets 함수의 두 번째 파라미터로 256 만큼씩 읽어 들이도록 설정했는데 이 값은 2 이상이면 어떤 값을 설정해도 상관없다.
- 단, 이 값이 너무 작으면 조금씩 자주 읽어야 하기 때문에 성능에 영향을 줄 수 있다.
#include <iostream>
int main()
{
char buffer[256];
FILE* file = nullptr;
fopen_s(&file, "Test.txt", "rt");
if (file)
{
while (true)
{
// 256개 단위로 buffer에 읽은 데이터 저장
if (fgets(buffer, 256, file) == nullptr)
{
break;
}
std::cout << buffer;
}
fclose(file);
}
}

feof 함수
- feof 함수는 파라미터로 전달받은 스트림이 파일의 끝에 도달했는지를 확인한다.
- eof는 End of File을 의미한다.
- 따라서 이 함수가 TRUE를 반환할 때까지 반복해서 fgets 함수를 호출하면 파일의 끝까지 모든 내용을 읽을 수 있다.
int feof(FILE* stream);
한 문자씩 읽고 쓰기
fgetc와 fputc 함수는 스트림으로부터 한 문자씩 입출력한다.
int fgetc(FILE* stream);
int fputc(int character, FILE* stream);
블록 단위로 읽고 쓰기
- 다음 두 함수는 블록 단위의 입출력이 필요할 때 사용할 수 있다.
- 두 함수는 buffer에 저장된 bufferSize(elementSize) 크기의 메모리 블록 elementCount 개를 스트림으로 입출력하며 실제로 입출력한 길이를 반환한다.
- 대부분 지정한 크기만큼 입출력을 처리하지만, 파일의 끝 부분을 읽거나 디스크가 가득 찬 경우에는 더 작은 크기를 출력할 수도 있다.
size_t fread(void* buffer, size_t elementSize, size_t elementCount, FILE* stream);
size_t fwrite(void const* buffer, size_t elementSize, size_t elementCount, FILE* stream);
fread / fwrite 함수를 활용한 파일 복사
- fread 함수를 통해 첫 번째 파일의 데이터를 읽고, fwrite 함수를 통해 두 번째 파일에 복사해 보자.
#include <iostream>
int main()
{
char buffer[256];
FILE* file = nullptr;
fopen_s(&file, "Test.txt", "rt");
if (file)
{
FILE* file2 = nullptr;
fopen_s(&file2, "Test2.txt", "wt");
if (file2)
{
while (!feof(file))
{
int readSize = fread(buffer, 1, 256, file);
fwrite(buffer, 1, readSize, file2);
}
fclose(file2);
}
fclose(file);
}
}


포맷 지정 파일 입출력
파일의 내용을 읽고, 쓸 때 데이터가 일반 텍스트가 아니라 int, float, 문자열 등 다양하다면 포맷을 지정해 입출력을 할 수 있는 함수를 활용하면 된다.
사용하는 방법은 scanf 및 printf와 동일한데 그 대상이 파일이라는 점만 다르다.
int fscanf(FILE* stream, const char* format, ...);
int fprintf(FILE* stream, const char* format, ...);
파일 읽고 출력하기 예제
다음과 같은 스테이지 데이터를 파일로 갖고 있다.
이를 파일 입출력을 통해 출력해 보자.

#include <iostream>
int main()
{
FILE* mapFile = nullptr;
fopen_s(&mapFile, "Stage_01.txt", "rt");
// 예외 처리
if (!mapFile)
{
__debugbreak();
return 0;
}
fseek(mapFile, 0, SEEK_END); // FP를 파일의 끝으로 이동
size_t fileSize = ftell(mapFile); // FP의 위치를 읽음 (파일의 크기)
rewind(mapFile); // FP를 다시 파일의 처음으로 돌림
char* buffer = new char[fileSize + 1]; // 파일의 크기만큼 버퍼 할당
memset(buffer, 0, fileSize + 1);
// fread 함수를 사용해 파일의 모든 문자를 버퍼에 저장
int size = static_cast<int>(fread(buffer, sizeof(char), fileSize, mapFile));
// 현재 위치
int index = 0;
int posX = 0; // 열
int posY = 0; // 행
// 문자 배열 순회
while (index < size)
{
// 맵 문자 확인
char mapCharacter = buffer[index++];
// 개행 문자 처리
if (mapCharacter == '\n')
{
// 다음 줄로 넘기면서, x 좌표 초기화
posX = 0;
++posY;
std::cout << "\n";
continue;
}
// 문자 출력 및 x좌표 증가 처리
std::cout << mapCharacter;
posX++;
}
// 리소스 정리
delete[] buffer;
fclose(mapFile);
}

728x90
'📕Programming > 📝C/C++' 카테고리의 다른 글
| [C / C++] C++ 스타일 파일 입출력 (0) | 2025.07.24 |
|---|---|
| [C / C++] static (0) | 2025.07.23 |
| [C / C++] RTTI (0) | 2025.07.23 |
| [C / C++] 스택 & 힙 메모리 할당 과정 (0) | 2025.07.20 |
| [C / C++] 동적 라이브러리 (dynamic link library) (0) | 2025.07.18 |