Why Does Calling Std::String.C_Str() on a Function That Returns a String Not Work

Why does calling c_str() on a function that returns a string not work?

SomeFunction().c_str() gives you a pointer to a temporary(the automatic variable str in the body of SomeFunction). Unlike with references, the lifetime of temporaries isn't extended in this case and you end up with charArray being a dangling pointer explaining the garbage value you see later on when you try to use charArray.

On the other hand, when you do

string str_copy = SomeFunction();

str_copy is a copy of the return value of SomeFunction(). Calling c_str() on it now gives you a pointer to valid data.

Why does calling std::string.c_str() on a function that returns a string not work?

getString() would return a copy of str (getString() returns by value);

It's right.

thus, the copy of str would stay "alive" in main() until main() returns.

No, the returned copy is a temporary std::string, which will be destroyed at the end of the statement in which it was created, i.e. before std::cout << cStr << std::endl;. Then cStr becomes dangled, dereference on it leads to UB, anything is possible.

You can copy the returned temporary to a named variable, or bind it to a const lvalue-reference or rvalue-reference (the lifetime of the temporary will be extended until the reference goes out of scope). Such as:

std::string s1 = getString();    // s1 will be copy initialized from the temporary
const char* cStr1 = s1.c_str();
std::cout << cStr1 << std::endl; // safe

const std::string& s2 = getString(); // lifetime of temporary will be extended when bound to a const lvalue-reference
const char* cStr2 = s2.c_str();
std::cout << cStr2 << std::endl; // safe

std::string&& s3 = getString(); // similar with above
const char* cStr3 = s3.c_str();
std::cout << cStr3 << std::endl; // safe

Or use the pointer before the temporary gets destroyed. e.g.

std::cout << getString().c_str() << std::endl;  // temporary gets destroyed after the full expression

Here is an explanation from [The.C++.Programming.Language.Special.Edition] 10.4.10 Temporary Objects [class.temp]]:

Unless bound to a reference or used to initialize a named object, a
temporary object is destroyed at the end of the full expression in
which it was created. A full expression is an expression that is
not a subexpression of some other expression.

The standard string class has a member function c_str() that
returns a C-style, zero-terminated array of characters (§3.5.1, §20.4.1). Also, the operator + is defined to mean string concatenation.
These are very useful facilities for strings . However, in combination they can cause obscure problems.
For example:

void f(string& s1, string& s2, string& s3)
{

const char* cs = (s1 + s2).c_str();
cout << cs ;
if (strlen(cs=(s2+s3).c_str())<8 && cs[0]==´a´) {
// cs used here
}

}

Probably, your first reaction is "but don’t do that," and I agree.
However, such code does get written, so it is worth knowing how it is
interpreted.

A temporary object of class string is created to hold s1 + s2 .
Next, a pointer to a C-style string is extracted from that object. Then
– at the end of the expression – the temporary object is deleted. Now,
where was the C-style string allocated? Probably as part of the
temporary object holding s1 + s2 , and that storage is not guaranteed
to exist after that temporary is destroyed. Consequently, cs points
to deallocated storage. The output operation cout << cs might work
as expected, but that would be sheer luck. A compiler can detect and
warn against many variants of this problem.

Is it safe to call c_str() directly on returned std::string from a function?

It is safe in the given example as the lifetime of the returned string ends at the end of the statement containing the function call.

In general I recommend reading https://en.cppreference.com/w/cpp/language/lifetime

C++ c_str of std::string returns empty

Presumably the string contains one or more nul characters, resulting in strcpy() not copying all of the bytes. You also have a memory leak from not freeing cstr before you return from the function.

You can avoid both problems by bypassing the unnecessary string array and calling PyBytes_FromStringAndSize:

return PyBytes_FromStringAndSize(string_repr.c_str(), string_repr.size());

Confusing behavior of C++ string returns and c_str() casts

The result of c_str() is only valid while the string you called it on still exists. In case 3, txtCpy still exists at the point you are writing cout << text. But in Case 1, the string was the return value of sample->getText which is temporary and stop existing at the end of that line .

This issue always will exist if you take pointers or references to other objects. A naked pointer or reference has its own lifetime which may differ from the lifetime of the targeted object. This is unlike Java where object references all participate in the lifetime of the object.

As such, you always need to think about object lifetimes when using these features, and it's commonly recommended to instead use higher level features or other code styles that do not permit lifetime management errors.

