2

I'm doing shenanigans with pointers for an Arduino hobby project, and I've come across a situation I can't resolve.

Basically, I have a global (because Arduino) array of pointers to Thing. I don't want to initialize it during the declaration, but I want to take advantage of the nice array initialization syntax.

Please note that this is an Arduino project and so uses an unusual mix of C and C++, because Arduino. From some basic research, looks like that if it'll work in C, or C++ without libraries, it'll work for this.

Note: I'm using NULL as a way to detect the end of the array as I loop over it, elsewhere in the code.

Something like so:

struct Thing {
  int i;
};
Thing * MakeThing() {
  return new Thing;
}
Thing * things[] = {
  NULL
};
void setup() {
  things = (Thing*[]){
    MakeThing(),
    NULL
  };
}

However, this gets me a error: incompatible types in assignment of 'Thing* [2]' to 'Thing* [1]'

I tried:

  Thing* _things[] {
    MakeThing(),
    NULL
  };
  things = _things;

Same error.

I tried:

  things =  new Thing*[] {
    MakeThing(),
    NULL
  };

Same error.

I tried:

  things = new Thing** {
    MakeThing(),
    NULL
  };

which gets a error: cannot convert '<brace-enclosed initializer list>' to 'Thing**' in initialization

I tried:

  things = new Thing*[] {
    MakeThing(),
    NULL
  };

which gets error: incompatible types in assignment of 'Thing**' to 'Thing* [1]'

What's going on that this doesn't work? How can I make it work?

Narfanator
  • 5,595
  • 3
  • 39
  • 71
  • 3
    Note: there is no `new` operator in C. – wildplasser Aug 03 '16 at 10:58
  • I shall change the tag from `c` to `c++` – chqrlie Aug 03 '16 at 11:02
  • You can't assign arrays. I don't think C++ has compound literals. – 2501 Aug 03 '16 at 11:04
  • @chqrlie The re-tagging is incorrect. This is for an Arduino project, which AFAIK is C only. My C is just fuzzy enough that I though `new` was a thing in it. – Narfanator Aug 03 '16 at 19:19
  • I've retagged and removed the example using `new`. Thank you for trying to be helpful. – Narfanator Aug 03 '16 at 19:23
  • @wildplasser Thanks for the info, looks like the Arduino folks added it: http://stackoverflow.com/questions/16274451/operator-new-for-arduino – Narfanator Aug 03 '16 at 19:30
  • Your question is not very clear. `I don't want to initialize it during the declaration, because static initialization order...` what is that ? Obviously: 1) you cannot have code in in initialisers , only expressions that can be evaluated at compile (+load) time, and 2) you cannot have `nice array initialization syntax` in code, oniy in initialisers. – wildplasser Aug 03 '16 at 19:30
  • Did some more research, looks like I might not have to worry about the initialization order, because this is one file. Sorry the question isn't clear. I want the global variable `things` to point to memory that has an array of `Thing` pointers. I want to populate that memory using the nice syntax, rather than index by index. Does that help? – Narfanator Aug 03 '16 at 19:38
  • Why do you want the global array **and** an array with pointers to them? `&array[13]` will yield a pointer value, which you can use without first storing it into another array. – wildplasser Aug 03 '16 at 19:42
  • AFAIK, global variables are the only way to pass information from the Arduino `setup` function to the `loop` function, as they're both `void` functions that take no parameters. – Narfanator Aug 03 '16 at 19:49

4 Answers4

1

With Thing* things[] = {NULL}:

You are declaring variable things as an array of a single element (of type Thing*).

You cannot change this array once you've declared it - neither its size nor its address.

The only thing that you can change is the contents of this array, i.e., the first (and only) element.

barak manos
  • 29,648
  • 10
  • 62
  • 114
1

I you want things to point to a allocated array of size 2, you must write 3 statements:

things = new Thing*[2];
things[0] = MakeThing();
things[1] = NULL;

The new style C99 compound literal syntax is not supported in C++, or only as an extension by some C++ compilers, but this would still not fit your purpose as the array would have automatic or static life span, not dynamic.

