The Definitive Answer: Is Arduino C or C++?
When makers first open the Arduino IDE, the immediate question is often: is Arduino C or C++? The short answer is that the Arduino environment is fundamentally built on C++, but it heavily relies on C libraries and abstractions. When you compile a sketch for an AVR-based board like the Uno R3 or Nano, the IDE uses avr-g++ (the C++ compiler from the GCC toolchain), not avr-gcc (the C compiler). For 32-bit ARM boards like the Zero or Portenta H7, it uses arm-none-eabi-g++.
However, because C++ was originally designed to be mostly backward-compatible with C, you can write standard C code in an Arduino sketch and it will compile perfectly. Furthermore, the underlying hardware abstraction libraries (like AVR-Libc for 8-bit chips) are written in pure C. Understanding how to leverage both languages is critical for writing memory-efficient, modular firmware in 2026.
Under the Hood: The Arduino Preprocessor
To understand how to mix C and C++, you must understand how the IDE processes your .ino files. According to the official Arduino build process documentation, the IDE does not compile .ino files directly. Instead, it performs the following steps:
- Concatenates all
.inotabs into a single file. - Automatically generates function prototypes for any functions you define, inserting them at the top of the file.
- Adds
#include <Arduino.h>to the very top. - Saves the result as a
.cppfile in the temporary build directory. - Compiles the
.cppfile using the C++ compiler.
Expert Insight: The automatic prototype generation is notorious for failing when you use advanced C++ features like templates, namespaces, or complex pointer returns. When the preprocessor fails, the C++ compiler throws obscure 'undefined reference' or 'syntax' errors. The solution is to bypass the
.inowrapper entirely for complex logic, which we will cover below.
Tutorial 1: Building Native C++ Classes in Arduino IDE
While you can write C++ classes directly inside your .ino file, it is terrible practice for project scalability. Instead, use the IDE's tab system to create dedicated C++ header and source files.
Step 1: Create the Header Tab
Click the downward arrow on the right side of the IDE tab bar and select New Tab. Name it MotorController.h. This file defines the class interface.
#ifndef MOTOR_CONTROLLER_H
#define MOTOR_CONTROLLER_H
#include <Arduino.h>
class MotorController {
private:
uint8_t _pinA;
uint8_t _pinB;
uint8_t _pwmPin;
public:
MotorController(uint8_t pinA, uint8_t pinB, uint8_t pwmPin);
void begin();
void setSpeed(int speed); // -255 to 255
};
#endif
Step 2: Create the Implementation Tab
Create another tab named MotorController.cpp. Notice the .cpp extension; this tells the IDE to compile it directly with avr-g++ without running the Arduino preprocessor on it.
#include 'MotorController.h'
MotorController::MotorController(uint8_t pinA, uint8_t pinB, uint8_t pwmPin) {
_pinA = pinA;
_pinB = pinB;
_pwmPin = pwmPin;
}
void MotorController::begin() {
pinMode(_pinA, OUTPUT);
pinMode(_pinB, OUTPUT);
pinMode(_pwmPin, OUTPUT);
}
void MotorController::setSpeed(int speed) {
if (speed > 0) {
digitalWrite(_pinA, HIGH);
digitalWrite(_pinB, LOW);
} else {
digitalWrite(_pinA, LOW);
digitalWrite(_pinB, HIGH);
}
analogWrite(_pwmPin, min(abs(speed), 255));
}
Step 3: Use the Class in Your Main Sketch
In your main main.ino file, simply include your header and instantiate the object.
#include 'MotorController.h'
MotorController leftMotor(4, 5, 6);
void setup() {
leftMotor.begin();
}
void loop() {
leftMotor.setSpeed(200);
delay(1000);
leftMotor.setSpeed(-200);
delay(1000);
}
Tutorial 2: Integrating Pure C Code with extern 'C'
Sometimes you will need to use a legacy C library, a vendor-provided sensor driver written in C, or highly optimized DSP algorithms from the AVR Libc manual. If you try to #include a pure C header into a C++ Arduino sketch, the compiler will fail during the linking stage due to C++ name mangling.
C++ alters function names in the compiled object file to support function overloading (e.g., readSensor becomes _Z11readSensorv). C compilers do not do this. To tell the C++ compiler to use C-style linkage for a specific block of code, you must use the extern 'C' directive.
How to Wrap C Headers
If you are writing a C header file (e.g., i2c_sensor.h) that needs to be compatible with both C and C++ Arduino sketches, add this boilerplate to the top and bottom of your header:
#ifndef I2C_SENSOR_H
#define I2C_SENSOR_H
#ifdef __cplusplus
extern 'C' {
#endif
// Your pure C function declarations
uint8_t sensor_init(uint8_t address);
float sensor_read_temperature(void);
#ifdef __cplusplus
}
#endif
#endif
This ensures that when avr-g++ compiles your Arduino sketch, it knows not to mangle the names of sensor_init and sensor_read_temperature, allowing the linker to successfully match them to the compiled .c object files.
Memory Overhead: C vs C++ on 8-Bit Microcontrollers
A common myth is that C++ inherently bloats microcontroller firmware. In reality, standard C++ features like classes, encapsulation, and templates resolve to the exact same machine code as C structs and macros when compiled with optimization flags (like -Os used by Arduino). However, specific C++ features do introduce overhead.
| Feature | C Equivalent | Flash Overhead (AVR) | RAM Overhead (AVR) | Execution Speed Impact |
|---|---|---|---|---|
| Standard Class (No Virtuals) | Struct + Functions | 0 Bytes | 0 Bytes | None |
| Virtual Functions | Function Pointers | +20 to +100 Bytes (vtable) | +2 Bytes per object (vptr) | Minor (indirect jump) |
| Exceptions (try/catch) | Error Codes | +2KB to +10KB | +100+ Bytes | Severe (disabled by default) |
| RTTI (dynamic_cast) | Manual Type Tags | +1KB+ | Minimal | Moderate |
| Templates | Macros | Code bloat if overused | 0 Bytes | None (compile-time) |
On an ATmega328P with only 2KB of SRAM, the 2-byte virtual pointer (vptr) added to every object instance using virtual functions can quickly consume memory if you are instantiating arrays of objects. For 32-bit ARM Cortex-M0+ boards (like the RP2040 or SAMD21) with 256KB+ of RAM, this overhead is entirely negligible.
Advanced: Upgrading the C++ Standard in Arduino
By default, many older Arduino cores compile using the C++11 standard (-std=gnu++11). If you want to use modern C++14 or C++17 features like std::optional, structured bindings, or constexpr if, you can modify the core's build configuration.
- Navigate to your Arduino15 packages directory (e.g.,
~/.arduino15/packages/arduino/hardware/avr/1.8.6/on Linux/Mac or%LOCALAPPDATA%\Arduino15\packages\arduino\hardware\avr\1.8.6\on Windows). - Open
platform.txtin a text editor. - Find the line starting with
compiler.cpp.flags=. - Change
-std=gnu++11to-std=gnu++17. - Save the file and restart the Arduino IDE.
Note: Modifying core files directly is fragile; updating the board manager will overwrite your changes. For production environments in 2026, it is highly recommended to use PlatformIO or Arduino CLI with custom build_flags in your platformio.ini or arduino-cli.yaml configuration files to enforce C++ standards safely.
Summary Best Practices
- Use C++ for architecture: Leverage classes, constructors, and RAII (Resource Acquisition Is Initialization) to manage hardware pins and peripheral states safely.
- Use C for low-level ISRs: Interrupt Service Routines should be kept as close to pure C as possible to minimize execution latency and avoid hidden C++ object context switching.
- Avoid the heap: Whether using C
malloc()or C++new, dynamic memory allocation causes fragmentation on 8-bit MCUs. Prefer static allocation or memory pools.






