세그멘테이션 폴트에서 가져온 문자열 뒤집기 문제입니다.
이 문제에 대한 해법은 몇 가지가 나올 수 있는데, 우선 문제의 정의에 따라 간단히 구현해 본다면...
물론, 메모리를 새로 할당받지 않고 그 자리에서 순서를 뒤집을 수 있는 방법도 있습니다.
하지만... 1번 문제를 풀면서 처음부터 이런 알고리즘으로 구현할 생각이었다면 최악의 선택이라고 감히 말할 수 있습니다. 말할 것도 없이, 이 알고리즘은 쓸데없이 많은 루프를 돌기 때문입니다.
자, 그럼 조금 다듬어 보기로 합시다.
예를 들어, "123456789"라는 문자열을 뒤집는다고 하면...
1 ↔ 9
2 ↔ 8
3 ↔ 7
4 ↔ 6
5는 제자리
결국 문자열 길이의 절반 만큼만 루프를 돌면 됩니다.
문제에서는 문자열 뒤집기를 다루고 있지만, 문자열도 결국은 배열. 좀 더 일반화시키면 어떤 종류의 배열도 순서를 뒤집어 볼 수 있습니다.
문제 1.
본인에게 가장 편한 프로그래밍 언어로 문자열의 순서를 뒤집는 함수를 만드시오.
이 문제에 대한 해법은 몇 가지가 나올 수 있는데, 우선 문제의 정의에 따라 간단히 구현해 본다면...
/* 답: A안 */가장 심플하고 확실한 방법이긴 하지만, 사전에 메모리 공간을 할당받아야 된다는 것과 잘못된 메모리 접근으로 인한 오류의 가능성도 내포하고 있습니다.
void reverse(const char* text, char* out_buff, size_t max_length)
{
const int src_length = strlen(text);
int length = src_length < max_length ? src_length : max_length;
for (int i = length-1; i >= 0; --i)
*out_buff++ = text[i];
*out_buff = NULL;
}
물론, 메모리를 새로 할당받지 않고 그 자리에서 순서를 뒤집을 수 있는 방법도 있습니다.
/* 답: B안 */이 방법은 if문이 빠졌다는 것을 제외하면 버블 정렬과 모양새가 많이 비슷합니다. 그리고 여기에다는 쓰지 않았지만, 2번 문제에 대한 답도 이런 형태입니다.
template<typename T>
inline void swap(T& __a, T& __b)
{
T __tmp = __a;
__a = __b;
__b = __tmp;
}
char* reverse(char* text)
{
const int length = strlen(text);
for (int i = length-1; i > 0; --i)
for (int j = 0; j < i; ++j)
swap(text[j], text[j+1]);
return text;
}
하지만... 1번 문제를 풀면서 처음부터 이런 알고리즘으로 구현할 생각이었다면 최악의 선택이라고 감히 말할 수 있습니다. 말할 것도 없이, 이 알고리즘은 쓸데없이 많은 루프를 돌기 때문입니다.
자, 그럼 조금 다듬어 보기로 합시다.
/* 답: C안 */문자열 뒤집기라는 것은 sort에서와는 달리 비교를 통해서 자리바꿈 하는게 아니라 처음부터 옮겨야할 자리가 정해져 있습니다. 게다가 서로 대칭되는 위치로 자리 바꿈만 하면 되는 겁니다.
template<typename T>
inline void swap(T& __a, T& __b)
{
T __tmp = __a;
__a = __b;
__b = __tmp;
}
char* reverse(char* text)
{
const int maxCnt = strlen(text) / 2;
for (int i = 0; i < maxCnt; ++i)
swap(text[i], text[length - i - 1]);
return text;
}
예를 들어, "123456789"라는 문자열을 뒤집는다고 하면...
1 ↔ 9
2 ↔ 8
3 ↔ 7
4 ↔ 6
5는 제자리
결국 문자열 길이의 절반 만큼만 루프를 돌면 됩니다.
문제에서는 문자열 뒤집기를 다루고 있지만, 문자열도 결국은 배열. 좀 더 일반화시키면 어떤 종류의 배열도 순서를 뒤집어 볼 수 있습니다.
/* C안의 일반화: 배열 뒤집기 */
template<typename T>
inline void swap(T& __a, T& __b)
{
T __tmp = __a;
__a = __b;
__b = __tmp;
}
template<typename T>
T* reverseArr(T* src, size_t length)
{
const int maxCnt = length / 2;
for (int i = 0; i < maxCnt; ++i)
swap(src[i], src[length - i - 1]);
return src;
}




