c언어에서 파라미터를 바로 수정하면 메모리 오류가 발생하는 이유는?

nimbusob의 이미지

저는 자바를 주로 사용하는 유저입니다만

이번에 일이 있어서 c언어로 프로그램을 다시 짜야합니다. (c++ 아닙니다.) 컴파일러는 gcc 입니다. 실행환경은 Windows 98/Legacy DOS 이고요.

제약 조건이 좀 있는데 ANSI 함수만 사용할 것, malloc 류를 사용하지 말 것 등이 있네요.
(질문에 관련된 제약조건은 이 두가지인듯...)

지금 하는 일은 문자열 처리 함수들을 만드는 것인데 자바의 trim 류 함수를 c로 만드는 도중

자꾸 메모리 오류가 생겨서 이렇게 질문드립니다.

코드를 설명해 드리면

main() {
...
char* testStr = "to be Rtrimmed   ";
char* result = "";

result = rtrim(testStr);
....
}

char* rtrim(char* buf) {
/* 아래는 오류가 발생하는 코드 */
char* end = buf + strlen(buf) - 1; /* 가장 끝은 \0 을 가리키므로 1을 빼준다. */
while (*end == ' ') end--; /* 공백아닌 문자를 찾을때 까지 값을 감소 */

/* 아래의 라인에서 segmentation fault 발생 */
if(end > buf) *(end + 1) = '\0'; /* 만약 end 가 buf 과 같다면 문장의 시작을 의미하는 것이고, end 가 buf 보다 작다면 메모리의 엉뚱한 부분이 된다. */
return buf;

/* 새로 고쳐서 잘 돌아가는 코드 */
char temp[MAX_STRLENGTH];
char *end;

strcpy(temp, buf);
end = temp + strlen(temp) - 1;

while ((*end) == ' ') end--; // 공백이 아닐때 까지 한칸 앞으로 이동한다.
*(end + 1) = '\0';
buf = temp; // buf에 새로운 문자열 temp의 주소를 넣어준다.
return buf;
}

두 소스의 차이라면 rtrim 내에서 임시로 사용할 문자열인 temp 를 선언해 줬다는 것,

오류있는 소스는 넘겨받은 파라미터를 바로 수정하려고 시도,

정상 소스는 버퍼를 선언하고 그것을 수정한 다음 그 주소를 본래 파라미터(의 주소)에 반환입니다.

제가 궁금한건 두 가지인데,

1. 메모리 오류가 발생하는 이유는 윗 소스의 파라미터로 받은 tempStr 이 heap 이 아닌 data 영역에 정의되기 때문이다. scanf 등을 사용하면 heap 에 저장되게 할 수 있나?

2. 그렇다면 rtrim 에서 선언한 temp 는 임시로 선언된 변수이니 언젠가는 사라지는데 만약 프로그램이 커져서 멀티 스레드 환경이 된다면 rtrim 호출 후 데이터를 바로 사용하지 않으면 그 리턴값이 우연히 바뀔수도 있다. (설령 바로 사용한다 하더라도 그 사이에 다른 스레드가 temp 의 영역을 차지해 버릴수도 있을 것이다).

의 제 추리가 과연 맞을까? 입니다. 뭐, 2번같은 경우는 프로그램 규모를 키워가다 보면 언젠간 알아낼 수 있는 문제겠지만 문제가 나중에 발생하면 디버그에 시간이 몇배로 걸릴 것이니 그 전에 확실히 짚고 넘어가고 싶기도 합니다. 두번째 문제의 경우 malloc 을 이용하면 저런 걱정은 하지 않아도 되겠지만 dangling pointer 의 문제가 발생할수도 있겠죠. (뭐, 전역변수로 포인터 배열을 선언해서 거기서 따로 관리하게 한다던가 하는 트릭이 있을 수는 있겠지만)

검색 실력이 부족해서인지 이런 고민에 대한 정보들은 쉽게 찾기 힘들더군요.

c / c++ 고수님들의 많은 답변 부탁드리겠습니다. 감사합니다.

klyx의 이미지

에러가 난 이유는 간단합니다.
문자열 상수를 수정할려고 했기 때문입니다.

char* testStr = "to be Rtrimmed   ";

이렇게 선언된 testStr은 문자열 상수 "to be Rtrimmed   "을 가리키고 있는 포인터입니다.

첫번째 rtrim 함수의 경우는 저기서 공백이 아닌 문자를 찾아내에서 직접 그 값을 변경할려고 하는데요, 저 문자열은 사용자가 선언하여 메모리가 할당되어 있는 변수가 아니라 상수이기때문에 값을 변경할 수 없고, 에러가 납니다.

두번째 함수가 에러가 안나는 것도 strcpy로 상수를 복사하여 메모리가 할당된 변수를 조작하기 때문에 에러가 안나는 거죠.

char testStr[] = "to be Rtrimmed   ";
라고 선언하고 원래 에러나던 함수로 시도해보세요.
이경우는 자동으로 문자열 길이 만큼의 문자배열을 선언해주기 때문에, 임의로 그 값을 조작할 수 있습니다.

d3m3vilurr의 이미지