EDIT: Given the rephrasing done on the question, the answer is quite simple: The global things array must be defined as having 2 elements and can be initialized in setup with 2 statements:

struct Thing {
  int i;
};
Thing *MakeThing(void) {
    Thing *t = calloc(sizeof(t));
    return t;
}
Thing *things[2];  // implicitly initialized to { NULL, NULL };
void setup(void) {
    things[0] = MakeThing();
    things[1] = NULL;
}

Or if you absolutely insist on using the C99 compound literals at the expense of readability you could use this:

void setup(void) {
    memcpy(things, (Thing*[]){ MakeThing(), NULL }, sizeof things);
}

But I am not sure the Arduino compiler supports this syntax. Arduino-C is neither C nor C++.

chqrlie
  • 131,814
  • 10
  • 121
  • 189
1

First of all, all of those fail to compile because it's illegal to assign to an array (to a variable of array type) in C.

Second, even if you could assign to an array in C (let's say it copies all the elements), what you're trying to do still won't work because you would be assigning between different array types -- the size is part of an array type. Thing *[1] and Thing *[2] are completely different types. If you have a variable of array type, the size is hard-coded at compile time. Your variable things has type Thing *[1] and that's hard-coded at compile time, and cannot be changed. (With C99 VLAs, you can have an array variable whose size is not hard-coded at compile time, but is fixed at the time the array variable is defined, and still cannot be changed afterwards during the lifetime of the variable.)

So if you want to have a variable which refers to arrays of different sizes over the lifetime of the variable, it cannot have array type. You have to declare it of pointer type (in this case Thing **). The array whose elements the pointer points to will then have to be dynamically allocated, and you will have to memory manage that allocation. This is basically what chrqlie's answer is showing. I am not sure what dynamic allocation mechanism is used in this Arduino that you're using, since you say that it is a mix of languages, but in C++ you would use new []/delete [] and in C you would use malloc/free for dynamic allocation.

newacct
  • 119,665
  • 29
  • 163
  • 224
0

You can do a lot with static initialisation only, for instance (this is a doubly linked list):

struct list {
        struct list *prev;
        struct list *next;
        int val;
        };

struct list arr[] =
{ {arr+1, arr+2, 1} 
, {arr+3, arr+4, 2} 
, {arr+5, arr+6, 3} 
, {arr+3, arr+4, 4} 
, {arr+3, arr+5, 5} 
, {arr+4, arr+6, 6} 
, {arr+5, arr+6, 7} 
        };

If you want extra space in the array you could use a sentinel to enable your code to fined the start of the unused space:

struct list arr[ 100] =
{ {arr+1, arr+2, 1}
, {arr+3, arr+4, 2}
, {arr+5, arr+6, 3}
, {arr+3, arr+4, 4}
, {arr+3, arr+5, 5}
, {arr+4, arr+6, 6}
, {arr+5, arr+6, 7}
, { NULL, NULL, -1}
        };

... Or abuse the preprocessor as a line counter:

struct list arr[ 100] =
# /* reset preprocessor line counter */
# 0 "omg"
{ {arr+1, arr+2, 1}
, {arr+3, arr+4, 2}
, {arr+5, arr+6, 3}
, {arr+3, arr+4, 4}
, {arr+3, arr+5, 5}
, {arr+4, arr+6, 6}
, {arr+5, arr+6, 7}
        };
unsigned watermark = (__LINE__ -1);

... and of course the above could be changed,using the designated initialiser syntax.

BTW: This is not a DoublyLinkedList, (the offsets look wrong), but you get the idea...

wildplasser
  • 43,142
  • 8
  • 66
  • 109
  • Thanks! If I wanted to do this, but have `arr` be allocated on the heap, not the stack, how would I do that? – Narfanator Aug 03 '16 at 20:33
  • 1
    That is impossible. Heap-allocation needs code, you need to call malloc(), or at least brk(). Again: you cannot use code (call functions) in initialisers. (except sometimes in c++, using a constructor) – wildplasser Aug 03 '16 at 20:56