[질문] return 0 과 exit(0) 의 차이점

jai의 이미지

Program received signal SIGSEGV, Segmentation fault.
0x4000726c in _dl_debug_bindings () from /lib/ld-linux.so.2
(gdb) bt
#0  0x4000726c in _dl_debug_bindings () from /lib/ld-linux.so.2
Cannot access memory at address 0x6d5f7250

1. main() 을 return 0 으로 종료하면, 위와 같은 에러가 납니다.
똑같은 데, return 0 대신 exit(0) 으로 종료하면, 정상적으로 마칩니다.
어찌해서 이런 당황스런 경우가 되는 건지, 부탁드리겠습니다.

2. int main() 이고, 인자는 적지 않았습니다.
main() 내에서는 지역 변수 선언을 않고,
system() 으로 실행파일을 불러서 쓰고 있습니다.

혹시 이런 것들이 영향을 미치는 것일까요?

return 도 내부적으로는 exit() 를 부른다고 적혀있었는데,
잘못한 것이 도대체 무엇인지 종잡을 수가 없어서,
더 막막하네요.

cdpark의 이미지

main의 stack이 침범당했다는데 100원 겁니다. 초기화되지 않은 포인터나 배열 범위를 벗어난 곳이 있는지 확인해보세요.

jai의 이미지

입력받는 문자열을 가리키는 포인터였는데,
초기화 않고 사용했던 것을,
문자배열로 바꾸니 세그먼트 폴트가 안나는군요.

sub routine 에서 선언한 포인터라서 제꼈는데..

system() 함수 호출한 이후에 세그먼트 폴트가 나서,
폴트 나기 직전에 사용한 system() 함수를 잘못된
것이라고 생각했어요.

왜 사용한 곳에서 폴트가 안나고,
프로그램 끝낼 때 났을까요?
포인터란 원래 그런 건가요??

정말 감사합니다. peace :)

peace :)

purewell의 이미지

그럼 백원 주셔야죠~?

ㅡ_-); 아닌가...

_____________________________
언제나 맑고픈 샘이가...
http://purewell.biz

codebank의 이미지

답이 나왔으니 추론하는건 어느정도 간단하다고 생각이 되어지네요.
결론은 포인터였고 원인은 바로 스텍이었을 거라고 추측합니다.
정확하게 집으면 스텍포인터겠죠.
여기서는 왜 exit()를 사용하면 괜찮은데 return을 사용하면 세그먼트
폴트가 일어나는지가 중요한 요점입니다.
exit()함수는 현단계에서 프로그램을 끝내는 함수입니다.
즉, 현재 스텍포인터가 무엇을가리키든 프로그램 카운트 포인터가 무엇을
가리키든 상관없이 무조건 프로그램을 종료시킵니다.
따라서 무엇이 잘못되어도 불평없이 프로그램을 종료시킵니다.
사실 귀찮게(하지만 꼼꼼히) 프로그램을 작성하고 싶다면 정상적인 종료에
exit ()함수를 쓰는것은 별로 권하고 싶지 않습니다. 이유는 exit ()는 말그
대로 무조건 나가버리는 함수입니다.
즉, 프로그램을 더이상 진행시키면 안될 위기에 처했을때나 쓸만한 함수라는
소리입니다.

자 이젠 주요한 원인이였던 return으로 돌아가서...
일단 하나의 함수가 호출이되면 호출된 함수는 누가 자신을 호출했는지에
대한 정보를 스텍에 저장해놓습니다.(스텍의 구조상 쌓아 놓는다고 표현을
합니다.) 그리고 함수가 종료되거나 return이 호출되면 이전에 스텍에 저장
해놓았던 호출한 함수의 번지값을 다시 끄집어내서 그 번지로 점프를하는게
정상적인 방식입니다.
문제는 변수선언에 있는데 선언된 변수가 전역변수(global)일 경우에는 heap
memory에 변수가 선언되는데(heap memory는 시스템에 따라서 크기가
달라질 수 있습니다. 즉, 전역변수를 선언할 때에도 한계가 있다는 소리입
니다.) 지역변수(local)일 경우에는 내부 data영역에 그 값을 쌓아 놓습니다.
이 data영역은 stack과 붙어있어서(자세한것은 시스템 프로그램 서적을
참조하시거나 어셈블리 부분을 참조하시면 됩니다. 자세한 C언어 책에도
설명이 있는 책이 있습니다.) 지정된 변수를 잘 못사용하면 문제가 발생할
수도 있습니다.
문제를 발생시킨 프로그램에서 포인터만 잡아놓고 그것을 초기화하지 않은
경우에는 그 변수에 엉뚱한값(보통 쓰레기값이라고 표현합니다.)이 들어가
있을 수가 있으므로 그 포인터변수에 값을 넣는것 자체가 상당히 위험 할
수가 있습니다. 요즘처럼 32Bit OS에는 시스템 영역에 침범을 할경우에는
적절하게 에러(세그먼트 폴트)를 발생시키지만 그것이 root유저로 잘못
실행될경우라면 자칫 시스템을 멈출 수있는 가능성도 배제할 수는 없습니다.

