【问题标题】:valgrind memory access error when accessing result of string::c_str()访问 string::c_str() 的结果时 valgrind 内存访问错误
【发布时间】:2019-10-23 08:16:55
【问题描述】:

因此,当我遇到最奇怪的内存问题时,我正在编写一些网络代码,但我无法正确解决这里可能发生的情况。我想知道c_str() 中是否有某种我没有正确观察到的暗示。

所以这是包含错误的代码。 (还有一个释放错误,但我将此功能作为一个宠物项目)。

#include <netdb.h>       // for AF_UNSPEC, AF_INET, AF_INET6
#include <stdint.h>      // for uint16_t, etc.
#include <sys/types.h>   // for AF_UNSPEC, AF_INET, AF_INET6
#include <sys/socket.h>  // for AF_UNSPEC, AF_INET, AF_INET6
#include <string>        // for std::strin
#include <stdio.h> 
#include <string.h>
int main() {
    uint16_t port = 2098;


    struct addrinfo hints;
    memset(&hints, 0, sizeof(struct addrinfo));
    hints.ai_family = AF_INET6;       // IPv6 (also handles IPv4 clients)
    hints.ai_socktype = SOCK_STREAM;  // stream
    hints.ai_flags = AI_PASSIVE;      // use wildcard "INADDR_ANY"
    hints.ai_protocol = IPPROTO_TCP;  // tcp protocol
    hints.ai_canonname = nullptr;
    hints.ai_addr = nullptr;
    hints.ai_next = nullptr;

    const char* port_num = (std::to_string(port)).c_str();

    struct addrinfo *result;
    int res = getaddrinfo(nullptr, port_num, &hints, &result);

    printf("HI\n");
}

如果你对生成的二进制文件进行 valgrind,你会得到:

==45919== Invalid read of size 1
==45919==    at 0x573BA5C: getaddrinfo (in /usr/lib64/libc-2.17.so)
==45919==    by 0x400C95: main (in /homes/iws/kieruc/Coding/a.out)
==45919==  Address 0x5a22058 is 24 bytes inside a block of size 29 free'd
==45919==    at 0x4C2B16D: operator delete(void*) (vg_replace_malloc.c:576)
==45919==    by 0x4EF3B62: std::basic_string<char, std::char_traits<char>, std::allocator<char> >::~basic_string() (in /usr/lib64/libstdc++.so.6.0.19)
==45919==    by 0x400C7C: main (in /homes/iws/kieruc/Coding/a.out)
==45919==  Block was alloc'd at
==45919==    at 0x4C2A1E3: operator new(unsigned long) (vg_replace_malloc.c:334)
==45919==    by 0x4EF3A18: std::string::_Rep::_S_create(unsigned long, unsigned long, std::allocator<char> const&) (in /usr/lib64/libstdc++.so.6.0.19)
==45919==    by 0x400FFE: char* std::string::_S_construct<char*>(char*, char*, std::allocator<char> const&, std::forward_iterator_tag) (in /homes/iws/kieruc/Coding/a.out)
==45919==    by 0x400F14: char* std::string::_S_construct_aux<char*>(char*, char*, std::allocator<char> const&, std::__false_type) (in /homes/iws/kieruc/Coding/a.out)
==45919==    by 0x400EDD: char* std::string::_S_construct<char*>(char*, char*, std::allocator<char> const&) (in /homes/iws/kieruc/Coding/a.out)
==45919==    by 0x400E8F: std::basic_string<char, std::char_traits<char>, std::allocator<char> >::basic_string<char*>(char*, char*, std::allocator<char> const&) (in /homes/iws/kieruc/Coding/a.out)
==45919==    by 0x400E20: std::string __gnu_cxx::__to_xstring<std::string, char>(int (*)(char*, unsigned long, char const*, __va_list_tag*), unsigned long, char const*, ...) (in /homes/iws/kieruc/Coding/a.out)
==45919==    by 0x400CDE: std::to_string(int) (in /homes/iws/kieruc/Coding/a.out)
==45919==    by 0x400C60: main (in /homes/iws/kieruc/Coding/a.out)
==45919== 
==45919== Invalid read of size 1
==45919==    at 0x573BACA: getaddrinfo (in /usr/lib64/libc-2.17.so)
==45919==    by 0x400C95: main (in /homes/iws/kieruc/Coding/a.out)
==45919==  Address 0x5a22058 is 24 bytes inside a block of size 29 free'd
==45919==    at 0x4C2B16D: operator delete(void*) (vg_replace_malloc.c:576)
==45919==    by 0x4EF3B62: std::basic_string<char, std::char_traits<char>, std::allocator<char> >::~basic_string() (in /usr/lib64/libstdc++.so.6.0.19)
==45919==    by 0x400C7C: main (in /homes/iws/kieruc/Coding/a.out)
==45919==  Block was alloc'd at
==45919==    at 0x4C2A1E3: operator new(unsigned long) (vg_replace_malloc.c:334)
==45919==    by 0x4EF3A18: std::string::_Rep::_S_create(unsigned long, unsigned long, std::allocator<char> const&) (in /usr/lib64/libstdc++.so.6.0.19)
==45919==    by 0x400FFE: char* std::string::_S_construct<char*>(char*, char*, std::allocator<char> const&, std::forward_iterator_tag) (in /homes/iws/kieruc/Coding/a.out)
==45919==    by 0x400F14: char* std::string::_S_construct_aux<char*>(char*, char*, std::allocator<char> const&, std::__false_type) (in /homes/iws/kieruc/Coding/a.out)
==45919==    by 0x400EDD: char* std::string::_S_construct<char*>(char*, char*, std::allocator<char> const&) (in /homes/iws/kieruc/Coding/a.out)
==45919==    by 0x400E8F: std::basic_string<char, std::char_traits<char>, std::allocator<char> >::basic_string<char*>(char*, char*, std::allocator<char> const&) (in /homes/iws/kieruc/Coding/a.out)
==45919==    by 0x400E20: std::string __gnu_cxx::__to_xstring<std::string, char>(int (*)(char*, unsigned long, char const*, __va_list_tag*), unsigned long, char const*, ...) (in /homes/iws/kieruc/Coding/a.out)
==45919==    by 0x400CDE: std::to_string(int) (in /homes/iws/kieruc/Coding/a.out)
==45919==    by 0x400C60: main (in /homes/iws/kieruc/Coding/a.out)

