Why don’t string pointers point to the first address?

I’m trying to understand how string pointers work(std::string*).
I created an array of character pointers to a string. The string pointer for some reason points to an address that is 4 addresses before the first character’s address, but it still prints the correct string when I deference it(I guess it’s programmed to print whatever is 4 addresses from it till the null0), but why? Why 4 addresses behind, why not just start from the first character?

int main() {
    std::string x = "hello";
    char* ptrs[6];
    for (int i = 0; i <= x.length(); i++) {
        ptrs[i] = &x[i];
        std::cout << (void*)ptrs[i] << "  " << *ptrs[i] << "n";
    }
    std::string* y = &x;
    std::cout << "n" << y << "n";
    std::cout << *y << "n";
    std::cout << (char*)y;
    return 0;
}

output:

0115F9A4  h
0115F9A5  e
0115F9A6  l
0115F9A7  l
0115F9A8  o
0115F9A9

0115F9A0
hello
ȉ2hello

Answer

Consider this very simplistic representation of the std::string container

#include <cstring>
#include <iostream>

struct StringContainer{
    explicit StringContainer(const char* str) {
        len_ = strlen(str);
        data_ = (char*)malloc(len_);
        for(int i = 0; i <len_; ++i){
            data_[i] = str[i];
        }
    }
    const char& operator[](size_t idx) {
        static char nullChar = '';
        return (idx < len_) ? data_[idx]: nullChar;
    }

    ~StringContainer() {
        if(data_){
            free(data_);
        }
    }

    size_t length() const {
        return len_;
    }
    private:
    char* data_;
    size_t len_;
};

int main() {
    StringContainer str("test");
    std::cout<<"Address of str: "<<std::hex<<(void*)&str<<'n';
    for(int i = 0; i < str.length(); ++i) {
        std::cout<<"Address of "<<str[i]<<": "<<std::hex<<(void*)&str[i]<<'n';
    }
}

Output:

Address of str: 0x7ffd85ed1108
Address of t: 0x1865eb0
Address of e: 0x1865eb1
Address of s: 0x1865eb2
Address of t: 0x1865eb3

The container (std::string or StringContainer in the example) is a separate entity from its data. Hence the different addresses. In this example (void*)&str gives the address of the container object which encapsulates the actual string data. &str[i] gives the address of that actual string data. Using std::string that looks as follows:

#include <string>
#include <iostream>

int main() {
    std::string str("test");
    std::cout<<"Address of str is "<<std::hex<<(void*)&str<<'n';
    for(char& a: str){
        std::cout<<"Address of "<<a<<" is "<<std::hex<<(void*)&a<<'n';
    }
    std::cout<<"Address of the data of str is "<<(void*)str.c_str()<<'n';
}

Output:

Address of str is 0x7fff0634b208
Address of t is 0x7fff0634b218
Address of e is 0x7fff0634b219
Address of s is 0x7fff0634b21a
Address of t is 0x7fff0634b21b
Address of the data of str is 0x7fff0634b218

As can be observed, the address of the data of str is 0x7fff0634b218 which matches with that of t i.e 0x7fff0634b218 but the address of the object str in itself is different 0x7fff0634b208.