C++ string 사용 중, 저의 짧은 지식으로는 이해가 안되어 가입하게 되었습니다...

leejk9592의 이미지

문제는 알고리즘 문제를 푸는 도중 발생하였습니다...

#include ...
 
void	Algorithm(int);
string	Reverse(string::iterator &it);
 
int main()
{
	...
	/*생략*/
}
 
void Algorithm(int caseNum)
{
	string str = string(arsQuadTree[caseNum]);
	string::iterator it = str.begin();
 
	// ①번 - 정상 결과 출력
	printf(Reverse(it).c_str());
 
	// ②번 - 비정상 결과 출력(쓰레기값이 출력)
	/* 
	const char *result = Reverse(it).c_str();
	printf(result);
	*/
}
 
string Reverse(string::iterator &it)
{
	...
	/*생략*/
}

제가 이해가 되지 않는 것은 ②번처럼 코드를 실행할 경우 쓰레기값이 출력이 된다는 것입니다.

const char *result = Reverse(it).c_str();
printf(result);

여러가지 이유를 혼자서 몇 시간동안 생각해보고 혹시 이건가 하는 생각이 들면 검증해보기도 하였습니다.
그 중에 제가 가장 가능성이 있다고 생각한 것이

const char *result = Reverse(it).c_str(); 코드가 실행 된후
result가 가리키는 문자열의 값이 초기화 되어 쓰레기 값이 출력되는 것이고

①번처럼 printf(Reverse(it).c_str()); 로 실행하면
Reverse(it).c_str()의 결과값이 가리키는 문자열의 값이 초기화 되기 전에
printf 함수가 출력을 마친다는 것입니다.

근데 이해가 안가는 것이 C++이 JAVA나 C# 처럼 가비지 콜렉터가 있는 것도 아니고
어떻게 아무 코드의 실행 없이 문자열 값이 초기화 되는 것이냐 입니다.

단순히 함수의 실행이 끝나고 스택이 정리 되면서 값이 사라지는 것 아니냐라고 생각해보았는데
그러면 ①번도 쓰레기 값이 출력되야 하는거 아닌가 라는 생각이 들면서 다시 혼란에 빠졌습니다.

그래서 더 나아가 함수 호출규약(__stdcall, __cdecl, ... 등) 까지 영향이 있는건지 생각해보았는데
int __cdecl printf(...) 이니 printf(호출한 함수)가 종료될 때 c_str(호출된 함수) 의 결과 값이 정리되는 건가...
라고도 생각해봤는데 단순히 함수의 실행이 끝나고 스택이 정리되는 것과 별차이 없는 것 같아 다시 혼란에 빠졌습니다.

MSDN에서 c_str 함수도 찾아 읽어 보았는데, 수능이후로 영어와 멀어져
"The pointer value is not valid after calling a non-const function"이 무슨 뜻인지 모르겠습니다.
함수 실행 후에는 결과값이 유효하지 않다 같은데 non-const function은 뭔지...

★ basic_string::c_str - Converts the contents of a string as a C-style, null-terminated string.

☆ 반환 값
A pointer to the C-style version of the invoking string. The pointer value is not valid after calling a non-const function, including the destructor, in the basic_string class on the object.

P.S①
=> 불쑥 가입하여 첫 질문이랍시고 이상한 걸 올려 죄송합니다. C/C++ 고수분들의 도움이 필요합니다... 정확한 답변을 얻게 된다면 정말 감사할 것 같습니다.

P.S②
=> 답변을 구하던 못 구하던 좋을 개발자 포럼을 찾게 된것 같습니다. 자주 들러 질문도 하고, 아는게 있으면 답변해주고 싶습니다.

...

P.S③
=> 자면서 생각해본건데 혹시 소멸자가 호출되는 것이 그냥 컴파일러가 다음줄에 ~basic_string() 추가를 해서 실행하는 것이라 이런 현상이 발생하는게 아닐까라는 생각이 들었는데 지금까지 생각했던 것들 중에 가장 설득력 있다고 생각되는데 어떻게 생각하시는지...
소멸자의 실행이 스택 수준의 영역이 아니라, 컴파일러가 단순히 다음 라인에 소멸자 실행 코드를 넣어 실행하는 방식인가요?

jick의 이미지

Reverse에서 돌려주는 string은 temporary object입니다. 따라서 Reverse를 부르는 문장이 끝날 때까지만 유지되고 그 문장이 끝나면 소멸자를 부르게 됩니다.

구글에서 C++ temporary object lifetime으로 찾아보세요.

따라서 1번의 경우 printf 문장이 끝날 때까지 temporary object가 유지되므로 정상적으로 출력되지만 2번의 경우 첫번째 문장이 끝날 때까지만 문자열 object가 유지되고 그 문장이 끝나는 순간 객체가 사라지므로 result는 더 이상 유효하지 않은 곳을 가리키게 됩니다.

