2

I have started out to write a simple console Yahtzee game for practice. I just have a question regarding whether or not this function will leak memory. The roll function is called every time the dices need to be re-rolled.

What it does is to create a dynamic array. First time it is used it will store 5 random values. For the next run it will only re-roll all except for the dice you want to keep. I have another function for that, but since it isn't relevant for this question I left it out

Main function

int *kast = NULL;           //rolled dice
int *keep_dice = NULL;    //which dice to re-roll or keep

kast = roll(kast, keep_dice);
delete[] kast;

and here's the function

int *roll(int *dice, int *keep) {

    srand((unsigned)time(0));
    int *arr = new int[DICE];
    if(!dice)
    {
        for(int i=0;i<DICE;i++)
        {

            arr[i] = (rand()%6)+1;
            cout << arr[i] << " ";
        }
    }
    else
    {
        for(int i=0;i<DICE;i++)
        {
            if(!keep[i])
            {
                dice[i] = (rand()%6)+1;
                cout << "Change ";
            }
            else
            {
                keep[i] = 0;
                cout << "Keep ";
            }
        }
        cout << endl;
        delete[] arr;
        arr = NULL;
        arr = dice;

    }
    return arr;
}
starcorn
  • 8,261
  • 23
  • 83
  • 124
  • 4
    Has someone told you to always assign NULL to pointers after deleting them? They're wrong. – Steve Jessop Jan 21 '10 at 15:27
  • `arr = NULL; arr = dice;` Rather redundant. :] If you build with even just the first level of optimization on, that line won't exist in the compiled output. (`arr = NULL;`) – GManNickG Jan 21 '10 at 15:35
  • @Steve: I remember reading about it in books. What makes it wrong? I thought it is just for safety reasons. – jasonline Jan 21 '10 at 15:35
  • @Steve: That's probably why a learner should read books instead of Internet tutorial. Anyway can you elaborate why you shouldn't assign pointers NULL? – starcorn Jan 21 '10 at 15:37
  • 1
    @klw:While it's not particularly harmful to assign NULL to a pointer after a deletion, it's also utterly pointless in a case like your: `arr = NULL; arr = dice;`. – Jerry Coffin Jan 21 '10 at 15:38
  • 1
    How about try to come up with a good reason to? @jason: If by "safety" you mean letting broken code run then sure. As for myself, if I'm double deleting something I want my program to crash hard. But yes, in this case it is definitely pointless, as it gets re-assigned one statement later. – GManNickG Jan 21 '10 at 15:39
  • You only need to call srand() once per execution of your program. – luke Jan 21 '10 at 15:41
  • I'm won't argue here about the general advice to NULL out pointers - there are comprehensive questions and discussion elsewhere on the site (http://stackoverflow.com/questions/1879550/should-one-really-set-pointers-to-null-after-freeing-them/1880915#1880915). As it affects this case, I think it's a mistake to set a rule so strongly that it causes people to write blatantly redundant lines of code. Most of the time assigning to the same thing twice in a row is an error - you meant to assign two different things but fumbled it. – Steve Jessop Jan 21 '10 at 15:58

4 Answers4

12

Yes, it can leak. Just for example, using cout can throw an exception, and if it does, your delete will never be called.

Instead of allocating a dynamic array yourself, you might want to consider returning an std::vector. Better still, turn your function into a proper algorithm, that takes an iterator (in this case, a back_insert_iterator) and writes its output there.

Edit: Looking at it more carefully, I feel obliged to point out that I really dislike the basic structure of this code completely. You have one function that's really doing two different kinds of things. You also have a pair of arrays that you're depending on addressing in parallel. I'd restructure it into two separate functions, a roll and a re_roll. I'd restructure the data as an array of structs:

struct die_roll { 
    int value;
    bool keep;

    die_roll() : value(0), keep(true) {}
};

To do an initial roll, you pass a vector (or array, if you truly insist) of these to the roll function, which fills in initial values. To do a re-roll, you pass the vector to re-roll which re-rolls to get a new value for any die_roll whose keep member has been set to false.

Jerry Coffin
  • 476,176
  • 80
  • 629
  • 1,111
  • Alternatively, the use of `auto_ptr` is beneficial. – Travis Gockel Jan 21 '10 at 15:26
  • Sry didn't mention that those cout is only used to bug check. Anyway, other than that is the function leak-proof and the arrays deleted correct? – starcorn Jan 21 '10 at 15:29
  • 1
    @klw:Yes, as long as you assure that no exception can be thrown between allocating and freeing the memory, your code should be free of memory leaks. That condition, however, means your code is (no pun intended) exceptionally fragile at best. – Jerry Coffin Jan 21 '10 at 15:37
  • 3
    @Travis: you better not use `auto_ptr` with memory allocated with `new []`. – David Rodríguez - dribeas Jan 21 '10 at 15:46
  • 1
    @klw: At Travis indicates the function is not 'exception tolerant'. This is bad design in C++ and means that anybody that comes after you to modify the code may be caught off guard (as they would expect the author or previous maintainer to be competent and not write code that is dangerous in the presence of exceptions). Also providing an interface that exposes RAW pointers is ambiguous on its intent. There is no indication if you are passing ownership or retaining ownership based on the function declaration (In C++ this is bad as we have a major issue with ownership semantics) – Martin York Jan 21 '10 at 16:32
4

Use a (stack-allocated) std::vector instead of the array, and pass a reference to it to the function. That way, you'll be sure it doesn't leak.

Thomas
  • 174,939
  • 50
  • 355
  • 478
  • I have consider to use vector, but since I already know how large list I want I thought using array would be better. – starcorn Jan 21 '10 at 15:30
  • @klw: What? That doesn't make sense. A `std::vector` is the standard and safer version of your code, knowing the size doesn't make sense, just call `resize` or `reserve` on the vector. – GManNickG Jan 21 '10 at 15:38
  • Indeed; if you use the proper constructor, so that no reallocations are needed, you won't pay for those. It is probably as fast as an array, and a heck of a lot easier. – Thomas Jan 21 '10 at 15:41
  • 1
    @klw: FYI, recent versions of gcc and the next C++ standard will have std::array arr; which would do what you want. The others commenters are, strictly speaking, wrong; vector allocates its array dynamically, and this is sometimes unwanted, and even sometimes impossible. I'm not saying that that is true in your case, though; if your developing an app on a full OS, vector<> is probably fine. – Tim Schaeffer Jan 21 '10 at 15:48
  • 1
    @Tim As far as I can see, everyone is recommending vector over a *dynamic* array, hence there's already allocation going on. – James Hopkin Jan 21 '10 at 16:47
  • @Tim: Which commentators said vector isn't dynamically allocated? Also, that's technically not always true; one could easily write an allocator that uses stack-space. – GManNickG Jan 21 '10 at 16:58
4

The way you allocate memory is confusing: memory allocated inside the function must be freed by code outside the function.

Why not rewrite it something like this:

int *kast = new int[DICE];           //rolled dice
bool *keep_dice = new bool[DICE];    //which dice to re-roll or keep
for (int i = 0; i < DICE; ++i)
    keep_dice[i] = false;

roll(kast, keep_dice);

delete[] kast;
delete[] keep_dice;

This matches your news and deletes up nicely. As to the function: because we set keep_dice all to false, neither argument is ever NULL, and it always modifies dice instead of returning a new array, it simplifies to:

void roll(int *dice, int *keep) {
    for(int i=0;i<DICE;i++)
    {
        if(keep[i])
        {
            keep[i] = false;
            cout << "Keep ";
        }
        else
        {
            dice[i] = (rand()%6)+1;
            cout << "Change ";
        }
    }
    cout << endl;
}

Also, you should move the srand call to the start of your program. Re-seeding is extremely bad for randomness.

Thomas
  • 174,939
  • 50
  • 355
  • 478
0

My suggestion would be to take time out to buy/borrow and read Scott Meyers Effective C++ 3rd Edition. You will save yourselves months of pain in ramping up to become a productive C++ programmer. And I speak from personal, bitter experience.

Steve Townsend
  • 53,498
  • 9
  • 91
  • 140