1

What's the correct way to add a character array to a constant character array in C++?

#include <iostream>
using namespace std;

int main(int argc, char** argv) {
    int pathSize = 0;
    char* pathEnd = &argv[0][0];
    while(argv[0][pathSize] != '\0') {
        if(argv[0][pathSize++] == '/')
            pathEnd = &argv[0][0] + pathSize;
    }
    pathSize = pathEnd - &argv[0][0];
    char *path = new char[pathSize];
    for(int i = 0; i < pathSize; i++)
        path[i] = argv[0][i];
    cout << "Documents Path: " << path + "docs/" << endl; // Line Of Interest
    delete[] path;
    return 0;
}
This code outputs: 
Documents Path: �\

Using 'path' instead of '*path' will give me the compile error: 
invalid operands of types ‘char*’ and ‘const char [6]’ to binary ‘operator+’
Jeroen
  • 15,257
  • 12
  • 59
  • 102
  • 12
    Just use `std::string`. – chris Jul 10 '13 at 18:11
  • 1
    Yap: `const char *foo = "bar"; std::string s(foo); s+= 'a';`, then extract the char pointer using `const char *bar = s.c_str();` –  Jul 10 '13 at 18:13
  • @chris The document path will be send to a function as parameter. The function only supports character arrays. – Jeroen Jul 10 '13 at 18:15
  • @Binero, No reason to use them from the start: http://en.cppreference.com/w/cpp/string/basic_string/c_str – chris Jul 10 '13 at 18:16
  • @chris That doesn't answer the question though. It only provides an alternative. – Jeroen Jul 10 '13 at 18:21
  • @Binero, Actually, it will, because `path + "docs/"` will work properly, not to mention cleaning up the code a lot and making any bugs more obvious. – chris Jul 10 '13 at 18:23
  • @chris path + "docs/" will not compile. See OP. – Jeroen Jul 10 '13 at 18:25
  • 1
    @Binero: `std::string` is a thousand times easier, and works with functions that only support character strings. using `char*` manually is tricky. Also, the parameter at index 0 does _not_ contain the first parameter you give to the file. – Mooing Duck Jul 10 '13 at 18:26
  • @Binero, It won't as is, and `*path + "docs/"` will assuredly fail. `std::string` causes `path + "docs/"` to work as expected. – chris Jul 10 '13 at 18:27
  • Why not just `cout << "Documents Path: " << path << "docs/" << endl;`? Especially since you just delete `path` immediately afterwards anyway... – twalberg Jul 10 '13 at 18:31
  • That's how it used to be, but I will need the documents path later on too, but as 1 character array. – Jeroen Jul 10 '13 at 18:33

4 Answers4

4

May I suggest using C++ to begin with, and (Boost) Filesystem for maximum benefits:

#include <iostream>
#include <boost/filesystem.hpp>
using namespace std;
using namespace boost::filesystem;

int main(int argc, const char *argv[])
{
    const std::vector<std::string> args { argv, argv+argc };

    path program(args.front());
    program = canonical(program);
    std::cout << (program.parent_path() / "docs").native();
}

This will use the platform's path separator, know how to translate 'funny' paths (e.g. containing ..\..\, or UNC paths).

sehe
  • 374,641
  • 47
  • 450
  • 633
  • Not to mention it's going to be super easy to turn this into standard library use soon. – chris Jul 10 '13 at 18:29
  • Is it worth thought to load a massive library while not even 1 function I'll be using will support it? – Jeroen Jul 10 '13 at 18:32
  • 1
    @Binero: since it changed from 20 wrong lines of code to 3 working lines of code, yes. There's _nothing_ wrong with loading a "massive library". What problems would you think would occur? – Mooing Duck Jul 10 '13 at 18:33
  • @Binero if you don't use it, don't link it. If you do, calculate your options. Anyways, you can look at BCP. And the future (c++1y) will bring this into the standard. – sehe Jul 10 '13 at 18:34
  • @MooingDuck "Cannot find include file " – Jeroen Jul 10 '13 at 18:36
  • Yeah, there is that one. You do have to install and set up boost first, that can be a pain. Highly recommended though. Try and use the `std` namespace instead of `boost`, that _might_ work. If those don't work for you, try the other answers. – Mooing Duck Jul 10 '13 at 18:38
  • @Binero MSVC indeed included the TR2 standard proposals for `` already: http://msdn.microsoft.com/en-us/library/vstudio/hh567368.aspx – sehe Jul 10 '13 at 18:49
  • @Binero then you'll probably be set using `apt-get install libboost-dev` and adding `-lboost_filesystem` to the `LDFLAGS` – sehe Jul 10 '13 at 19:02
1