但是如果我把代码改成这个

std::string portstr = std::to_string(port);

struct addrinfo *result;
int res = getaddrinfo(nullptr, portstr.c_str(), &hints, &result);

那么就没有内存错误了。而且我不太明白为什么。

要注意的另一件事是,如果我尝试使用char* 编译带有const char* 变量的版本,则会收到警告说变量应该是常量。

这是怎么回事?

【问题讨论】:

  • 我 +1 了你,因为你提供了一个(几乎)最小的、完整的、可验证的例子,并且还自己尝试了一些事情。如果只有所有新手用户都这样做!

标签: c++ pointers valgrind stdstring


【解决方案1】:

使用const char* port_num = (std::to_string(port)).c_str();,您将创建一个std::string 类型的临时对象,该对象将与使用它的表达式一样长。因此,.c_str() 将指向在您声明后立即释放的内存。

把它分成两行:

auto portStr = std::to_string(port);
const char* port_num = portStr.c_str();

因此,portStr-object 将一直存在到函数结束,并且您可以使用 .c_str-call 的结果直到函数结束(除非您在两者之间更改 protStr) .

【讨论】:

    【解决方案2】:

    c_str 返回的缓冲区仅在关联的std::string 对象存在时才有效。还有这里

    const char* port_num = (std::to_string(port)).c_str();
    

    一个临时字符串对象被创建,它的缓冲区地址被占用,然后它在完整表达式的结尾处死掉。使用悬空指针会出现内存错误。

    如果您希望使用临时的std::string,则必须在使用缓冲区的完整表达式期间创建它:

    int res = getaddrinfo(nullptr, std::to_string(port).c_str(), &hints, &result)
    

    【讨论】:

    • 我更喜欢 Stephan Lechner 的建议,因为我的编码理念(以及我教给人们的)是:除非有必要,否则每行只做一件事。它不会改变编译的输出,而且更容易阅读恕我直言。有人可能会说它更具程序性且功能更少,但那是not necessarily true
    • @einpoklum - 另一种哲学是不要命名不重要和短暂的事物。而“每行一件事”的方法在任何 C++ 程序中都失败得很快。取hintsresult 的地址也不内联。大多数人,包括我自己,在这两者之间做一些事情是有充分理由的。无论如何,这既不是这里也不是那里。我试图澄清的重点是生命周期问题,仅此而已。
    • 我确实说过“除非另有必要”——而且这通常是必要的,尤其是在 C++ 中……你确实有关于命名瞬态的观点,但是:1. 为即将成为的事物命名并不坏使用参数名称的函数参数,了解如何不使用命名参数分配调用 C/C++ 函数。 2. 瞬态在命名后立即使用,因此您很容易忘记名称 3. 过于冗长而不是晦涩难懂。 4. 随着时间的推移,我发现比我预期的更多的东西可以被赋予合理的名字。
    猜你喜欢
    • 1970-01-01
    • 2016-06-30
    • 2023-03-29
    • 2013-11-30
    • 2011-12-02
    • 2023-01-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多