인프런 커뮤니티 질문&답변
바이너리 파일을 읽을 때 관련해서 질문 드립니다.
해결된 질문
작성
·
27
1
안녕하세요. 13.5 강의를 듣고 실습 도중 궁금한 점이 있어서 질문 드립니다.
수업처럼 sample.txt 파일을 하나 만들고 나서 fread()로 파일을 어떻게 읽는지 궁금해서 frwite()로 쓸 때와 다른 순서로 fread()를 해보았습니다. 그랬더니 순서를 맞췄을 때와 다른 결과가 나와서 관련되어 질문 드립니다.
우선 다음과 같이 코드를 작성했습니다.
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
int main()
{
// FILE write
{
FILE* fp = fopen("sample.txt", "wb");
int n = 100;
double d = 10.0;
float f = 3.14f;
int* parr = (int*)malloc(sizeof(int)*n);
if (!parr) exit(1);
for (int i = 0; i < n; ++i)
*(parr + i) = i * 2;
fwrite(&n, sizeof(n), 1, fp);
fwrite(&d, sizeof(d), 1, fp);
fwrite(&f, sizeof(f), 1, fp);
fwrite(&parr, sizeof(parr), 1, fp);
}
// FILE read
{
FILE* fp = fopen("sample.txt", "rb");
int n = 0;
double d = 0.0;
float f = 0.0f;
size_t c0 = fread(&n, sizeof(n), 1, fp);
printf("n: %d\tcount:%d\n", n, c0);
size_t c2 = fread(&f, sizeof(f), 1, fp);
printf("f: %f\tcount:%d\n", f, c2);
size_t c1 = fread(&d, sizeof(d), 1, fp);
printf("d: %f\tcount:%d\n", d, c1);
int* parr = (int*)malloc(sizeof(int) * n);
if(!parr) exit(1);
size_t c3 = fread(&parr, sizeof(int), n, fp);
printf("parr: %d\tcount:%d\n", *parr, c3);
printf("fepf: %d\n", feof(fp));
free(parr);
fclose(fp);
}
}size_t c1 과 size_t c2 의 코드상 순서를 위와 같이 했을 때는 출력 결과가 다음과 같았습니다.
출력 결과
n: 100 count:1
f: 0.000000 count:1
d: 49.920021 count:1
parr: -842150451 count:100
feof: 1size_t c1 과 size_t c2 의 코드상 순서를 뒤집어서 fwrite()을 했을 때와 동일하게 한 경우 출력 결과가 다음과 같았습니다.
출력 결과
n: 100 count:1
d: 10.000000 count:1
d: 3.140000 count:1
parr: -842150451 count:100
feof: 1여기서 질문입니다.
파일을 fread()로 sample.txt 파일을 읽을 때마다 인자로 입력한 크기만큼 책 읽듯이 앞에서 부터 데이터를 읽어서 파일 쓰기 값과 다른 f와 d의 결과가 나온 것일까요?
바이너리 파일을 읽을 때 fwrite()한 데이터 순서 상관 없이 존재하면 읽을 수 있는 방법이 있을까요? 웬지 일반적인 방법으로는 안될 것 같은데 순서대로 읽을 수 밖에 없는 것일까요?
또한 바이너리 파일 안에 어떠한 데이터 타입 포함되어 있는지 알 수 있는 방법이 있을까요?
*parr의 경우 printf()로 출력하면 0이 출력 될 것이라 생각했는데 비정상적인 값이 나와서 놀랐습니다. 여러 생각을 해보았으나 원인이 잘 떠오르지 않아 왜 이런 값이 나오는지 여쭙습니다.
수업 주제를 넘어서는 질문인 것 같은데, 교수님께서 중간에 언급하신 '바이너리 파일의 구조를 모르면 해킹과 같은 방법으로 데이터를 읽어야 한다'고 하는게 상상을 해보면, 처음 부분부터 순차적으로 맞는 데이터를 찾을 때까지 (예를 들면) fread()를 반복해서 실행해 값을 확인하고 다음 데이터로 넘어가고를 반복하는 것일까요?
답변 2
1
안녕하세요? 질문&답변 도우미 durams입니다.
먼저 제시해주신 코드에 문제가 있습니다. 파일에 쓰기 작업을 하고 난 후, fp를 닫아줘야 제대로 동작합니다. 또한 동적 할당한 parr을 해제하는 코드도 없습니다. 제시해주신 실행 결과가 잘 나오는걸로 봐선, 이전의 클립보드 내용을 제시해주신 것 같습니다.
네 맞습니다. 파일에 쓰기 작업을 하신
int,float,double등의 크기가 다르기 때문에, 순차적으로 나열된 비트 패턴이 다르게 해석되었네요.구현하기 나름입니다. 파일 내 특정 부분을 가리키는 커서는 프로그래머가 마음대로 이동시킬 수 있습니다.
이것 역시 구현하기 나름입니다. 대부분의 바이너리 파일들은 특정 확장자를 가짐으로써 파일 내 데이터가 어떠한 양식대로 들어있다는 것을 나타냅니다. 어떤 데이터들이 어떤 순서로 어떠한 위치에 있는지를 모두 파일 입출력을 통해 처리할 수 있게 되어있습니다. 그러한 규칙 없이 단순히 데이터를 순서대로 저장해놓기만 한다면 해석하는 건 힘들겠죠.
*parr의 출력을 통해 나온 값은 비정상적인 값이 아닙니다. 사실 제시해주신 코드에 문제가 많습니다.write 블록에서
parr에 저장한int들을 파일에 쓰는 코드가 잘못되었습니다.fwrite는 첫 번째 인자로 데이터가 위치한 버퍼의 주소를 받습니다.&parr이 아니라parr을 전달해야 합니다.&parr은 버퍼의 주소를 담은 변수의 주소입니다.두 번째 인자와 세 번째 인자는 각각
sizeof(int),100으로 수정해야 합니다.parr은 배열이 아니기 때문에,sizeof(parr)은 포인터 변수의 크기를 반환합니다.read 블록에서도 역시
int100개를 읽을 때size_t c3 = fread(&parr, sizeof(int), n, fp);가 아니라size_t c3 = fread(parr, sizeof(int), 100, fp);와 같이 작성해야 합니다. 이유는 b에 설명된 것과 동일합니다.-842150451이라는 값은 hex value로 나타냈을 때0xcdcdcdcd로 나오는데요, 이는 초기화되지 않은 힙 메모리에 기본값으로 설정되는 값입니다.fread시 잘못된 주소를 전달했기 때문에 애초에parr이 가리키는 메모리는 아무런 값도 할당되지 않았던 것입니다.
3번의 답변에서 말씀드린것처럼, 바이너리 파일들은 목적과 용도에 따라 담는 데이터의 종류와 형식이 다양합니다. 어떠한 형식으로 데이터가 위치해있는지 알고 있다면, 파일을 해석하는 것도 가능하겠죠. 다만 파일의 내용을 단순히
fread를 반복하는 방식으로 해석하는 것은 사실상 불가능에 가깝습니다. 어떤 데이터를 읽을 지, 각 데이터의 크기는 어느정도일지 등을 모두 고려해야한다는 것인데 절대 쉬운 일이 아니죠. 더군다나 최근의 파일들은 그러한 리버싱을 막기 위해 중간중간에 파일의 난독성을 증가시키는 데이터를 집어넣는 등, 방어 전략도 존재합니다.
0
안녕하세요, 인프런 AI 인턴입니다. Lucas님
바이너리 파일을 읽고 쓸 때 여러 궁금점이 생기셨군요. 각 질문에 대해 간단히 설명드리겠습니다.
파일을 fread()로 읽을 시 크기 단위로 각각 읽음:
fread()함수는 바이너리 파일의 내용을 정의된 크기를 기준으로 순서대로 차례로 읽습니다. 따라서fwrite()로 작성된 순서를 그대로 따라하지 않으면 예상치 못한 다른 결과가 나오는 것은 자연스럽습니다. 바이너리 파일의 경우 데이터가 이진 형태로 저장되기 때문에 정확한 타입과 순서가 중요합니다.
fwrite()한 데이터 순서 상관없이 읽기:
- 안타깝지만, 일반적으로 데이터는 작성된 순서대로 읽어야 합니다. 파일 포인터의 위치를 변경하여 특정 데이터에 접근할 수는 있지만, 타 데이터를 무작위로 읽는 방법은 데이터의 구조 또는 인덱스를 사전에 알고 있어야 합니다.
- 안타깝지만, 일반적으로 데이터는 작성된 순서대로 읽어야 합니다. 파일 포인터의 위치를 변경하여 특정 데이터에 접근할 수는 있지만, 타 데이터를 무작위로 읽는 방법은 데이터의 구조 또는 인덱스를 사전에 알고 있어야 합니다.
바이너리 파일의 데이터 타입 확인:
- 파일에 기록된 데이터 타입을 자동으로 판별할 수는 없습니다. 이를 위해서는 데이터 구조와 관련된 메타정보가 파일 내에 저장되거나, 데이터의 크기에 대해 사전 지식이 있어야 합니다.
- 파일에 기록된 데이터 타입을 자동으로 판별할 수는 없습니다. 이를 위해서는 데이터 구조와 관련된 메타정보가 파일 내에 저장되거나, 데이터의 크기에 대해 사전 지식이 있어야 합니다.
비정상적 값 출력:
- 타입 또는 잘못된 메모리 접근으로 인해 예기치 않은 값이 나오게 됩니다. 데이터를 읽을 때 올바른 포인터를 사용하지 않으면, 할당된 메모리상의 의미 없는 데이터를 읽을 수 있습니다.
- 타입 또는 잘못된 메모리 접근으로 인해 예기치 않은 값이 나오게 됩니다. 데이터를 읽을 때 올바른 포인터를 사용하지 않으면, 할당된 메모리상의 의미 없는 데이터를 읽을 수 있습니다.
바이너리 파일의 구조 파악:
- 언급하신 것처럼, 해킹이나 파일 분석의 경우 파일의 구조를 알 수 없는 상태에서 brute-forth하게 파일을 읽으려면 다양한 방법이 사용될 수 있습니다. 주로 여러 개의 읽기 사이즈를 테스트하며 데이터 포맷을 찾습니다. 그러나 이는 매우 복잡하고 시간이 많이 소요될 수 있습니다.
- 언급하신 것처럼, 해킹이나 파일 분석의 경우 파일의 구조를 알 수 없는 상태에서 brute-forth하게 파일을 읽으려면 다양한 방법이 사용될 수 있습니다. 주로 여러 개의 읽기 사이즈를 테스트하며 데이터 포맷을 찾습니다. 그러나 이는 매우 복잡하고 시간이 많이 소요될 수 있습니다.
이와 관련된 유사한 질문이 여기에서 확인하실 수 있습니다.
저는 질문자님의 이해를 돕고자 지식공유자님의 기존 답변을 참고하여 유사한 답변 링크를 추천해드리고 있습니다. 현재 베타 기능으로 답변이 만족스럽지 않을 수 있는 점 양해 부탁드립니다. 🙏 추가적으로 궁금한 점이 있으시면, 이어서 질문해 주세요. 곧 지식공유자께서 답변해 주실 것입니다.






아 코드상의 실수까지 바로 잡아 주시고 ! 감사합니다! 다시 한 번 복기해 보도록 하겠습니다!!