This comes up relatively frequently. A trivial sketch, not even referencing any of the Arduino functions, ends up occupying over 400 bytes of flash memory. Is the compiler THAT inefficient, that a few lines of code that ought to generate single AVR instructions, generates 400 bytes instead?
Well, NO! Of course not. Most of the space is occupied by overhead functions that you might need in a sketch, whether or not you actually use them. The Arduino environment ends up doing a pretty good job of excluding unused function (if you don't use "digitalWrite", the code that implements it is not included), but it's not perfect.
I decided to compile an empty sketch and analyze the code produced, to see which pieces remained, and whether they looked like they were reasonably optimized. Here are the results.
First, the empty sketch:
void setup(){
}
void loop(){
}
Next, the breakdown. I'm not going to boggle everyone with all of the assembly language produced; just some analysis (I'll put the assembly in the next message in this thread.)
Let's get right to those empty setup() and loop() functions. They end up compiling to bare "return" instructions; two bytes each!
;;; Empty setup() function
;;; length 2 bytes
;;; Provided by: user sketch
;;; required by: Arduino environment
;;; Empty loop() function
;;; length 2 bytes
;;; Provided by: user sketch
;;; required by: Arduino environment
The setup and loop functions in a user sketch are called by another function "main", that is the traditional C/C++ language main function. main() in turn is called by startup code generated by the C compiler. This string of function calls occupies another couple of bytes
;;; Linkage to main() program
;;; length 8 bytes
;;; provided by: gcc compiler.
;;; main()
;;; length: 14 bytes
;;; provided by: Arduino environment
;;; required by: C language convention
;;; exit and __stop_program
;;; length: 4 bytes
;;; provided by: gcc C compiler
;;; required by: nothing.
;;; Comment pretty much unused in the arduino environment.
main() also calls init(), an Arduino environment function that sets up the AVR peripherals (Timers, A-D converter, etc) to the state that the rest of the Arduino functions are expecting. init() is 114 bytes of code, and is one of the few places where the code and/or the compiler did some obviously inefficient things. But it only executes once anyway, and 114 bytes is not a lot compared to the 7k of space you have even on an ATmega8, so it's not really worth optimizing. Really.
;;; init()
;;; Length: 114 bytes
;;; provided by: Arduino Environment
;;; Required by: Arduino Environment, user sketches
;;; Comments: initializes peripherals (especially timers) as expected by
;;; the ISR and PWM output, and so on.
;;; The compiler seems to do a particularly poor job of optimizing
;;; what ought to be straightforward code.
Now, before the startup code calls main(), it has to do some initialization as "required" by the C standards. It sets up a stack, and makes sure the CPU status register is in a known state. Initialized variables are copied from flash to RAM, and uninitialized variables are cleared to zero. This code will be present whether or not you have any variables at all.
;;; Basic core startup code;
;;; Length 12 bytes;
;;; Provided by: gcc Compiler
;;; required by: gcc Compiler
;;; Copy initialized data from Flash to RAM.
;;; Length 22 bytes
;;; Provided by: gcc compiler
;;; required by: any sketch using initialized data.
;;; Clear uninitialized data to 0s.
;;; length 16 bytes
;;; Provided by: gcc compiler
;;; required by: C language specification.
;;; Comments: not necessary if there are no uninitialized variables.
Now, the Arduino environment uses a timer interrupt to maintain the millis() clock, uses interrupts for the serial port, and allows users to attach interrupts to a couple of the pins. The AVR uses "vectored interrupts", which means that each potential source of interrupts has a function ("vector") registered to it. The AVR has 26 interrupt sources, and this table occupies 104 bytes.
;;; Table of interrupt vectors
;;; Size: 104 bytes.
;;; Provided by: gcc C compiler
;;; Required by: RESET, Timer, UART, etc.
;;; Comment: in theory, unused interrupt vectors could hold other data.
Finally, there is the timer interrupt service routine itself, which is present and running whether you use it or not. This is 142 bytes long, which is pretty long (especially for an interrupt service routine.) Unfortunately, this is already the "optimized" version; it ends up having to maintain TWO 32-bit counters in memory for the sake of backward compatibility, and you get to see firsthand just how inefficient 32bit math can be on an 8bit CPU. Each load/increment/store takes about 30 bytes, plus overhead for saving the registers used, plus the math to keep track of milliseconds when your interrupt happens every 1.024 ms...
;;; Timer0 interrupt service routine
;;; Length: 142 bytes
;;; Provided by: Arduino Environment
;;; Required by: Arduino Environment (millis(), delay(), etc)
;;; Comments: long due to several 32-bit variable modifications.