2

I am using a library that returns the image as a big 2D array int**. I need to convert it into int* 1D array. I think I've managed to do it quite fast by copying memory blocks:

// have int labels** as 2D array, also have rows and cols 

//create 1D array
int *labels1D = new int[rows*cols];

//copy contents
for(int i = 0; i < rows; i++) {        
    // Here I don't know anything about how those arrays were allocated
    // Maybe std::copy would handle it for me?
    std::copy_n(labels[i], cols, labels1D + i*cols);
}

So the first question is whether I can do something better here? Is everything safe here assuming that library is a black box?


I do not want much to modify the library code, but I've found additionally how the source array in my side library this->currentLabels was created:

int** currentLabels; //in class declaration
...
// in the code
this->currentLabels = new int*[this->height];

for (int i = 0; i < this->height; ++i) {
    this->currentLabels[i] = new int[this->width];

    for (int j = 0; j < this->width; ++j) {
     // some code for setting the value
        }

    }

Looks like the values for rows and cols are known.

So the second question is: can I modify this code to make it allocate the 2D array in one memory block:

this->currentLabels = malloc(nrows*sizeof(int*) + (nrows*(ncolumns*sizeof(int)));

to allow me then just map it somehow to my 1D array without copying memory?


EDIT: Thanks to @SamVarshavchik , the mapping seems to be working in the following way:

// Allocate 2-D array as one block:

// Allocate pointers:
int** labels = new int*[rows];
// Allocate data:
auto ptr=new int[rows*cols];

for(int i = 0; i < rows; i++) {
    labels[i] = &ptr[i*cols];
}

// fill with values ranging 0 to certain number
for(int i = 0; i < rows; i++){
    for(int j = 0; j < cols; j++){
        // the code for setting the values
        labels[i][j] = i+j;
    }
}    

// have int labels** as 2D array, also have rows and cols 

//create 1D array
int *labels1D; // = new int[rows*cols];

//assign contents:
labels1D = &labels[0][0];

The right way to destroy it in the library code seems to be

delete[] ptr;  //user2079303 fixed
delete[] labels;
Slowpoke
  • 1,069
  • 1
  • 13
  • 37
  • 2
    Yes, you can modify the library code to allocate a one dimensional array, and then just allocate an array of pointers, separately, to each row. However, there's probably code in the library that attempts to deallocate this 2d array, and you will need to find it and make a similar change there, too. Also, the existing library code uses `new`, so you better stick with it, instead of using `malloc`. There's no ultimate difference betwen `malloc` and `new`, in this use case. – Sam Varshavchik Dec 28 '16 at 23:39
  • @SamVarshavchik Thanks, I'm searching for the code how to do it, but I still can't get how to implement it, because for 1-D array I need to address the memory block after the block of pointers `nrows*sizeof(int*)` – Slowpoke Dec 28 '16 at 23:50
  • 1
    This is not complicated. The one-dimensional array gets allocated as `auto ptr=new int[width*height]`, and the pointer to row #n is `&ptr[n*width]`. Fairly straightforward. – Sam Varshavchik Dec 28 '16 at 23:52
  • @SamVarshavchik Thanks very much, will try to get it working... – Slowpoke Dec 28 '16 at 23:55
  • 1
    @Slowpoke [More information on creating a contiguous 2D array](http://stackoverflow.com/questions/21943621/how-to-create-a-contiguous-2d-array-in-c/21944048#21944048). BTW, what you posted in the 2d array creation done by the third-party code is naive, at best. It can cause heap fragmentation if the height is large, plus the creation becomes slower due to making excessive calls to the allocator. – PaulMcKenzie Dec 29 '16 at 00:48

2 Answers2

3

So the first question is whether I can do something better here?

You could use std::vector to make the memory management safer and simpler. I don't see much else to improve.

The problem is that I need to send int * into another object (OpenCV Mat constructor) so I'm limited with exactly those types. Thanks anyway!

This is not a problem. You can use the data member function of vector, that returns a pointer to the internal array that you can send into your another project.


So the second question is: can I modify this code to make it allocate the 2D array in one memory block:

I'm assuming that you're bound to the interface that requires you to pass around int**.

If you can accept two allocations, that is simple: First allocate the array of pointers of appropriate size. Then allocate a flat array containing all values, and assign it to the first element of the pointer array. Then assign the rest of the pointers to correct positions of the value array.

A single allocation is possible, but tricky. You can allocate a raw char array big enough for both the array of pointers and the value array, and construct with placement new. This is tricky since it is very low level, and you must make sure that the arrays are aligned properly, and you must allocate extra space to make the alignment possible. This would be easier to implement in C which has aligned_alloc (which seems to be in the upcoming C++17 as well).


The right way to destroy it in the library code seems to be

delete ptr;
delete labels;

No, that seems to be the wrong way. The correct way to delete memory allocated with new[] is delete[].

Community
  • 1
  • 1
eerorika
  • 232,697
  • 12
  • 197
  • 326
  • The problem is that I need to send `int *` into another object (OpenCV Mat constructor) so I'm limited with exactly those types. Thanks anyway! – Slowpoke Dec 29 '16 at 00:39
  • Ok, thanks for corrections! I've edited the code in the post in case someone would use it – Slowpoke Dec 29 '16 at 00:42
  • 1
    @Slowpoke - You could use a vector of type , just pass a pointer to the first element; | vector example(number_of_elements); | then an int * is created using &example[0]. – rcgldr Dec 29 '16 at 00:47
  • @Slowpoke I added answer to second question as well. – eerorika Dec 29 '16 at 00:57
  • @user2079303 Thanks for the detailed explanation. Yes, I need to handle `int **` as it is modified further in the library code before the result is obtained, maybe I will study the ways to replace it on the second stage. I think I've managed to do it in two stages using SamVarshavchik's hints and it seems that the library is working with the modified code. My first idea was to allocate everything in one stage using malloc (see in the question) but I think I would need to do some calculations then to address array correctly – Slowpoke Dec 29 '16 at 01:09
1

You might get a bit of an improvement by using pointer arithmetic rather than array access.

We may use pointers to track our source and target and increment them on each pass of the loop, this way we save multiplications. Doing so with each involved pointer also has the advantage of removing the need for variable i, which saves all operations involving it, just need to compute the end pointer. Also checking for inequality is usually faster than a "less than" comparission.

//create 1D array
int *labels1D = new int[rows*cols];

//copy contents
int *targetPointer = labels1D;
int **sourcePointer = labels;
int *endTargetPointer = targetPointer + rows*cols;
while( targetPointer != endTargetPointer) {        
    std::copy_n(*sourcePointer++, cols, targetPointer );
    targetPointer += cols;
}

Though I would not be surprised if some optimizers are able to get code like this from OP's original code.

Thomas
  • 21
  • 4
  • Thanks! Though I've ended up with modifying the library to receive contiguous arrays, that may be useful for someone with the same problem as mine – Slowpoke Dec 29 '16 at 01:19