1

I have a struct with a single member. This member is actually a pointer to pointer to integer, in order to represent a 2-dimensional integer array.

Can I use scalar initialization while creating an instance of that struct?

I am trying to create a 2-dimensional array to represent a collection of pixels for an algorithm exercise.

I should represent something like this:

{
  { 0, 0, 1, 0, 0 },
  { 0, 1, 1, 1, 0 },
  { 1, 1, 1, 1, 1 }
}

In order to represent a generic abstraction of an image, I tried to create a struct with the following structure:

struct image_t
{
  unsigned short int** pixels;
};

And so I try to init an instance of that by using:

int
main()
{
  struct image_t image = {
    {
      {0, 0, 1, 0, 0},
      {0, 1, 1, 1, 0},
      {1, 1, 1, 1, 1},
    }
  };

  return 0;
}

When I try to compile, the following warnings are given:

gcc -I src src/main.c -o bin/flood-fill.bin
src/main.c: In function ‘main’:
src/main.c:41:5: warning: braces around scalar initializer
     {
     ^
src/main.c:41:5: note: (near initialization for ‘image.pixels’)
src/main.c:42:7: warning: braces around scalar initializer
       {0, 1, 0},
       ^
src/main.c:42:7: note: (near initialization for ‘image.pixels’)
src/main.c:42:11: warning: excess elements in scalar initializer
       {0, 1, 0},
           ^
src/main.c:42:11: note: (near initialization for ‘image.pixels’)
src/main.c:42:14: warning: excess elements in scalar initializer
       {0, 1, 0},

After making some research, I realized that, as it is gonna be an image representation, each row will have the same total of columns. Due that, I can just use a single array and store everything in a single block of memory. It resolves my problem.

However, as a curious guy, I would like to know if there is any way to use scalar initialization for such cases - if so, how can I do that?

Most likely I'm missing some critical basic concept from C language, so explanations are more than welcome. I really want to understand better the way it works under the hood.

Stanley Sathler
  • 368
  • 2
  • 12
  • Related: https://stackoverflow.com/questions/8108416/excess-elements-of-scalar-initializer-for-pointer-to-array-of-ints – Jerry Jeremiah Jul 21 '19 at 23:04
  • 2
    The compiler is warning you about excess elements in a scalar initializer because, where it expects there to be an initial value for `pixels`, you have a brace-enclosed list. The outer braces are fine; `image` is a struct, and its initial values should be enclosed in braces. Inside those, there should be one value, the initial value for `pixels`. Since `pixels` is an `unsigned short **`, the initial value should be either a pointer to a pointer to an `unsigned short` or a null pointer. `pixels` is a **pointer**, so you cannot initialize it with an **array**. – Eric Postpischil Jul 21 '19 at 23:12
  • 3
    And, of course, you can use scalar initialization—`pixels` is a scalar, so, to initialize it, give a single value. But that is not what you actually want. Your declaration of `unsigned short** pixels` suggests you want pixels to be a two-dimensional array, and you have been misled by advice to form two-dimensional arrays using pointers to pointers. In general, a two-dimensional array should be either an array of arrays (when allocated statically or automatically) or should be a pointer to an array (when allocating dynamically, as with `malloc`). – Eric Postpischil Jul 21 '19 at 23:14
  • 3
    In your case, if the number of columns is the constant (defined with `#define`) `Columns`, you can get started by changing the declaration of `pixels` to `unsigned short (*pixels)[Columns];`. Then make an array with the data, as with `unsigned short MyData[Rows][Columns] = { { 0, 0, 1, 0, 0 }, { 0, 1, 1, 1, 0 }, { 1, 1, 1, 1, 1 } };`, and then define `image` with `struct image_t image = { MyData };`. There are improvements that can be made on that, but it is a reasonable way to start. – Eric Postpischil Jul 21 '19 at 23:17
  • Many thanks for your answer, @EricPostpischil. I made this confusion due the previous knowledge I had about arrays (without any explicit index) being a pointer to its first item. That's why I though this design would be the correct one. It is a lot cleaner now the reason it didn't work as expected. – Stanley Sathler Jul 22 '19 at 11:14

1 Answers1

4

It probably isn't a good idea, but you can use compound literals to create the structure you want, like this:

struct image_t
{
    unsigned short int **pixels;
};

int main(void)
{
    struct image_t image =
    {
        (unsigned short *[]) {
            (unsigned short []){ 0, 0, 1, 0, 0 },
            (unsigned short []){ 0, 1, 1, 1, 0 },
            (unsigned short []){ 1, 1, 1, 1, 1 },
        }
    };

    return image.pixels[1][4];
}

Note that one of the problems with the data structure shown is that there is no information about how many rows are in the array of pointers, nor how many items are in each row (and there's no guarantee that each row is the same length as the other rows). That alone is reason enough to think that the structure is broken (badly designed, at any rate). If you added information about the rows and columns, then it could be OK.

Note that unsigned short **pixels; is not a pointer to a 2D array. It is a pointer to (an array of) pointer(s), which is quite different.

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
  • Ouch! A compound literal of compound literals. Wholly agree with the introductory caution -- but very creative solution.... – David C. Rankin Jul 22 '19 at 06:03
  • Thank you very much, Jonathan. The plan is to add two more members to the `image_t` struct: `width` and `height`, which represent the image's size. Perhaps I can use your approach and then create a function that acts as a constructor, receiving the bits, the width, height, and then using compound literals limiting each dimension, as you suggested? – Stanley Sathler Jul 22 '19 at 12:07
  • 1
    With the size information too, it is doable and usable, but the notation is fairly inflexible. What I show is something of a technical tour de force, but I’d probably look for a more orthodox way of doing it. To some extent, it depends on how many of these initialized structures you’re going to need, and how big the attached arrays are. If there are only a few (1..9 say) and the array sizes are small (1..16 say), it may be OK. More than that and you should start looking at code to generate the initializers. – Jonathan Leffler Jul 22 '19 at 13:39