What you mean a pointer to a temporary? Iâm in debug mode and as soon as that line is executed *p2 is wrong, while for *p1 its ok. I tried with different sizes of string, and it breaks exactly at >128. I tried your code and works, but Iâm not sure of the difference and why TCHAR_TO_ANSI doesnât work in my example.
We are using char* everywhere in the code, and I donât think the answer can be char* is not reliable. right? Otherwise it means we cannot use TCHAR_TO_ANSI at all as it will break in case of large strings.
Btw I tested now this line, based on your example and it gives me also invalid pointer:
The macro basically wraps up the StaticCast code above, but it does so by creating a temporary. The macro is useful as a handy conversion when passing to functions, but is dangerous to use on the stack:
void FuncTakingCharPtr(const char* String);
FString Str = TEXT("Hello");
// This is ok
FuncTakingCharPtr(TCHAR_TO_ANSI(*Str)); // A
// This is bad
const char* Ptr = TCHAR_TO_ANSI(*Str); // B
FuncTakingCharPtr(Ptr); // C
Itâs bad because the macro hides the lifetime of the converted string. On line B, a string object is created to hold the converted string. You then get a pointer to that string, but on the same line B, the string object gets destroyed. Now you are holding a pointer to freed memory, which you pass on line C, and everything explodes - maybe. (see below)
On line A, the same thing happens, except that the string object isnât destroyed until after the function has run. So itâs definitely fine.
The 128 byte thing is an arbitrary result. At this point you have undefined behaviour and anything. It isnât really ânot brokenâ when the string < 128 bytes, itâs just that you have been lucky to not have any visibly negative effects. Such is the nature of undefined behaviour.
Usage within standard C++ functions:
strcpy(dest, TCHAR_TO_ANSI(*sourceFString));
If I need a char pointer to write into the memory of an FString, I was doing something like:
char* p = TCHAR_TO_ANSI(*input);
*p++ = 'x â
I understand this is not good, as the first line will invalidate my data, but what is the right way to do it keeping a pointer? (I ask this because we have some legacy functions working on char * and now we need to pass in an FString.
Same problem here, got some issues converting TCHAR to ANSI. The string above 128 characters are returning garbage. ( also tested using StringCast, same issue).
(version 4.14.3)
This is bad because the temporary created inside TCHAR_TO_ANSI is a local variable in the function, will be destroyed as the function returns, so the memory it owns will be freed and youâll now have a pointer to freed memory.
This is good, because the temporary created inside TCHAR_TO_ANSI is being used as an argument to strcpy, so it will not be destroyed until strcpy returns. As strcpy is the only user of that pointer, and strcpy has just returned, the pointer cannot be used unsafely.
This is the same situation as your first post, i.e. bad. The memory that p is pointing to is both allocated and freed on the same line that p is assigned. Thus p points to destroyed memory.
In FuncA, the following happens in this order, on these lines:
A1: StringCast returns an object which holds/owns the converted string. As it is not a named variable, this object is a temporary (letâs call it T).
A1: Get() is called on T, which returns a pointer (also a temporary - call it P) to the converted string memory held by T.
A1: Use() is called with P.
A1: P is destroyed.
A1: T is destroyed, freeing its converted string memory.
Note that all of this happens on line A1 because of the lifetime of temporaries.
This is important! Temporaries are destroyed at the end of the statement in which they are used, whereas named variables are not destroyed until the end of the scope in which the name resides. T is destroyed after P because temporaries are destroyed in the reverse order they were constructed.
So in FuncB:
B1: StringCast returns an object which owns the converted string. This object is also a temporary (letâs call it T again).
B1: Get() is called on T, which returns a pointer to the converted string memory held by T which we call Ptr. Ptr is not a temporary, because it has a name.
B1: End of statement, so temporary T gets destroyed, freeing its converted string memory. Now Ptr is invalid!
B2: Use() is called with Ptr, but Ptr is invalid, so weâre now into undefined behaviour!
B3: Ptr is destroyed here, but itâs irrelevant as the program is in a broken state by this point. If weâre lucky, it has already crashed and isnât actively corrupting memory.
In FuncC:
C1: StringCast returns an object which holds the converted string which we call Conv. Conv is not a temporary, because itâs named. This also means that itâs not destroyed at the end of the statement.
C2: Get() is called on Conv, which returns a pointer to the converted string memory held by Conv which we call Ptr.
C3: Use() is called with Ptr.
C4: Ptr is destroyed.
C4: Conv is destroyed, freeing the converted string memory.
FuncA and FuncC are safe because the pointer to the converted string is always destroyed before the object which owns the memory for that string. FuncB is not safe because the converted string object is temporary and has a shorter lifetime than the pointer which is pointing to its internal string buffer.
And because using TCHAR_TO_ANSI is equivalent to whatâs happening in FuncA and FuncB, you can hopefully see when this is going to go wrong. The moral of the story is basically âOnly use TCHAR_TO_ANSI as a function argumentâ.
FuncC isnât possible with TCHAR_TO_ANSI, because here the StringCast() and the Get() have been separated. But this is why using StringCast is preferable to TCHAR_TO_ANSI, because you are capable of getting back to safety again.
So basically, the pointer returned from Get() is valid only while the object on you called Get() still exists.
As to what is the âright wayâ for your legacy API, I canât say, because I donât know if your API tried to take ownership of those strings or not. Assuming that your functions only read from strings passed to them (possibly to make their own copy) but donât store those pointers, then what Iâve said here should apply.
Finally, I suggest reading up on C++ temporaries, object lifetimes, ownership and RAII in order to get a handle on this stuff. It is dangerous to use C++ pointers in the same way as Java/C# references.
Thanks Steve.
This is indeed the exact code Iâve used first. Work fine until 128 chars. Above 128 chars, the filenamewithpath pointer is pointing to a bad memory aera.