Philosophy: Instantiating inside vs outside loop

Cool Javelin
Posts: 12
Joined: Wed Oct 20, 2021 3:58 am

Philosophy: Instantiating inside vs outside loop

Postby Cool Javelin » Wed Oct 18, 2023 6:17 pm

Hello IoT experts:

I see this in a lot of sample code:
  1. #include <ESP8266WiFi.h>
  2.  
  3. loop {
  4.   WiFiClient client;                 // WiFiClient inside loop
  5.     if (client.connect(host, 80)) {
  6.       ...
  7.     }
  8. }
vs...
  1. #include <ESP8266WiFi.h>
  2. WiFiClient client;                 // WiFiClient outside loop
  3.  
  4. loop {
  5.     if (client.connect(host, 80)) {
  6.       ...
  7.     }
  8. }
Why do people do this?

It seems to me instantiating inside the loop puts a lot of pressure on the stack.

Rather, if it is instantiated outside loop...
. the memory gets allocated on the heap,
. it doesn't have to be recreated each time through the loop, (I understand creating stack space doesn't take a lot of time)
. we don't run the risk of using too much stack,
. loop should run faster.

Thoughts?

Thanks, Mark.

MicroController
Posts: 1552
Joined: Mon Oct 17, 2022 7:38 pm
Location: Europe, Germany

Re: Philosophy: Instantiating inside vs outside loop

Postby MicroController » Wed Oct 18, 2023 7:05 pm

Why do people do this?
1. To have a "clean",internally consistent, properly initialized instance in a known state everytime loop() is called
2. To limit the scope, and with it the life time, of the instance and have the compiler take care of proper cleanup/deinit of the instance no matter where, how and at how many places loop() potentially exits

3. Potentially: Support for re-entry
4. Encapsulation: Local vs. global variable

Cool Javelin
Posts: 12
Joined: Wed Oct 20, 2021 3:58 am

Re: Philosophy: Instantiating inside vs outside loop

Postby Cool Javelin » Wed Oct 18, 2023 8:41 pm

Thank you MicroController, I sometimes forget about the "structured" programming practices.

I really do like the structured approach, I grew up back when the Z80 or 6800 was popular, using assembly a lot, and I tend to worry about every byte of memory and unnecessary clock cycle. Of course "spaghetti" code is a challenge to debug.

I'd like to discuss some of the answers,

1) you say to have a "clean, internally consistent..." but when the compiler creates space on the stack, doesn't it leave whatever garbage is in memory there? Does calling the instantiating actually initialize the RAM?

2) I do see the advantages to limiting the scope. I spend a lot of time deciding if a variable should be local or global.

3) Again, I see the need to do if the code is reentrant. I try very hard to not use reentrant code for fear of overrunning the stack so it never occurs to me.

Thanks, Mark.

MicroController
Posts: 1552
Joined: Mon Oct 17, 2022 7:38 pm
Location: Europe, Germany

Re: Philosophy: Instantiating inside vs outside loop

Postby MicroController » Thu Oct 19, 2023 7:53 pm

Cool Javelin wrote:
Wed Oct 18, 2023 8:41 pm
I tend to worry about every byte of memory and unnecessary clock cycle.
I hear you :)
Re: memory - It may even be beneficial to have things live on the stack temporarily instead of permanently in statically allocated RAM...
I'd like to discuss some of the answers,

1) you say to have a "clean, internally consistent..." but when the compiler creates space on the stack, doesn't it leave whatever garbage is in memory there? Does calling the instantiating actually initialize the RAM?
Yes, in C++ a class' constructor is always executed when an instance is created, either explicitly (MyClass inst {99,100,true};) or implicitly by the compiler (MyClass inst; is the same as MyClass inst { /* nothing */ };). The constructor (together with the compiler) takes care of initializing the instance's memory.
2) I do see the advantages to limiting the scope. I spend a lot of time deciding if a variable should be local or global.
In C++ there's the additional aspect of instances being implicitly destroyed via their destructor when their scope is exited. "RAII" is the relevant pattern/acronym. This way, the compiler makes sure the instance is properly cleaned up and resources released.
Think

Code: Select all

int func() {

  MyClass inst {true,255};
  
  if(inst.doSomehing() == false) {
    return -1;
  }
  
  if(noonAlready) {
    if (user == nullptr) {
    
      throw UserUnavailabeException {};
      
    } else {
      inst.restUser(user);
    }
  }
  
  return inst.getResult();
}
The destructor will always be executed when func() is exited. This is especially relevant when an instance internally holds resources, whether that's memory allocated from the heap or a specific network connection.
3) Again, I see the need to do if the code is reentrant. I try very hard to not use reentrant code for fear of overrunning the stack so it never occurs to me.
Think about a function potentially being called concurrently from more than one task.
In this case, where it may be impossible to know beforehand how many "instances" of the function may actually be running concurrently at some point, the most reasonable thing may be use the calling task's stack, so that memory is only used if&when any given task actually calls the function.

Who is online

Users browsing this forum: ESP_Sprite and 387 guests