0

I want to declare a global 2d array in C, and allocate contiguous memory at runtime because the minor dimension is unknown at compile time. I would like to dereference the array with 2-indices notation A[i][j]. If the array wasn't global c99 notation "double A[m][n]" would be handy but in my case does not apply. What would be the right strategy?

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

//TO DO
//DECLARE array here

void fun1() {
    array[3][2] = 42.0;
}

int main(int argc,char *argv[])
{
    int rows = atol(argv[1]);
    int cols = atol(argv[2]);

    //TO DO
    //Allocate memory for array here
    fun1();
    printf("Array[3][2]=%f\n",array[3][2]);

    return(0);
}
NickSar68
  • 55
  • 5
  • 1
    Your question title says `at compile time C`, but in the post itself you say `allocate contiguous memory at runtime`. So... which is it? – user4520 Dec 12 '15 at 12:50
  • The dimensions are passed through command line arguments, but the array has to be declared global. – NickSar68 Dec 12 '15 at 15:39
  • Then you need to allocate it at _runtime_; command line arguments with which the program is invoked are obviously unknown at compile time. Please edit the question to reflect this. – user4520 Dec 12 '15 at 15:47
  • I did some modifications. I hope now it's more clear. – NickSar68 Dec 12 '15 at 15:58

2 Answers2

1

Unfortunately, it not quite possible in C to achieve what you are asking for.

There is a slightly ugly solution with a macro. Since the macro refers to both the global array itself and a global variable containing its second dimension, you have to be careful to not shadow the globals in any function which uses the macro. Here, I used clunky names ending with underscores to try to avoid name reuse.

Other than that, it should work:

void*  global_array_void_;
size_t global_array_minor_dim_;

#define global_array ((double(*)[global_array_minor_dim_])global_array_void_)    

Before you can use the macro, you need to allocate it and initialize the global variables:

void init_global_array(int rows, int cols) {
    global_array_minor_dim_ = cols
    global_array_void_ = malloc(rows * cols * sizeof global_array[0][0]);
}

From then on, you can use use global_array as though it were a regular array:

void f(int r, int c, double v) {
    global_array[r][c] = v;
}

Live on coliru.

The type in the cast ((double (*)[cols])(array_void_)) might not be obvious. It represents a pointer to an array of cols doubles, which is what double[][cols] would decay to as a pointer. Note that double[][cols] does not decay to double**, which is a completely different (and incompatible) type.

With that definition, sizeof global_array[r] has the correct value: cols * sizeof(double). Contrast that with sizeof argv[i].


A more traditional way of doing this is to use a function to compute the precise index. That still depends on the minor dimension being available:

double* global_array;
size_t global_array_minor_dim_;

void init_global_array(int rows, int cols) {
    global_array_minor_dim_ = cols
    global_array_void_ = malloc(rows * cols * sizeof global_array[0][0]);
}

double* global_array_at(int r, int c) {
    return &global_array[r * global_array_minor_dim_ + c];
}

Now you can use *global_array_at(r, c) as a replacement for global_array[r][c]. In C, it's impossible to eliminate the * and still have assignment work (in C++, the function could have returned a double& instead of a double*), but this could be solved, once again, with a macro.

rici
  • 234,347
  • 28
  • 237
  • 341
-2

Here is a sample for you to follow:

#include <stdlib.h>
void main(void)
{
    int **f;
    int n = 2, m = 3;

    f = (int **)malloc(sizeof(int)*n *m);

    f[1][2] = 3;
}
MByD
  • 135,866
  • 28
  • 264
  • 277
nicomp
  • 4,344
  • 4
  • 27
  • 60
  • 3
    The code you posted does not compile, is badly formatted and uses a bad practice (casting the result of `malloc`). – user4520 Dec 12 '15 at 12:57
  • It compiles just fine. The formatting is OK. There are some blank lines that could be removed but the indenting is correct. Variable names are weak because there is no context, but that can't be avoided. Casting malloc is one way to solve the problem presented buy the OP. There are probably other solutions as well. Feel free to write your own if you like. – nicomp Dec 12 '15 at 13:08
  • No, it doesn't compile for me (MSVC 14.0 which implements most of the C99 standard). IdeOne, which uses GCC, also complains: https://ideone.com/9i7ysI . What should this mean, according to you: `sizeof(int()*n *m)`? Oh, and yet another issue: `void main(void)` is not a valid entry point signature. – user4520 Dec 12 '15 at 13:14
  • It compiles in Visual Studio 2013 Version 12.0.40629.00 Update 5, .Net Framework Version 4.5.51209. – nicomp Dec 12 '15 at 13:23
  • I fixed a typo in the malloc statement that was allocating an int() rather than int: http://ideone.com/lI1qMm. My bad, but I will not get into arguing about the entry point because it's like arguing Kirk vs Picard and it does compile using ideone as well as Visual Studio and GCC and Turbo C and probably Atari C. It's all good. – nicomp Dec 12 '15 at 13:25
  • 2
    Also, it doesn't work even if the syntax error is fixed. `f[1][2]` compiles if `f` is `int**` but it does not refer to a position in a two-dimensional array. It expects `f[1]` to be an index into a pointer array. – rici Dec 12 '15 at 13:56
  • @nicomp: "It's all good" is an odd description for code which segfaults (or otherwise exhibits UB) by dereferencing uninitialized memory. – rici Dec 12 '15 at 14:03
  • It's also really bad for holding open a screen door. I never said it would produce useful results. It's a template for the OP to follow, which is clearly indicated in the original answer I wrote. – nicomp Dec 12 '15 at 14:08
  • 2
    @nicomp: OP asked for a 2d array in contiguous memory. You are offering a 1D array of pointers to 1D arrays, but you don't allocate or initialize thr index array. So it is not an answer ti the question. And it doesn't work. – rici Dec 12 '15 at 15:05
  • @rici, if you learn to manipulate the indices properly it works just fine. And it is an answer to the question because a 2D array is a pointer and an offset just like a 1D array. – nicomp Dec 12 '15 at 15:36
  • 1
    @nicomp: If by "manipulate the indices properly", you mean "write `f[i][j]` as `f[i*m+j]`, then that would work (provided `f` were declared as `int*` instead of `int**`). But the OP asks for a solution in which you can write `f[i][j]` and your sample code actually uses `f[i][j]`. And, not by coincidence, it segfaults. Now, if you disagree with my analysis, you can prove me wrong easily by editing the code in your answer so that it *doesn't segfault* (and, ideally, without changing `f[1][2] = 3;` to `f[1*3+2] = 3;`). That should be simple, ¿no? – rici Dec 12 '15 at 17:16