【问题标题】:Why don't string pointers point to the first address?为什么字符串指针不指向第一个地址?
【发布时间】:2021-06-28 19:59:05
【问题描述】:

我试图了解字符串指针是如何工作的(std::string*)。
我创建了一个指向字符串的字符指针数组。出于某种原因,字符串指针指向一个地址,该地址比第一个字符的地址早 4 个地址,但是当我尊重它时它仍然打印正确的字符串(我猜它被编程为打印从它到 null0 的 4 个地址),但是为什么?为什么后面有4个地址,为什么不从第一个字符开始呢?

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;
}

输出:

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

0115F9A0
hello
ȉ2hello

【问题讨论】:

  • std::string* 指向string 对象的内部,而不是字符串本身。存储在哪里是私事。
  • std::string 是一个内部有多个成员的对象。它与 c-string 不同。
  • @PaulSanders Private-ish。使用成员函数.data()获取指向字符数组开头的指针。
  • 请注意,如果您的编译器的std::string 类实现了短字符串优化,那么字符串字符缓冲区的位置取决于字符串字符的长度。它可能是完全存储在std::string 对象本身内部的缓冲区,也可能是分配在内存中其他位置的缓冲区。所以string::data()返回的指针可以随着std::string内容的变化而随着时间的推移而变化。
  • 如果你想要字符数据:c_str()。请注意,在 C++ 中,不要只对(char*)y 之类的任意转换进行打击。那是无效的。使用static_cast密切关注编译器警告和错误

标签: c++ string pointers


【解决方案1】:

std::string 类的实现通常使用小对象优化。这意味着std::string 类型的对象不必为存储的字符串动态分配内存。也就是说,在std::string 类型的对象中,保留了一些小范围的内存,如果字符串不大于内存范围的大小,则可以在其中存储字符串。

考虑以下演示程序。

#include <iostream>
#include <string>

int main() 
{
    std::string s;

    for ( size_t i = 0; i < 20; i++ )
    {
        s += 'A';
        std::cout << i << ": " << &s << " -> " << ( void * )&s[0] << '\n';
    }

    return 0;
}

它的输出可能看起来像

sizeof( s ) = 32
0: 0x7ffe94e82100 -> 0x7ffe94e82110
1: 0x7ffe94e82100 -> 0x7ffe94e82110
2: 0x7ffe94e82100 -> 0x7ffe94e82110
3: 0x7ffe94e82100 -> 0x7ffe94e82110
4: 0x7ffe94e82100 -> 0x7ffe94e82110
5: 0x7ffe94e82100 -> 0x7ffe94e82110
6: 0x7ffe94e82100 -> 0x7ffe94e82110
7: 0x7ffe94e82100 -> 0x7ffe94e82110
8: 0x7ffe94e82100 -> 0x7ffe94e82110
9: 0x7ffe94e82100 -> 0x7ffe94e82110
10: 0x7ffe94e82100 -> 0x7ffe94e82110
11: 0x7ffe94e82100 -> 0x7ffe94e82110
12: 0x7ffe94e82100 -> 0x7ffe94e82110
13: 0x7ffe94e82100 -> 0x7ffe94e82110
14: 0x7ffe94e82100 -> 0x7ffe94e82110
15: 0x7ffe94e82100 -> 0x55ff0bc85e80
16: 0x7ffe94e82100 -> 0x55ff0bc85e80
17: 0x7ffe94e82100 -> 0x55ff0bc85e80
18: 0x7ffe94e82100 -> 0x55ff0bc85e80
19: 0x7ffe94e82100 -> 0x55ff0bc85e80

从程序的输出可以看出std::string 类型的对象的大小等于32。它不依赖于存储字符串的大小。

从输出也可以看出,存储字符串的地址总是等于0x7ffe94e82110,直到字符串的大小大于15。并且这个地址大于对象的起始位置的地址通过 0x10。也就是说,此地址在为std::string 类型的对象分配的内存范围内。

因此长度小于 16 的字符串存储在对象本身中。对于长度大于 15 的字符串,对象会动态分配内存。在这种情况下,std::string 类型的对象本身的大小不会改变。它只是有一个指针作为它的数据成员,指向分配的内存。

至于你问题中的这段代码sn-p

std::string* y = &x;
std::cout << "\n" << y << "\n";
std::cout << *y << "\n";

然后在这个语句中

std::cout << "\n" << y << "\n";

正在输出std::string本身类型的对象的地址。

在此声明中

std::cout << *y << "\n";

由于表达式*y 给出了std::string 类型的对象,并且对于std::string 类型的此类对象,定义了operator &lt;&lt;,其输出存储在对象字符串。

至于这个说法

std::cout << (char*)y;

那么一般来说它没有意义,因为它试图将std::string 类型的对象的数据成员输出为包含字符串的字符数组。它会导致未定义的行为。

【讨论】:

    【解决方案2】:

    考虑std::string 容器的这个非常简单的表示

    #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 = '\0';
            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
    

    容器(示例中为std::stringStringContainer)是与其数据分离的实体。因此不同的地址。在此示例中,(void*)&amp;str 给出了封装实际字符串数据的容器对象的地址。 &amp;str[i] 给出了 actual 字符串数据的地址。使用std::string,如下所示:

    #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
    

    可以看出,str数据的地址是0x7fff0634b218,与t的地址匹配,即0x7fff0634b218,但对象str的地址在本身不一样0x7fff0634b208

    【讨论】:

    • 那么 tldr,“容器”与“数据”是分开的,对吧?
    • @HaydenSoares 没错。其他答案很好地解释了您的特定示例中的地址在其实际数据的几个字节内的原因,但一般不能对容器实际存储其数据的方式做出任何假设应该使用[]at()front()等访问器方法。
    猜你喜欢
    • 2021-03-07
    • 1970-01-01
    • 1970-01-01
    • 2014-11-03
    • 1970-01-01
    • 2017-07-19
    • 2021-03-27
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多