1

I am new to C.

I have a 2D array of structs whose sizes i,j are determined at runtime.

I want to pass this array as argument to a function and iterate over all elements.

Here is a prototype of the code:

typedef struct obj {
    char id[10];
    int value;
} Object;

I tried the following but it didn't work:

int sum_all_elements(Object *obj_ptr);

int main() {
    int i, j; //gotten from user input which I omitted

    Object o[i][j];

    int total = sum_all_elements(&o);
}

What is the correct way to pass o[i][j] and how to iterate over it in the function?

Also, I would like to create functions that update elements in the 2D array, is there a way I can make changes in a function and have the effects persist when I return from the function?

rrtyui
  • 19
  • 2

3 Answers3

1

With your typedef as follows:

enum { IDL = 10 };

typedef struct {
    char id[IDL];
    int value;
} obj;

If you are intent on using a 2D VLA, then you are better off thinking of passing the address of the first row, rather than the address of the first element. (even though the two address are the same) Why? An array (e.g. obj o[i][j];) when passed as a parameter to a function will have the first level of indirection converted to a pointer (e.g. obj o[i][j] becomes obj (*o)[j]).

Take for example your VLA declared in main as follows:

    obj o[i][j];    /* declare VLA */

So your function declaration will need to match (and since the number of columns is non-constant, be in the correct order) For instance:

int sum_all_elements (int i, int j, obj (*obj_ptr)[j]);

You can then call the function within your program as, for example:

    printf ("\n sum : %d\n\n", sum_all_elements (i, j, &o[0]));

which is simply equivalent to:

    printf ("\n sum : %d\n\n", sum_all_elements (i, j, o));

Putting a short example together (and just filling obj[a][b].value = a + b;), you can do something similar to the following:

#include <stdio.h>

enum { IDL = 10 };

typedef struct {
    char id[IDL];
    int value;
} obj;

int sum_all_elements (int i, int j, obj (*obj_ptr)[j]);

int main (void) {

    int i, j;

    printf ("\n enter i & j: ");
    if (scanf (" %d %d", &i, &j) != 2) {
        fprintf (stderr, "error: invalid input, i, j.\n");
        return 1;
    }

    obj o[i][j];    /* declare VLA */

    for (int a = 0; a < i; a++)
        for (int b = 0; b < j; b++)
            o[a][b].value = a + b;  /* fill value with misc a + b */

    printf ("\n sum : %d\n\n", sum_all_elements (i, j, o));

    return 0;
}

/* some all .value for i x j array of obj */
int sum_all_elements (int i, int j, obj (*obj_ptr)[j])
{
    int sum = 0;

    for (int a = 0; a < i; a++)
        for (int b = 0; b < j; b++)
            sum += obj_ptr[a][b].value;

    return sum;
}

Example Use/Output

$ ./bin/p2p2a

 enter i & j: 10 10

 sum : 900
David C. Rankin
  • 81,885
  • 6
  • 58
  • 85
  • note: it's also possible to write `obj objptr[i][j]` as the function parameter, some find this easier to read – M.M Oct 15 '16 at 01:20
  • Yes, agreed, good point. I always try and think of it in terms of the conversion, e.g. what is happening to the array when passed as a parameter, but it is equally correct written in full array form. – David C. Rankin Oct 15 '16 at 02:09
0

If you are using C99 or later you can use VLAs to accomplish this. Here is an example that asks the user to enter the information for the id and value fields of your Object struct, and then calls a function to display the contents of the 2d VLA:

#include <stdio.h>

typedef struct obj {
    char id[10];
    int value;
} Object;

void show_obj(int rows, int cols, Object arr[rows][cols]);

int main(void)
{
    int num_rows, num_cols;
    int row, col;

    num_rows = 3;
    num_cols = 3;

    Object obj_arr[num_rows][num_cols];

    for (row = 0; row < num_rows; row++)
        for (col = 0; col < num_cols; col++) {
            printf("Element [%d][%d]:\n", row, col);
            printf("    enter id: ");
            scanf("%9s", obj_arr[row][col].id);
            printf("    enter value: ");
            scanf("%d", &(obj_arr[row][col].value));
        }

    show_obj(num_rows, num_cols, obj_arr);

    return 0;
}

void show_obj(int num_rows, int num_cols, Object obj_arr[num_rows][num_cols])
{
    int row, col;

    for (row = 0; row < num_rows; row++)
        for (col = 0; col < num_cols; col++) {
            printf("Element [%d][%d]:\n", row, col);
            printf("    id: %s\n", obj_arr[row][col].id);
            printf("    value: %d\n", obj_arr[row][col].value);
        }
}

