Okay, we all have seen those questions all over the web such as Arduino v.s. C++, or other similar questions. And a vast majority of the answers do not even touch compilation differences other than through abstracted information.

My question aims to solve the actual differences (not preferences) in how a .ino file renamed to a .cpp file or other similar file extension for c++ would compile using GCC-AVR. I know that at a minimum you have to include the Arduino header file, but beyond that, what would cause a compilation error if compiling said .ino to .cpp file using, say, for instance GCC-AVR. For simplicity's sake, lets use the classic blink example to explain what the differences are. Or if you have a better code snippet to use, please by all means, include the snippet in your answer and explain the differences thoroughly.

Please no opinions as to which is a better way or tool to use.

FYI. I use Platformio for development and I notice a conversion process happening behind the scenes during compilation. I am tryng to understand what is actually happening there, so when I code in Arduino, I understand the "pure" C++ version as well.

Thanks for your thoughtful answers to my question in advance.

  • Are you asking specifically about gcc on your desktop, or the GCC for AVR compiler avr-gcc? there is a much bigger difference there than there is between a .ino and a .cpp file. Mar 7, 2016 at 1:31
  • @BrettAM The GCC-AVR toolkit as the Arduino UNO is the target board and uses a Atmel AVR chip, as I am sure you know. Thanks for the call out to ambiguity in my question. And yes I know that there is a much bigger difference. That is why I am asking this question. To learn what those differences are! Mar 7, 2016 at 1:35

2 Answers 2


See my answer here: Classes and objects: how many and which file types do I actually need to use them? - specifically: How the IDE organizes things.

I know that at a minimum you have to include the Arduino header file

Yes you would need to do that.

but beyond that, what would cause a compilation error if compiling said .ino to .cpp file using, say, for instance GCC-AVR.

The IDE generates function prototypes for you. Code in a .ino file may or may not need this (it probably will unless the author is disciplined enough to code in the usual C++ way and do them themselves).

If the "sketch" contains other files (eg. other .ino, .c or .cpp files) then these would need to be incorporated in the compile process as I describe in my answer mentioned above.

Also you would need to (compile and) link in any libraries used by the sketch.

You haven't asked about the linking side of things, but naturally the various files, as compiled, need to be linked together, and then turned into a .elf and .hex file for uploading purposes. See below.

Example makefile

Based on the IDE output I made a simple makefile a while back:

# Simple Arduino Makefile
# Author: Nick Gammon
# Date: 18th March 2015

# where you installed the Arduino app
ARDUINO_DIR = C:/Documents and Settings/Nick/Desktop/arduino-1.0.6/

# various programs
CC = "$(ARDUINO_DIR)hardware/tools/avr/bin/avr-gcc"
CPP = "$(ARDUINO_DIR)hardware/tools/avr/bin/avr-g++"
AR = "$(ARDUINO_DIR)hardware/tools/avr/bin/avr-ar"
OBJ_COPY = "$(ARDUINO_DIR)hardware/tools/avr/bin/avr-objcopy"

MAIN_SKETCH = Blink.cpp

# compile flags for g++ and gcc

# may need to change these
F_CPU = 16000000
MCU = atmega328p

# compile flags
GENERAL_FLAGS = -c -g -Os -Wall -ffunction-sections -fdata-sections -mmcu=$(MCU) -DF_CPU=$(F_CPU)L -MMD -DUSB_VID=null -DUSB_PID=null -DARDUINO=106
CPP_FLAGS = $(GENERAL_FLAGS) -fno-exceptions

# location of include files
INCLUDE_FILES = "-I$(ARDUINO_DIR)hardware/arduino/cores/arduino" "-I$(ARDUINO_DIR)hardware/arduino/variants/standard"

