문자열 스트림, 문자열 및 char * 변환 혼동


141

내 질문은 삶의 문자열 stringstream.str().c_str()이 메모리 에서 어디로 반환 되는지, 왜 const char*?에 할당 할 수 없습니까?

이 코드 예제는 내가 할 수있는 것보다 더 잘 설명합니다.

#include <string>
#include <sstream>
#include <iostream>

using namespace std;

int main()
{
    stringstream ss("this is a string\n");

    string str(ss.str());

    const char* cstr1 = str.c_str();

    const char* cstr2 = ss.str().c_str();

    cout << cstr1   // Prints correctly
        << cstr2;   // ERROR, prints out garbage

    system("PAUSE");

    return 0;
}

stringstream.str().c_str()할당 될 수있는 가정은 const char*추적하는 데 시간이 걸리는 버그로 이어졌습니다.

보너스 포인트의 경우, 누군가가 cout명세서를 대체하는 이유를 설명 할 수 있습니까?

cout << cstr            // Prints correctly
    << ss.str().c_str() // Prints correctly
    << cstr2;           // Prints correctly (???)

문자열을 올바르게 인쇄합니까?

Visual Studio 2008에서 컴파일 중입니다.

답변:


201

stringstream.str()전체 표현식의 끝에서 파괴 된 임시 문자열 객체를 반환합니다. 해당 ( stringstream.str().c_str()) 에서 C 문자열에 대한 포인터를 얻으면 명령문이 끝나는 위치에서 삭제되는 문자열을 가리 킵니다. 그렇기 때문에 코드가 쓰레기를 출력합니다.

임시 문자열 객체를 다른 문자열 객체로 복사하고 C 문자열을 가져올 수 있습니다.

const std::string tmp = stringstream.str();
const char* cstr = tmp.c_str();

임시 문자열을 const변경하면 변경 사항이 다시 할당되어 cstr무효화 될 수 있기 때문에 임시 문자열을 만들었습니다 . 호출 결과를 전혀 저장하지 않고 전체 표현식이 끝날 때까지만 str()사용 하는 것이 더 안전합니다 cstr.

use_c_str( stringstream.str().c_str() );

물론 후자는 쉽지 않을 수 있으며 복사 비용이 너무 비쌀 수 있습니다. 대신 임시를 const참조 에 바인딩하는 것이 가능합니다 . 이는 수명을 참조 수명으로 연장합니다.

{
  const std::string& tmp = stringstream.str();   
  const char* cstr = tmp.c_str();
}

최고의 솔루션 인 IMO. 불행히도 잘 알려져 있지 않습니다.


13
첫 번째 예제에서와 같이 복사본을 수행해도 오버 헤드가 발생하지는 않습니다. str()RVO가 시작될 수있는 방식으로 구현되는 경우 (아마 가능성이 높음) 컴파일러는 결과를 직접 생성 할 수 있습니다 에 tmp일시적으로 빠짐; 최신 C ++ 컴파일러는 최적화가 활성화되면 그렇게합니다. 물론 Const-to-Const-Reference 솔루션은 복사를 보장하지 않으므로 바람직 할 수 있습니다.
Pavel Minaev

1
"물론, 참조에 바인딩 기준 솔루션은 복사를 보장하지 않습니다"<-그렇지 않습니다. C ++ 03에서는 복사 생성자에 액세스 할 수 있어야하며 구현에서 이니셜 라이저를 복사하고 참조를 복사본에 바인딩 할 수 있습니다.
Johannes Schaub-litb

1
첫 번째 예가 잘못되었습니다. c_str ()에 의해 리턴 된 값은 일시적입니다. 현재 진술이 끝난 후에는 신뢰할 수 없습니다. 따라서이 값을 사용하여 값을 함수에 전달할 수 있지만 c_str () 결과를 로컬 변수에 할당해서는 안됩니다.
Martin York

2
@litb : 기술적으로 정확합니다. 포인터는 문자열에서 다음 비 비용 메소드 호출까지 유효합니다. 문제는 사용법이 본질적으로 위험하다는 것입니다. 아마도 원래 개발자에게는 아니지만 (이 경우에는 그렇습니다) 특히 유지 보수 수정에 대해서는 이런 종류의 코드가 매우 취약합니다. 이 작업을 수행하려면 포인터 범위를 감싸서 사용법이 가능한 한 짧아야합니다 (표현식의 길이가 가장 낫습니다).
Martin York

1
@ sbi : 감사합니다. 더 명확합니다. 엄밀히 말하면, 위의 코드에서 'string str'var가 수정되지 않았기 때문에 str.c_str ()은 완벽하게 유효하지만 다른 경우에는 잠재적 위험에 감사합니다.
William Knight

13

당신이하고있는 일은 임시를 만드는 것입니다. 그 임시는 컴파일러가 결정한 범위에 존재하므로, 어디로 가고 있는지의 요구 사항을 충족시키기에 충분히 길다.

