9

I have a huge code using a 3D array managed with pointers. Something like:

int *** tab;
tab = malloc(m*sizeof(int**));
for(i= 1..n) tab[i] = malloc(n*sizeof(int*));  
... etc...

and later the elements are accessed with:

tab[i][j][k] = ...

But because of specific issues with this structure, I would like to declare tab as a contiguous array instead but still use the syntax with 3 brackets in the code. The compiler will internally replace them like this:

tab[i][j][k] = ...  =>  tab[i*m*n+j*m+k] = ...

So the array is accessed with only one pointer dereference. I'd like not to change the source code (no sed).

For example I could do this by declaring tab in stack:

int tab[n][m][l];

but unfortunately this doesn't work if m and n are runtime variables.

Saiph
  • 510
  • 3
  • 16

6 Answers6

5

A C++ way is to enclose the 3D array in a class to have a natural accessor:

struct Arr3D
{
    int *arr;
    const int n, m, p; //size of the tab in 3 dimensions

public:
    Arr3D(int n, int m, int l): n(n), m(m), p(l) {
        arr = new int[n * m * p];
    }

    ~Arr3D() {
        delete[] arr;
    }

    int& val(int i, int j, int k) {   // read-write accessor
        // optionaly test for 0<=i<n...
        return arr[k + p * (j + i * m)];
    }
};

You create and use an array simply with:

Arr3D * parr = new Arr3D(3,4,5); // dynamic allocation
Arr3D arr(3, 4, 5);              // automatic allocation
...
arr(1,2,3) = 5;
int i = arr(2,0,1);

Alternatively, if you want to keep the syntax tab[i][j][k] you can if you use an auxilliary Arr2D class able to provide a view on a 2D array:

struct Arr2D
{
    int *arr;
    const int n, m; //size of the tab in 2 dimensions
    const bool is_view;

public:
    Arr2D(int n, int m): n(n), m(m), is_view(false) {
        arr = new int[n * m];
    }
    Arr2D(int *arr, int n, int m): arr(arr), n(n), m(m), is_view(true) {}

    ~Arr2D() {
        if (! is_view) delete[] arr;
    }

    int * operator[] (int i) {
        return arr + i * m;
    }
};

struct Arr3D
{
    int *arr;
    const int n, m, p; //size of the tab in 3 dimensions

public:
    Arr3D(int n, int m, int l): n(n), m(m), p(l) {
        arr = new int[n * m * p];
    }

    ~Arr3D() {
        delete[] arr;
    }

    Arr2D operator[](int i) {
        return Arr2D(arr + i * p * m, m, p);
    }
};

You can now simply use arr[i][j][k] ...

Serge Ballesta
  • 143,923
  • 11
  • 122
  • 252
4

To achieve this you can first allocate the full "tab"

int* tab = malloc(sizeof(int)* i * j * k);

Which will give you your data. This pointer will serve as the owner of the array.

After that you can create your "3d" array by building up your array of access pointer to the tab variable. Which will result in you having a int*** access; that has all of its internal pointers pointing to the correct portions of tab allowing you to access the tab via access.

this assumes you are sticking to C style code.

If You are looking to do this using a C++ style:

See: What is the most efficient way to initialize a 3D vector?

This post provides a good starting point to doing the same thing using std::vector

Community
  • 1
  • 1
Alex Zywicki
  • 2,263
  • 1
  • 19
  • 34
  • I removed the cast, I had originally added the cast because it seemed that the OP was working with "C style code" but possibly compiling using a C++ compiler which would require the cast. "C style" meaning using the C memory allocation mechanisms. – Alex Zywicki May 26 '16 at 14:56
  • Since you removed the malloc cast, and thun prevented compiling with a C++ compiler, why don't you instead show an example using pointers to variable length arrays, which would prevent the unnecessary memory and time usage. – 2501 May 26 '16 at 14:58
  • If I understand correctly, this would require me to change every occurence of tab[...] by access[...]. I don't want it. My goal is to access the array with only one dereferencing , and I want to change the code as little as possible. – Saiph May 26 '16 at 14:58
  • @Saiph that may not be true, assuming that 'tab' is the variable that the rest of your code uses access like 'tab[0][0][0]` simply make 'tab' your access variable. And have the actual memory be owned by a pointer with another name of your choice. – Alex Zywicki May 26 '16 at 15:01
  • @Alex Zywicki That's right. However, tab will then be a int *** and the compiler will access it with 3 pointers. I want the array to be accessed with [ i * m * n+j * m+k ] (only one dereference, I edited my post to highlight that part). The reason why I do that is that I'm porting the code on GPU with a framework that doesn't allow to copy multidimensional arrays. – Saiph May 26 '16 at 15:08
  • @Saiph Then just allocate a 1D array with a size of (d1*d2*d3*sizeof(int)) and access using the index calculations you are using – Alex Zywicki May 26 '16 at 15:12
  • @Alex Zywicki But this would require modifying all the source, and won't look very nice. That's the purpose of my question. I believe there's a simple way to replace it, because that's already what the compiler natively does when I write tab[i][j][k] and tab is allocated in the stack. – Saiph May 26 '16 at 15:21
2

