База знаний · Язык скетчей

Переменные и типы данных Arduino

Сколько байт занимает int на Uno и на ESP8266, почему double не точнее float и как 40000 превращается в −25536.

Переменная — это именованная ячейка памяти, а тип говорит, сколько байт она занимает и какие числа в неё поместятся. На плате с двумя килобайтами оперативной памяти это не теория: неверно выбранный тип либо съедает память, либо молча портит числа.

Сколько байт занимает каждый тип

Главный подвох в том, что «типы данных Arduino» — это не одна таблица. Размеры зависят от платы. Мы собрали один и тот же скетч для Arduino Uno и для NodeMCU (ESP8266) и получили разные числа:

ТипUno, байтESP8266, байтЧто важно знать
bool11true или false
char11буква или число; знаковость разная (см. ниже)
byte110…255, псевдоним uint8_t из ядра Arduino
int24на Uno −32768…32767, на ESP8266 вчетверо шире
unsigned int24на Uno 0…65535
word22на ESP8266 это уже НЕ то же самое, что unsigned int
long44−2 147 483 648…2 147 483 647
unsigned long440…4 294 967 295, тип millis()
long long88нужен редко, арифметика дорогая
float446–7 значащих цифр
double48на Uno это буквально тот же float
указатель24адрес в памяти
size_t24тип размеров и длин

Пять строк расходятся: int, unsigned int, double, указатель и size_t. Именно поэтому скетч, отлаженный на ESP8266, при переносе на Uno может начать врать без единой ошибки при сборке — числа перестают помещаться в переменные.

Переполнение: как 40000 превращается в −25536

Всё, что помещается в int на Arduino Uno−3276803276740000 — сюда не помещается−2553640000 − 65536 = −25536
В int на Uno помещается 65 536 разных значений. Число 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