# library sources
LIBRARY_DIR = "$(ARDUINO_DIR)hardware/arduino/cores/arduino/"


    $(CC) $(CC_FLAGS) $(INCLUDE_FILES) $(LIBRARY_DIR)avr-libc/malloc.c -o malloc.c.o 
    $(CC) $(CC_FLAGS) $(INCLUDE_FILES) $(LIBRARY_DIR)avr-libc/realloc.c -o realloc.c.o 
    $(CC) $(CC_FLAGS) $(INCLUDE_FILES) $(LIBRARY_DIR)WInterrupts.c -o WInterrupts.c.o 
    $(CC) $(CC_FLAGS) $(INCLUDE_FILES) $(LIBRARY_DIR)wiring.c -o wiring.c.o 
    $(CC) $(CC_FLAGS) $(INCLUDE_FILES) $(LIBRARY_DIR)wiring_analog.c -o wiring_analog.c.o 
    $(CC) $(CC_FLAGS) $(INCLUDE_FILES) $(LIBRARY_DIR)wiring_digital.c -o wiring_digital.c.o 
    $(CC) $(CC_FLAGS) $(INCLUDE_FILES) $(LIBRARY_DIR)wiring_pulse.c -o wiring_pulse.c.o 
    $(CC) $(CC_FLAGS) $(INCLUDE_FILES) $(LIBRARY_DIR)wiring_shift.c -o wiring_shift.c.o 
    $(CPP) $(CPP_FLAGS) $(INCLUDE_FILES) $(LIBRARY_DIR)HardwareSerial.cpp -o HardwareSerial.cpp.o 
    $(CPP) $(CPP_FLAGS) $(INCLUDE_FILES) $(LIBRARY_DIR)IPAddress.cpp -o IPAddress.cpp.o 
    $(CPP) $(CPP_FLAGS) $(INCLUDE_FILES) $(LIBRARY_DIR)main.cpp -o main.cpp.o 
    $(CPP) $(CPP_FLAGS) $(INCLUDE_FILES) $(LIBRARY_DIR)new.cpp -o new.cpp.o 
    $(CPP) $(CPP_FLAGS) $(INCLUDE_FILES) $(LIBRARY_DIR)Print.cpp -o Print.cpp.o 
    $(CPP) $(CPP_FLAGS) $(INCLUDE_FILES) $(LIBRARY_DIR)Stream.cpp -o Stream.cpp.o 
    $(CPP) $(CPP_FLAGS) $(INCLUDE_FILES) $(LIBRARY_DIR)Tone.cpp -o Tone.cpp.o 
    $(CPP) $(CPP_FLAGS) $(INCLUDE_FILES) $(LIBRARY_DIR)USBCore.cpp -o USBCore.cpp.o 
    $(CPP) $(CPP_FLAGS) $(INCLUDE_FILES) $(LIBRARY_DIR)WMath.cpp -o WMath.cpp.o 
    $(CPP) $(CPP_FLAGS) $(INCLUDE_FILES) $(LIBRARY_DIR)WString.cpp -o WString.cpp.o 
    rm core.a
    $(AR) rcs core.a malloc.c.o 
    $(AR) rcs core.a realloc.c.o 
    $(AR) rcs core.a WInterrupts.c.o 
    $(AR) rcs core.a wiring.c.o 
    $(AR) rcs core.a wiring_analog.c.o 
    $(AR) rcs core.a wiring_digital.c.o 
    $(AR) rcs core.a wiring_pulse.c.o 
    $(AR) rcs core.a wiring_shift.c.o 
    $(AR) rcs core.a CDC.cpp.o 
    $(AR) rcs core.a HardwareSerial.cpp.o 
    $(AR) rcs core.a HID.cpp.o 
    $(AR) rcs core.a IPAddress.cpp.o 
    $(AR) rcs core.a main.cpp.o 
    $(AR) rcs core.a new.cpp.o 
    $(AR) rcs core.a Print.cpp.o 
    $(AR) rcs core.a Stream.cpp.o 
    $(AR) rcs core.a Tone.cpp.o 
    $(AR) rcs core.a USBCore.cpp.o 
    $(AR) rcs core.a WMath.cpp.o 
    $(AR) rcs core.a WString.cpp.o 
    $(CC) -Os -Wl,--gc-sections -mmcu=$(MCU) -o $(MAIN_SKETCH).elf $(MAIN_SKETCH).o core.a -lm 
    $(OBJ_COPY) -O ihex -j .eeprom --set-section-flags=.eeprom=alloc,load --no-change-warnings --change-section-lma .eeprom=0 $(MAIN_SKETCH).elf $(MAIN_SKETCH).eep 
    $(OBJ_COPY) -O ihex -R .eeprom $(MAIN_SKETCH).elf $(MAIN_SKETCH).hex 

