4

I am coding a function that will return a 2D array. This leds me to think about the implications of this in C# (with garbage collection) and in C++(no GC)

(Why in both-you may ask: I am now writing it on a windows platform with C# but in some months I will implement my algorithms on an embedded device using C++)

So basically I have a 2D array say table, and through a function I assigned to it the return value. My question is: what happen to the original piece of memory that hold the original table??

Now to the code: In C#

    using System;

    public class Test
    {
        public static void Main()
        {
            int[,] table= new int [10,10];  //Here some memory is separated for table 
            int[,] table= createTable(10,10); //Here the return value of createTable is assigned to the original value 

//WHAT HAPPENED TO THE ORIGINAL MEMORY THAT table HAD?

            printTable(table,10,10); //disregard this. Not that important
        }

        public static int[,] createTable(int rows, int columns)
        {
            int[,] t = new int[rows,columns];
            for(int i=0;i<rows;i++)
              for(int j=0;j<columns;j++)
                 t[i,j]=(i+j);

             return t;    
        }

        public static void printTable(int[,]t, int rows, int columns)
        {
            for(int i=0;i<rows;i++)
              for(int j=0;j<columns;j++)
                Console.WriteLine(t[i,j]);

             foreach( var im in t)
               Console.WriteLine(im);
        }
    }

(Please don't tell me that the first new int is not necessary, etc. It is necessary for the question, it could be replaced by calling createTable twice)

I am guessing that in C#, the garbage collector takes care of this and I don't have to worry?


Now in C++

#include <cstdio>
#include <cstdlib>


int** createTable(int rows, int columns){
    int** table = new int*[rows];
    for(int i = 0; i < rows; i++) {
        table[i] = new int[columns]; 
        for(int j = 0; j < columns; j++){ table[i][j] = (i+j); }// sample set value;    
    }
    return table;
}
void freeTable(int** table, int rows){
    if(table){
        for(int i = 0; i < rows; i++){ if(table[i]){ delete[] table[i]; } }
        delete[] table;    
    }
}
void printTable(int** table, int rows, int columns){
    for(int i = 0; i < rows; i++){
        for(int j = 0; j < columns; j++){
            printf("(%d,%d) -> %d\n", i, j, table[i][j]);
        }    
    }
}
int main(int argc, char** argv){

    int** table = createTable(5, 5);
    table = createTable(10,10);
    printTable(table, 10, 10);
    freeTable(table, 10);
    return 0;
}

What happened to the original memory hold by table (5x5). Does it create a memory leak when table get assigned the 10x10 table?? how do I avoid this?

KansaiRobot
  • 7,564
  • 11
  • 71
  • 150
  • Just bear in mind that pointers aren't arrays and pointers to pointers aren't "2D arrays". And you can't return arrays in C++. That is a limitation of the language. – juanchopanza Aug 09 '17 at 08:44
  • This looks more like a C style approach rather than C++. Maybe use of smart pointers in C++ will solve your problem. – macroland Aug 09 '17 at 08:45
  • 2
    Use Raii objects (as `vector>`) to avoid to worry about memory deallocation. – Jarod42 Aug 09 '17 at 08:48
  • The `createTable` function itself already has a potential leak as every `new` may throw. Please do not try to write C++ based on the observation that some similar syntax works in another language, that will get you *very* poor results. If you want to write C++, learn the language systematically from a good book. – Baum mit Augen Aug 09 '17 at 08:50
  • I would suggest if you're going to allocate, use either std::make_shared or std::make_unique and learn the semantics of those. Then unless you're careless (circular references in shared_ptr) you'll never "leak" again. – Robinson Aug 09 '17 at 08:52
  • 1
    As a side note, if this is supposed to implement a matrix instead of an array of arrays, do *not* use nested vectors, but write a proper matrix class that properly guarantees invariants like the rectangular form of the matrix instead. There are also many good and free matrix implementations readily available in libraries like eigen. – Baum mit Augen Aug 09 '17 at 08:52

3 Answers3

2

For C++ part, yes, you create memory leak.you have to call freeTable:

int** table = createTable(5, 5);
freeTable(table, 5);
table = createTable(10,10);
printTable(table, 10, 10);
freeTable(table, 10);

Better to use Raii object as vector<vector<int>> (or dedicated class Matrix) to avoid to worry about memory deallocation (and you can add size information in class too).

So it would simply be:

auto table = createTable(5, 5);
table = createTable(10,10);
printTable(table);
Jarod42
  • 203,559
  • 14
  • 181
  • 302
  • `vector>` is a poor data structure for a matrix, even if you don't care about performance at all. It does not even guarantee that all rows have the same length. – Baum mit Augen Aug 09 '17 at 08:54
  • Why would using vectors avoid me creating leaks? – KansaiRobot Aug 09 '17 at 08:58
  • @BaummitAugen: I agree, but std doesn't have class `Matrix`, so without implementing one (or use external library), the faster/easier way is vector of vector. – Jarod42 Aug 09 '17 at 08:58
  • 1
    @KansaiRobot: Because vector is a [Raii](https://en.wikipedia.org/wiki/Resource_acquisition_is_initialization) object which deallocate internal memory correctly. – Jarod42 Aug 09 '17 at 09:00
  • @Jarod42 Meh. Either write a small one yourself, which is not hard or much work at all, or, if you need anything non-trivial, just use a library. Half-assed solutions like the `vector>` do not make for good and maintainable code bases. – Baum mit Augen Aug 09 '17 at 09:04
  • @Jarod42 Thanks for the link. I ll read it. Just one quick thing: is this a C++ 11 thing? (I hope not, the C++ compiler of my embedded device is not 11) – KansaiRobot Aug 09 '17 at 09:04
  • @KansaiRobot No, it's been around since the first standard from '98. I really highly recommend to properly learn C++ from a good book if you are going to use it, or use some other language instead. – Baum mit Augen Aug 09 '17 at 09:05
  • @BaummitAugen Thanks. Any recommendation of a good book on Advanced C++? I have read many of the C++ essentials and wrote working code for my embedded platform but I usually reflect on this things that I dont find on a basic text. – KansaiRobot Aug 09 '17 at 09:14
  • @KansaiRobot [Here](https://stackoverflow.com/a/388282/3002139) is our canonical list. – Baum mit Augen Aug 09 '17 at 09:16
2

In C# the garbage collector will take care of memory that isn't reachable any more.

I compiled your C++ program with gcc (Version: (Debian 6.4.0-1) 6.4.0 20170704 ) on Debian Buster.

valgrind which is a very useful tool for checking for various kinds of memory problems, gave me the following output with valgrind --leak-check=full ./test :

Memcheck, a memory error detector
Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
Using Valgrind-3.13.0 and LibVEX; rerun with -h for copyright info
Command: ./test

[... program output ...]

HEAP SUMMARY:
    in use at exit: 140 bytes in 6 blocks
  total heap usage: 19 allocs, 13 frees, 74,348 bytes allocated

140 (40 direct, 100 indirect) bytes in 1 blocks are definitely lost in loss record 2 of 2
   at 0x4C2C97F: operator new[](unsigned long) (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
   by 0x10883E: createTable(int, int) (in /home/user/code/test/test)
   by 0x108A26: main (in /home/user/code/test/test)

LEAK SUMMARY:
   definitely lost: 40 bytes in 1 blocks
   indirectly lost: 100 bytes in 5 blocks
     possibly lost: 0 bytes in 0 blocks
   still reachable: 0 bytes in 0 blocks
        suppressed: 0 bytes in 0 blocks

For counts of detected and suppressed errors, rerun with: -v
ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)

As you can see, you are definitely loosing memory. Either call your freeTable() function first and then reassign your pointer or use smart pointers to get around this problem.

EXIT_FAILURE
  • 278
  • 1
  • 13
0

At this line:

//WHAT HAPPENED TO THE ORIGINAL MEMORY THAT table HAD?

You're overwriting its reference. Since the original table is not referenced anymore, garbage collection will clean up the used space once it runs.

I can't answer your C++ regarding question, sorry for that.

Baum mit Augen
  • 49,044
  • 25
  • 144
  • 182
Ravior
  • 561
  • 2
  • 9
  • 30