Go Down

Topic: Variable scope, libraries (Read 3077 times) previous topic - next topic

TomLeMort

Hi,

I have a problem with variable scope, using libraries. I wrote a library to store and manage objects that I create using parameters I read through the serial port. Let's call this library A. It looks like this:
Code: [Select]
...
class A
{
  public:
    A(unsigned int nb, byte *data);
    ...
  private:
    ...
}

I need to store a list of A objects, however they may use a lot of memory (regarding the available memory space). I don't know the length of the list in advance, but I set a limit. I need to perform operations using these, according to events coming from the serial port. I wrote another library (say B) to manage the list, as well as the actions.

Code: [Select]
...
class B
{
  public:
    ...
    void add(A *a);
    const A *get(byte index) const;
    ...
  private:
    A *listofa[MAXSIZE];
    ...
}


So my problem is that currently I create the objects of type A in the loop function, and give a ref to the B object:
Code: [Select]
B b;
void loop()
{
  ...
  A a(x, y);
  b.add(&a);
  ...
}

However I fear that the scope of a is limited to the loop function, and therefore the instance of A is deleted and my pointer in listofa points to ?*$!@¤\# in the next execution of loop.

I considered doing something like this:
Code: [Select]
B b;
void loop()
{
  ...
  b.add(x,y);
  ...
}

and create the instance of A in b.add. However I prefer to keep a list of A* to save memory. And I am unsure I can do something like
Code: [Select]
listofa[i] = new A(x,y);

Anyone has an hint?

Thank you

PaulS

Quote
However I fear that the scope of a is limited to the loop function, and therefore the instance of A is deleted and my pointer in listofa points to ?*$!@¤\# in the next execution of loop.

That's a reasonable fear, because that is exactly what happens.

Quote
I considered doing something like this ... and create the instance of A in b.add. However I prefer to keep a list of A* to save memory.

Creating an instance takes space. Storing that instance, or a pointer to the instance that is stored elsewhere, will take the same amount of space.

Quote
And I am unsure I can do something like

Perhaps you've noticed that the new and delete operators are not implemented on the Arduino. They can be added, but creating an instance takes space, whether you have an object to deal with, or a pointer to the object to deal with.
The art of getting good answers lies in asking good questions.

jraskell

Quote
I don't know the length of the list in advance, but I set a limit.


Since you've already set a limit, just create an array of that size ahead of time.  If you don't have enough memory for that array, then your limit is too high.  What exact benefit do you see using a dynamic list, that justifies the added complexity and potential issues involved?

TomLeMort

That's a reasonable fear, because that is exactly what happens.

Is there a precise documentation somewhere? Because I mostly guess this kind of things with my knowledge of C, C++, etc. However it does not apply to everything (new/delete for example).

Creating an instance takes space. Storing that instance, or a pointer to the instance that is stored elsewhere, will take the same amount of space.

No, I meant that since I do not know in advance how many objects I will have, I created a table of pointers. I create the instance only when necessary. So I actually save memory.


Perhaps you've noticed that the new and delete operators are not implemented on the Arduino. They can be added, but creating an instance takes space, whether you have an object to deal with, or a pointer to the object to deal with.

I noticed, but I don't understand why. :~ So If I understad I have to allocate the memory, and copy the instance I created?

Since you've already set a limit, just create an array of that size ahead of time.  If you don't have enough memory for that array, then your limit is too high.  What exact benefit do you see using a dynamic list, that justifies the added complexity and potential issues involved?

My explanation was wrong, I use an array of course.

jraskell

Quote
My explanation was wrong, I use an array of course.


I wasn't talking about an array of pointers to dynamically allocated objects.  I was talking about an array of objects.  Forgo the dynamic allocation entirely.  On platforms with such limited memory footprints, dynamic allocation is rarely of any true benefit.

Quote
Is there a precise documentation somewhere?

Variable scope on the Arduino is the same as any standard C/C++ environment.  There are no differences for an Arduino.

TomLeMort


I wasn't talking about an array of pointers to dynamically allocated objects.  I was talking about an array of objects.  Forgo the dynamic allocation entirely.  On platforms with such limited memory footprints, dynamic allocation is rarely of any true benefit.

OK. However the case is a little bit more complex, since my objects (of type A) contain arrays that can have any size. I must allocate them with a malloc. I don't think using an oversized big array is a good solution.