결론적으로 초기화하지 않은 변수에 값을 받아들여서 스텍값에 변형이
생겼고 그 변형된 값으로 return을 하려고 했지만 그곳이 시스템 영역이라
컴퓨터가 세그먼트 폴트를 발생시켰다고 보아집니다.

한가지 집고 넘어가야할 말이 있습니다.
return을 호출하면 exit()함수를 호출한다고 하셨는데 그것은 잘못아신
것입니다.
main()도 함수입니다. 이 main()을 부르는것은 일반 프로그래머의 눈에
보이지않는 begin(또는 start)로 라벨이 붙여져있는 c0.lib(DOS에서는
co.obj, LINUX에서는 c0... 잘모르지만 앞에 관용적으로 c0를 사용합니다.)
가 그 시작점으로 OS가 프로그램을 메모리를 할당하고 로드시켜서 실행
권한을 넘겨주면 실행되는 그 첫번째 위치입니다.
그리고 여기서 프로그램이 실행되는데 필요한 몇가지 준비를하고나서
사용자 시작함수인 main()을 호출하게 되고 main()에서 반환되면 다시
몇가지 끝내는 작업을 하고 종료를하게 됩니다.

return은 순차적인 끝마침을 유도하지만 exit()는 비정상적인 끝마침을
유도합니다. 그렇다고 파격적인 끝마침은 아닙니다만 될 수 있으면 순차
적인 끝마침을 유도하는 return을 사용할 것을 권하는 바입니다.
(exit()함수가 절대적으로 않좋다는 것은 아닙니다. main()마지막에
exit()를 넣어도 상관은 없습니다만 프로그램 문맥상으로는 exit()함수
보다는 return을 호출하는 것이 더 적당하다고 생각할 뿐입니다.)

------------------------------
좋은 하루 되세요.

익명 사용자의 이미지

codebank wrote:
답이 나왔으니 추론하는건 어느정도 간단하다고 생각이 되어지네요.
결론은 포인터였고 원인은 바로 스텍이었을 거라고 추측합니다.
정확하게 집으면 스텍포인터겠죠.
여기서는 왜 exit()를 사용하면 괜찮은데 return을 사용하면 세그먼트
폴트가 일어나는지가 중요한 요점입니다.
exit()함수는 현단계에서 프로그램을 끝내는 함수입니다.
즉, 현재 스텍포인터가 무엇을가리키든 프로그램 카운트 포인터가 무엇을
가리키든 상관없이 무조건 프로그램을 종료시킵니다.
따라서 무엇이 잘못되어도 불평없이 프로그램을 종료시킵니다.
사실 귀찮게(하지만 꼼꼼히) 프로그램을 작성하고 싶다면 정상적인 종료에
exit ()함수를 쓰는것은 별로 권하고 싶지 않습니다. 이유는 exit ()는 말그
대로 무조건 나가버리는 함수입니다.
즉, 프로그램을 더이상 진행시키면 안될 위기에 처했을때나 쓸만한 함수라는
소리입니다.

exit()와 _exit()를 혼동하고 계시군요. exit()는 전세계 수백만 C 프로그래머들의 애용 함수입니다. 8)