In that particular case, the .ino file compiled without any problems after renaming it to Blink.cpp and adding this line:

#include <Arduino.h>
  • Thank you Nick for your concise answer, I am nowhere near the level you are, and didn't even think about a make file. So basically the syntax is there same, it is just all about linking objects, right? Thank you for sharing your make file for me to dissect. I am sure there will be more questions arising from that! Thanks again! Mar 7, 2016 at 4:45
  • My file above worked when I posted it, but I believe may need tweaking for the later IDEs (because they move around or rename library files). Still, doing a verbose compile shows what the IDE is currently generating, which should get you started.
    – Nick Gammon
    Mar 7, 2016 at 4:49

I would just like to add a few points to Nick Gammon's answer:

  • You do not need to rename a .ino file in order to compile it: if you explicitly tell the compiler it's C++ (option -x c++), it will ignore the unusual file extension and compile it as C++.
  • You do not need to add #include <Arduino.h> in the .ino file: you can tell the compiler to do that for you (-include Arduino.h).

Using those tricks, I can compile Blink.ino with no modification, just by invoking avr-g++ with the proper command-line options:

avr-g++ -mmcu=atmega328p -DARDUINO=105 -DF_CPU=16000000L \
    -I/usr/share/arduino/hardware/arduino/cores/arduino \
    -I/usr/share/arduino/hardware/arduino/variants/standard \
    -Os -fno-exceptions -ffunction-sections -fdata-sections \
    -Wl,--gc-sections -g -Wall -Wextra \
    -x c++ -include Arduino.h \
    /usr/share/arduino/examples/01.Basics/Blink/Blink.ino \
    -x none /usr/local/lib/arduino/uno/libcore.a -lm \
    -o Blink.elf

A few notes on the above command-line:

  • /usr/local/lib/arduino/uno/libcore.a is where I saved the compiled Arduino core. I hate recompiling over and over again the same stuff.
  • -x none is needed to tell the compiler to mind the file extensions again. Without it, it would assume libcore.a is a C++ file.

I learned those tricks from Sudar Muthu's Arduino-Makefile. This is a very general Makefile that works with many boards and with libraries. The only thing missing relative to the Arduino IDE is the forward declarations.

  • Very nice, Edgar! My solution basically mimics what the IDE does, yours solves the actual problem to hand in a much neater way. Of course you would have to make the libcore.a file in advance. I suppose that the lines in my answer which build core.a could be done in advance, so they don't have to be part of each build. Experience has shown that more complex sketches (eg. using Wire or SPI) need more files to be added to core.a.
    – Nick Gammon
    Mar 7, 2016 at 19:47
  • @NickGammon: That's right, Muthu's Makefile (and, I assume, the Arduino IDE) tends to put whatever libraries you use in libcore.a. I do not really like this approach, as it makes the supposedly “core library” dependent on the particular program you compile. For single-file libraries, like Wire or SPI, I prefer to just put the library C++ file in the same compile command as the main program. That command line gets easily quite long, so I use a Makefile. Mar 7, 2016 at 20:50
  • 1
    One of the things I like about the IDE is that you don't have to muck around. For simple projects anyway, it "just works".
    – Nick Gammon
    Mar 7, 2016 at 21:05
  • Hi! I'm trying to figure out how to save some space in a large Arduino sketch, and would like to inspect the generated object file. I'm not sure I understand all this business about pre-compiling libcore.a, however. Is there an ELI5 for obtaining/inspecting the compiled code in a form similar to that which the Arduino IDE would generate?
    – MRule
    Oct 17, 2021 at 15:00
  • @MRule: Disassemble the final .elf with avr-objdump. There are a few answers here explaining how. Oct 17, 2021 at 17:59

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.