-4

I'm encountering a run time error attempting to use std::strcopy with elements in a vector of std::string.

There is no problem with the vector. I have higher level functions that work without a hitch. I'm running into an issue with my low level function char ** argv().

Here is a chunk of a class I'm writing. I think I've posted enough of it for the question. I'm trying to focus attention to the problem.

At runtime, the line indicated in the code below blows up.

class ArgParser{
    public:

        ... MORE CODE ...

        int & argc()
        {
            argc_ = exePath_.empty() ? 0 : 1 + args_.size();
            return argc_;
        }

        char ** argv()
        {
            const int argCount = argc();
            if( argCount==0 ) return argv_;
            if( argv_ )
            {
                for( int i=0; i < argCount; i++ )
                    delete argv_[i];
                delete argv_;
            }
            argv_ = new char*[argCount];
            *(argv_ + 0)=new char[ exePath().size() ];
            strcpy( *(argv_ + 0), exePath_.c_str() );
            int i=1;
            for( auto &arg : args_ )
            {
                *(argv_ + i++)=new char[ arg.size() ];
                strcpy( *(argv_ + i++), arg.c_str() ); // SEG FAULT!
            }
            return argv_;
        }
    private:
        int argc_;
        char **argv_;
        std::vector <std::string> args_;
        std::string exePath_;
};
BuvinJ
  • 10,221
  • 5
  • 83
  • 96
  • 2
    It find it very suspicious that you return `int&` from `argc()`. Do you intend to allow the user to modify `argc_` externally? – François Andrieux Feb 02 '18 at 16:49
  • `if( argCount==0 ) return argv_;` Is it initialized in the constructor? How is it? – Bob__ Feb 02 '18 at 16:53
  • I need to return the int & for my use case. If you notice, everything here is a copy of the "original" data, which is retained by this class and then passed to a client which can mess with the copy, but not the original data. – BuvinJ Feb 02 '18 at 16:54
  • 1
    I assume you don't use `std::vector` and `std::string` everywhere to be able to pass the argc&argv to some C api? Even if so, `argv_` could be `std::vector`. – HolyBlackCat Feb 02 '18 at 16:54
  • Yes, argv_ is initialized in the constructor. It's valid. – BuvinJ Feb 02 '18 at 16:55
  • 1
    `*(argv_ + i)=new char[ arg.size() + 1 ];` now you increment `i` double, and add +1 for null character. – rafix07 Feb 02 '18 at 16:56
  • I'm not using STL in the client code. – BuvinJ Feb 02 '18 at 16:56
  • @rafix07 you maybe right about the null. Why does the strcpy work before that though on my 0 element? – BuvinJ Feb 02 '18 at 16:58
  • 1
    With `std::vector argv` you can `return argv.data();` – Caleth Feb 02 '18 at 16:58
  • You don't have to `new` *any* arrays here, just `argv.push_back(arg.c_str());` – Caleth Feb 02 '18 at 17:01
  • I need a copy of the data, not the original so the client code can screw with the data. Some other library does this that I'm implementing. It erases the arguments. That's the point of what I'm handling. I think argv.data() return const char ** not char **, right? It can't be const. – BuvinJ Feb 02 '18 at 17:02
  • 1
    In your case, it will be [`char **`](http://en.cppreference.com/w/cpp/container/vector/data), as `this->argv` is not const in this method – Caleth Feb 02 '18 at 17:04
  • @rafix07 that didn't change the result. Same thing. Note that my prior strcpy works fine. Also, the i++ increments after not before, so that should be fine I think... – BuvinJ Feb 02 '18 at 17:05
  • @Caleth That sounds promising. I'll see if I can get that to work... – BuvinJ Feb 02 '18 at 17:07

4 Answers4

2

The following lines suffer from couple of problems.

*(argv_ + i++)=new char[ arg.size() ];
strcpy( *(argv_ + i++), arg.c_str() ); // SEG FAULT!
  1. i is incremented twice. Let's say i is 0 before those two lines. The first line allocates memory for argv_[0]. i is incremented and its value becomes 1. In the second line you try to copy to argv_[1] and i is incremented again. That is a problem since you have not allocated memory for argv_[1] yet.

    That leads to further problems. In the next iteration of the for loop, you access argv_[2] and argv_[3], which compounds the problem further since they might be invalid indices for argv_.

    You can fix that by incrementing i after the lines have been executed. As a coding practice, using i++ and ++i in such places is best avoided.

  2. The first line does not allocate enough space. It needs one more character to hold the terminating null character.

Change those lines to:

argv_[i] = new char[arg.size() + 1];
strcpy(argv_[i], arg.c_str());
++i;
R Sahu
  • 204,454
  • 14
  • 159
  • 270
  • That double incrementing was a typo as I rapidly revised my post. That was a mistake for sure. – BuvinJ Feb 02 '18 at 17:42
  • I tested the null char issue, and it didn't seem to make a difference, but conceptually that makes sense. – BuvinJ Feb 02 '18 at 17:44
  • @BuvinJ, that underscores the importance of posting a [mcve]. It allows others to run the code in their setup and troubleshoot the problem relatively easily. Posting bits and pieces of code gives you only guidelines that could potentially fix your problem. – R Sahu Feb 02 '18 at 17:45
  • I do appreciate the help! Sorry I posted something bogus. Once I posted this, I starting editing it and rapidly testing and responding to the comments that poured in. – BuvinJ Feb 02 '18 at 17:53
  • The problem with posting "complete code", is that often it's impossible because the "full use case" is vast (hence why I had to defend the premise of all this even). Further, when you over post, you lose people in extraneous details. – BuvinJ Feb 02 '18 at 17:53
  • @BuvinJ, the process of creating a [mcve] often results in an AHA moment and you are able to resolve your problem without outside assistance. That side benefit is not always stated clearly and/or well understood. – R Sahu Feb 02 '18 at 17:55
  • You're right about the AHA moment! I've had that happen countless times when I've posted on SO, and then I just delete the post. I upvoted you for the help. – BuvinJ Feb 02 '18 at 18:04
  • @BuvinJ, appreciate the the upvote. – R Sahu Feb 02 '18 at 18:11
1

You don't assign argv_[1] to argv_[argCount-1].

You increment i twice, so you still dereference an invalid pointer

You don't allocate enough space for the terminating '\0'.

Stop doing things like you are writing C. There is a reason std::string and std::vector have data members. Use them.

"Some other library ... erases the arguments" If it calls delete on pointers it didn't new, you should stop using it, because that's undefined behaviour waiting to happen. If it doesn't, and argv() is called once per instance, you can just let it play around in the data that args_ allocated

Caleth
  • 52,200
  • 2
  • 44
  • 75
  • The program loops back and restarts itself on unhandled exceptions (within more extended logical controls). I can't simply chose to not use the class which is erasing the arguments I'm afraid and I have to use it in this loop I'm describing. That's why I'm preserving the data and passing it back in the same format this troublesome class requires. – BuvinJ Feb 02 '18 at 17:59
1

Doing things the right way

Another answers already explain mistakes in your implementation.

In this answer I just want to show you a simpler way of implementing the same thing, without the obnoxious (i.e. any) amount of manual allocation:

int argc_;
std::vector <std::string> args_;
std::string exePath_;
// New fields:
std::vector<std::string> argv_data_;
std::vector<char *> argv_;

char **argv()
{
    argv_data_.clear();
    argv_data_.push_back(exePath_);
    argv_data_.insert(argv_data_.end(), args_.begin(), args_.end());
    argv_.clear();
    for (auto &it : argv_data_)
        argv_.push_back(it.c_str());
    argv_.push_back(0); // The standard `argv` is null-terminated, we should do it to.
    return argv_.data();
}

That's all. No new, no risk to leak anything.

This code still allows your C api to modify argv[i] and argv[i][j] safely, exactly as if it was a plain argv received by main().

HolyBlackCat
  • 78,603
  • 9
  • 131
  • 207
  • 1
    I just read this, after writing basically the same thing and having it wortk! I believe your answer is the best here. – BuvinJ Feb 02 '18 at 17:45
0

Here's what I went with, which worked. It's very much like what HolyBlackCat posted.

class ArgParser{
    public:
    ...

        int & argc()
        {
            argc_ = exePath_.empty() ? 0 : 1 + args_.size();
            return argc_;
        }

        char ** argv()
        {
            if( argc()==0 ) return 0;
            argv_.clear();
            argvData_.clear();
            argvData_.push_back( exePath_ );
            for( auto arg : args_ )
                argvData_.push_back( arg );
            for( auto & arg : argvData_ )
                argv_.push_back( &arg.front() );
            return argv_.data();
        }

    private:
        std::string exePath_;
        std::vector <std::string> args_;

        int argc_;
        std::vector <std::string> argvData_;
        std::vector <char*> argv_;
};
BuvinJ
  • 10,221
  • 5
  • 83
  • 96
  • Some suggestions: `for( auto arg : args_ )` probably should be `for( const auto &arg : args_ )` to avoid extra copies. (If not a single `.insert()` call). Also, the code using `argv` may depend on it having a null pointer at the end, so you probably should add it. – HolyBlackCat Feb 02 '18 at 17:53
  • Excellent suggestions. Thanks @HolyBlackCat – BuvinJ Feb 02 '18 at 18:02