Quote:
자 이젠 주요한 원인이였던 return으로 돌아가서...
일단 하나의 함수가 호출이되면 호출된 함수는 누가 자신을 호출했는지에
대한 정보를 스텍에 저장해놓습니다.(스텍의 구조상 쌓아 놓는다고 표현을
합니다.) 그리고 함수가 종료되거나 return이 호출되면 이전에 스텍에 저장
해놓았던 호출한 함수의 번지값을 다시 끄집어내서 그 번지로 점프를하는게
정상적인 방식입니다.
문제는 변수선언에 있는데 선언된 변수가 전역변수(global)일 경우에는 heap
memory에 변수가 선언되는데(heap memory는 시스템에 따라서 크기가
달라질 수 있습니다. 즉, 전역변수를 선언할 때에도 한계가 있다는 소리입
니다.) 지역변수(local)일 경우에는 내부 data영역에 그 값을 쌓아 놓습니다.
이 data영역은 stack과 붙어있어서(자세한것은 시스템 프로그램 서적을
참조하시거나 어셈블리 부분을 참조하시면 됩니다. 자세한 C언어 책에도
설명이 있는 책이 있습니다.) 지정된 변수를 잘 못사용하면 문제가 발생할
수도 있습니다.

지역 변수는 스택 근처의 데이터 영역에 쌓이는 게 아니라 스택안에 쌓입니다.

Quote:
한가지 집고 넘어가야할 말이 있습니다.
return을 호출하면 exit()함수를 호출한다고 하셨는데 그것은 잘못아신
것입니다.
main()도 함수입니다. 이 main()을 부르는것은 일반 프로그래머의 눈에
보이지않는 begin(또는 start)로 라벨이 붙여져있는 c0.lib(DOS에서는
co.obj, LINUX에서는 c0... 잘모르지만 앞에 관용적으로 c0를 사용합니다.)
가 그 시작점으로 OS가 프로그램을 메모리를 할당하고 로드시켜서 실행
권한을 넘겨주면 실행되는 그 첫번째 위치입니다.
그리고 여기서 프로그램이 실행되는데 필요한 몇가지 준비를하고나서
사용자 시작함수인 main()을 호출하게 되고 main()에서 반환되면 다시
몇가지 끝내는 작업을 하고 종료를하게 됩니다.

NetBSD/FreeBSD/리눅스의 C 스타트업 루틴은 전부

__start(...)
{
    ...
    exit(main(argc, argv, envp));
}

처럼 되어 있습니다. 따라서 main()에서 exit(0)을 해서 종료하나 return 0으로 __start()로 돌아와서 종료하나 결과는 같습니다.

Quote:
return은 순차적인 끝마침을 유도하지만 exit()는 비정상적인 끝마침을
유도합니다. 그렇다고 파격적인 끝마침은 아닙니다만 될 수 있으면 순차
적인 끝마침을 유도하는 return을 사용할 것을 권하는 바입니다.
(exit()함수가 절대적으로 않좋다는 것은 아닙니다. main()마지막에
exit()를 넣어도 상관은 없습니다만 프로그램 문맥상으로는 exit()함수
보다는 return을 호출하는 것이 더 적당하다고 생각할 뿐입니다.)

exit()를 main() 함수내에서 쓰는 것은 매우 적법한 방법입니다.
skjk의 이미지

혹시 C++에서 작업하시고, main안에서 지역변수로 어떤 클래스의 인스턴스를 쓰시지는 않으신지요?

클래스를 사용하는 경우

main에서 return을 사용하시면 main의 지역변수로 클래스의 인스턴스가 있는 경우 destructor가 호출되지만

exit를 사용하시면 destructor가 실행되지 않고 바로 종료됩니다.
(전역변수로 선언되어있는 변수의 destructor는 그래도 실행되는 것 같습니다)

만약 지역변수로 쓰고있는 클래스의 destructor에 문제가 있다고 하는 경우

return을 하시면 destructor가 실행되므로 에러가 날 수 있지만 exit를 하시면 destructor가 실행되지 않으므로 에러가 발생하지 않을 것입니다.

한번 destructor를 확인해보세요.

그리고 위와같은 이유때문에 C++에서는 되도록 exit를 사용하지 않는 게 좋을 것 같습니다.

jai의 이미지

저는 gcc 이고, 파란이네요.
c++ 에서도 이런 경우가 발생하는 군요--;
주의하겠습니다.
답변해주신 분들께 감사드립니다.

계속 에러가 나면서도 보고싶은 것만, 볼 수 있는 것만 보았군요.
그런 원칙들, 지식들이 관련되어 있다고는 상상해보질 못했습니다.
많이 가르쳐 주셔서 감사합니다.

