1

I am having trouble creating and accessing a struct pointer as an array. This is a homework question but it is just my misunderstanding of operations.

My header file (which I cannot change and have to implement this way) contains:

typedef struct gw_struct GW;

extern GW *gw_build(int nrows, int ncols, int pop, int rnd);

In the implementation file I have implemented both of the declarations

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

struct gw_struct {
    int Alive;
    int row;
    int column;
    int id;
};

GW *gw_build(int nrows, int ncols, int pop, int rnd){

    GW* list = NULL;
    int rows = nrows;
    int cols = ncols;

    for(nrows = 0; nrows < rows; nrows++){
        for(ncols = 0; ncols < cols; ncols++){
            GW *nn = malloc(sizeof *nn);

            if (nn == NULL){
                break;
            }

            list = nn;
        }

    }

    return list;
}

And finally my test file contains:

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

int main (){

    GW* World = gw_build(50, 50, 34, 1);

    World[0][0] -> Alive = 2;         //Can't get this to work
}

Lastly, I compile my program with

gcc -c gw.c
gcc gw.o main.c

The struct pointer seems to be created, however I cannot access it like an array.

I have tried to solve this in a number of ways. I originally initialized the variable world as

GW* World[50][50] = gw_build(50,50,34,1);

and i tried the same thing with "list" in the gw_build function yet it returned initialization errors.

Going without the loop and doing

GW* list[nrows][ncols];

is not allowed as well because you cannot have a variable as a size field.

I could create, maloc, and access a

GW* World[50][50];

in the main function itself and everything worked fine, but once I try to set it by returning from the gw_build function, it simply does not work. I therefore tried changing the return type but once again, it led to an invalid return error.

Any guidance is appreciated, thank you!

mrybak3
  • 385
  • 1
  • 3
  • 14

4 Answers4

2

You have stumbled onto the challenge where you must simulate a 2d array from a simple 1d array. If you look at the function declaration you are given, you have GW *gw_build(...) which tells you gw_build will return an array of struct GW. Now ideally, you could make things easier by creating an array of pointers to struct GW, but obviously part of the assignment is to have you work with a simple array of struct.

This simplifies the allocation, but complicates the indexing. Meaning, your declaration of gw_build can simply be:

GW *gw_build(int nrows, int ncols, int pop, int rnd){

    GW *list = calloc (nrows * ncols, sizeof *list);

    if (!list) {
        fprintf (stderr, "gw_build() error: virtual memory exhausted.\n");
        return NULL;
    }

    return list;
}

But the challenge then comes in filling and referencing each, especially if you want a pseudo-2d representation. The assignment is really an exercise in understanding pointers as well as index manipulation. The best way to talk about this is by example. (note: below, I've included the declaration of gw_struct in gw.h, but if you can't do that, just put the declaration in each source file)

#ifndef _gw_header_
#define _gw_header_ 1

typedef struct gw_struct GW;

struct gw_struct {
    int Alive;
    int row;
    int column;
    int id;
};

extern GW *gw_build(int nrows, int ncols, int pop, int rnd);

#endif

The #ifdef _gw_header_ is just an example of controlling header file inclusion. It insures gw.h is only included once. You can scrap it if you can't add to the header file.

The source file gw.c simply needs to allocate nrows * ncols structs, so it can be as simple as:

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

#include "gw.h"

GW *gw_build(int nrows, int ncols, int pop, int rnd){

    GW *list = calloc (nrows * ncols, sizeof *list);

    if (!list) {
        fprintf (stderr, "gw_build() error: virtual memory exhausted.\n");
        exit (EXIT_FAILURE);
    }

    return list;
}

Both standard and not much to discuss. The real work comes in your source file containing main() where you will fill and use the array returned by gw_build.

#include <stdio.h>

#include "gw.h"

#define ROWS 10
#define COLS 10

int main (void) {

    GW *world = gw_build (ROWS, COLS, 34, 1);
    int i, j;

    for (i = 0; i < ROWS; i++)
        for (j = 0; j < COLS; j++)
            (world + i * ROWS + j)->Alive = (i*ROWS + j) % 3;

    for (i = 0; i < ROWS; i++)
        for (j = 0; j < COLS; j++)
            printf (" world[%2d][%2d] : %d\n", i, j, (world + i*ROWS + j)->Alive);


    return 0;
}

