1

This is not a duplicate of something like "why simple system(variable) doesn't work".

Solution for that one would be just store string to a variable convertable by c_str() and then just call: system(variable.c_str())

However I am looking for a way to do that without c_str() direct call.

So I've tried something like

class systemRunner{
    private:
        stringstream prepareStream;
    public:
        void setProgram( string s ){
            prepareStream.str(""); // empty stream
            prepareStream.clear(); // reset stream - !IMPORTANT!
            prepareStream << "\"" << s << "\"";
        }
        void appendParam( string s ){ this->prepareStream << " " << s; }
        void appendParam( int i ){    this->prepareStream- << " " << i; }
        const char* getSystemRunCString(){
            //const std::string s = ;
            return this->prepareStream.str().c_str();
        }

};

One would then think that this would be enaugh:

system ( systemRunner->getSystemRunCString() )

But it fails, resp. compiles fine, but when system() is called like this - system says it can't find the specified path.

However when I restore it and use c_str() in direct system call e.g. like this:

string tmp = (string)systemRunner->getSystemRunCString();
system( tmp.c_str() );

This works fine.

One would expect that if I create a method which returns the same thing as c_str(),
which is const char*, that I would get the same result, but I am not getting it...

I even tried to put both inputs not in the system() but in the file - same results so it stores the same info...

Am I missing something? Is this even possible?


PS: I am talking about using system() in Windows 7 console application...

EDIT: Well @ravi is right about what is causing this particular example to fail - but what about the answer to the main question - in the title - is it possible to call system(variable) without directly calling c_str() in it ? :)

jave.web
  • 13,880
  • 12
  • 91
  • 125
  • 2
    You used a temporary after it was destroyed. This is a common duplicate... – Deduplicator Nov 13 '14 at 12:52
  • 1
    possible duplicate of [Is it safe to use (str1 + str2).c\_str()?](http://stackoverflow.com/questions/18146267/is-it-safe-to-use-str1-str2-c-str) – Deduplicator Nov 13 '14 at 12:54
  • 1
    Why does your `systemRunner` not "run" `system` as its name implies? Just store the string as a member variable and have a member function call `system`. – rubenvb Nov 13 '14 at 13:03
  • UB is UB, there's no use trying to reason with it. Is that what you wanted to know? Or what do you mean with base of your problem? Anyway, read the duplicate. – Deduplicator Nov 13 '14 at 13:07
  • The base of your problem is that you're returning a pointer to something inside a local variable, which goes out of scope through which you end up with a dangling pointer. Note the only real question in your "question" is answered by @ravi below. – rubenvb Nov 13 '14 at 13:16
  • @rubenvb great point - it actually solves my current issue - which was not to handle extra function call in another function call in the main - but it does not solve the base of the problem which is how to make variable acceptable for the system without direct calling c_str() :) – jave.web Nov 13 '14 at 13:17
  • @ravi well no it just says why it is so, not how can you call system(variable) without direct calling c_str() ... or whether is it possible or not – jave.web Nov 13 '14 at 13:19
  • @jave.web The problem is not you not handling extra function calls in main, it's you not keeping track of the lifetime of objects returned by functions. Oh, and calling `system` is a terrible way of doing these things, but I trust this is for academic purposes only, if I interpret your question correctly. – rubenvb Nov 13 '14 at 13:23

4 Answers4

5
return this->prepareStream.str().c_str();

would return const char*. This pointer would be the pointer to the internal character pointer used by string class. Through this operation you would get access to that internal data of string but as it's const pointer you would not be able to modify it's contents.

What is happening over here is after execution of this function that string ( whose internal data you still hold ) goes out of scope. So, you are just kind of working on the handle of whose string gone out of scope.

You should never rely on this.