변수초기화와 관련해서 한가지 더 여쭤봐도 될런지,
해제에 대한 부분입니다.
free() 에서 chunk_free() ?? 에서 SIGSEGV 가 발생한데서,
어디가 문제일지 종잡을 수가 없습니다.

어떤 지점에서 무엇에 근거해서 찾아볼 수 있을지,
key 라도 부탁드립니다.

------------------

파일에서 한 행씩 끊어 읽어올려고,
getline(char **lineptr, size_t *n, FILE *stream) 을 적었습니다.

char *line = NULL;
int len = 0;
getline(&line, &len, fp);
....
if (line)
	free(line); 

getline() 함수는 line=NULL, len = 0 이면,
내부적으로 malloc() 을 통해 메모리를 할당한다고 합니다.

그래서 사용한 함수를 마칠 때 free()를 호출했는데,
이 곳에서 세그먼트 폴트가 납니다.

--------------------------

제가 생각한 지점은

1. 힙오염일까요?

포인터를 두번 해제하면 heap 오염이 일어난다고 해서,
if (line) 을 사용했습니다.

환경변수에
export MALLOC_CHECK_ = 2 를 설정했습니다.

이 변수가 있으면 힙오염이 발견되면 abort() 를 호출해서 종료시킨다고 합니다. 하지만 strace 를 해서 보면 여전히 SIGSEGV 까지 나옵니다.

2. 명시적으로 malloc() 을 한 경우에만, free() 를 사용할 수 있는 것일까요?

man 페이지에 보면 strdup() 라는 함수도 내부적으로 malloc()을 통해
메모리를 할당하고 그 주소에 인자를 카피하니, free() 하라고 적혀 있습니다.
그러나

 char *dup_line;
dup_line = (char*)strdup(line);
.....
if (dup_line)
	free(dup_line); 

이 경우에도 free(dup_line) 에서 SIGSEGV 가 발생합니다.

------------------------------

다른 이유에서 나는 것일까요?

peace :)

익명 사용자의 이미지

skjk wrote:
혹시 C++에서 작업하시고, main안에서 지역변수로 어떤 클래스의 인스턴스를 쓰시지는 않으신지요?

클래스를 사용하는 경우

main에서 return을 사용하시면 main의 지역변수로 클래스의 인스턴스가 있는 경우 destructor가 호출되지만

exit를 사용하시면 destructor가 실행되지 않고 바로 종료됩니다.
(전역변수로 선언되어있는 변수의 destructor는 그래도 실행되는 것 같습니다)

만약 지역변수로 쓰고있는 클래스의 destructor에 문제가 있다고 하는 경우

return을 하시면 destructor가 실행되므로 에러가 날 수 있지만 exit를 하시면 destructor가 실행되지 않으므로 에러가 발생하지 않을 것입니다.

한번 destructor를 확인해보세요.

그리고 위와같은 이유때문에 C++에서는 되도록 exit를 사용하지 않는 게 좋을 것 같습니다.


동적 오브젝트의 경우 디스트럭터와 exit()는 무관합니다. 아래 예를 컴파일해 보시기 바랍니다.
#include <stdio.h>

class test {
public:
	~test(void) {
		printf("destructor invoked\n");
	}
};

main()
{
	test *object = new test();

	return 0;
}

결국 exit()를 쓰고 안쓰고가 문제가 아니라 모든 변수는 사용하기 전 반드시 초기화를 해주고, 배열이나 포인터 연산시 바운드 체크를 확실히 해주는 것이 중요합니다.

sliver의 이미지

코드: 
#include <stdio.h> 

class test { 
public: 
   ~test(void) { 
      printf("destructor invoked\n"); 
   } 
}; 

main() 
{ 
   test object; 

   return 0; 
} 

skjk님은 위와 같이 스택에 object를 생성하는 경우를 말하신건 같은데요..
익명 사용자의 이미지

Quote:
skjk님은 위와 같이 스택에 object를 생성하는 경우를 말하신건 같은데요..

그래서 동적 오브젝트의 예를 든 것이지요. 8)

문제는 exit()에 있는 것이 아니고 exit()를 하기 전 오브젝트를 제대로 클린업하지 않은 데 있는 것입니다.

댓글 달기

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
이것은 자동으로 스팸을 올리는 것을 막기 위해서 제공됩니다.