You could consider adding a member function to Sample which gets a const char * pointing at the original string (although this is a wee violation of encapsulation, and still has a similar class of problem if you hold onto the pointer and then modify the underlying string). Better would be to just avoid working with the naked pointers entirely.

c_str() returns empty string

The result of c_str() is only valid as long as the std::string object that produced that result is valid.

In your case, AsString() call produces a temporary std::string object which is then immediately destroyed. After that the result of that c_str() call no longer makes any sense. Trying to access the memory pointed by that pointer leads to undefined behavior.

Don't attempt to store the pointer returned by c_str(). If you need that string as a C-string for an extended period of time, allocate memory buffer for it yourself and copy the result of c_str() to that buffer.

Another (much better) idea would be not to rush the conversion to C-string. Return the result as std::string and call c_str() on it at the very last moment: when you reall really really need a C-string.

My helper function is returning an empty string

il2cppi_to_string() returns a temporary std::string, which will be destroyed at the end of the expression that calls il2cppi_to_string(). You are obtaining a const char* pointer to the data of that temporary std::string, which is what ReSharper is warning you about. Since the temporary std::string is destroyed before the return, that means getGhostName() is returning a dangling pointer to invalid memory.

To fix this, change getGhostName() to return a std::string instead of a const char*:

std::string getGhostName(GhostAI* ghostAI)
{
if (ghostAI) {
GhostInfo* ghostInfo = getGhostInfo(ghostAI);
const auto ghostName = ghostInfo->fields.u0A6Du0A67u0A74u0A71u0A71u0A66u0A65u0A68u0A74u0A6Au0A6F.u0A65u0A66u0A6Eu0A67u0A69u0A74u0A69u0A65u0A74u0A6Fu0A67;
return il2cppi_to_string(ghostName);
}
return "UNKNOWN";
}

C++ strange behavior with string's c_str() function

What's happening in the invalid case is that GetTheStr() is returning a temporary, then c_str() is returning a reference to its internal data, then the temporary goes out of scope and suddenly you have a dangling reference to storage that is no longer valid. When you assign the returned value of GetTheStr() to a named variable, the variable is still alive and the result of its c_str() is still pointing to valid data.

Lifetimes of temporaries is something that varies between implementations. It is my understanding that a temporary lives for the entire statement (std::cout << GetTheStr().c_str() << endl; is technically valid to my understanding because the lifteime is required to last for the entire statement, but poorly written because it is relying on a very subtle aspect of lifetime); however, whether a temporary lives beyond that statement to the end of the scope or not is, to my understanding, implementation-defined. I'm probably going to be pilloried for this last paragraph (especially by people with more precise knowledge on the topic), but the short story is that well written code should be more explicit when the lifetime of an object needs to be extended; if you need to retain a reference to the internal data of an object, then it's always best to guarantee that there is a named variable referring to the object to ensure that the containing object's lifetime exceeds the lifetime of the usage of its internal data.

Inspect the returning value of std::string.c_str()

First Question

The pointer returned by std::c_str() remain valid if the string is not modified. From cppreference.com:

The pointer obtained from c_str() may be invalidated by:

  • Passing a non-const reference to the string to any standard library function, or
  • Calling non-const member functions on the string, excluding operator[], at(), front(), back(), begin(), rbegin(), end() and rend().

In your posted code,

std::string strTest = "This is a test";
const char* s1 = strTest.c_str();
strTest = "This is b tests"; // This line makes the pointer invalid.

and then use of the pointer to access the string is undefined behavior.

std::cout << s1 << std::endl; // Undefined behavior.

After that, it's pointless to try to make sense of what the code does.

Second Question

The standard library provides an operator overload function between std::ostream and char const* so C-style strings can be printed in a sensible way. When you use:

std::cout << "Hello, World.";

you would want to see Hello, World. as output, not the value of the pointer that points to that string.

For reasons beyond the scope of this answer, that function overload is implemented as a non-member function.

template< class CharT, class Traits >
basic_ostream<CharT,Traits>& operator<<( basic_ostream<CharT,Traits>& os,
const CharT* s );

After all the template related tokens are substituted, that line translates to:

std::ostream& operator<<(std::ostream& os, const char* s );

You can see the list of non-member overload functions at cppreference.com.



Related Topics



Leave a reply



Submit