0

I have a const char pointer which I know for sure came from a string. For example:

std::string myString = "Hello World!";
const char* myCstring = myString.c_str();

In my case I know myCstring came from a string, but I no longer have access to that string (I received the const char* from a function call, and I cannot modify the function's argument list).

Given that I know myCstring points to contents of an existing string, is there any way to safely access the pointer of the parent string from which it originated? For example, could I do something like this?

std::string* hackyStringPointer = myCstring - 6; //Along with whatever pointer casting stuff may be needed

My concern is that perhaps the string's contents possibly cannot be guaranteed to be stored in contiguous memory on some or all platforms, etc.

The Mawg
  • 13
  • 5

4 Answers4

2

Given that I know myCstring points to contents of an existing string, is there any way to safely access the pointer of the parent string from which it originated?

No, there is no way to obtain a valid std::string* pointer from a const char* pointer to character data that belongs to a std::string.

I received the const char* from a function call, and I cannot modify the function's argument list

Your only option in this situation would be if you can pass a pointer to the std::string itself as the actual const char* pointer, but that will only work if whatever is calling your function does not interpret the const char* in any way (and certainly not as a null-terminated C string), eg:

void doSomething(void (*func)(const char*), const char *data)
{
    ...
    func(data);
    ...
}
void myFunc(const char *myCstring)
{
    std::string* hackyStringPointer = reinterpret_cast<std::string*>(myCstring);
    ...
}

...

std::string myString = "Hello World!";
doSomething(&myFunc, reinterpret_cast<char*>(&myString));
Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
1

You cannot convert a const char* that you get from std::string::c_str() to a std::string*. The reason you can't do this is because c_str() returns a pointer to the string data, not the string object itself.

If you are trying to get std::string so you can use it's member functions then what you can do is wrap myCstring in a std::string_view. This is a non-copying wrapper that lets you treat a c-string like it is a std::string. To do that you would need something like

std::string_view sv{myCstring, std::strlen(myCstring)};
// use sv here like it was a std::string
NathanOliver
  • 171,901
  • 28
  • 288
  • 402
  • I don't think this will work for me. I need a pointer to the original string so that when its contents are updated I stay in sync. Originally I just used C Strings to point to the original string's contents, but that breaks down when the original string grows in size and moves in memory, making my pointer stale. – The Mawg Oct 22 '19 at 21:00
  • @TheMawg The only way to solve that is to pass a reference/pointer to the string to your function. – NathanOliver Oct 22 '19 at 21:02
  • Or pass a `std::shared_ptr` to your function. – Eljay Oct 22 '19 at 21:23
0

Yes (it seems), although I agree that if I need to do this it's likely a sign that my code needs reworking in general. Nevertheless, the answer seems to be that the string pointer resides 4 words before the const char* which c_str() returns, and I did recover a string* from a const char* belonging to a string.

    #include <string>
    #include <iostream>
    std::string myString = "Hello World!";
    const char* myCstring = myString.c_str();
    unsigned int strPtrSize = sizeof(std::string*);
    unsigned int cStrPtrSize = sizeof(const char*);
    long strAddress = reinterpret_cast<std::size_t>(&myString);
    long cStrAddress = reinterpret_cast<std::size_t>(myCstring);
    long addressDifference = strAddress - cStrAddress;
    long estStrAddress = cStrAddress + addressDifference;
    std::string* hackyStringPointer = reinterpret_cast<std::string*>(estStrAddress);

    cout << "Size of String* " << strPtrSize << ", Size of const char*: " << cStrPtrSize << "\n";
    cout << "String Address: " << strAddress << ", C String Address: " << cStrAddress << "\n";
    cout << "Address Difference: " << addressDifference << "\n";
    cout << "Estimated String Address " << estStrAddress << "\n";
    cout << "Hacky String: " << *hackyStringPointer << "\n";

    //If any of these asserts trigger on any platform, I may need to re-evaluate my answer
    assert(addressDifference == -4);
    assert(strPtrSize == cStrPtrSize);
    assert(hackyStringPointer == &myString);

The output of this is as follows:

Size of String* 4, Size of const char*: 4

String Address: 15725656, C String Address: 15725660

Address Difference: -4

Estimated String Address: 15725656

Hacky String: Hello World!

It seems to work so far. If someone can show that the address difference between a string and its c_str() can change over time on the same platform, or if all members of a string are not guaranteed to reside in contiguous memory, I'll change my answer to "No."

Community
  • 1
  • 1
The Mawg
  • 13
  • 5
-1

This reference says

The pointer returned may be invalidated by further calls to other member functions that modify the object.

You say you got the char* from a function call, this means you do not know what happens to the string in the mean time, is that right? If you know that the original string is not changed or deleted (e.g. gets out of scope and thus is destructed) then you can still use the char*.

Your example code however has multiple problems. You want to do this:

std::string* hackyStringPointer = myCstring - 6;

but I think you meant

char* hackyStringPointer = myCstring;

One, you cannot cast the char* to a string* and second you do not want to go BEFORE the start of the char*. The char* points to the first character of the string, you can use it to access the characters up to the trailing 0 character. But you should not go before the first or after the trailing 0 character though, as you do not know what is in that memory or if it even exists.

gonutz
  • 5,087
  • 3
  • 22
  • 40
  • `char* hackyStringPointer = myCstring;` is illegal. – NathanOliver Oct 22 '19 at 20:38
  • Why? What compiler are you using? – gonutz Oct 22 '19 at 20:39
  • Because you are removing `const`. `myCstring` is a `const char *` and doing this assignment would strip the `const` away. This has been illegal since C++98. – NathanOliver Oct 22 '19 at 20:40
  • I don't think you understand what the OP was asking. They were trying to recover a pointer to the original string, not to copy the array into a new string. – Sneftel Oct 22 '19 at 20:43
  • @gonutz I want a pointer to the string which I know exists. I'm picturing std::string as some class/structure that contains a number of members stored in contiguous memory, including a pointer to the first valid character as one of those. What I'm looking for is a consistent relationship between the addresses of &myString and what is returned by myString.c_str(). Are those addresses the same? Always 6 words apart? Variable? – The Mawg Oct 22 '19 at 20:52
  • OK now I understand your problem. There are a number of ways to solve the real problem that you have I imagine. Pass the std::string instead of the char*, if that is not possible, copy the char* into a string of its own and use the copy, if you want the calling thing to be changed, pass a string* or string& directly, return a string from your function, etc. Without knowing the broader context I cannot propose a concrete solution. – gonutz Oct 23 '19 at 11:18