ravi
  • 10,994
  • 1
  • 18
  • 36
  • I guess I see your point, but how does this answer my question ? :) – jave.web Nov 13 '14 at 13:03
  • 2
    This answers the question "Am I missing something?" – Pedro Lamarão Nov 13 '14 at 13:12
  • Actually prepareStream.str() returns *a copy* of its internal string, so `c_str()` is called on a temporary string which gets destroyed immediately afterwards. So `string tmp = (string)systemRunner->getSystemRunCString();` is UB as well. – Anton Savin Nov 13 '14 at 13:14
  • This answers why your call to above statement failed...while other call where you stored the result of return value from function succeeded. – ravi Nov 13 '14 at 13:15
  • Well but that does not answer the question which is "call system(variable) without direct calling c_str()" :) Is it possible? – jave.web Nov 13 '14 at 13:19
  • @jave.web: You can use `s.data()`. Or `&s.front()`. Or `&s[0]`. Or `&s.at(0)` or `&*s.begin()` or `&*s.cbegin()`. Or avoid `std::string` completely. ... – Deduplicator Nov 13 '14 at 13:27
  • @ravi this does not answer the title question, however I appreciate very much that you made this explanation of why my particular example doesn't work... :) So at least I will give you a +1 :) – jave.web Nov 13 '14 at 13:41
  • 1
    Most of the question was about why does this temporary object not work, and that was answered. For the title question, http://stackoverflow.com/questions/4096210/why-does-stdstring-not-provide-a-conversion-to-const-char If you are using windows and mfc, you can go back to CString, which has the one function you are looking for. Personally, I'm still trying to replace all my CString's with std::string. – Kenny Ostrom Nov 13 '14 at 13:47
3

@ravi has perfectly explained why the code is failing, since the value returned by str() goes out of scope, but since it the comments it is mentioned that it is still not clear how to solve the actual issue, here is a possible answer:

class systemRunner{
    private:
        string systemCommand;
    public:
        void setProgram( string s ){
            systemCommand.clear();
            systemCommand.append("\"").append(s).append("\"");
        }
        void appendParam( string s ){ systemCommand.append(" ").append(s); }
        void appendParam( int i ){
            // VS 2013:
            // systemCommand.append(" ").append(to_string(i)));
            // VS 2010:
            // systemCommand.append(" ").append(to_string(static_cast<long long>(i))); 
            // General stringstream conversion
            stringstream argumentStream;
            argumentStream << i;
            systemCommand.append(" ").append(argumentStream.str());
        }

        const char* getSystemRunCString(){
            return systemCommand.c_str();
        }

};

Use a string instead of a stringstream so then the pointer returned by c_str() will be valid as long as the instance of systemRunner is not destroyed. Also, for such simple formatting as in your example, you don't need the stringstream, you can just used the append() and to_string() methods.

Rudolfs Bundulis
  • 11,636
  • 6
  • 33
  • 71
2

As long as systemRunner itself is not a temporary object, you can do the following:

class systemRunner {
    private:
        stringstream prepareStream;
        string commandLine;
    public:
        //...
        const char* getSystemRunCString() {
            commandLine = prepareStream.str();
            return commandLine.c_str();
        }
};

This way you return a pointer not to a temporary string but to the string which has the same lifetime as systemRunner object.

Note though, that getSystemRunCString() should be used only as provider for "temporary" strings, you must never use it like:

const char* cmd1 = runner.getSystemRunCString();
runner.setProgram(...);
const char* cmd2 = runner.getSystemRunCString();
// cmd1 is now invalid.
system(cmd1); // UB

This applies to Rudolfs Bundulis's answer as well.

Anton Savin
  • 40,838
  • 8
  • 54
  • 90
1

I will decompose the operation prepareStream.str().c_str(); to precisely show what happens.

Let's write instead :

const char* getSystemRunCString(){
    const string s = prepareStream.str();
    const char * c = s.c_str();
    printf("%s - %p\n", c, c);
    return c;
}

it works fine and shows the prepares command line

But on return on method if I use

const char *c = systemRunner->getSystemRunCString();
printf("%s - %p\n", c, c);

the pointer address in the same, but the string itself has gone.

Explaination :

Under the hood, prepareStream.str().c_str(); creates a local string and gets a const char * on its content. But as the string is not saved anywhere, it goes out of scope and its contents vanishes.

So your problem is not at the system level, but is related to the fact that for a stringstream ss, ss.str().c_str() is incorrect because you get a pointer to a string that goes out of scope at the end of the instruction.

It works if you write string s = prepareStream.str().c_str(); because the inner string only goes out of scope at the end of the instruction, and during the instruction the value is copied to the new string.

Serge Ballesta
  • 143,923
  • 11
  • 122
  • 252