title: Optimising C code for small size - sharing static variables?
body: <p>I have two functions, both are similar to this:</p>
  <pre><code>void Bit_Delay()
  {
      //this is a tuned tight loop for 8 MHz to generate timings for 9600 baud
      volatile char z = 12;
      
      while(z)
      {
          z++;
          z++;
          z++;
          z++;
          z -= 5;
      }
  }
  </code></pre>
  <p>(The second function is analogous instead it uses 18 instead of 12 for the counter).</p>
  <p>The code works flawlessly as it is (with z appearing locally to each function internally), but I'm trying to cram a little more functionality into my executable before I hit the (horribly) limited FLASH memory available.</p>
  <p>My thought was to promote the <code>z</code> variable to be a global one (a volatile static).  Because these two functions are effectively atomic operations (it's a single-threaded CPU and there are no interrupts at play to interfere), I figured that these two functions could share the single variable, thus saving a tiny bit of stack manipulation.</p>
  <p>This didn't work.  It is clear that the compiler is optimising-out much of the code related to <code>z</code> completely!  The code then fails to function properly (running far too fast), and the size of the compiled binary drops to about 50% or so.</p>
  <p>I realised that I needed the z variable to be marked volatile to prevent the compiler from removing code it knows is counting a fixed (and thus reducible to a constant) number each time.</p>
  <p>Question:</p>
  <p>Can I optimise this any further, and trick the compiler into keeping both functions intact?  I'm compiling with &quot;-Os&quot; (optimise for small binary).</p>
  <p>Here's the entire program verbatim for those playing along at home...</p>
  <pre><code>#include &lt;avr/io.h&gt;
  
  #define RX_PIN (1 &lt;&lt; PORTB0) //physical pin 3
  #define TX_PIN (1 &lt;&lt; PORTB1) //physical pin 1
  
  void Bit_Delay()
  {
      //this is a tuned tight loop for 8 MHz to generate timings for 9600 baud
      volatile char z = 12;
      
      while(z)
      {
          z++;
          z++;
          z++;
          z++;
          z -= 5;
      }
  }
  
  void Serial_TX_Char(char c)
  {
      char i;
      
      //start bit
      PORTB &amp;= ~TX_PIN;
      Bit_Delay();
      
      for(i = 0 ; i &lt; 8 ; i++)
      {
          //output the data bits, LSB first
          if(c &amp; 0x01)
              PORTB |= TX_PIN;
          else
              PORTB &amp;= ~TX_PIN;
          
          c &gt;&gt;= 1;
          Bit_Delay();
      }
  
      //stop bit  
      PORTB |= TX_PIN;
      Bit_Delay();
  }
  
  char Serial_RX_Char()
  {
      char retval = 0;
      volatile char z = 18; //1.5 bits delay
  
      //wait for idle high
      while((PINB &amp; RX_PIN) == 0)
      {}
      
      //wait for start bit falling-edge
      while((PINB &amp; RX_PIN) != 0)
      {}
  
      //1.5 bits delay
      while(z)
      {
          z++;
          z++;
          z++;
          z++;
          z -= 5;
      }
  
      for(z = 0 ; z &lt; 8 ; z++)
      {
          retval &gt;&gt;= 1; //make space for the new bit
          retval |= (PINB &amp; RX_PIN) &lt;&lt; (8 - RX_PIN); //get the bit and store it
          Bit_Delay();
      }
      
      return retval;      
  }
  
  int main(void)
  {
      CCP = 0xd8; //protection signature for clock registers (see datasheet)
      CLKPSR = 0x00; //set the clock prescaler to &quot;div by 1&quot;
      DDRB |= TX_PIN;
      PORTB |= TX_PIN; //idle high
          
      while (1) 
          Serial_TX_Char(Serial_RX_Char() ^ 0x20);
  }
  </code></pre>
  <p>The target CPU is an <code>Atmel ATTiny5</code> microcontroller, the code above uses up 94.1% of the FLASH memory!  If you connect to the chip using a serial port at 9600 Baud, 8N1, you can type characters in and it returns them with bit 0x20 flipped (uppercase to lowercase and vice-versa).</p>
  <p>This is not a serious project of course, I'm just experimenting to see how much functionality I could cram into this chip.  I'm not going to bother with rewriting this in assembly, I seriously doubt I could do a better job than GCC's optimiser!</p>
  <h2>EDIT</h2>
  <p>@Frank asked about the IDE / compiler I'm using...</p>
  <p>Microchip Studio (7.0.2542)</p>
  <p>The &quot;All Options&quot; string that is passed to the compiler <code>avr-gcc</code>...</p>
  <pre><code>-x c -funsigned-char -funsigned-bitfields -DDEBUG  -I&quot;C:\Program Files (x86)\Atmel\Studio\7.0\Packs\atmel\ATtiny_DFP\1.8.332\include&quot;  -Os -ffunction-sections -fdata-sections -fpack-struct -fshort-enums -g2 -Wall -mmcu=attiny5 -B &quot;C:\Program Files (x86)\Atmel\Studio\7.0\Packs\atmel\ATtiny_DFP\1.8.332\gcc\dev\attiny5&quot; -c -std=gnu99 -MD -MP -MF &quot;$(@:%.o=%.d)&quot; -MT&quot;$(@:%.o=%.d)&quot; -MT&quot;$(@:%.o=%.o)&quot; 
  </code></pre>