When you pass a VLA to a function, notice that you have to pass the dimensions of the array first.

Another approach is to manually allocate memory for the structs, and then to simulate a 2d array. You can allocate space for a number of pointers to pointers to Object structs. Each of these pointers points to the first Object of a row of Object structs, so you then allocate space to store Objects in each row. The following code demonstrates this second approach. Once it is set up, obj_arr is accessed and passed around as if it were a 2d array. One downside of this approach is that you are required to manually free your memory allocations. But, this approach will work on pre-C99 compilers:

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

typedef struct obj {
    char id[10];
    int value;
} Object;

void show_obj(Object **arr, int rows, int cols);

int main(void)
{
    int num_rows, num_cols;
    int row, col;

    num_rows = 3;
    num_cols = 3;

    Object **obj_arr = malloc(sizeof(Object *) * num_rows);

    for (row = 0; row < num_rows; row++)
        obj_arr[row] = malloc(sizeof(Object) * num_cols);

    for (row = 0; row < num_rows; row++)
        for (col = 0; col < num_cols; col++) {
            printf("Element [%d][%d]:\n", row, col);
            printf("    enter id: ");
            scanf("%9s", obj_arr[row][col].id);
            printf("    enter value: ");
            scanf("%d", &(obj_arr[row][col].value));
        }

    show_obj(obj_arr, num_rows, num_cols);

    /* Free allocated memory */
    for (row = 0; row < num_rows; row++)
        free(obj_arr[row]);
    free(obj_arr);       

    return 0;
}

void show_obj(Object **obj_arr, int num_rows, int num_cols)
{
    int row, col;

    for (row = 0; row < num_rows; row++)
        for (col = 0; col < num_cols; col++) {
            printf("Element [%d][%d]:\n", row, col);
            printf("    id: %s\n", obj_arr[row][col].id);
            printf("    value: %d\n", obj_arr[row][col].value);
        }
}
M.M
  • 138,810
  • 21
  • 208
  • 365
ad absurdum
  • 19,498
  • 5
  • 37
  • 60
  • OP clearly asks for a 2D array, which you don't show. You use a completely different datastructure. – too honest for this site Oct 14 '16 at 23:08
  • @Olaf-- maybe I misunderstood something, but I thought that OP wanted a 2d array of arbitrary dimensions known only at runtime. VLA approach would be one option, but this solution allows access as if it were a 2d array, and works on pre-C99 compilers. – ad absurdum Oct 14 '16 at 23:13
  • A pointer is not an array. A jagged array is very different from a 2D array and cannot represent one. You don't answer the question. (I didn't even account for your code not freeing memory correctly) – too honest for this site Oct 14 '16 at 23:14
  • Note to beginners: VLA stands for ‘variable length array’. – SevenBits Oct 15 '16 at 01:25
  • @M.M-- that was sloppy of me to use `scanf()` to read a string in like that. Thanks for the edit. – ad absurdum Oct 15 '16 at 01:27
  • To add: The VLA is the way to go and support by modern compilers. Ancient C90 should not be used anyway, it is not just VLAs, but e.g. designated initialisers and other features whioch make life easier. – too honest for this site Oct 15 '16 at 01:39
-2

Are you absolutely, positively sure that you need:

  • a 2-dimensional array?
  • …allocated as a VLA? VLAs are considered bad practice.