Variable scope on the Arduino is the same as any standard C/C++ environment.  There are no differences for an Arduino.

OK. But 1) it is written nowhere; 2) what about other aspects?

rbtying

The documentation you're looking for isn't available from Arduino because they didn't write the compiler, and few people need to know the underlying details.  You can look here for the avr-libc documentation, which covers all the stuff you're wondering about.



I wasn't talking about an array of pointers to dynamically allocated objects.  I was talking about an array of objects.  Forgo the dynamic allocation entirely.  On platforms with such limited memory footprints, dynamic allocation is rarely of any true benefit.

OK. However the case is a little bit more complex, since my objects (of type A) contain arrays that can have any size. I must allocate them with a malloc. I don't think using an oversized big array is a good solution.

I think jraskell's point is that you don't NEED to have 'A' contain dynamically allocated arrays, at all.  There isn't enough available RAM to make this practical.  On an embedded processor, using malloc() is rarely a good idea, and you'll find that in just about every case, a fixed-size array will work out better. 

nickgammon


I need to store a list of A objects, however they may use a lot of memory (regarding the available memory space). I don't know the length of the list in advance, but I set a limit. I need to perform operations using these, according to events coming from the serial port.
...
Anyone has an hint?


The Standard Template Library (STL) may help you here. I did a brief write-up here:

http://www.gammon.com.au/forum/?id=11119

The original library can be downloaded from:

http://andybrown.me.uk/ws/2011/01/15/the-standard-template-library-stl-for-avr-with-c-streams/

Amongst other things, that library gives you new and delete operators. The overhead isn't too bad for a vector (which sounds like what you want). Using the vector code consumed around 1000 bytes of program memory (which you may have spare) and an overhead of something like 6 bytes of RAM per vector (on top of the memory needed by the vector elements themselves of course).

The good thing about vectors is that you can decide their size at runtime, and indeed add to an existing one dynamically. Ultimately you still need the RAM, of course, but you can save it by not having to pre-allocate more than you really need.

Resizing vectors could be expensive, leading to fragmented memory, probably the last thing you want. Another alternative would be a list structure, which is fast and easy to extend, and since lists are by nature fragmented, adding to the list shouldn't cause more fragmentation.

I don't totally agree that malloc is bad (or new for that matter). Certainly if it gets out of control you will run out of memory very quickly. However fixed-size arrays can be worse, if you have to keep allocating the maximum size that you might get.

If you have a situation where you might have X of object A, and Y of object B, and aren't sure until runtime which you need a lot of, then dynamic allocation is about the only way of solving it.

Also consider adding a RAM chip (eg. using SPI) for a couple of dollars. That might help if you need to store a lot of stuff.
Please post technical questions on the forum, not by personal message. Thanks!

More info: http://www.gammon.com.au/electronics

nickgammon

#8
May 27, 2011, 12:11 am Last Edit: May 27, 2011, 12:29 am by Nick Gammon Reason: 1
New and delete can be implemented very easily, if that is all that is required:

Code: [Select]

void* operator new(size_t n, void * p) { return p;  }  // placement new
void* operator new(size_t n) { return malloc (n); }
void operator delete (void * p) { free (p); };


The important thing about new compared to malloc is that the object's constructor is called (and the destructor with delete) which is almost certainly what you want.
Please post technical questions on the forum, not by personal message. Thanks!

More info: http://www.gammon.com.au/electronics

jraskell

Let me just repeat one last time: On platforms with such limited memory footprints, dynamic allocation is rarely of any true benefit.

If you don't have enough memory to handle your worst case scenario, dynamic allocation isn't going to help that, because it actually adds overhead, since you not only need memory allocated for the object itself, but you need at least one pointer for each object allocated as well.

If you do have enough memory to handle your worst case scenario, then just code for that scenario.  Your code will be cleaner, simpler, easier to read, and probably most important of all, easier to debug as well.  I'll tell you ahead of time, memory leaks and memory fragmentation can quickly cripple an Arduino.

TomLeMort


The documentation you're looking for isn't available from Arduino because they didn't write the compiler, and few people need to know the underlying details.  You can look here for the avr-libc documentation, which covers all the stuff you're wondering about.


