【问题标题】:Manipulating std::string操作 std::string
【发布时间】:2018-04-09 14:36:17
【问题描述】:

下面的代码没有给出任何故障/错误/警告(尽管我认为可能发生了一些非法内存访问)。奇怪的是,使用 2 种不同方法(strlen 和 std::string.size() 打印的字符串的大小不同。

strlen(l_str.c_str()-> 给出的大小为 1500,而, l_str.size()-> 将大小设为 0。

#include <string.h>
#include <string>
#include <stdio.h>
#include<iostream>

using namespace std;
void strRet(void* data)
{
        char ar[1500];
        memset(ar,0,1500);
        for(int i=0;i<1500;i++)
                ar[i]='a';
        memset(data,0,1500); // This might not be correct but it works fine
        memcpy(data,ar,1500);
}
int main()
{
        std::string l_str;
        cout<<endl<<"size before: "<<l_str.length();
        int var=10;
        strRet((void *)l_str.c_str());
        printf("Str after call: %s\n",l_str.c_str());
        cout<<endl<<"size after(using strlen): "<<strlen(l_str.c_str());
cout<<endl<<"Size after(using size function): "<<l_str.size();
        printf("var value after call: %d\n",var);
        return 0;
}

请建议,如果我正在做一些我不应该做的事情!

另外,我想知道当我执行memset(data,0,1500); 时哪些内存字节被设置为 0?我的意思是,如果假设我的字符串变量的起始地址是 100,那么 memset 命令是否将内存范围 [100,1600] 设置为 0?还是设置其他内存范围?

【问题讨论】:

  • 你为什么在 std::string 上使用 memset?使用 std::fill en.cppreference.com/w/cpp/algorithm/fill
  • 看起来你在玩未定义的行为很有趣......你不能在 data() 中使用超过字符串大小的内存
  • C++ std::string 类不是 C 字符串。一个是具有状态和行为的对象,包括非常常见一个存储长度的数据成员,另一个是char 的平面数组,约定应该有一个\0 在结束。
  • 这和void *ptr; memset(ptr, 0, 1500); 一样没有意义,它会写入一大堆尚未分配的内存。
  • 为什么投反对票?所以是错误的代码做坏事,但这不是问问题的重点吗?至少它是一个编译和重现问题的 sn-p。所以有时真的很粗糙。

标签: c++


【解决方案1】:
memset(data,0,1500); // This might not be correct but it works fine

这是不正确的,它不能“正常工作”。这是未定义的行为,您犯了一个常见的错误,即假设如果它可以编译,并且您的计算机没有立即着火,那么一切都很好。

真的没有。

我做了不该做的事!

是的,你有。您将指针指向std::string,这是一个具有自己的状态和行为的非平凡对象,向它询问它控制的一些内存的地址,并将其转换为void*。 p>

没有理由这样做,您应该非常很少在 C++ 代码中看到 void*,并且看到任何类型的 C 样式强制转换非常令人担忧。

在您了解自己在做什么以及为什么这是错误的之前,不要将void* 指针指向具有类似std::string 的状态和行为的对象。那么,当那一天到来时,你还是不会去做,因为你会知道得更多。


如果有帮助,我们可以详细了解第一个问题:

(void *)l_str.c_str()
  • c_str() 返回什么?指向l_str 拥有的一些内存的指针
  • 这个内存在哪里?不知道,那是l_str 的事。如果这个标准库实现使用了小字符串优化,它可能l_str 对象中。如果没有,它可能是动态分配的。
  • 在这个位置分配了多少内存?不知道,那是l_str 的事。我们可以肯定的是,至少有一个可合法寻址的字符 (l_str.c_str()[0] == '\0'),并且使用地址 l_str.c_str()+1 是合法的(但只能作为一个过去的指针,所以你可以'不要取消引用它)

所以,声明

strRet((void *)l_str.c_str());

传递strRet 指向包含一个或多个可寻址字符的位置的指针,其中第一个字符为零。这就是我们可以说的所有

现在让我们再看看有问题的那一行

memset(data,0,1500); // This might not be correct but it works fine

为什么我们希望这个位置有 1500 个字符?如果您将 strRet 记录为需要至少 1500 个已分配字符的缓冲区,那么当您知道 l_str 刚刚默认构造为 时,实际传递 l_str.c_str() 是否合理em>空字符串?不像您要求 l_str 为您分配该存储空间。

你可以通过给l_str一个分配你打算写入的内存的机会来开始这项工作,方法是调用

l_str.reserve(1500);

在致电strRet 之前。这仍然不会通知l_str 你用'a's 填充它,因为你是通过改变它背后的原始内存来做到的。

如果您希望它正常工作,您可以将整个 strRet 替换为

std::string l_str(1500, 'a');

或者,如果您想正确更改现有字符串,使用

void strRet(std::string& out) {
    // this just speeds it up, since we know the size in advance
    out.reserve(1500);
    // this is in case the string wasn't already empty
    out.clear();
    // and this actually does the work
    std::fill_n(std::back_inserter(out), 1500, 'a');
}

【讨论】:

  • 公平地说 - OP 不会将 string 转换为 void*;他们投了 const char*.... 也好不到哪里去,不过我并不反对
  • 还有为什么strlen和string.size()的输出有区别。那里到底发生了什么?
  • @VishalSharma string.size() 是对象的大小,在std::string 的情况下,很可能只是一个保存该值的成员变量。当字符串被修改时,这个值被更新。另一方面,strlen 读取内存,直到它到达一个空终止符,并将其作为大小。
  • 当我这样做时,memset(data,0,1500);我是否设置了 1500 个连续字节,从地址 &l_str(我的代码中的字符串变量)开始为 0?
  • @VishalSharma 这就是你试图做的。但是由于您访问的是未分配的内存,所以它是 UB,您不能再对实际结果有任何期望。它最终可能会做各种各样的事情。
猜你喜欢
  • 1970-01-01
  • 2018-03-13
  • 2010-09-20
  • 2019-06-04
  • 2012-01-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多