3

I have a variable N. I need a 6xNxN array.

Something like this:

int arr[6][N][N];

But, obviously, that doesn't work.

I'm not sure how I'd go about allocating this so that I can access, e.g. arr[5][4][4] if N is 5, and arr[5][23][23] if N is 24.

Note that N will never change, so I'll never have to reallocate arr.

What should I do? Will int ***arr = malloc(6 * N * N * sizeof(int)); work?

No one
  • 73
  • 1
  • 6
  • 1
    In C99 and up, `int arr[6][N][N];` actually *does* work, but it also uses a lot of stack space and risks a stack overflow, so `malloc`ing the array is better. (`int ***arr = ...` is wrong, though.) – user2357112 Aug 09 '17 at 04:00
  • `int ***arr = malloc(6 * N * N * sizeof(int));` No, this will not work. – Ajay Brahmakshatriya Aug 09 '17 at 04:02
  • @user2357112 if `int ***arr = ...` is wrong, what is right? – No one Aug 09 '17 at 04:03
  • `int (*arr)[N][N]` – user2357112 Aug 09 '17 at 04:04
  • @user2357112 So `int (*arr)[N][N] = malloc(6 * N * N * sizeof(int));` would work? And how would I declare `arr` as an `extern` variable in a header file? – No one Aug 09 '17 at 04:06
  • Similarly as you declare other extern variables. Here N should also be an extern variable supposedly. – bit_cracker007 Aug 09 '17 at 04:08
  • @bit_cracker007 `extern int (*arr)[N][N];` gives `error: variably defined 'arr' at file scope` – No one Aug 09 '17 at 04:13
  • Ideally you should put extern just on N. And declare the array where it is actually required. – bit_cracker007 Aug 09 '17 at 04:23
  • The presence of the keyword `extern` in a C program is a pretty certain sign that your program design is crap. The only case where it is acceptable to use is when declaring `const` qualified variables than need to be accessed by more than one translation unit - but even then it is rather questionable practice. – Lundin Aug 09 '17 at 06:32
  • @Lundin depends. I agree for typical application code. If you're writing a single threaded interrupt-driven embedded code, having some `extern`s can be quite fine. –  Aug 09 '17 at 06:58
  • @FelixPalmen Not really. The interrupt callback should be located in the same source file as the code using the results from the interrupt. Shared variables between the ISR and the main program should then be local file scope variables declared as `static`. – Lundin Aug 09 '17 at 07:29
  • @Lundin which would require function calls to access objects of other translation units in more complex code, and you *might* want to optimize that, knowing that every part of your program works correctly. The uses are still rare, I only know about one single instance in [my own code](https://github.com/Zirias/shuttercontrol/blob/master/shutterctl_attiny84/eepdata.h). –  Aug 09 '17 at 07:34
  • @FelixPalmen You can write an inline function, but unfortunately that probably means that you'll end up having to expose the private variable anyway. If there are such extreme real-time requirements that even a single function call overhead is too much, then that's a very rare case. In such systems, the correct solution is most likely to pick the right MCU for the task. An 8 bit AVR is a very unlikely candidate for such a system... – Lundin Aug 09 '17 at 07:58
  • 3
    @Noone: `6 * N * N * sizeof(int)` is problematic because of potential overflow if type `int` is smaller than `size_t`, as is common on current 64-bit architectures. I was humbled some time ago by this very type of problem. For example: `int N = 30000;` should cause an allocation of about 21 billion bytes, which might fit in memory, but `6 * N * N` exceeds 32-bit `INT_MAX` and at best wraps before conversion to `size_t`, so `malloc` is passed a much lower value than expected. Always put the `sizeof()` operator first to avoid this problem. – chqrlie Aug 09 '17 at 07:59
  • @FelixPalmen Peeking at your project... in your specific case, the awkward Harvard architecture is the very reason why you need that variable to begin with. Von Neumann MCUs would simply read the data straight from the EEPROM with no overhead. So if you _must_ have direct access to that data without setter/getter functions because your real-time spec dictates it, the correct solution would have been to pick another MCU, for example Freescale HCS08, or better yet a modern Cortex M0. – Lundin Aug 09 '17 at 08:03
  • @Lundin it's just an example ;) I don't have a hard requirement here (the tiny MCU has more than enough power to do what it's supposed in this project), but I don't feel it's necessary to provide accessor functions for everything. In this case, the only thing that could ever change is more elements in the struct, it's just some configuration table. –  Aug 09 '17 at 08:13

4 Answers4

3

You can allocate your 3-dimensional array on the heap as

int (*arr)[N][N] = malloc(sizeof(int[6][N][N]));

After use, you can free as

free(arr);

Another way of writing the same as suggested by @StoryTeller is -

int (*arr)[N][N] = malloc(6u * sizeof(*arr));

But here you need to be careful about the u after 6 to prevent signed arithmetic overflow.

Also, there can still be issues on platforms where size_t is smaller in width that int as suggested by @chqrlie, but that won't be the case on "most" commonly used platforms and hence you are fine using it.

Ajay Brahmakshatriya
  • 8,993
  • 3
  • 26
  • 49
  • 2
    I upvoted, but I think `malloc(6u * sizeof *arr);` would be more idiomatic. Despite the nice demonstration of the "array type". – StoryTeller - Unslander Monica Aug 09 '17 at 06:09
  • @StoryTeller I believe that the version posted is more idiomatic, since your version does not necessarily result in a parameter of type `size_t`, which is what malloc expects. Implicit type conversions should be avoided. – Lundin Aug 09 '17 at 06:36
  • @Lundin - Are you familiar with a platform where `size_t` is of lower rank than unsigned int? `6u` should be fine. – StoryTeller - Unslander Monica Aug 09 '17 at 06:37
  • @StoryTeller Nope :) This is just language-lawyer stuff - in practice I very much doubt `size_t` will ever be smaller than the size of an `int`. However, there are no guarantees. And regardless, it is quite likely that `size_t` is _larger_ than unsigned int, in which case you still have an implicit type promotion inside the expression. – Lundin Aug 09 '17 at 06:41
  • @StoryTeller: the real problem with `malloc(6 * N * N * sizeof(int))` is the potential overflow in `6 * N * N` before conversion to `size_t`. A more generic and safer alternative would be `int (*arr)[N][N] = malloc(sizeof(*arr) * 6);` – chqrlie Aug 09 '17 at 07:34
  • @chqrlie - I suggested `6u` (note the unsigned suffix) precisely to avoid signed overflow. This leaves the promotion rank of course, but I think it's a non-issue. – StoryTeller - Unslander Monica Aug 09 '17 at 07:35
  • @StoryTeller: my bad, your `malloc(6u * sizeof *arr);` does not have this problem. Another commenter (Noone) used `malloc(6 * N * N * sizeof(int));`. In your case, there is no compelling reason to use `6u` instead of `6`, the unparenthesized `sizeof *arr` is somewhat confusing for beginners and placing the `sizeof` first would be a safer habit. – chqrlie Aug 09 '17 at 08:03
  • @chqrlie - It probably would be better, but a conscious effort is required to break certain habits. – StoryTeller - Unslander Monica Aug 09 '17 at 08:07
  • @StoryTeller: the only possibility for signed overflow in `6 * sizeof(*arr)`, is for `size_t` to be smaller than `int` by less than 3 bits. I am not sure if it is even allowed by the standard. – chqrlie Aug 09 '17 at 08:07
  • @StoryTeller I understand why you say `malloc(6u * sizeof *arr);` is idiomatic. Because usually when we say `malloc(sizeof(T))` we expect pointer of type T. Here we don't need a pointer to `int[6][N][N]` but a pointer to `6 int[N][N]`. I will add your suggestion to the answer. – Ajay Brahmakshatriya Aug 09 '17 at 08:20
  • @StoryTeller it's just that some one looking for the answer (not going through the comments) might miss why the `u` after `6` is important. – Ajay Brahmakshatriya Aug 09 '17 at 08:21
  • @AjayBrahmakshatriya - Be sure to take note of chqrlies comments too. They are most prudent. – StoryTeller - Unslander Monica Aug 09 '17 at 08:21
  • @StoryTeller Yes, I corrected. Could you explain why `int[N][N]` is not the same type as `*arr` ? Because of VLAs? – Ajay Brahmakshatriya Aug 09 '17 at 08:37
  • @AjayBrahmakshatriya - It is the same from the POV of `sizeof`. My issue with it is that one repeats the type. The point of the idiom is to never allow an accidental mismatch between the parameter to sizeof and the type of the pointee. If `arr` is ever modified to point at `int[M][N]`, for instance, the call to malloc will stay correct without requiring modification (that a poor careless programmer may forget). – StoryTeller - Unslander Monica Aug 09 '17 at 08:40
  • @StoryTeller Yes, that makes sense. I understood your point about not repeating before. It's just I couldn't follow why you said they were entirely different types. Thanks for the suggestion! – Ajay Brahmakshatriya Aug 09 '17 at 08:44
  • @AjayBrahmakshatriya - I meant `int[N][N]` not being the same as `int[6][N][N]`. At least the second one is what the OP wanted to have. Less confusion from that standpoint. – StoryTeller - Unslander Monica Aug 09 '17 at 08:49
2

int arr[6][N][N]; will work just fine. You merely need to update your compiler and C knowledge to the year 1999 or later, when variable-length arrays (VLA) were introduced to the language.

(If you have an older version of GCC than 5.0, you must explicitly tell it to not use an ancient version of the C standard, by passing -std=c99 or -std=c11.)

Alternatively if you need heap allocation, you can do:

int (*arrptr)[Y][Z] = malloc( sizeof(int[X][Y][Z]) );

You cannot do int ***arr = malloc(6 * N * N * sizeof(int)); since a int*** cannot point at a 3D array. In general, more than two levels of indirection is a certain sign that your program design is completely flawed.

Detailed info here: Correctly allocating multi-dimensional arrays.

Lundin
  • 195,001
  • 40
  • 254
  • 396
  • Be aware that VLAs are only *optional* in C11. And of course, they can't work at file scope, as OP was trying (see comments). Another reason to avoid them is possible stack overflow if you can't know the size. But of course, I agree about the *three-star programmer* issue. I'd opt for using a flat allocated array and writing expressions for indexing. –  Aug 09 '17 at 06:53
  • @FelixPalmen In practice nobody would use a compiler that supports C11 but not VLA, since VLAs are nowadays idiomatic C. Not so much because of actual VLAs, but because they allow array pointers and function parameter arrays with variable length. A compiler which did not support that would be useless for modern C programming. We need not concern ourselves with portability to useless compilers. – Lundin Aug 10 '17 at 06:17
  • that's a very strong opinion, but I don't follow it. *Idiomatic* C is still passing pointers and `size_t` parameters. VLAs received a lot of criticism, e.g. because you can't recover from OOM conditions. [This usenet post](https://groups.google.com/d/msg/comp.std.c/AoB6LFHcd88/BvA4NiBe2mcJ) (not authorative of course) has a nice list. –  Aug 10 '17 at 06:58
  • @FelixPalmen Modern, idiomatic C is for example: `void func (size_t n, int array[n])`. It is not `void func(int* array, size_t n)`, that's old style. Modern idiomatic C is `int (*arr)[y] = malloc( sizeof(int[x][y] );`, it is not `int* arr = malloc( x*y*sizeof(int) );`, that's C90 style mangled arrays. And so on. The modern version require that a pointer to VLA type can be used. To not use them is to return to 1990s style C programming. – Lundin Aug 10 '17 at 09:15
  • I don't think so. There are reasons to make VLA optional and very few cases where using VLA really makes for better code. You should at least be aware you're using an optional feature and make a concious decision about it. –  Aug 12 '17 at 07:50
  • @FelixPalmen I really can't see a reasons. Rather, it seems that the only reason they are optional is because of some political reason, such as the Microsoft lobby insisting on it. MS were too lazy to implement C99 and still don't want to do it now, 18 years later. – Lundin Aug 14 '17 at 06:14
1

What you want can't work directly. For indexing a multi-dimensional array, all but the very first dimension need to be part of the type and here's why:

The indexing operator operates on pointers by first adding an index to the pointer and then dereferencing it. The identifier of an array evaluates to a pointer to its first element (except when e.g. used with sizeof, _Alignof and &), so indexing on arrays works as you would expect.

It's very simple in the case of a single-dimension array. With

int a[42];

a evaluates to a pointer of type int * and indexing works the following way: a[18] => *(a + 18).

Now in a 2-dimensional array, all the elements are stored contiguously ("row" after "row" if you want to understand it as a matrix), and what's making the indexing "magic" work is the types involved. Take for example:

int a[16][42];

Here, the elements of a have the type int ()[42] (42-element array of int). According to the rules above, evaluating an expression of this type in most contexts again yields an int * pointer. But what about a itself? Well, it's an array of int ()[42] so a will evaluate to a pointer to 42-element array of int: int (*)[42]. Then let's have a look at what the indexing operator does:

a[3][18] => *(*(a + 3) + 18)

With a evaluating to the address of a with type int (*)[42], this inner addition of 3 can properly add 42 * sizeof(int). This would be impossible if the second dimension wasn't known in the type.

I guess it's simple to deduce the example for the n-dimensional case.


In your case, you have two possibilities to achieve something similar to what you want.

  1. Use a dynamically allocated flat array with size 6*N*N. You can calculate the indices yourself if you save N somewhere.

  2. Somewhat less efficient, but yielding better readable code, you could use an array of pointers to arrays of pointers to int (multiple indirection). You could e.g. do

    int ***a = malloc(6 * sizeof *int);
    for (size_t i = 0; i < 6; ++i)
    {
        a[i] = malloc(N * sizeof *(a[i]));
        for (size_t j = 0; j < N ++j)
        {
            a[i][j] = malloc(N* sizeof *(a[i][j]));
        }
    }
    // add error checking to malloc calls!
    

    Then your accesses will look just like those to a normal 3d array, but it's stored internally as many arrays with pointers to the other arrays instead of in a big contiguous block.

    I don't think it's worth using this many indirections, just to avoid writing e.g. a[2*N*N+5*N+4] to access the element at 2,5,4, so my recommendation would be the first method.

0

Making a simple change to the declaration on this line and keeping the malloc can easily solve your problem.

int ***arr = malloc(6 * N * N * sizeof(int));

However, int *** is unnecessary (and wrong). Use a flat array, which is easy to allocate:

int *flatarr = malloc(6 * N * N * sizeof(int));

This works for three dimensions, and instead of accessing arr[X][Y][Z] as in the question, you access flatarr[(X*N*N) + (Y*N) + Z]. In fact, you could even write a handy macro:

#define arr(X,Y,Z) flatarr[((X)*N*N) + ((Y)*N) + (Z)]

This is basically what I've done in my language Cubically to allow for multiple-size cubes. Thanks to Programming Puzzles & Code Golf user Dennis for giving me this idea.

MD XF
  • 7,860
  • 7
  • 40
  • 71