덧글
char* reverse(char* text) 또는
char* reverse(char* text, size_t length)일 경우
후출하는 쪽에서
char *p = "hello world";를 넘길 경우
세그먼트 폴트 에러가 발생합니다.
호출하는 쪽은 이유도 모르고 당하는거죠.
(이런 어이없는 실수때문에 날밤을 새게 되죠. 이 경우, 제3자가 봤을때 호출한쪽의 실수가 아니라 호출당한쪽, reverse가 잘못한게 되죠)
차라리
char *p = "hello world";
char q[256];
이 훨씬 낫죠.
q에 대해 꼭 메모리 할당이라는 개념을 적용할 필요는 없겠죠~
void reverse(char* in_out_text, size_t length)
같이 선언하겠습니다. reverse에 들어가는 문자열 인수가 상수가 되서는 안된다는 점을 강조하는 거죠.
그리고 char *p = "hello world"; 처럼 쓰는건 좋은 표현이 아니죠.
"hello world"가 본래 상수이기 때문에 const char *p = "hello world"; 같이 써줘야 컴파일 단계에서 상수성을 체크해 줄 수 있습니다.
(좀 더 정확한 선언은 const char *p const = "hello world";가 되겠습니다)
A안 같은 경우는 상수를 날려도 문제 없습니다.
그리고 char buff[256]; 같이 버퍼를 잡는 것도 안전하다고는 할 수 억습니다.
255글자를 넘는 문자열이 넘어오지 않는다는 보장이 없으니까요.
B,C안의 경우는 퍼퍼를 따로 잡지 않고 소스를 직접 수정하는 방식이니, 문자열 길이만 제대로 넘겨주는 그 문제는 회피할 수 있습니다.
/* 세그먼트 폴트를 일으키는 코드(#1) */
char *p = "hello world";
void reverse(p, strlen(p));
/* 세그먼트 폴트를 일으키지 않는 코드(#2) */
char p[] = "hello world";
void reverse(p, strlen(p));
문제는 #1처럼 쓰는 경우가 아주 일상적이고 당연하다는 점이죠.
** 그리고 오해가 있으신 것 같은데, size_t length가 필요한 이유는 받는 쪽의
크기를 알 수 없기 때문에 사용하는 것이지,
시즈하님의 경우처럼 받는쪽의 버퍼를 reverse함수쪽에서 알고 있는 경우는
size_t length가 필요가 없죠.
아래의 경우에만 length가 필요하죠.
void reverse(const char* text, char* out_buff, size_t out_buf_length)
length를 두는 이유는... 그 크기까지만 "안전하게" 변환을 하겠다는 의미입니다.
윈도의 ini파일 읽어들이는 함수들의 프로토타입을 참고하시면 될 겁니다.
(문자열이 더 길어도 버퍼에서 잘라버리도록 api가 설계되어 있죠)
문제의 정의 자체는 문자열 전체를 뒤집는 거였으니, 문자열 처리에만 국한한다면 말씀하신대로 문자열 길이는 알아서 계산해주면 되겠죠.
reverseArr()도 템플릿을 약간 수정하면 length를 뺄 수 있죠.
BufferedReader in new =BufferedReader(new InputStreamReader(Systen.in));
StringBuffer str = new StringBuffer();
str.append(in.readerLine());
System.out.println(str.reverse());
}
라고 하면 안될까요?....-_-;;