After days of attempting to create a shell I ask for a bit of help. I have started over 4 or so times with different data structures and plea for a solution to the below problem. I have a string that I need to break into individual arguments and have a pointer to that. I ultimately pass args to an exec function but since i cant seem to fill the args correctly i am getting funny results, here is a simplified version of whats happening
char* args[100];
int counter=0;
string temp = "some text and stuff here";
stringstream s (temp);
while(s>> temp)
{
cout << "TOKEN " << counter << " =" << temp <<endl;
args[counter]=const_cast<char *> (temp.c_str());
counter++;
}
//print the debug info
for( int ii=0; args[ii] != NULL; ii++ )
{
cout << "Argument OUT " << ii << ": " << args[ii] << endl;
}
This code doesn't work and I cant grasp why. The result stores "here" in every value of args but counter gets changed.
TOKEN 0 =some
TOKEN 1 =text
TOKEN 2 =and
TOKEN 3 =stuff
TOKEN 4 =here
Argument OUT 0: here
Argument OUT 1: here
Argument OUT 2: here
Argument OUT 3: here
Argument OUT 4: here
Probably because the temp
object is re-using its internal allocation. When you store the c_str()
result you are only storing a memory address. The std::string
class does not create a completely new allocation each time you read into it from your string stream, rather it reuses the allocation it already has (if possible).
Further, using a pointer returned by c_str()
after you have done anything else to the std::string
object from which it was obtained invokes undefined behavior.1
If possible, just change args
to std::vector<std::string>
. If this is not possible then you need to strdup()
the pointer returned by c_str()
in order to create an entirely new allocation that copies the value of the string at that moment. You must, of course, remember to free()
the allocations when you are done.
Additionally, casting away the const
qualifier and writing to the pointer results in undefined behavior.2 At a minimum you need to change args
to be const char * args[100];
, but I would strongly suggest using a vector of strings instead.
1 http://en.cppreference.com/w/cpp/string/basic_string/c_str
The pointer obtained from
c_str()
may be invalidated by:
- Passing a non-const reference to the string to any standard library function, or
- Calling non-const member functions on the string, excluding
operator[]
,at()
,front()
,back()
,begin()
,rbegin()
,end()
andrend()
.
2 http://en.cppreference.com/w/cpp/string/basic_string/c_str
Writing to the character array accessed through
c_str()
is undefined behavior.
Based on your comment indicating that you need to use exec()
, it sounds like you do need an array of pointers-to-char
. However, we can still do this using vectors. You need one vector to hold the std::string
objects, which will own the char*
allocations. Then you can use another vector to hold the actual pointers. Something like this:
const char * binaryPath = "/bin/foo";
std::vector<std::string> argStrings;
std::vector<char *> argPointers;
std::string temp = "some text and stuff here";
istringstream s(temp);
// argv[0] should always contain the binary's path.
argPointers.push_back(binaryPath);
while (s >> temp) {
argStrings.push_back(temp);
std::cout << "TOKEN " << argStrings.size()
<< " =" << argStrings.back() << std::endl;
// We cast away the const as required by the exec() family of functions.
argPointers.push_back(const_cast<char *>(argStrings.back().c_str()));
}
// exec() functions expect a NULL pointer to terminate the arguments array.
argPointers.push_back(nullptr);
// Now we can do our exec safely.
execv(binaryPath, &argPointers[0]);
In this case argStrings
owns the actual string allocations, and we are using argPointers
only to hold the array of pointers we will be passing to execv()
. The const_cast
is safe because execv()
does not modify the strings. (The argument is char * const []
for compatibility with older C code; the functions behave as though the argument is const char * const []
.)