9

There's lots of stuff on this already. They all seem to suggest that all you need to do is call free for each time you call malloc, and probably free the "pointer of pointers" last - so you don't end up referencing that memory after it's been released

For example, if I have

int** myPtr

Then I just need to

free(myPtr[j])

for each j, before finally releasing the pointer of pointers

free(myPtr)

Cool. I've done this a few times in my program, and it seems to work just fine - except in one case. I've tried to figure out what's different about this case, and I was able to replicate the symptom in a minimal program.

#include "stdafx.h"
#include <stdlib.h>

signed char** getPtr()
{
    unsigned char n = 10;
    unsigned char k = 2;

    signed char** myPtr = (signed char**)malloc(n*sizeof(signed char));
    if (myPtr == NULL) {return NULL;}

    for (unsigned char i = 0; i < n; i++)
    {
        myPtr[i] = (signed char*)malloc(k*sizeof(signed char)); 
        if (myPtr[i] == NULL) {return NULL;}
    }

    for (unsigned char j = 0; j < n; j++)
    {
        for (unsigned char k = 0; k <= 1; k++)
        {
            myPtr[j][k] = 42; 
        }
    }

    return myPtr; 

}

int _tmain(int argc, _TCHAR* argv[])
{
    signed char** potato = getPtr();
    if (potato != NULL)
    {
        for (unsigned char j = 0; j < 10; j++)
        {
            free(potato[j]);
        }
        free(potato); 
    }
    return 0;
}
jww
  • 97,681
  • 90
  • 411
  • 885
user2864293
  • 380
  • 2
  • 10

6 Answers6

11

Your immediate problem is the following line:

signed char** myPtr = (signed char**)malloc(n*sizeof(signed char));

This is wrong because it allocates enough space for n characters. Instead, it should be:

signed char** myPtr = (signed char**)malloc(n*sizeof(signed char*));
//                                                              ^
//                                                      char POINTER

The size of a char is generally less than the size of a pointer but, even if it weren't, you should be using the right type. In fact, I tend to prefer the following form so that I don't have to repeat the type (repetition has the possibility that I may change one in the future, forgetting the other):

signed char** myPtr = (signed char**) malloc (n * sizeof (*myPtr));

Beyond that, you should consider which language you're actually coding for. While C and C++ share quite a bit of basic stuff, they are in no way the same language, and how you write your code depends on which one you're targeting.

Let's cover C first. In that language, you shouldn't cast the return value from malloc because it can hide certain subtle errors that are not fun to debug. Dropping the cast gives you:

signed char **myPtr = malloc (n * sizeof (*myPtr));

Additionally, since sizeof (char) (naked, signed and unsigned) is always one, whenever you see yourself multiplying by that, it's easier to just drop the multiplication. Combining that with the previous guideline allows you to turn:

myPtr[i] = (signed char*)malloc(k*sizeof(signed char));

into the much more readable:

myPtr[i] = malloc (k);

Now, if you're coding for C++, there are additional things to look into.

In C++, it's more acceptable to use the new and delete aspects of the language rather than the legacy C stuff like malloc and free. The guidelines you've already mentioned (ensuring you have a delete for each new) still hold, but there's the added complication of ensuring you use delete[] when freeing arrays of items.

That's not to say malloc and free won't work; they're still very much part of the C++ language specification. It's just that the C++ way of doing things can be much more powerful in that it can both allocate and initialise objects in one hit, and raise exceptions on out-of-memory, meaning you don't have to pepper your code with NULL checks.

In addition, C++ provides, in its standard library, a great many collections which make handling groups of things a lot easier than the use of raw arrays. One of these is a vector, basically a resizable array, and you should consider using it in situation where you may have previously used arrays.

The code you've shown would be much easier to implement as a vector of strings (C++ strings, not those puny C ones). And, in fact, I'd consider going one step further and actually encapsulating the entire data structure into its own class, hiding the fact that it uses vectors at all.

If you do that, and provide all the setter/getter methods that you need, you can later replace the entire underlying data structure without affecting the code that uses it.

