Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
341 views
in Technique[技术] by (71.8m points)

c++ - Manipulating std::string

The below code does not give any fault/error/warning(although I think there might be some illegal memory access happening). Strangely, the size of the string being printed using 2 different methods(strlen and std::string.size() is coming out differently.

strlen(l_str.c_str()-> is giving the size as 1500, whereas, l_str.size()-> is giving the size as 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
",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
",var);
        return 0;
}

Please suggest, if I'm doing something which I'm not supposed to do!

Also, I wanted to know which memory bytes are being set to 0 when I do memset(data,0,1500);? What I mean to ask is that if suppose, my string variable's starting address is 100, then does memset command sets the memory range [100,1600] as 0? Or is it setting some other memory range?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)
memset(data,0,1500); // This might not be correct but it works fine

It isn't correct, and it doesn't "work fine". This is Undefined Behaviour, and you're making the common mistake of assuming that if it compiles, and your computer doesn't instantly catch fire, everything is fine.

It really isn't.

I've done something which I wasn't supposed to do!

Yes, you have. You took a pointer to a std::string, a non-trivial object with its own state and behaviour, asked it for the address of some memory it controls, and cast that to void*.

There's no reason to do that, you should very rarely ever see void* in C++ code, and seeing C-style casts to any type is pretty worrying.

Don't take void* pointers into objects with state and behaviour like std::string until you understand what you're doing and why this is wrong. Then, when that day comes, you still won't do it because you'll know better.


We can look at the first problem in some fine detail, if it helps:

(void *)l_str.c_str()
  • what does c_str() return? A pointer to some memory owned by l_str
  • where is this memory? No idea, that's l_str's business. If this standard library implementation uses the small string optimization, it may be inside the l_str object. If not, it may be dynamically allocated.
  • how much memory is allocated at this location? No idea, that's l_str's business. All we can say for sure is that there is at least one legally-addressable char (l_str.c_str()[0] == '') and that it's legal to use the address l_str.c_str()+1 (but only as a one-past-the-end pointer, so you can't dereference it)

So, the statement

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

passes strRet a pointer to a location containing one or more addressable chars, of which the first is zero. That's everything we can say about it.

Now let's look again at the problematic line

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

why would we expect there to be 1500 chars at this location? If you'd documented strRet as requiring a buffer of at least 1500 allocated chars, would it look reasonable to actually pass l_str.c_str() when you know l_str has just been default constructed as an empty string? It's not like you asked l_str to allocate that storage for you.

You could start to make this work by giving l_str a chance to allocate the memory you intend to write, by calling

l_str.reserve(1500);

before calling strRet. This still won't notify l_str that you filled it with 'a's though, because you did that by changing the raw memory behind its back.

If you want this to work correctly, you could replace the entirety of strRet with

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

or, if you want to change an existing string correctly, with

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

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...