I suggest to add such a link in the library tutorial/documentation. I don't understand how one can use safely a language without knowing its specifications.

Amongst other things, that library gives you new and delete operators. The overhead isn't too bad for a vector (which sounds like what you want). Using the vector code consumed around 1000 bytes of program memory (which you may have spare) and an overhead of something like 6 bytes of RAM per vector (on top of the memory needed by the vector elements themselves of course).

This is too much for me, an array of pointers is fine. The funny thing of the story is that I don't like object programming, but anyway.


I don't totally agree that malloc is bad (or new for that matter). Certainly if it gets out of control you will run out of memory very quickly. However fixed-size arrays can be worse, if you have to keep allocating the maximum size that you might get.

If you have a situation where you might have X of object A, and Y of object B, and aren't sure until runtime which you need a lot of, then dynamic allocation is about the only way of solving it.

This is exactly my case. See below.

Also consider adding a RAM chip (eg. using SPI) for a couple of dollars. That might help if you need to store a lot of stuff.

I don't think I can do that. I use a Lilypad, for a small wearable device. I do not have space for this.

The important thing about new compared to malloc is that the object's constructor is called (and the destructor with delete) which is almost certainly what you want.

This is exactly what I want. The solution I currently use is creating an "dummy" instance with a default constructor. I override operator =, and do something like this:
Code: [Select]
listofa[index] = A(x,y);
This is pretty ugly, but it works.


Let me just repeat one last time: On platforms with such limited memory footprints, dynamic allocation is rarely of any true benefit.

Rarely is not never

If you don't have enough memory to handle your worst case scenario, dynamic allocation isn't going to help that, because it actually adds overhead, since you not only need memory allocated for the object itself, but you need at least one pointer for each object allocated as well.

If you do have enough memory to handle your worst case scenario, then just code for that scenario.  Your code will be cleaner, simpler, easier to read, and probably most important of all, easier to debug as well. I'll tell you ahead of time, memory leaks and memory fragmentation can quickly cripple an Arduino.

I start over the explanation. My object of type B stores a table of objects of type A. Each object of type A contains an unsigned int and several tables of objects (2 bytes + 2 unsigned int = 6 bytes). I do not know the size of these tables, except they are the same for a given instance. If n is the number of objects of type A, and tn the size of the lists of object n:
- size of object n = 2 + 6 * tn (+4 pointers?)
- size of all objects = sum(1,n) (2 + 6 * tn (+4 pointers?)) (+1 pointer?)
I clearly have a trade-off between the maximum n, and the maximum tn. In some cases I can have few big objects (small n, large tn). In other cases I can have a lot of small objects (large n, small tn). If I use a fixed value for both n and tn, I cannot manage both cases. So I prefer to use (4 * n + 1) pointers and have this liberty. I will most likely never have a lot of big objects. And in that case, I will just not create them.

TomLeMort

In the end my solution:

In the pde file:
Code: [Select]
...
void* operator new(size_t n, void * p) { return p; }
void* operator new(size_t n) { return malloc(n); }
void operator delete (void * p) { free(p); };
B b;
...
void loop()
{
  ...
  b.add(x,y);
  ...
}

A:
Code: [Select]
class A
{
  public:
    A(unsigned int x, byte *y);
    ...
  private:
    unsigned int _x;
    byte *_a;
    byte *_b;
    unsigned int *_c;
    unsigned int *_d;
};

A::A(unsigned int x, byte *y)
:_x(x),
_a((byte *)malloc(x * sizeof(byte))),
_b((byte *)malloc(x * sizeof(byte))),
_c((unsigned int *)malloc(x * sizeof(unsigned int))),
_d((unsigned int *)malloc(x * sizeof(unsigned int)))
{
  ...
}
...


B:
Code: [Select]
#define MAXA (value)
class B
{
  public:
    ...
    void add(unsigned int nbframes, byte *desc);
  private:
    byte _nba;
    A *_listofa[MAXA];
    ...
}

void B::add(unsigned int x, byte *y)
{
if (_nba < MAXA)
{
_listofa[_nba] = new A(x, y);
if (_listofa[_nba])
_nba++;
}
}


So if I have few big objects, most of the objects in _listofa are pointers (which are smaller than a dummy A). If I have a lot of small objects, _listofa will be full (or almost), which is OK.

Go Up