I`d recommend to use a «linear» array and allocate memory via malloc() (or calloc() if you want it zero-initialized upon creation). Something like that:

Object *o;
o = (Object*)calloc(i * j, sizeof(Object));

... (doing something with o)

int total = sum_all_elements(o, i, j);

... (now it is time to free the allocated memory)

free(o);
o = 0;

 

int sum_all_elements(Object *obj_ptr, int i, int j) {
    int x, y, s;
    for (s = y = 0; y < j; y++)
        for (x = 0; x < i; x++) {
            s += obj_ptr[j * y + x].value;
        }
    return s;
}
Community
  • 1
  • 1
hidefromkgb
  • 5,834
  • 1
  • 13
  • 44
  • 1
    VLAs are a tool. Just like pointers - and hammers - they're dangerous if you don't know how to use them. – Andrew Henle Oct 14 '16 at 22:53
  • 1
    VLAs are a very useful tool. The link is nonsense, the answer applies to all automatic variables, not specifically VLAs. Also There is no need to allocate a VLA as automatic variable, you can also use them with `malloc` etc. Said that, in which way is your approach safer? It leaves more to tthe programmer to remember and is more error-prone and less intuitive. – too honest for this site Oct 14 '16 at 23:05
  • For example, in a way that by VLA-ing a large enough array that the heap can effortlessly contain, one may summon a stack overflow /no pun intended/. – hidefromkgb Oct 14 '16 at 23:13
  • @hidefromkgb In general, if the heap "can effortlessly contain" an allocation request, there's enough memory to get a stack that big, too. We're back to knowing how to use the tool. – Andrew Henle Oct 15 '16 at 00:40
  • @AndrewHenle Not necessarily. Here are the current limits for my desktop OS, for example: $ ulimit -a | grep stack stack size (kbytes, -s) 8192 As can be seen, the process is not allowed to have more than 8 MB of stack, which is the default for Linux, AFAIK. – hidefromkgb Oct 15 '16 at 01:09
  • 1
    Let apart that you can allocate a VLa with `malloc` as well and C does not even mention a stack for automatic variables, according to your statement, a fixed-length array or a very large `struct` cannot overflow the stack? That's nonsense. As @AndrewHenle wrote: know your tools and now the limits of your implementation. There are only two reasons not to use a VLA: broken compilers which don't support modern C and users who don't know the language. – too honest for this site Oct 15 '16 at 01:34
  • @Olaf, okay, then please provide me with a compiler option that allows C99 VLAs to be automatically created on heap. The exact compiler does not matter, be it MSVC, GCC, ICC/ISPC or Clang. Any of them will do. W.R.T. static arrays: \`scuse me, did I really state what you think I did? No, I did not. – hidefromkgb Oct 15 '16 at 01:53
  • @hidefromkgb: Read my comment carefully **again** and think about what I wrote. If you don't understand a particular part, feel free to ask. A good reading would be the standard. As a sidenote: MSVC does not support standard C since ca. 17 years. Oh, and where did I even mention objects with static storage duration? You cannot even decleare a static VLA - for obvious reason. – too honest for this site Oct 15 '16 at 01:55
  • «There are only two reasons not to use a VLA: broken compilers which don't support modern C and users who don't know the language» — and also the cruel reality of modern operating systems which makes it happen so that VLAs are inferior to manually allocated storage. E.g., allocators can at least tell the program that there\`s no memory left. VLAs can not. Just execute and wait for an OS-level fault, simple as that. – hidefromkgb Oct 15 '16 at 02:31
  • @hidefromkgb *Here are the current limits for my desktop OS, for example: $ ulimit -a | grep stack stack size (kbytes, -s) 8192 As can be seen, the process is not allowed to have more than 8 MB of stack, which is the default for Linux, AFAIK.* Try it with `-h` to find your *real* limit. There are also other limits that can restrict heap size. But you have *control* of your limits. Again, *know your tools*. You're acting like someone who doesn't know how to use a chainsaw so you tell everyone they're dangerous. Nevermind your stack limit only applies to the main thread in the first place... – Andrew Henle Oct 15 '16 at 11:09
  • @hidefromkgb *allocators can at least tell the program that there`s no memory left. VLAs can not* Yes they can. Do you want to learn something? Go figure out how much virtual address space is available for the main thread stack on a 64-bit Linux process. Note also that on a 32-bit Linux process, the stack grows *down* from 0xC0000000 to 0x40000000, and the heap grows *up* from close to 0x00000000 to 0x40000000. That leaves somewhat less than 1 GB for the heap, but - get this - somewhat less than 2 GB for the stack. **The stack can be bigger than the heap**. Again - *know your tools*. – Andrew Henle Oct 15 '16 at 11:33
  • «Yes they can.» — Oh really. How? The exact method, please. «Try it with -h to find your real limit» — Not -h, but -Hs. _Know your tools_, all that stuff. «You're acting like someone who doesn't know how to use a chainsaw so you tell everyone they're dangerous» — Unfortunately, comment section is not the right place to flood you with all the reasons why using VLAs is wrong, so I\`ll keep distributing them one at a time. Our today\`s reason is that once the stack is allocated, it stays allocated until the program terminates, even when it is not used. And again you have no control over that. – hidefromkgb Oct 15 '16 at 14:58
  • @hidefromkgb *Unfortunately, comment section is not the right place to flood you with all the reasons why using VLAs is wrong* But you're trying anyway. Well, I guess if you don't know how to use a chainsaw, you'll cut your leg off. So, if *you* can't, *you* shouldn't. Meanwhile, those of us who know how to safely use the tools provided will continue to use them. – Andrew Henle Oct 15 '16 at 16:02
  • One rule of thumb the Internet taught me is to quit any discussion when the opponent\`s reasoning degrades to «argumentum ad hominem». This is it, you made it. I quit. – hidefromkgb Oct 15 '16 at 16:26