글이 너무 길어져서, 대답 부분과 코드 부분을 접어두었습니다.
접힌 부분 누르면 내용이 나옵니다.
이 부분이 너무 이해가 안가는데, 어디서부터 검색을 해야할지 막막했다. 마침 chat gpt가 생각나서 컴공과 친구가 옆에 앉아있다고 생각하고 질문을 해보기로 했다.
연습문제와 개념 부분만 chat gpt에게 물어보고, 제출하는 과제에는 절대 사용하지 않을 예정이다.
우선 아래와 같이 코드를 짜줬는데, 이해가 안가는 부분을 여러 단계에 걸쳐 물어봤더니, 정말 상세하게 알려준다.
우선 이 친구가 제시한 코드 (예시문제에서 에러가 발생하는 코드를 제시해주고, 수정하는 문제였다.)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, char *argv[])
{
// Check for command line args
if (argc != 2)
{
printf("Usage: ./read infile\n");
return 1;
}
// Create buffer to read into
char buffer[7];
// Create array to store plate numbers
char *plates[8];
FILE *infile = fopen(argv[1], "r");
if(infile == NULL)
{
return 1;
}
int idx = 0;
while (fread(buffer, 1, 7 , infile) == 7)
{
char *temp = malloc(sizeof(char) * 7);
// Replace '\n' with '\0'
buffer[6] = '\0';
if(temp != NULL)
{
strcpy(temp, buffer);
plates[idx] = temp;
idx++;
}
}
fclose(infile);
for (int i = 0; i < idx; i++)
{
printf("%s\n", plates[i]);
free(plates[i]);
}
return 0;
}
이 코드는 입력 파일에서 자동차 번호판 정보를 읽어서 문자열 배열에 저장하고, 저장된 내용을 출력하는 프로그램입니다.
-7바이트 크기의 buffer 배열과 8개의 문자열 포인터가 들어있는 plates 배열을 생성합니다.
-파일을 열어서 열기에 실패하면 프로그램을 종료합니다.
-파일에서 7바이트씩 읽어서 buffer 배열에 저장하고, fread 함수가 반환한 값이 7이 아닌 경우 while 루프를 빠져나갑니다.
-buffer 배열의 마지막 문자를 널 문자 '\0'으로 대체합니다.
-7바이트 크기의 동적 할당을 통해 temp 포인터를 생성합니다.
-temp 포인터가 NULL이 아니면 buffer 배열의 내용을 temp 포인터가 가리키는 메모리에 복사합니다.
-plates 배열의 idx 인덱스에 temp 포인터를 저장하고, idx 값을 1 증가시킵니다.
-파일을 닫습니다.
-plates 배열에 저장된 문자열을 출력하고, 각 문자열에 대해 동적으로 할당한 메모리를 해제합니다.
-프로그램을 종료합니다.
이 코드에서는 메모리 누수가 발생하지 않도록 문자열 포인터 배열에 문자열을 저장하기 전에 메모리를 할당하고, 출력이 끝난 후에 메모리를 해제하도록 수정되었습니다.
약간 대충 말하는것 같아서 다시한번 물어본 답변
이 코드는 입력 파일에서 자동차 번호판 정보를 읽어서 문자열 배열에 저장하고, 저장된 내용을 출력하는 프로그램입니다.
-7바이트 크기의 buffer 배열과 8개의 문자열 포인터가 들어있는 plates 배열을 생성합니다.
-파일을 열어서 열기에 실패하면 프로그램을 종료합니다.
-파일에서 7바이트씩 읽어서 buffer 배열에 저장하고, fread 함수가 반환한 값이 7이 아닌 경우 while 루프를 빠져나갑니다.
-buffer 배열의 마지막 문자를 널 문자 '\0'으로 대체합니다.
-7바이트 크기의 동적 할당을 통해 temp 포인터를 생성합니다.
-temp 포인터가 NULL이 아니면 buffer 배열의 내용을 temp 포인터가 가리키는 메모리에 복사합니다.
-plates 배열의 idx 인덱스에 temp 포인터를 저장하고, idx 값을 1 증가시킵니다.
-파일을 닫습니다.
-plates 배열에 저장된 문자열을 출력하고, 각 문자열에 대해 동적으로 할당한 메모리를 해제합니다.
-프로그램을 종료합니다.
이 코드에서는 메모리 누수가 발생하지 않도록 문자열 포인터 배열에 문자열을 저장하기 전에 메모리를 할당하고, 출력이 끝난 후에 메모리를 해제하도록 수정되었습니다.
질문 : 위에 코드에서 char buffer[7]; 이 부분을 문자열배열 말고 char* 타입으로 선언해서 코드를 짤 수도 있어?
다음은 buffer를 char* 타입으로 선언하여 코드를 수정한 전체 예시 코드입니다:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, char *argv[])
{
// Check for command line args
if (argc != 2)
{
printf("Usage: ./read infile\n");
return 1;
}
// Create buffer to read into
char* buffer = malloc(sizeof(char) * 7);
// Create array to store plate numbers
char* plates[8];
FILE* infile = fopen(argv[1], "r");
if (infile == NULL)
{
return 1;
}
int idx = 0;
while (fread(buffer, 1, 7, infile) == 7)
{
char* temp = malloc(sizeof(char) * 7);
// Replace '\n' with '\0'
buffer[6] = '\0';
if (temp != NULL)
{
strcpy(temp, buffer);
plates[idx] = temp;
idx++;
}
}
fclose(infile);
free(buffer);
for (int i = 0; i < idx; i++)
{
printf("%s\n", plates[i]);
free(plates[i]);
}
return 0;
}
위의 코드에서 buffer는 char* 타입으로 선언되었고, malloc 함수를 사용하여 메모리를 동적으로 할당하였습니다. 나머지 부분은 이전의 코드와 동일합니다. 다만, buffer가 char* 타입이므로 fread() 함수에서 읽어들인 데이터를 buffer에 바로 대입할 수 있습니다.
단, buffer를 char* 타입으로 선언하고 동적 할당을 하게 되면, 적절한 해제를 위해 메모리를 반드시 해제해주어야 합니다. fclose() 함수 호출 이후에 free(buffer)를 통해 동적 할당된 메모리를 해제해주어야 합니다.
질문 : 위에서 buffer가 char* 타입으로 선언하는거랑 char 배열로 선언하는거랑 차이가 뭐야? 성능에서 차이가 있는거야? 아니면 그저 문자열배열이 좀더 코드가 짧아서 그렇게 쓰는거야?
char* 타입과 char 배열로 선언된 변수의 차이점은 다음과 같습니다:
메모리 할당: char* 타입은 포인터로써 메모리를 가리키는 역할을 합니다. 메모리는 동적으로 할당되어야 하며, malloc()이나 new를 통해 메모리를 할당해야 합니다. 반면에 char 배열은 선언 시에 크기를 지정하여 정적으로 할당되며, 별도의 메모리 할당 과정이 필요하지 않습니다.
크기 조정: char* 타입은 동적으로 메모리를 할당하여 문자열을 저장하기 때문에, 문자열의 크기를 필요에 따라 조정할 수 있습니다. 반면에 char 배열은 선언 시에 크기가 고정되어 있어서 배열 크기를 변경할 수 없습니다.
문자열 처리: char* 타입은 가리키는 주소를 변경하여 다른 문자열로 바로 변경할 수 있습니다. 반면에 char 배열은 배열의 요소를 하나씩 변경하거나, 새로운 문자열을 복사해야만 문자열을 변경할 수 있습니다.
성능 측면에서는 큰 차이는 없습니다. 둘 다 문자열을 저장하고 사용할 수 있으며, 성능은 대부분 문자열 처리 알고리즘에 따라 결정됩니다. 선택하는 것은 개발자의 코딩 스타일과 요구사항에 따라 다릅니다. char 배열은 크기가 고정되어 있어서 메모리 사용이 명확하고, 문자열을 저장하는 데 편리합니다. 반면에 char* 타입은 동적으로 메모리를 할당하고, 문자열의 크기를 조정하거나 문자열을 동적으로 변경할 수 있는 유연성을 제공합니다. 따라서, 어떤 방식을 선택할지는 프로그램의 요구사항과 개발자의 선호도에 따라 결정되어야 합니다.
질문 : strcpy(temp, buffer); temp, buffer 둘다 문자열인데 왜 위에처럼 복사하는거야? 그냥 temp = buffer 하면 안되는거야?
temp와 buffer는 모두 문자열을 가리키는 포인터입니다. 그러나 temp = buffer와 같이 단순히 포인터를 대입하는 것은 문자열을 복사하는 것이 아닙니다.
문자열을 복사하려면 문자열의 내용을 새로 할당된 메모리에 복사해야 합니다. strcpy() 함수는 이를 수행하기 위해 사용됩니다. strcpy() 함수는 소스 문자열(buffer)의 내용을 대상 문자열(temp)에 복사합니다.
따라서 strcpy(temp, buffer)는 buffer의 내용을 temp가 가리키는 메모리로 복사하는 역할을 합니다. 이를 통해temp는 buffer와 동일한 문자열을 가리키는 새로운 메모리를 갖게 됩니다.
반면에temp = buffer는 단순히 temp 포인터가 buffer를 가리키도록 포인터를 복사하는 것이므로, 문자열 자체가 복사되지 않습니다. 이렇게 하면 plates 배열에 저장된 모든 문자열이 서로 같은 메모리를 가리키게 되어 문제가 발생할 수 있습니다. 따라서, 문자열을 복사하기 위해서는 strcpy() 함수를 사용해야 합니다.
질문 : 근데 저기서 temp 는 없으면 안되는거야?
temp는 동적으로 할당된 메모리를 가리키기 위한 포인터 변수입니다. temp 변수를 사용하지 않고 buffer를 바로 plates 배열에 할당하는 것은 문제를 일으킬 수 있습니다.
만약 temp를 사용하지 않고 buffer를 바로 plates 배열에 할당한다면, 모든 plates 요소는 buffer를 가리키게 됩니다. 그러면 buffer의 내용이 변경되면 모든 plates 요소가 동일한 변경된 내용을 가리키게 됩니다. 이는 원하지 않는 동작을 일으킬 수 있습니다.
따라서, 각각의 plates 요소가 독립적으로 문자열을 가리키기 위해 temp 포인터를 사용하여 buffer의 내용을 복사하고, temp를 plates 배열에 할당하는 것이 필요합니다. 이렇게 하면 각각의 plates 요소는 고유한 메모리를 가리키게 되어, 독립적인 문자열을 저장할 수 있습니다.
질문 : 만약 temp를 사용하지 않고 buffer를 바로 plates 배열에 할당한다면, 모든 plates 요소는 buffer를 가리키게 됩니다. 그러면 buffer의 내용이 변경되면 모든 plates 요소가 동일한 변경된 내용을 가리키게 됩니다. 이는 원하지 않는 동작을 일으킬 수 있습니다. <- 답변중에 이 부분이 왜 그렇게 되는지 자세하게 설명 부탁할께.
buffer는 char 배열로 선언된 변수입니다. 배열은 메모리의 연속된 공간에 데이터를 저장하는 구조입니다.
만약 temp를 사용하지 않고 buffer를 바로 plates 배열에 할당한다면, 모든 plates 요소는 buffer 배열의 시작 주소를 가리키게 됩니다. 이는 포인터 변수의 특성입니다.
즉, plates[0], plates[1], plates[2], ... 등 모든 plates 요소는 동일한 메모리 주소인 buffer의 시작 주소를 가리키게 됩니다.
따라서, buffer의 내용이 변경되면 plates 배열의 모든 요소가 동일한 변경된 내용을 가리키게 됩니다. 이는 원하지 않는 동작을 일으킬 수 있습니다. 예를 들어, buffer에 새로운 문자열을 복사하면 모든 plates 요소가 동일한 새로운 문자열을 가리키게 됩니다.
이러한 상황에서는 각각의 plates 요소가 독립적인 문자열을 가리키기 위해 temp 포인터를 사용하여 buffer의 내용을 복사해야 합니다. 이렇게 하면 각각의 plates 요소가 서로 다른 메모리를 가리키게 되어 독립적인 문자열을 저장할 수 있습니다.
질문 : 그럼 temp를 사용하면 왜 주소값이 매번 바뀌는거야?
temp는 반복문 내에서 동적으로 할당된 메모리를 가리키는 포인터입니다. 반복문이 실행될 때마다 temp은 새로운 메모리를 할당받게 됩니다.
반복문이 한 번 실행될 때마다 temp은 malloc 함수를 사용하여 새로운 메모리를 할당받습니다. 이렇게 하면 각 반복에서 temp이 가리키는 메모리의 주소가 변경되는 것입니다. 새로운 메모리가 할당되기 때문에 이전 반복에서 할당된 메모리와는 다른 주소를 가지게 됩니다.
따라서, 각 반복에서 temp이 가리키는 메모리의 주소가 바뀌게 되고, 해당 메모리에는 각각 다른 문자열이 복사되어 저장됩니다. 이렇게 하면 plates 배열의 각 요소는 서로 다른 메모리를 가리키게 되어 독립적인 문자열을 저장할 수 있습니다.
고마워
처리되지 않은 다른 질문이 있거나 도움이 필요한 추가 사항이 있으면 언제든지 말씀해주세요. 기꺼이 도움을 드리겠습니다. 행운을 빕니다!
다시 검토해보니 malloc이 실패할 경우를 대비한 if(temp != NULL) 이 있는데, 그 대처방안이 누락되어 있었다.
그래서 temp 의 동적할당이 실패할경우 대비하려 아래 코드를 추가해주었다.
if (temp != NULL)
{
// 할당 성공한 경우의 처리
strcpy(temp, buffer);
plates[idx] = temp;
idx++;
}
else
{
// 할당 실패한 경우의 처리
printf("Memory allocation failed.\n");
fclose(infile);
//infile 파일 헨들을 닫아줌.
return 1;
}
여기서 infile을 닫지 않는 경우 어떤 문제가 생길 수 있는지 문의해보았다.
네, 동적으로 할당한 메모리 해제와 마찬가지로 파일을 열었을 경우에는 파일을 닫는 것이 중요합니다. 파일을 닫지 않으면 다음과 같은 문제가 발생할 수 있습니다:
1. 자원 누수: 파일을 닫지 않으면 해당 파일에 대한 파일 핸들이 여전히 열린 상태로 유지됩니다. 이는 시스템 리소스인 파일 핸들을 계속 점유하게 되어 다른 프로세스나 자원에서 해당 파일에 접근하지 못하게 할 수 있습니다. 이는 파일 디스크립터나 파일 개수에 대한 제한이 있는 시스템에서 문제를 일으킬 수 있습니다.
2. 데이터 손실: 파일을 닫기 전에 모든 데이터가 적절히 파일에 기록되지 않을 수 있습니다. 파일을 닫을 때, 시스템은 내부 버퍼를 비워서 파일에 쓰지 않은 데이터를 기록하는데, 파일을 닫기 전에 프로그램이 비정상적으로 종료되거나 오류가 발생하면 기록되지 않은 데이터가 손실될 수 있습니다.
따라서, 파일을 열었을 경우 fclose() 함수를 사용하여 파일을 명시적으로 닫아주는 것이 좋습니다. 파일 작업이 완료되었을 때, 즉시 fclose()를 호출하여 파일을 닫아 자원을 제대로 해제하는 것이 좋은 프로그래밍 관행입니다.
최종적으로 아래 코드가 완성이 되었다.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, char *argv[])
{
// Check for command line args
if (argc != 2)
{
printf("Usage: ./read infile\n");
return 1;
}
// Create buffer to read into
char buffer[7];
// Create array to store plate numbers
char *plates[8];
FILE *infile = fopen(argv[1], "r");
if (infile == NULL)
{
return 1;
}
int idx = 0;
while (fread(buffer, 1, 7, infile) == 7)
{
// Replace '\n' with '\0'
char* temp = malloc(sizeof(char) * 7);
buffer[6] = '\0';
if (temp != NULL)
{
strcpy(temp, buffer);
plates[idx] = temp;
idx++;
}
else
{
printf("Dynamic memory allocation failed(temp).\n");
fclose(infile);
return 1;
}
}
fclose(infile);
for (int i = 0; i < idx; i++)
{
printf("%s\n", plates[i]);
free(plates[i]);
}
return 0;
}
질문 : 여기서, char* temp가 선언되고, while loop를 돌면서 계속 다시 선언이 되는데, 동적으로 선언되면 메모리에 계속 남아있는걸로 알고있어. 근데 여기서는 루프를 돌면서 계속 다시 선언이 되는건데, 이 경우에는 free를 안하고 다시 선언을 해도 메모리는 자동으로 해제가 되는거야?
대답(gpt대답 여러가지를 정리했습니다)
여기서 char* temp는 while loop 내부에서 선언되지만, 실제로는 메모리 할당이 동적으로 이루어지고 있습니다.
...
char* temp = malloc(sizeof(char) * 7);
//새로운 temp 포인터(문자열) 선언한 뒤 동적 메모리 할당해서 메모리 주소를 temp에 저장.
//항상 문자열은 제일 처음 char의 주소값을 가진 포인터임.
...
strcpy(temp, buffer);
//할당한 메모리 내부에 내용을 복사 (주소값이 아닌 내용을 복사하므로 strcpy사용)
plates[idx] = temp;
//plates(문자열)에 temp의 메모리 주소를 지정. 값을 복사하지 않더라도
//여기서는 루프를 빠져나오면 plates혼자 메모리를 가지게 되므로 복사와 마찬가지 효과를 가져옴.
idx++;
//다음 문자열을 지정하고 루프가 끝나면, 다음 루프에서 새로운 temp가 생성되어 만나게 됨.
...
위 부분에서 malloc을 사용하여 메모리를 할당하고 temp에 저장한 뒤, 이를 plates[idx]에 복사합니다. 이렇게 하면 plates 배열의 각 요소는 각각 다른 메모리 주소를 가리키게 됩니다.
루프가 반복될 때마다 temp에 새로운 메모리 주소가 할당되지만, 이전에 할당한 메모리는 여전히 plates 배열에 저장되어 있으므로, 메모리 누수가 발생하지 않습니다. 다만 루프를 돌면서 temp에 할당한 메모리를 해제하지 않고 있기 때문에, 메모리를 사용한 후에는 free() 함수를 호출하여 해제해야 합니다.
...
for (int i = 0; i < idx; i++)
{
printf("%s\n",
plates[i]);
free(plates[i]);
...
이 프로그램의 경우, 루프가 종료된 후에 plates 배열에 저장된 각 요소를 출력하고, 각각의 메모리를 free(plates[i]); 함수를 사용하여 할당된 메모리를 해제합니다. 따라서 이 프로그램에서는 메모리 누수가 발생하지 않으며, 모든 동적 할당된 메모리가 올바르게 해제됩니다.