(for example purposes, I've just filled Alive with values between 0-2 depending on the index value)

When you receive the allocated array back from gw_build you must manage filling and use by offset from the beginning of the array. You can simply use (0 < index < (nrows*ncols)), but that will defeat using the pseudo 2D indexing. The trick is simply finding a way to calculate and reference each offset using 2D array syntax. If you note above, the offset of each struct is accessed by i * ROWS + j. That allows the psuedo-2D reference of array[i][j] where behind the scene i represents i * nrows and j just the additional offset from that address.

You also have the choice of using the -> operator to reference the cells as shown above, or you can write an equivalent form that uses the . operator for struct member reference. The alternative is shown below:

#include <stdio.h>

#include "gw.h"

#define ROWS 10
#define COLS 10

int main (void) {

    GW *world = gw_build (ROWS, COLS, 34, 1);
    int i, j;

    for (i = 0; i < ROWS; i++)
        for (j = 0; j < COLS; j++)
            world[i * ROWS + j].Alive = (i * ROWS + j) % 3;

    for (i = 0; i < ROWS; i++)
        for (j = 0; j < COLS; j++)
            printf (" world[%2d][%2d] : %d\n", i, j, world[i * ROWS + j].Alive);


    return 0;
}

Look over both and let me know if you have questions. I've used two #define statements to fix nrows and ncols for the example. Compiling and running the code, you will see the output:

$ ./bin/gwtest
 world[ 0][ 0] : 0
 world[ 0][ 1] : 1
 world[ 0][ 2] : 2
 world[ 0][ 3] : 0
 world[ 0][ 4] : 1
 world[ 0][ 5] : 2
 world[ 0][ 6] : 0
 world[ 0][ 7] : 1
 world[ 0][ 8] : 2
 world[ 0][ 9] : 0
 world[ 1][ 0] : 1
 world[ 1][ 1] : 2
 world[ 1][ 2] : 0
 world[ 1][ 3] : 1
 world[ 1][ 4] : 2
 world[ 1][ 5] : 0
 world[ 1][ 6] : 1
 world[ 1][ 7] : 2
 world[ 1][ 8] : 0
 world[ 1][ 9] : 1
 world[ 2][ 0] : 2
...
 world[ 8][ 8] : 1
 world[ 8][ 9] : 2
 world[ 9][ 0] : 0
 world[ 9][ 1] : 1
 world[ 9][ 2] : 2
 world[ 9][ 3] : 0
 world[ 9][ 4] : 1
 world[ 9][ 5] : 2
 world[ 9][ 6] : 0
 world[ 9][ 7] : 1
 world[ 9][ 8] : 2
 world[ 9][ 9] : 0
David C. Rankin
  • 81,885
  • 6
  • 58
  • 85
  • Doing it the correct and nice way sometime is the hard way ... ;-) – alk Oct 04 '15 at 08:08
  • Amen..., but doing it the hard way is probably just what the devious professor planned -- just to cement some of the basics... – David C. Rankin Oct 04 '15 at 08:14
  • Absolutely marvelous answer. Thank you so much! – mrybak3 Oct 04 '15 at 20:29
  • Glad I could help. Once you get over the hump of truly learning how to manage pointers, allocation and the subtleties passing them between functions, you can really start to see why there is no language that can match the flexibility, power and control of C. When you then combine that with ability to handle arrays of pointers to functions, there are very few limitations on what can be accomplished in C. The learning curve may be a little steep, but nothing truly worth learning is ever easy.. – David C. Rankin Oct 04 '15 at 22:59
1

Under the hood a 2-d array is just a one dimensional array. You can treat a one-dimensional array as a 2-d array by doing a little bit of math when referencing an element.

Check out this post for some good details

Performance of 2-dimensional array vs 1-dimensional array

Community
  • 1
  • 1
Michael Albers
  • 3,721
  • 3
  • 21
  • 32
  • I was worried I would have to do that. It just makes things harder to access. But thank you, I will try to implement that now! – mrybak3 Oct 04 '15 at 03:39
0
extern GW *gw_build(int nrows, int ncols, int pop, int rnd);

This implies that gw_build returns a pointer to one dimensional array.

What you are trying to achieve is that of a 2 dimensional array:

World[0][0] -> Alive = 2

anyway, the way to initialise a 2 dimensional array would be:

gw *world[r];
for (i=0; i<r; i++)
world[i] = (int *)malloc(rowsize * sizeof(gw));

now, call gw_build column number of times, since gw_build can only return a row.

 for(i=0;i<row;i++)
  world[i] = gw_build();
basav
  • 1,475
  • 12
  • 20
0

As pointed out by basav in his/her answer the definition of the creator function allows for returning a pointer to 1D-array only.

You could however return a 2D array by using the nasty casting-hammer:

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

struct gw_struct
{
  int Alive;
  int row;
  int column;
  int id;
};

typedef struct gw_struct GW;

#define ROWS 50
#define COLS 50

GW * gw_build(int nrows, int ncols, int pop, int rnd)
{
  GW * pgw = malloc(nrows * ncols * sizeof *pgw);

  /* To access the array members do: */
  {
    GW (*world)[ncols] = (GW (*)[ncols]) pgw; /* "Dirty" casting here. */
    world[0][0].row = 0;
    world[0][0].column = 0;
    ...
  }

  return pgw;
}

int main(void)
{
  GW (*world)[COLS] = (GW (*)[COLS]) gw_build(ROWS, COLS, 34, 1); /* "Dirty" casting here. */

  world[0][0].Alive = 2;
  world[0][1].Alive = 2;         

  world[1][0].Alive = 2;         
  world[1][1].Alive = 2;         

  world[2][0].Alive = 2;         
  world[2][1].Alive = 2;         

  ...

  free(world);

  return 0;
}

The difference to basav's solution is that you end up with a linear array, that is all elements are placed in one memory block.

Note: To get rid of the "dirty" casting you could use another trick, that is introduce a temporary void-pointer.

To do so replace this

  GW (*world)[COLS] = (GW (*)[COLS]) gw_build(ROWS, COLS, 34, 1);

by

  GW (*world)[COLS] = NULL;

  {
    void * p = gw_build(ROWS, COLS, 34, 1);
    world = p;
  }

Final note: The tricks I show above are not nice. Do not do this on the green field. I just showed how to get around broken specifications.

Community
  • 1
  • 1
alk
  • 69,737
  • 10
  • 105
  • 255