명령문 const char* cstr2 = ss.str().c_str();이 완료 되 자마자 컴파일러는 임시 문자열을 유지할 이유가 없으며, 소멸되므로 const char *메모리가 비어 있습니다.

당신의 진술 string str(ss.str()); 은 임시가 로컬 스택에 넣은 string변수 의 생성자에서 사용되며 str, 블록의 끝 또는 작성한 함수가 끝날 때까지 예상되는 한 유지됩니다. 따라서 const char *를 시도 할 때 within은 여전히 ​​좋은 메모리 cout입니다.


6

이 줄에서 :

const char* cstr2 = ss.str().c_str();

ss.str()a를한다 사본 이제 stringstream의 내용을. c_str()같은 줄 을 호출하면 합법적 인 데이터를 참조하게되지만 그 줄 후에는 문자열이 파괴 char*되어 소유하지 않은 메모리를 가리 킵니다.


5

ss.str ()에 의해 리턴 된 std :: string 오브젝트는 수명이 표현식으로 제한되는 임시 오브젝트입니다. 따라서 휴지통을 가져 오지 않고 임시 객체에 포인터를 할당 할 수 없습니다.

이제 한 가지 예외가 있습니다. 임시 객체를 얻기 위해 const 참조를 사용하는 경우 더 긴 수명 동안 사용하는 것이 합법적입니다. 예를 들어 다음을 수행해야합니다.

#include <string>
#include <sstream>
#include <iostream>

using namespace std;

int main()
{
    stringstream ss("this is a string\n");

    string str(ss.str());

    const char* cstr1 = str.c_str();

    const std::string& resultstr = ss.str();
    const char* cstr2 = resultstr.c_str();

    cout << cstr1       // Prints correctly
        << cstr2;       // No more error : cstr2 points to resultstr memory that is still alive as we used the const reference to keep it for a time.

    system("PAUSE");

    return 0;
}

그렇게하면 문자열을 더 오래 얻을 수 있습니다.

이제 컴파일러가 함수 호출을 통해 초기화를보고 해당 함수가 임시를 반환하면 복사를 수행하지 않고 할당 된 값을 임시로 만든다는 RVO라는 최적화가 있다는 것을 알아야합니다. . 그렇게하면 실제로 참조를 사용할 필요가 없습니다. 필요한 참조를 복사하지 않을 것입니다. 그렇게 :

 std::string resultstr = ss.str();
 const char* cstr2 = resultstr.c_str();

더 좋고 더 간단 할 것입니다.


5

ss.str()일시적는 초기화 후 파괴 cstr2완료됩니다. 따라서로 인쇄하면 임시 cout와 관련된 c-string std::string이 오랫동안 destory되었으므로 충돌하고 주장하면 운이 좋으며 쓰레기를 인쇄하거나 작동하는 것처럼 보일 때 운이 좋지 않습니다.

const char* cstr2 = ss.str().c_str();

cstr1그러나 지시 하는 C- 문자열 은 수행 할 때 여전히 존재하는 문자열과 연관 cout되므로 결과를 올바르게 인쇄합니다.

다음 코드에서는 첫 번째 코드 cstr가 정확합니다 ( cstr1실제 코드에 있다고 가정 합니까?). 두 번째는 임시 문자열 객체와 관련된 c- 문자열을 인쇄합니다 ss.str(). 표시되는 전체 표현 평가가 끝나면 오브젝트가 삭제됩니다. 전체 표현식은 전체 cout << ...표현식이므로 c- 문자열이 출력되는 동안 관련 문자열 객체는 여전히 존재합니다. 들어 cstr2- 성공할 순수 불량입니다. 아마도 내부적으로 새 임시에 대한 동일한 저장 위치를 ​​내부에서 선택하여 초기화에 사용 된 임시로 이미 선택했습니다 cstr2. 충돌이 발생할 수도 있습니다.

cout << cstr            // Prints correctly
    << ss.str().c_str() // Prints correctly
    << cstr2;           // Prints correctly (???)

의 반환 c_str()은 일반적으로 내부 문자열 버퍼를 가리 키지 만 요구 사항은 아닙니다. 내부 구현이 연속적이지 않은 경우 문자열은 버퍼를 구성 할 수 있습니다 (잘 가능하지만 다음 C ++ 표준에서는 문자열을 연속적으로 저장해야합니다).

GCC에서 문자열은 참조 계산 및 COW (Copy-On-Write)를 사용합니다. 따라서 다음 사항이 사실임을 알 수 있습니다 (적어도 GCC 버전에서는 가능합니다)

string a = "hello";
string b(a);
assert(a.c_str() == b.c_str());

두 문자열은 여기서 동일한 버퍼를 공유합니다. 그중 하나를 변경하면 버퍼가 복사되고 각각이 별도의 사본을 보유합니다. 그러나 다른 문자열 구현은 다르게 작동합니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.