0

I need a char* for my filename. It should be something like this: cards/h11.bmp

i have a function I cobbled together from various SO articles:

char* getFileName(int* pc1_no, char* suite)
{
    int number;
    char pCard1[80];
    strcpy_s(pCard1, "cards/");
    strcat_s(pCard1, suite);
    number = *pc1_no;
    cout << number << endl;
    string str = to_string(number);
    char const *pchar = str.c_str();
    strcat_s(pCard1, pchar);
    strcat_s(pCard1, ".bmp");

    return pCard1;
}

Which of course, returns garbage. I don't quite get getting the pointer value. I pretty sure I have made a dumb mistake with the pointer. Thanks in advance.

Kerrek SB
  • 464,522
  • 92
  • 875
  • 1,084
Randy
  • 1,137
  • 16
  • 49
  • 1
    What are you *actually* trying to do? – Kerrek SB Feb 05 '17 at 00:15
  • I have no choice but to use an int pointer to store an integer value. I need the integer value to be put in between to two other strings: "cards/h" + pc1_no + ".bmp". – Randy Feb 05 '17 at 00:19
  • 1
    Probably easier to use strings throughout (possibly even involving `stringstream`s), and only extract a char pointer at the place where you actually need it. That would prevent you from making all the typical mistakes of treating C-style strings as if they were string-like objects rather than as arrays or pointers-to-arrays, and of making typical pointer mistakes, such as returning a pointer to local memory that vanishes as soon as the function exits. –  Feb 05 '17 at 00:19
  • 1
    your main problem looks to be from returning a pointer to local variable: `pCard1` is destroyed when the function ends, but you return (and I'm assume, use) the pointer you returned. – kmdreko Feb 05 '17 at 00:23
  • Irrelevant note: `pc1_no` should be a simple `int`. Right now, there's no special behaviour for a null pointer. The function simply dereferences it. Therefore, the caller should not be passing a null pointer. Removing the pointer from the type makes this very clear and saves the extra thought process of what happens when the pointer is null for someone looking at this function later, including you in a few weeks. On that topic, if `suite` is a C string, it should be `const char*`. As is, I wouldn't even be able to pass a string literal or another constant string I have, and for no good reason. – chris Feb 05 '17 at 00:27
  • Your main problem is all of this C-ness. – Lightness Races in Orbit Feb 05 '17 at 01:34

3 Answers3

10

The best way to do all this is get rid of all the pointers and use a string all of the way through:

std::string getFileName(int pc1_no, 
                        const std::string & suite)
{
    std::string pCard1 = "cards/" + suite + std::to_string(pc1_no) + ".bmp";
    return pCard1;
}

or if building to older C++ standards where std::to_string is not available:

std::string getFileName(int pc1_no, 
                        const std::string & suite)
{
    std::stringstream pCard1;
    pCard1<< "cards/" << suite << pc1_no << ".bmp";
    return pCard1.str();
}

Rational

char pCard1[80];

is a local variable. It will die at the end of the function, so the function returns a pointer to invalid memory. Many bad things can happen as a result and few good. Watch out for the few good. They are liars waiting for the most opportune time to strike.

The simplest solution maintaining OP's structure is use a std::string to perform string manipulation inside the function.

std::string getFileName(int* pc1_no, char* suite)
{
    std::string pCard1 = "cards/";
    pCard1 += std::string(suite);
    pCard1 += std::to_string(*pc1_no);
    pCard1 += std::string(".bmp");
    return pCard1;
}

The above is Horrible Code, both excessively verbose and inefficient, but we've already covered the right way in the intro. This is just a touch-point on the logical progression to that right way.

Faster and less complex is to take advantage of std::stringstream's ability to format c-style strings and numbers without any outside help. This approach is probably the best up until std::to_string became available in the C++11 standard.

std::string getFileName(int* pc1_no, char* suite)
{
    std::stringstream pCard1;
    pCard1<< "cards/" << suite << *pc1_no << ".bmp";
    return pCard1.str();
}

There is the possibility of a performance penalty returning the string by value, but the compilers of the past few decades are good at detecting and taking advantage of opportunities to omit unnecessary copying and employing move semantics behind the scenes.

Returning string by value is also far less error-prone than dynamically allocating storage and returning the storage to a caller with the expectation that the caller release it. Any performance penalty that may remain is highly likely to be worth the price. Profile the code to be sure.

Improving the function call:

Passing pc1_no in as a pointer is not helping in any way. Unless you need to modify the value inside the function, just pass by value. If you do need to change the value, prefer a reference.

std::string getFileName(int pc1_no, char* suite)

If you try to pass a string literal: eg:

getFileName(&somenumber, "string literal");

string literals may be in non-writable memory and are always const char * in C++. Passing a const value into a space that could attempt to change the value is bad form. This was allowed for backwards compatibility with C under older C++ Standards, though it may generate an warning, but is illegal in after the C++ 11 Standard.

If your function does not need to modify the contents of the char array, and this doesn't, it's a good practice to tag the string as const and allow the compiler to prevent accidents regardless of whether your compiler allows const to non-const assignments:

std::string getFileName(int pc1_no, const char* suite)

You may have more versatility if you use a reference to a const std::string as it is implicitly convertible from both const and non-const char arrays and allows the rest of your program to take advantage of the many benefits of std::string without needless calls to c_str and data.

std::string getFileName(int pc1_no, 
                        const std::string & suite)

That brings us back to where this answer came in.

Community
  • 1
  • 1
user4581301
  • 33,082
  • 7
  • 33
  • 54
7

Here is how this code would look like in C++:

#include <string>

std::string getFileName(int* n, const std::string& suite) {
  return "cards/" + suite + std::to_string(*n) + ".bmp";
}

Even better would be to take the first parameter by value (int n) and dereference at the call site if necessary.

Kerrek SB
  • 464,522
  • 92
  • 875
  • 1,084
0
char* getFileName(int pc1_no, char* suite)
{
    static char pCard1[80]; // Guarantee safe pointer return
    strcpy_s(pCard1, "cards/");
    strcat_s(pCard1, suite);
    string str = to_string(pc1_no);
    char const *pchar = str.c_str();
    strcat_s(pCard1, pchar);
    strcat_s(pCard1, ".bmp");

    return pCard1; // Now you don't lose the result.
}

Or you can use recommended C++ style which is already answered.

CnCPPcoder
  • 84
  • 5