База знаний · Язык скетчей
Переменные и типы данных Arduino
Сколько байт занимает int на Uno и на ESP8266, почему double не точнее float и как 40000 превращается в −25536.
Переменная — это именованная ячейка памяти, а тип говорит, сколько байт она занимает и какие числа в неё поместятся. На плате с двумя килобайтами оперативной памяти это не теория: неверно выбранный тип либо съедает память, либо молча портит числа.
Сколько байт занимает каждый тип
Главный подвох в том, что «типы данных Arduino» — это не одна таблица. Размеры зависят от платы. Мы собрали один и тот же скетч для Arduino Uno и для NodeMCU (ESP8266) и получили разные числа:
| Тип | Uno, байт | ESP8266, байт | Что важно знать |
|---|---|---|---|
bool | 1 | 1 | true или false |
char | 1 | 1 | буква или число; знаковость разная (см. ниже) |
byte | 1 | 1 | 0…255, псевдоним uint8_t из ядра Arduino |
int | 2 | 4 | на Uno −32768…32767, на ESP8266 вчетверо шире |
unsigned int | 2 | 4 | на Uno 0…65535 |
word | 2 | 2 | на ESP8266 это уже НЕ то же самое, что unsigned int |
long | 4 | 4 | −2 147 483 648…2 147 483 647 |
unsigned long | 4 | 4 | 0…4 294 967 295, тип millis() |
long long | 8 | 8 | нужен редко, арифметика дорогая |
float | 4 | 4 | 6–7 значащих цифр |
double | 4 | 8 | на Uno это буквально тот же float |
указатель | 2 | 4 | адрес в памяти |
size_t | 2 | 4 | тип размеров и длин |
Пять строк расходятся: int, unsigned int, double, указатель и size_t. Именно поэтому скетч, отлаженный на ESP8266, при переносе на Uno может начать врать без единой ошибки при сборке — числа перестают помещаться в переменные.
Переполнение: как 40000 превращается в −25536
Правило простое: если значение может превысить 32 767 (миллисекунды, расстояние в микрометрах, сумма показаний), берите long или unsigned long. Кстати, беззнаковые типы переполняются по-своему: 0u - 1u даёт не −1, а 65535.
Шпаргалка, которую можно проверить без платы
Этот скетч ничего не делает — он проверяет утверждения о числах прямо во время сборки. Если он собрался без ошибок, значит все семь утверждений верны именно на вашей плате. Нажмите «Проверить» — плата не нужна:
// Шпаргалка-самопроверка: плата не нужна, достаточно нажать «Проверить».
// Каждая строка — утверждение о числах. Если скетч собрался без ошибок,
// значит все утверждения верны именно на вашей плате.
void setup() {
static_assert(sizeof(int) == 2, "На этой плате int НЕ 2 байта");
static_assert(sizeof(double) == sizeof(float), "double НЕ равен float");
static_assert(7 / 2 == 3, "Целые делятся с отбрасыванием дробной части");
static_assert(7 / 2.0 == 3.5, "Достаточно одного вещественного числа");
static_assert((int)40000 == -25536, "40000 не помещается в int");
static_assert((unsigned int)(0u - 1u) == 65535, "Ноль минус один у беззнакового");
static_assert(-1 > 1u, "Знаковое и беззнаковое сравнивать опасно");
}
void loop() {
}
На Arduino Uno скетч собирается (444 байта), то есть верно всё: и 7 / 2 == 3, и double равен float, и превращение 40000 в −25536. Поменяйте плату на ESP8266 — и сборка упадёт на первой же строке: там int занимает 4 байта. Это самый быстрый способ узнать правду про свою плату.
float и double: почему на Uno это одно и то же
На AVR-платах double занимает те же 4 байта, что float, и считает с той же точностью — так прямо написано в официальном справочнике Arduino. Писать double «для надёжности» бессмысленно: точности это не добавит.
Зато вещественная арифметика на Uno дорогая: у ATmega328P нет блока для дробных чисел, и компилятор подшивает программные подпрограммы. Мы собрали два одинаковых скетча, отличающихся только типом переменных: целочисленный занял 1868 байт, вещественный — 2772 байта. Разница — 904 байта, почти килобайт из 32. Аппаратного блока вещественной арифметики нет и у ESP8266, но там его подпрограммы уже входят в системную библиотеку, которая подключается к любому скетчу, — поэтому тот же переход на float добавляет считанные байты.
Заодно развеем популярный миф: «0.1 + 0.2 никогда не равно 0.3». На Arduino Uno в 32-битном float эти числа равны — миф пришёл из языков, где числа восьмибайтовые. На ESP8266 с честным double они действительно не равны.
char и byte: один байт, разное поведение
Оба типа занимают один байт, но char предназначен для букв, а byte — для чисел 0…255. Мелочь, которая ловит многих: Serial.println() печатает char как символ, а byte — как число.
Есть и ловушка посерьёзнее: на Uno char знаковый (−128…127), а на ESP8266 — беззнаковый (0…255). Мы проверили это сборкой на обеих платах. Значит char value = 200; на Uno превращается в −56, а на ESP8266 остаётся 200, и проверка if (value > 100) даёт разный ответ. Настройками среды это не лечится: знаковость приходит от самого компилятора. Вывод: для чисел берите byte, а char оставьте буквам.
Где объявлять переменную
До setup() — глобальная, видна всем функциям и живёт всё время работы. Внутри функции — локальная, создаётся при входе и исчезает при выходе. Внутри функции со словом static — создаётся один раз, но видна только в этой функции; памяти занимает столько же, сколько глобальная. Подробный разбор с примером счётчика — в статье про setup() и loop().
const и #define: что выбрать
По расходу памяти — ровно ничего не меняется: мы собрали два варианта одного скетча и получили одинаковые числа до байта. Выбирайте по удобству: у const int PIN = 13; есть тип, его проверит компилятор, а #define PIN 13 — просто текстовая замена без всякой проверки. Современный совет — const.
А вот чего const НЕ делает на Uno — не убирает данные из оперативной памяти. Массив const int table[100] всё равно занял 200 байт ОЗУ; убрать его во флеш умеет только PROGMEM, причём бесплатно — прошивка в нашем замере не выросла ни на байт (1978 байт в обоих случаях), а ОЗУ освободилось с 388 до 188 байт.
String: удобно, но дорого
Класс String позволяет складывать текст знаком «плюс», и это соблазнительно. Цена на Uno: скетч со String занял 3186 байт флеша, а тот же вывод на массиве char — 1652 байта. Разница полторы тысячи байт.
Хуже другое: отчёт сборки в этом случае обманывает. У варианта со String он показал даже меньший расход ОЗУ (210 против 218 байт), потому что сам текст живёт в куче и в отчёт не попадает. Именно так рождаются «необъяснимые» зависания на Uno. Разбор — на странице «Память Arduino».
Отдельный совет для русских текстов: каждая кириллическая буква в строке занимает два байта. Лечится макросом F() — он оставляет текст во флеше. Мы собрали скетч с четырьмя русскими сообщениями: без F() они съели 466 байт ОЗУ из 2048, с F() осталось 188 байт. Освободилось 278 байт — четверть всей памяти платы, а прошивка выросла всего на 16 байт.
Проверено на нашей сборке
Все числа получены 21 августа 2026 года: arduino-cli 1.5.2-rc.1, ядра arduino:avr 1.8.8 и esp8266:esp8266 3.1.2, платы Arduino Uno и NodeMCU 1.0. Расход ОЗУ взят из строки отчёта «Глобальные переменные используют…» — она показывает только глобальные переменные, без стека и кучи.
Частые вопросы
Сколько байт занимает int в Arduino?
Зависит от платы. На Arduino Uno, Nano и Mega (архитектура AVR) int занимает 2 байта и хранит числа от −32768 до 32767. На ESP8266, ESP32 и других 32-битных платах int занимает 4 байта. Мы измерили это компилятором: один и тот же скетч даёт 2 байта на Uno и 4 на NodeMCU.
Чем отличается float от double?
На Arduino Uno — ничем: double там занимает те же 4 байта и считает с той же точностью, что float. Официальная документация Arduino это подтверждает. На ESP8266 и ESP32 double честно занимает 8 байт, поэтому один и тот же расчёт на разных платах даёт разную точность.
Что лучше — String или массив char?
На Uno с его 2 КБ памяти надёжнее массив char. В нашем замере скетч со String занял 3186 байт флеша, а тот же вывод на char-массиве — 1652 байта. Хуже другое: отчёт сборки показывает у String даже меньший расход ОЗУ, потому что текст живёт в куче и в отчёт не попадает.
Почему число больше 32767 становится отрицательным?
Это переполнение: в int на Uno помещается только 65536 разных значений. Запись int big = 40000; молча превращается в −25536 (40000 − 65536), и компилятор об этом не предупреждает даже на максимальном уровне предупреждений. Для больших чисел берите long или unsigned long.
Читайте также
Обновлено: 21 августа 2026