위의 분도 설명해 주셨지만,
char* 에 대입하여 선언한 문자열은 상수로 할당됩니다.

char *str1 = "read only string";
char str2[] = "write able string";

간혹 문자열 상수가 할당되는 메모리 영역이 ro가 아닌 rw 로 잡혀있다면
대입연산시 에러를 내지 않고 정상적으로 넘어갈 수도 있습니다.

chadr의 이미지

buf = temp; // buf에 새로운 문자열 temp의 주소를 넣어준다.
return buf;

이 부분도 문제가 됩니다.

지역변수의 포인터를 리턴 하는건 안됩니다.
지역변수는 스택에 저장되어있는 것인에 함수가 리턴되는 순간
그 지역변수의 영역은 명시적으로 "사용되지않음"으로 되기때문에
어떤 일이 발생할지 알수 없습니다.

함수를 실행하기 전에 함수에 결과가 저장될 메모리를 두번째인자로 넘겨주시면 충분히
해결이 될것 같습니다.
-------------------------------------------------------------------------------
It's better to appear stupid and ask question than to be silent and remain stupid.

-------------------------------------------------------------------------------
It's better to appear stupid and ask question than to be silent and remain stupid.

IsExist의 이미지

java에서는 문자열이 객체로 생성되어 사용되기 때문에 문제가
없지만 c에서는 소스상의 " " 문자열들은 (컴파일러 디폴트 옵션에 의존)은
TEXT 영역으로 잡습니다. TEXT는 execution intruction이 존재하는 영역으로
read only 입니다.
그에 비해 char *a[] = ".." 은 stack에 a의 공간을 확보하고
".."가 복사가 됩니다. 스택에 존재하는 영역이라 수정이 가능합니다.

다른 부분은 위에 분들이 잘 설명하셨네요.
---------
간디가 말한 우리를 파괴시키는 7가지 요소

첫째, 노동 없는 부(富)/둘째, 양심 없는 쾌락
셋째, 인격 없는 지! 식/넷째, 윤리 없는 비지니스

이익추구를 위해서라면..

다섯째, 인성(人性)없는 과학
여섯째, 희생 없는 종교/일곱째, 신념 없는 정치

---------
간디가 말한 우리를 파괴시키는 7가지 요소

첫째, 노동 없는 부(富)/둘째, 양심 없는 쾌락
셋째, 인격 없는 지! 식/넷째, 윤리 없는 비지니스

이익추구를 위해서라면..

다섯째, 인성(人性)없는 과학
여섯째, 희생 없는 종교/일곱째, 신념 없는 정치

klyx의 이미지

아마 오타로 잘못 적으신듯합니다.

char *a[] = ".."
가 아니라
char a[] = ".."
이죠...?

IsExist의 이미지

네 오타네요.

char a[] = ".."; 

이게 맞습니다. xylosper 님 예리하십니다.
---------
간디가 말한 우리를 파괴시키는 7가지 요소

첫째, 노동 없는 부(富)/둘째, 양심 없는 쾌락
셋째, 인격 없는 지! 식/넷째, 윤리 없는 비지니스

이익추구를 위해서라면..

다섯째, 인성(人性)없는 과학
여섯째, 희생 없는 종교/일곱째, 신념 없는 정치

---------
간디가 말한 우리를 파괴시키는 7가지 요소

첫째, 노동 없는 부(富)/둘째, 양심 없는 쾌락
셋째, 인격 없는 지! 식/넷째, 윤리 없는 비지니스

이익추구를 위해서라면..

다섯째, 인성(人性)없는 과학
여섯째, 희생 없는 종교/일곱째, 신념 없는 정치

nimbusob의 이미지

제 추측이 맞긴 맞는거군요. 친절한 답변들 감사드립니다.

사실 rtrim 은 구현해 놓고도 찝찝했었는데 IsExist 님이 제시해 주신 방법으로 해결해야겠군요.

댓글 달기

Filtered HTML

  • 텍스트에 BBCode 태그를 사용할 수 있습니다. URL은 자동으로 링크 됩니다.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>
  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.

BBCode

  • 텍스트에 BBCode 태그를 사용할 수 있습니다. URL은 자동으로 링크 됩니다.
  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param>
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.

Textile

  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • You can use Textile markup to format text.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>

Markdown

  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • Quick Tips:
    • Two or more spaces at a line's end = Line break
    • Double returns = Paragraph
    • *Single asterisks* or _single underscores_ = Emphasis
    • **Double** or __double__ = Strong
    • This is [a link](http://the.link.example.com "The optional title text")
    For complete details on the Markdown syntax, see the Markdown documentation and Markdown Extra documentation for tables, footnotes, and more.
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>

Plain text

  • HTML 태그를 사용할 수 없습니다.
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.
  • 줄과 단락은 자동으로 분리됩니다.
댓글 첨부 파일
이 댓글에 이미지나 파일을 업로드 합니다.
파일 크기는 8 MB보다 작아야 합니다.
허용할 파일 형식: txt pdf doc xls gif jpg jpeg mp3 png rar zip.
CAPTCHA
이것은 자동으로 스팸을 올리는 것을 막기 위해서 제공됩니다.