* 그리고 *문자열 변수*를 printf의 첫번째 인자로 넣는 것은 매우 위험한 행동이므로 하지 마시고 그냥 문자열 하나를 출력하려면 puts, fputs, cout 등등을 쓰시는 것이 좋습니다. 문자열에 누가 "%d%d%d" 같은 걸 넣으면 어떻게 될지 생각해 보세요.

leejk9592의 이미지

소멸자 때문인 것이라는 뜻으로 이해한건데 맞는건지?
printf에 문자열 변수를 넣는 것이 위험하다는 것도 알아가네요, 감사합니다!

klyx의 이미지

'C++ temporary object lifetime' 찾아보셨어요?

leejk9592의 이미지

https://msdn.microsoft.com/en-us/library/a8kfxa78.aspx

이 내용을 읽고 있는데

Temporary objects have a lifetime that is defined by their point of creation and the point at which they are destroyed.
Any expression that creates more than one temporary object eventually destroys them in the reverse order in which they were created. The points at which destruction occurs are shown in the following table.

Destruction Points for Temporary Objects

* Reason Temporary Created - Destruction Point

1. Result of expression evaluation - All temporaries created as a result of expression evaluation are destroyed at the end of the expression statement (that is, at the semicolon), or at the end of the controlling expressions for for, if, while, do, and switch statements.

2. Initializing const references - If an initializer is not an l-value of the same type as the reference being initialized, a temporary of the underlying object type is created and initialized with the initialization expression. This temporary object is destroyed immediately after the reference object to which it is bound is destroyed.

에서 1번의 "(that is, at the semicolon) = 세미콜론이 나타나는 부분에서 임시 객체가 소멸된다" 이 부분이 핵심인건지..

그리고 at the end of the controlling expressions for for, if, while, do, and switch statements. 에서

제어문[for, if, while, do 그리고 switch]
{
...

}

에서 임시객체가 소멸되는게 ①에서 일어나는 건지 ②에서 일어난다는 건지 해석을 잘 못하겠는데, 어느 것이 맞는건가요?

jick의 이미지

설명이 좀 불충분해 보이긴 하는데 제가 보기에 controlling expression이라는 건 if (x == func()) 이런 류의 구문에서 "x == func()" 이 부분을 뜻한다고 보아야 말이 되는 것 같습니다.

그러니까 ①도 아니고 ②도 아니고

제어문: if (....)
<= 여깁니다.
{

}

그리고 처음 글에서 질문하신 내용은 어차피 이 controlling expression에 해당되지 않기 때문에 관계없습니다.

leejk9592의 이미지

①도 아니고 ②도 아니었군요 ㅋㅋ

나름 C++ 책을 5번도 넘게 읽어서 왠만한거는 안다고 생각했는데
아직 모르는게 많은 것 같습니다..

Temporary Object Lifetime 에 대해 더 알아보려 합니다.

왠지 앞으로 비슷한 일이 많이 생길 것 같아, 확실히 알아두고 넘어가려 합니다.
답변해 주신 분들 모두 감사합니다.

익명 사용자의 이미지

const char *result = strdup(Reverse(it).c_str());

같이 Reverse()에서 반환한 std::string 객체가 소멸하기 전에 duplicate하는 방법 뿐이겠죠.

leejk9592의 이미지

앞으로는 안전빵(?)으로 strdup를 사용하도록 하겠습니다 ㅋㅋ...

klyx의 이미지

쓰지마세요. 왜 안전하고 편리한 객체들을 두고 따로 해제해줘야 하는 strdup같은 걸 쓰시나요.
임시 객체의 생명을 임시로 연장하고 싶은거라면 const lvalue reference 또는 rvalue reference로 받으면 되고, 다른곳에 넘기고 싶은거면 객체를 복사하면 됩니다.
애당초 string::c_str()을 여기서 쓸 이유는 전혀 없습니다. 그냥 std::cout 에 넘겨버리면 출력됩니다.

C++에서 char*를 문자열로 써야하는 경우는 두 경우밖에 없습니다.
첫번째는 C 함수에 넘겨줘야 하는 경우고 두번째는 상수 리터럴인 경우입니다.
이경우는 printf라는 C함수를 쓰려고 하니까 char*가 튀어나오는데, 그건 그냥 std::cout을 쓰면 되는 일이고요.

leejk9592의 이미지

임시로 잠깐 쓰는 것에 strdup를 쓰는건 과한거 같다라는 의견이신 거지요?
메모리도 해제해야 하는 불편함과 해제를 안하는 실수할 가능성도 있고

string tmpStr = Reverse(it);
const char *pc_str = tmpStr.c_str();
printf(pc_str);

이게 const lvalue reference 로 받는 걸 말하는 거 맞나요?

rvalue reference로 받으면 된다는 것이 어떤 경우인지 떠오르지가 않는데
예제 하나만 간단히 만들어 설명해주실 수 있나요..?
애초에 제가 rvalue reference라는 걸 잘 모르는거 같습니다...

