우선
문의한 코드는 삭제합니다.
이런식으로 정답은 맞췄습니다만 제가 생각한 방식이 왜 안되는 건지 알고 싶습니다.
다음은 제가 짠 코드 전문입니다.
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<stdbool.h>
int main()
{
char a[31];
scanf("%[^\n]s",a);
char* result = malloc(sizeof(char) * 31);
char* ptr = strtok(a, " ");
strcpy(result, ptr);
while (ptr != NULL)
{
ptr = strtok(NULL, " ");
strcat(result, ptr);
}
int length = strlen(result);
bool isok = true;
for (int i = 0; i < length / 2; i++)
{
if (result[i] != result[length - i - 1])
isok = false;
break;
}
printf("%d", isok);
return 0;
}
저는 공백을 없애기 위해 제시된 문자열을 공백 단위로 끊어 포인터배열에 저장한 뒤 메모리를 할당한 저장할포인터배열에 카피 후 자르는포인터배열이 NULL이 될 때까지 반복하며 자를 때마다 저장할포인터배열에 strcat을 이용하여 계속 이어붙이기를 해 결과적으로 공백이 없는 문자열을 얻고, 이 문자열로 회문확인처리를 하면 된다고 생각하였으나, strcat부분에서 메모리를 잘못 참조하였을 때 발생하는 0xC0000005: 0x0000000000000000 위치를 읽는 동안 액세스 위반이 발생했습니다. 라는 오류메세지가 떴습니다.
strcat을 사용 할 때 최종문자열이 메모리를 할당받은 포인터배열이라면 이어붙이기가 가능하다고 나와있는데, 무엇이 문제여서 저런 오류가 발생했는지 알고 싶습니다.
(도장_ 관리자님이 수정함 - 원문 제출일: 2020년 7월 21일, 화요일, 오전 10:32)
Visual Studio에서 ptr의 값을 보면 0x00000000인데요.
하단 로컬 창을 보면 ptr 값이 NULL이라는 뜻입니다.
입력 값으로 hello를 넣으면 공백으로 자를 게 없으니 ptr 값은 NULL이 됩니다.
NULL은 읽을 수 없으니까 액세스 위반이 발생합니다.
strtok 함수는 더 이상 자를 게 없을 때 마지막에는 NULL을 반환합니다.
항상 마지막에는 ptr이 NULL 일 수 밖에 없습니다.
따라서 이런 로직을 짤 때는 ptr이 NULL이 아니라는 조건을 반드시 검사한 다음에 해야 합니다.
strcat 함수는 아무 죄가 없습니다. NULL을 붙이려고 하기 때문이고, 액세스 위반은 무조건 포인터를 잘못 사용한 것입니다. 메모리 접근을 잘못했거나 범위를 벗어났거나 등의 문제가 발생했기 때문입니다.
답변 감사합니다. 위에서 strcat(result, ptr)를 먼저 선언하지 않고 반복문 내에서 첫 행으로 입력하면 NULL이 입력이 되지 않는다는 걸 깨달았네요. 감사합니다~
while (ptr != NULL)
{
strcat(result, ptr);
ptr = strtok(NULL, " ");
}
다만 이렇게 해도 프로그램은 정상 작동합니다만 결과값이 정상적으로 출력이 안되는데, 이유를 여쭤봐도 될까요?
답변 감사합니다. 위에서 strcat(result, ptr)를 먼저 선언하지 않고 반복문 내에서 첫 행으로 입력하면 NULL이 입력이 되지 않는다는 걸 깨달았네요. 감사합니다~
while (ptr != NULL)
{
strcat(result, ptr);
ptr = strtok(NULL, " ");
}
다만 이렇게 해도 프로그램은 정상 작동합니다만 결과값이 정상적으로 출력이 안되는데, 이유를 여쭤봐도 될까요?
그림 45-1~4번까지 보면서 strtok 함수의 동작을 제대로 이해해야 합니다.
strtok 함수가 마지막 문자열까지 탐색하면 ptr은 반드시 NULL이 됩니다. 따라서 로직 진입, 로직 중간, 로직 종료에서 종료 처리가 필요합니다.
종료일 때 ptr이 NULL이 될 때는 중간 로직 실행을 하지 말아야 합니다.
로직 종료일 때 처리하는 부분은 UNIT 45.7의 심사문제 해설을 보면 다음과 같은 예시 코드를 제시하고 있습니다.
if (ptr != NULL && strcmp(ptr, "the") == 0) // ptr이 NULL이 아니면서 ptr이 the일 때
count++; // count를 1 증가시킴
UNIT 45.7을 통과했었다면 해당 부분을 다시 한 번 복습하세요.
무작정 키보드 타이핑하고 실행하는 반복하지 말고, 손코딩하세요.
로직을 제대로 못 짜는 상황입니다. 물론, strcat으로 해결하는 방식은 일반적인 해법은 아니지만, 한다면 할 수 있겠지요.
답변 감사드립니다.
저는 절대로 주먹구구식으로 기계처럼 함수넣고 안되면 다른함수넣고 또 안되면 다른함수넣는 식으로 답이 나올때까지 반복해서 코딩을 짜는게 아닙니다. 또한 연속하여 말씀해주시는 strtok함수 NULL관련 처리는 첫 답변을 보고 추가하여 NULL관련 문제는 해결했습니다. 문제를 해결하기 위해 어떤 로직이 필요한가 생각하고, 그 로직대로 현재까지 책에서 배운 내용을 바탕으로 짜려고 합니다. 그래서 38.8심사문제 지뢰찾기를 풀 때 엉뚱하게도 NULL이 탐색되면 문자열의 탐색이 끝난다는 것을 잊어버린 채 입력된 문자열의 크기보다 가로,세로를 2줄 더해 외곽엔 NULL을 넣고 지뢰를 탐색하는 로직을 짜서 한동안 엑세스위반 경고문을 보면서 깨닫고 로직을 바꿔 해결했었습니다.
이 문제 또한 malloc으로 할당한 최종결과배열을 NULL로 초기화하지 않는다면 입력값의 크기가 최종결과배열의 크기보다 작을때 회문판별코드에서 쓰레기값을 비교할 수 있다는 것을 캐치하지 못하여 문제를 해결하지 못했습니다. 물론 답변을 본 지금은 memset을 통하여 포인터변수로도 해결했습니다.
문제를 해결할 때 효율성을 따진다면 배열을 써서 푸는게 맞겠으나, 저는 배운 문자열관련 함수들(str어쩌구)을 사용해서도 내가 생각한 로직대로 풀릴까 호기심이 생겨 포인터배열쪽으로 풀어보려고 했었습니다. 절대로 아무생각없이 대충 짠 코드 올려놓고 정답 알려주세요 라는 식으로 질문을 올린건 아니라는걸 알아주셨으면 합니다. 다만 초심자이기에 똑똑한 사람들은 자연스럽게 캐치할 수 있는 부분들을 머리가 좋지 않은지라 캐치하지 못해 이런 문제가 생길 뿐입니다..ㅠㅠ
여하튼 다시한번 답변주신거 감사합니다~~
포인터를 사용해서 의도적으로 연습하려는 것은 눈치채고 있었습니다.
다만, 어렵게 코딩하지 말고 쉽게 할 수 있는 건 쉽게 하는 게 더 중요하다는 뜻이었습니다. 포인터로 풀이하려고 하다보니 쉬운 길이 있는데, 너무 어려운 길로 가는 것 같았습니다.
나중에 디자인 패턴을 배우면 모든 코딩에 디자인 패턴을 사용하려고 해서 불필요하게 코드가 복잡해지는 문제가 발생합니다. 주니어 레벨에서 흔히 빠지는 오류입니다. 항상 적재적소를 잊지 마세요!