Something like this should do it (totally untested):

const char* end = strrchr(argv[0], '/');
std::string docpath = end ? std::string(argv[0], end) : std::string(".");
docpath += '/docs/';
Mark B
  • 95,107
  • 10
  • 109
  • 188
  • Although I do use a variation of this now, I still need that character array. – Jeroen Jul 10 '13 at 18:47
  • @Binero: http://stackoverflow.com/questions/17577898/whats-the-correct-way-to-add-a-character-array-to-a-constant-character-array-in/17578271?noredirect=1#comment25576584_17577898 – Mooing Duck Jul 10 '13 at 19:03
1

Your way:

#include <iostream>
using namespace std;

int main(int argc, char** argv) {
    int pathSize = 0;

    char* pathEnd = &argv[0][0];
    while(argv[0][pathSize] != '\0') {
        if(argv[0][pathSize++] == '/')
            pathEnd = &argv[0][0] + pathSize;
    }

    pathSize = pathEnd - &argv[0][0];
    char *path = new char[pathSize + 5]; //make room for "docs/"
    for(int i = 0; i < pathSize; i++) 
        path[i] = argv[0][i];

    char append[] = "docs/";
    for(int i = 0; i < 5; i++) 
        path[pathSize+i] = append[i];

    cout << "Documents Path: " << path << endl;
    function_expecting_charptr(path);

    delete[] path;
    return 0;
}

Sane C way:

#include <iostream>
using namespace std;

int main(int argc, char** argv) {

    char* pathEnd = strrchr(argv[0], '/');
    if (pathEnd == NULL)
        pathEnd = argv[0];
    int pathSize = (pathEnd-argv[0]) + 5; //room for "docs/"

    char *path = new char[pathSize];
    if (pathSize)
        strncpy(path, argv[0], pathSize+1);
    strcat(path, "docs/");

    cout << "Documents Path: " << path << endl;
    function_expecting_charptr(path);

    delete[] path;
    return 0;
}

C++ way:

#include <iostream>
#include <string>

int main(int argc, char** argv) {
    std::string path = argv[0];

    size_t sep = path.find('/');
    if (sep != std::string::npos)
        path.erase(sep+1);
    else
        path.clear();

    path += "docs/";
    std::cout << "Documents Path: " << path << endl;
    function_expecting_charptr(path.c_str());

    return 0;
}

Note that argv[0] holds an implementation defined value, and especially in *nix environments isn't guaranteed to hold anything useful. The first parameter passed to the program is found in argv[1].

Mooing Duck
  • 64,318
  • 19
  • 100
  • 158
0

I combined some of your guys' ideas into this compact code:

#include <iostream>
#include <cstring>
using namespace std;

int main(int argc, char** argv) {
    const string path_this = argv[0];
    const string path = path_this.substr(0, strrchr(argv[0], '/') - argv[0] +1);
    const string path_docs = path + "docs/";
    cout << "Documents Path: " << path_docs << endl;
    return 0;
}

To get the character array from this I can then run 'path_docs.c_str()'.

Credits: @MooingDuck, @MarkB, Google.

Community
  • 1
  • 1
Jeroen
  • 15,257
  • 12
  • 59
  • 102
  • 1
    Note this crashes if `/` is not in `argv[0]`, handling that is why my code is so long. – Mooing Duck Jul 10 '13 at 19:58
  • @MooingDuck It'll always be in there though. If it's placed at the root it'll be '/'. (Linux) I don't plan on adding Windows compatibility as performance in crucial for this application, and, well, Windows... :P Thanks for pointing it out though. – Jeroen Jul 10 '13 at 20:01
  • 1
    [No, it _might not_ always be there in linux.](http://www.lst.de/~okir/blackhats/node40.html) – Mooing Duck Jul 10 '13 at 20:07
  • @MooingDuck It's up to the end user to make sure it's there, and if not set the absolute path manually. A malicious program will have no use for setting the path as false as the only thing it does with the files in the path is read them. – Jeroen Jul 11 '13 at 15:36
  • It's usually considered poor programming to crash if the user makes a typo. – Mooing Duck Jul 11 '13 at 16:58
  • wherever you think code might be "verbose", instead of making it "concise", put it in a function. Functions are good, and are less error-prone: http://ideone.com/272J6U – Mooing Duck Jul 11 '13 at 17:17
  • I think my way is just a tiny bit easier to overlook but that's personal preference. Also you should have made those functions inline to not loose performance. – Jeroen Jul 11 '13 at 17:39
  • 1
    Nope, those functions should not be inline: http://stackoverflow.com/questions/1759300/when-should-i-write-the-keyword-inline-for-a-function-method – Mooing Duck Jul 11 '13 at 17:52