다른곳에 넘기고 싶은거면 객체를 복사하면 된다는 건 무슨 말인지 알겠습니다.

아 그리고 여기서 string::c_str()을 쓰려는 이유가 알고리즘 결과를 제출하려 하는데
iostream을 쓰지 말라고 해서 그랬습니다... string은 되면서 iostream은 왜 안되는지 저도 모르겠지만...

klyx의 이미지

적으신건 객체를 복사하는 경우입니다. 복사하면 완전한 객체를 하나 더 만들어주니까 그렇게 해도 전혀 문제되지 않습니다.
복사를 피하고 싶은 경우에는 const lvalue reference 로 받으면 됩니다.

#include <iostream>
#include <string>
 
std::string foo()
{
    return std::string("a string");
}
 
int main()
{
    std::string s1 = foo(); // 1. copy
    const std::string &s2 = foo(); // 2. const lvalue reference
    std::string &&s3 = foo(); // 3. rvalue reference
 
    std::cout << s1 << std::endl
              << s2 << std::endl
              << s3 << std::endl;
    return 0;
}

위에서 s1은 복사한 객체고, s2는 임시 객체를 const lvalue reference로 바인딩 시켜서 수명을 늘린경우이고 s3은 rvalue reference로 받아서 수명을 늘린 경우입니다.
임시객체에 const lvalue reference 또는 rvalue reference가 바인딩되면 자동으로 수명이 해당 스코프의 끝까지 연장됩니다.
1.의 foo()가 반환하는 임시 객체는 s1에 복사된 후 소멸하지만 2.와 3.의 foo()들이 반환하는 임시 객체는 해당 스코프(이경우는 main() 함수)가 끝날 때 소멸합니다.
참고로 3.은 C++11부터 가능합니다.

klyx의 이미지

그리고 strdup을 쓰지 말라는 것은 strdup이 과하기 때문이냐고 물어보셨는데, '과하다'라는게 성능상의 잇점을 말씀하시는 거라면 아닙니다.
오히려 퍼포먼스만 놓고 따지면 char*를 그대로 쓰는게 std::string을 쓰는것보다 당연히 좋습니다.
다만 그렇게 해서 성능 높이자고 해봤자 이 경우를 포함하여 대부분의 경우 프로그램 전체 성능에는 의미도 없기 때문에 그런 불편한 방법을 쓰는 것보다 무시가능한 오버해드를 감수하고 안전하게 클래스와 객체를 쓰는게 낫다는 뜻입니다.

leejk9592의 이미지

사실 reference에 거부감이 있어서 주로 포인터를 이용했었는데,
이번 질문을 계기로 reference에 대해 다시 공부하게 됐습니다.

오래된 책으로 공부하다 보니 rvalue reference(&&)라는 새 문법이
나온 것도 모르고 있었는데 알게 되었네요

앞으로 rvalue reference(&&)가 friend, explicit, mutable 같이
(제 기준으로)가끔 사용하는 생소한, 필요할 때 찾아보는 문법이 될지
사람들이 C/C++ 하면 가장 먼저 떠올릴 포인터처럼 당연히 알아둬야 할
핵심 문법이 될지 모르겠지만, 저는 이 질문을 계기로 평생 기억할거 같습니다

답변 감사합니다

익명 사용자의 이미지

> 사람들이 C/C++ 하면 가장 먼저 떠올릴 포인터처럼

C 라면 가장 먼저 포인터를 떠올리겠지만, C++이라면 가장 먼저 레퍼런스를 떠올리는 것이 정상입니다.
friend와 mutable은 피할 수 있다면 피하는 것이 좋겠지만,
의도적으로 암시적 형변환을 허용하는 경우가 아니라면 explicit은 반드시 써주는 것이 좋습니다.
별 이득이 없는, 또 일반적이지 않은 습관(취향)을 가지고 계신데, 버리시는 것이 좋을 듯 합니다.
C++문법에 대한 책보다, C++을 사용해서 "프로그래밍"을 가르치는 책을 찾아 공부해보시면 좋겠네요.

익명 사용자의 이미지

rvalue reference는 이미 핵심(?) 문법입니다.
"핵심"을 무슨 뜻으로 사용하신 것인지 모르겠는데 여하튼 rvalue reference는 중요한 기능입니다.
사정상 오래된 컴파일러를 사용할 수 밖에 없는 경우가 아니라면 현업에서 반드시 사용해야할 기능입니다.

leejk9592의 이미지

학교 과제를 위한 프로그램만 만들다 보니 실무를 위한 기술이 많이 부족하다는 걸
댓글들을 보며 느꼈습니다... 3, 4학년 때는 실무를 위한 기술들을 꼭 익히려 노력해보겠습니다 ㅠ

rvalue reference는 앞으로 많이 사용해 볼 것 같습니다. explicit도 쓰는 버릇을 들이겠습니다..

댓글 달기

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