paxdiablo
  • 854,327
  • 234
  • 1,573
  • 1,953
  • What's the potential danger with casting the return value of malloc? (what subtle errors?) – user2864293 Sep 24 '14 at 04:24
  • 1
    @user2864293, if you don't include the correct header and your pointers are a different size to your integers, you can get unexpected results. – paxdiablo Sep 24 '14 at 04:26
  • This is a C answer. For C++ the answer would be different. – n. m. could be an AI Sep 24 '14 at 12:29
  • @n.m. This is both a C and C++ answer. The C-specific stuff is clearly delineated into the bottom section but I'll add a note in the top section to nudge developers into using the more accepted C++ stuff, assuming that's what you were referring to. – paxdiablo Sep 24 '14 at 13:33
  • This is correct C++ code, but it's *not* a recommended way to do C++. – n. m. could be an AI Sep 24 '14 at 13:37
  • @n.m. If you have a better suggestion on how better to answer the question in which OP is using the C-style stuff, I'm all ears. OP has stated they'd like to know for _both_ languages. – paxdiablo Sep 24 '14 at 13:43
  • 5
    The right way to do C++ is "it's 2014 for Pete's sake, use `std::vector` already" and I'm sick of leaving this comment under every other C++ question that has "array" in it. I guess I could find a good question that has an accepted answer to this effect and just close 'em all as duplicates, would that be a right move? – n. m. could be an AI Sep 24 '14 at 13:52
  • @n.m., if you're sick of leaving comments, stop leaving them. Use your votes or write your _own_ answer. Bottom line is OP asked about specific code no matter how abhorrent you think that code was. We can suggest doing it a better way (and I've again incorporated your suggestion) but that's entirely up to the OP. If you have an issue with their way of doing things, you should probably rant at them rather than me. – paxdiablo Sep 24 '14 at 18:49
  • 1
    There are two unfortunate problems with answering questions like that. A suggestion to use std::vector would not answer a question about malloc, which breaks the rules of this site. And an answer about malloc would perpetuate the usage of malloc. See? Whichever way I answer, I only make things worse. So I comment, but comments are not really effective. – n. m. could be an AI Sep 24 '14 at 20:17
  • @n.m.: I wouldn't say the comments are ineffective because now you have an answer that both fixes their immediate problem and suggests they modify their ways. In fact, I'll even beef up the bit that suggests that. Have a look and see if this latest iteration seems okay. I'm open to further suggestion if you wish. – paxdiablo Sep 25 '14 at 02:01
  • 2
    And this is why leaving such comments _is_ useful: it has led to the improvement of this answer! – Lightness Races in Orbit Sep 25 '14 at 09:05
  • 1
    @Lightness: Engrish! I think I'm offended. No, not really, looking through your edits, I'm almost ashamed it was that bad. Thanks :-) – paxdiablo Sep 25 '14 at 09:39
5

You're not allocating enough space for your pointer to pointers. This:

signed char** myPtr = (signed char**)malloc(n*sizeof(signed char));

should be:

signed char** myPtr = malloc(n*sizeof(signed char*));

You also would leak memory here if malloc would fail:

for (unsigned char i = 0; i < n; i++)
{
    myPtr[i] = (signed char*)malloc(k*sizeof(signed char)); 
    if (myPtr[i] == NULL) {return NULL;}
}

I'm kind of assuming you want the C version of this code not fully portable to C++. There's probably a more appropriate way of accomplishing the same thing in C++ for what you really want to do.

ldav1s
  • 15,885
  • 2
  • 53
  • 56
4

You need to allocate space for n pointers of signed char, not for n signed char :

signed char** myPtr = (signed char**)malloc(n*sizeof(signed char*));
                                                               ^^^
quantdev
  • 23,517
  • 5
  • 55
  • 88
3

You should use:

signed char** myPtr = (signed char**)malloc(n*sizeof(signed char*));
                                                                ^^ The missing *

instead of

signed char** myPtr = (signed char**)malloc(n*sizeof(signed char));

Otherwise, you are not allocating enough memory for the pointers.

Of course, you should not cast the return value of malloc. It's known to cause problems. See Specifically, what's dangerous about casting the result of malloc? for more details.

Use:

signed char** myPtr = malloc(n*sizeof(signed char*));
Community
  • 1
  • 1
R Sahu
  • 204,454
  • 14
  • 159
  • 270
1

In getPtr you:

// 1 byte
malloc n*sizeof(signed char)

But you are storing a pointer to a signed char, which is 32 or 64 bits, 4 or 8 bytes.

You could fix it with:

signed char*  zz;
signed char** myPtr = (unsigned char)malloc(n*sizeof(zz));
jww
  • 97,681
  • 90
  • 411
  • 885
Alex Cornford
  • 148
  • 1
  • 10
1

The easiest way is to use a true 2D array, not just an array of pointers to arrays. You can allocate your 2D array like this:

signed char (*myPtr)[k] = malloc(n*sizeof(*myPtr));

With this allocation, you do not need a loop to malloc() line arrays. You can directly use your 2D array like this:

for (unsigned char j = 0; j < n; j++)
{
    for (unsigned char i = 0; i < k; i++)
    {
        myPtr[j][i] = 42; 
    }
}

Note that there is no change in how you access the array elements, even though the memory layout is quite a bit different. But, since you allocated your memory in a single large block, you can also deallocate it in one free() call:

free(myPtr);

It really pays off to learn C's pointer to array syntax.

cmaster - reinstate monica
  • 38,891
  • 9
  • 62
  • 106