In C (C99 or C11), the tab array with variable dimensions can be passed as a function parameter as long as its dimensions are also passed in the preceding parameters. Here is an example to show what I mean:

#include <stdio.h>
#include <stdlib.h>

int sum3d(unsigned int dim_n, unsigned int dim_m, unsigned int dim_l,
          int tab[dim_n][dim_m][dim_l])
{
    int total = 0;
    int n, m, l;

    for (n = 0; n < dim_n; n++)
    {
        for (m = 0; m < dim_m; m++)
        {
            for (l = 0; l < dim_l; l++)
            {
                total += tab[n][m][l];
            }
        }
    }
    return total;
}

int main(void)
{
    unsigned int dim_n, dim_m, dim_l;
    unsigned int n, m, l;
    int tot;

    dim_n = 10;
    dim_m = 5;
    dim_l = 4;

    int (*tab)[dim_m][dim_l] = calloc(dim_n, sizeof(*tab));
    if (!tab)
    {
        fprintf(stderr, "Memory allocation failure!\n");
        exit(1);
    }
    for (n = 0; n < dim_n; n++)
    {
        for (m = 0; m < dim_m; m++)
        {
            for (l = 0; l < dim_l; l++)
            {
                tab[n][m][l] = 1;
            }
        }
    }

    printf("total = %d\n", sum3d(dim_n, dim_m, dim_l, tab));
    return 0;
}

In function sum3d, tab could have been declared as int tab[][dim_m][dim_l], or as int (*tab)[dim_m][dim_l], omitting the leftmost dimension in both cases.

Ian Abbott
  • 15,083
  • 19
  • 33
1

You have tagged this as both C and C++, so different solutions are possible.

A C solution would be of the form

#include <stdlib.h>    /*  assumed */

int* tab = malloc(sizeof(int)*n*m*l);

tab[i*m*n+j*m+k] = 42;

free(tab);   /* when done */

This C solution can technically be used in C++ (albeit with an (int *) type conversion to the result of malloc()). However, in C++ this is discouraged in favour of

int* tab = new int[n*m*l];

tab[i*m*n+j*m+k] = 42;

delete [] tab;    // when done

An even better C++ approach is to use the standard library

#include <vector>

std::vector<int> tab(n*m*l);

tab[i*m*n+j*m+k] = 42;

//  tab will be automatically released when it passes from scope.

Of course, all of these will access elements with a single deference. But computing the indices involves a number of multiplications which are not particularly inexpensive operations either. It would be necessary to test to determine which is more efficient/effective.

Peter
  • 35,646
  • 4
  • 32
  • 74
  • I am on a hardware where I know the multiplications will be a lot less expensive. However my problem is that I don't want to replace all occurences of tab[i][j][k] by tab[i\*m\*n+j\*m+k] in my code. I'd like a way to tell the compiler to do it automatically, like it already does when using stack arrays. – Saiph May 26 '16 at 15:14
  • 1
    @Saiph in that case you are asking a very different question that we all had originally though. Unfortunately the best long term solution would be to refactor the code entirely rather than trying to patch the existing code using some clever preprocessor solution. How big is your code base actually? – Alex Zywicki May 26 '16 at 15:18
  • The current code is already huge (hundreds of accesses) but it would still be easy with a sed. The problem is I may also have other codes like that in the future so I'd like a more generic solution. – Saiph May 26 '16 at 15:39
  • There is no "generic solution" within the constraints you describe. You'll need to relax one or more constraints. For example, C++ only rather than C and C++. Allowing some selective modification of the code. There will not be some solution whereby you just add an `#include "some_header"` to your files, since the processor isn't able to do the sort of transformations needed. – Peter May 27 '16 at 10:18
0

For a two-dimensional int array with dimensions n x m, you can do this (in C):

size_t n;
size_t m;
...
// base all sizeof() calls directly on array so it's
// trivial to change the type from int to something else
int **array = malloc( n * sizeof( *array ) );
array[ 0 ] = malloc( n * m * sizeof( **array ) );
size_t rowSize = m * sizeof( **array );
for ( size_t i = 1; ii < m; ii++ )
{
    array[ i ] = array[ i - 1 ] + rowSize;
}

Expanding that to three dimensions is fairly easy (as is adding error checking...)

The real trick is doing everything with a single call to malloc().

Andrew Henle
  • 32,625
  • 3
  • 24
  • 56
0
#define SUB(x,y,z) ((x)*m + (y))*n + (z)

         . . . . .

tab[SUB(i,j,k)] = ....
PMar
  • 11
  • 1
    This does not provide an answer to the question. Once you have sufficient [reputation](http://stackoverflow.com/help/whats-reputation) you will be able to [comment on any post](http://stackoverflow.com/help/privileges/comment); instead, [provide answers that don't require clarification from the asker](http://meta.stackexchange.com/questions/214173/why-do-i-need-50-reputation-to-comment-what-can-i-do-instead). – help-info.de May 26 '16 at 19:16
  • 1
    Welcome to Stack Overflow! Could you explain how your answer addresses the problem(s) from the question? Code-only answers are not very useful, especially for further readers that stumble upon this post. Thanks! – Cristik May 26 '16 at 19:27
  • @help-info.de This is a valid answer...OP even specifically asked for preprocessor macro. – gariepy May 27 '16 at 04:07