Перейти к основному содержимому

Выполнение кода — итоги

Разработчику Аналитику Тестировщику Архитектору Инженеру

Кратко — что стоит унести из раздела "Выполнение кода". Если пункт кажется туманным — откройте указанную главу или оглавление.


FAQ — Часто задаваемые вопросы

Типичные сбои при переходе от исходника к работающей программе, плюс формулировки, как их ищут в Google — с кратким ответом и ссылкой на главу. Определения для зачёта — в чек-листе.

Вопрос. Программа падает с StackOverflowError или "maximum recursion depth exceeded".

Ответ. Стек вызовов переполнен — обычно бесконечная или слишком глубокая рекурсия без базового случая. Перепишите на цикл или добавьте явное условие выхода и ограничение глубины. Подробнее здесь — вызовы, функции.

Вопрос. OutOfMemoryError, хотя в диспетчере задач "ещё много RAM".

Ответ. Процесс упёрся в лимит кучи или фрагментацию, а не в физическую память целиком. Ищите бесконечное накопление объектов, огромные массивы в памяти и утечки через коллекции. Подробнее здесь — память процесса, переменные.

Вопрос. Локальная переменная в C/C++ показывает "мусор" при первом чтении.

Ответ. На стеке лежит неинициализированное значение — чтение даёт неопределённое поведение. Всегда присваивайте начальное значение перед использованием. Подробнее здесь — неопределённое поведение, переменные.

Вопрос. В отладчике переменная "optimized out" — не вижу значение.

Ответ. Компилятор в Release убрал переменную, оставив только регистры. Соберите Debug, временно пометьте volatile или отключите оптимизацию для проблемного участка. Подробнее здесь — архитектура процессора, отладка.

Вопрос. .exe собран на 64-bit, на старом ПК "не запускается" — связано с разрядностью?

Ответ. 64-битный процесс не стартует на 32-битной ОС; обратная совместимость часто есть, но не гарантирована для нативного кода. Собирайте под целевую платформу AnyCPU/x86/x64 явно. Подробнее здесь — машинное слово, программа.

Вопрос. Массив создал в функции — после return указатель "битый".

Ответ. Память на стеке освобождается при выходе из функции; возвращать адрес локального массива в C нельзя. Выделяйте в куче (malloc, new) или возвращайте копию по значению. Подробнее здесь — память, функции.

Вопрос. Конкatenация строк в цикле на Python/C# стала тормозить на больших данных.

Ответ. Строки неизменяемы — каждый + создаёт новый объект. Используйте StringBuilder, список с одним join или буфер. Подробнее здесь — переменные, оптимизация.

Вопрос. Java-приложение на секунду "замирает" — виноват сборщик мусора?

Ответ. Длинная stop-the-world пауза GC возможна при большой куче или утечке ссылок. Смотрите логи GC, уменьшайте аллокации в горячем пути, проверьте удержание объектов. Подробнее здесь — байт-код и VM, архитектура выполнения.

Вопрос. Скопировал .class или .jar на машину без Java — двойной щелчок ничего не делает.

Ответ. Байт-код исполняет JVM; без установленного JRE или bundled runtime файлы не запустятся. Используйте java -jar из терминала или поставьте JRE. Подробнее здесь — байт-код.

Вопрос. Путаю .NET Framework, .NET Core и просто ".NET" — runtime не находится.

Ответ. Это разные поколения платформы с разными установщиками. Смотрите TargetFramework в проекте и ставьте matching SDK/runtime на машину запуска. Подробнее здесь — байт-код, проект.

Вопрос. Декомпилировал .exe, восстановленный код не компилируется.

Ответ. Декомпиляция теряет имена, типы и структуру; оптимизации стирают "читаемость". Это инструмент анализа, а не кнопка "вернуть исходник". Подробнее здесь — дизассемблирование.

Вопрос. В Debug всё работает, в Release падает без понятной ошибки.

Ответ. Классика неопределённого поведения — выход за границы массива, гонка, чтение освобождённой памяти. Оптимизация меняет порядок операций и "проявляет" баг. Подробнее здесь — неопределённое поведение.

Вопрос. sizeof структуры больше суммы полей — откуда лишние байты?

Ответ. Компилятор вставляет padding для выравнивания под размер машинного слова — так CPU читает быстрее. Менять pack без нужды ломает бинарную совместимость. Подробнее здесь — расположение в памяти, машинное слово.

Вопрос. Бинарный файл, записанный на одном компьютере, на другом читается "задом наперёд".

Ответ. Разный порядок байтов (endianness) — little-endian vs big-endian. Для сетевых форматов и файлов фиксируйте порядок явно или используйте текстовые протоколы. Подробнее здесь — машинное слово, шестнадцатеричная система.

Вопрос. Глобальная переменная меняется "сама" — я её нигде не трогал.

Ответ. Другой модуль, поток или неинициализированное чтение могли изменить значение. Минимизируйте глобальное состояние, ищите все записи через поиск по проекту. Подробнее здесь — переменные.

Вопрос. Замыкание в цикле всегда возвращает последнее значение счётчика.

Ответ. Лямбда захватывает ссылку на переменную, а не снимок на момент создания. Создайте локальную копию внутри тела цикла или используйте default-аргумент. Подробнее здесь — переменные, функции.

Вопрос. Написал if (x = 5) вместо if (x == 5) — программа ведёт себя странно.

Ответ. В C-подобных языках это присваивание, результат всегда "истина". Включите предупреждения компилятора и используйте Yoda-стиль или === там, где язык различает типы. Подробнее здесь — условия.

Вопрос. Цикл for выполнился на одну итерацию больше, чем я считал на бумаге.

Ответ. Проверьте границу: < или <=, off-by-one при индексации с нуля и изменение счётчика внутри тела. Подробнее здесь — циклы.

Вопрос. JVM выдаёт VerifyError при запуске чужого .class.

Ответ. Байт-код не прошёл верификацию — повреждённый файл, несовместимая версия class file или подмена после компиляции. Пересоберите из исходников той же версии JDK. Подробнее здесь — байт-код.

Вопрос. Первый запуск Java/.NET медленный, следующие быстрее — это нормально?

Ответ. Да, JIT прогревает горячие методы после интерпретации. Для бенчмарков делайте несколько прогонов и не сравнивайте "холодный" старт с уже скомпилированным AOT. Подробнее здесь — байт-код.

Вопрос. Стек вызовов в логе ошибки — десятки непонятных строк, с чего начать?

Ответ. Читайте сверху вниз до первого вашего файла — там место, где исключение "всплыло". Ниже — цепочка вызовов, которая к нему привела. Подробнее здесь — функции, ошибки.

Вопрос. Добавил одну глобальную переменную — программа стала вдвое медленнее. Совпадение?

Ответ. Глобальные данные хуже ложатся в кэш CPU и мешают оптимизациям; иногда включается лишняя синхронизация. Профилируйте, прежде чем обвинять одну строку. Подробнее здесь — регистры, архитектура.

Вопрос. Hex-дамп в отладчике выглядит как случайные числа — как им пользоваться?

Ответ. Память — байты под тип данных; учите представление int, pointer и строк в hex параллельно с шестнадцатеричной системой. Сверяйте адрес и размер поля со структурой. Подробнее здесь — шестнадцатеричная система, битовые операции.

Вопрос. AOT-сборка (Native AOT, GraalVM) не находит reflection-классы в runtime.

Ответ. AOT включает только статически известный код; динамическая загрузка требует конфигурации трассировки. Либо оставьте JIT, либо явно зарегистрируйте типы для AOT. Подробнее здесь — байт-код.

Вопрос. Процесс "ест" всю RAM, хотя объектов в коде немного.

Ответ. Проверьте подкачку и виртуальную память, нативные буферы (изображения, mmap), утечки в C-библиотеках через P/Invoke. Снимите heap dump или используйте perf/monitors. Подробнее здесь — память процесса, метрики.

Вопрос. Не понимаю разницу между "программа на диске" и "процесс в памяти".

Ответ. Файл .exe — статический образ; после запуска ОС создаёт процесс с сегментами кода, данных, стеком и кучей в RAM. Один файл может породить несколько процессов. Подробнее здесь — выполнение, программа.

Вопрос. Функция "чистая" на бумаге, но результат разный при одинаковом входе.

Ответ. Скрытые зависимости — глобальное состояние, время, I/O, random. Чистая функция зависит только от аргументов; вынесите побочные эффекты наружу. Подробнее здесь — функции.

Вопрос. Как компьютер выполняет программу пошагово?

Ответ. Цикл fetch → decode → execute: процессор читает инструкцию из памяти, расшифровывает и меняет регистры/память; так до конца процесса. Подробнее здесь — выполнение, архитектура CPU.

Вопрос. Что такое стек и куча (heap) в программировании?

Ответ. Стек — локальные переменные и вызовы функций LIFO; куча — динамические объекты с неограниченным временем жизни. Подробнее здесь — память, архитектура.

Вопрос. Чем стек отличается от кучи?

Ответ. Стек автоматически освобождается при выходе из функции; куча требует free/delete или сборщика мусора. Стек быстрее и ограничен по размеру. Подробнее здесь — память.

Вопрос. Что такое стек вызовов (call stack)?

Ответ. Цепочка активных функций от main до текущей — в логе ошибки читайте сверху вниз до своего файла. Подробнее здесь — процесс выполнения, функции.

Вопрос. Что такое JVM и зачем нужна Java Virtual Machine?

Ответ. JVM исполняет байт-код .class на любой ОС с установленной машиной; JIT ускоряет горячие методы. Подробнее здесь — байт-код.

Вопрос. Что такое .NET runtime CLR?

Ответ. Среда выполнения для C#, F#, VB.NET — загрузка сборок, JIT, GC, безопасность типов. Аналог JVM для экосистемы Microsoft. Подробнее здесь — байт-код.

Вопрос. Что такое JIT-компиляция простыми словами?

Ответ. Компиляция во время работы программы — часто используемый байт-код переводят в машинный код для скорости. Подробнее здесь — байт-код, теория кода.

Вопрос. Что такое AOT-компиляция?

Ответ. Ahead-of-time — машинный код до запуска, без JIT на клиенте; быстрый старт, меньше surprise в prod. Подробнее здесь — байт-код.

Вопрос. Чем компилятор отличается от интерпретатора?

Ответ. Компилятор заранее строит исполняемый файл или байт-код; интерпретатор читает строки при каждом запуске (или смешанный JIT). Подробнее здесь — теория, выполнение.

Вопрос. Что такое garbage collection (сборка мусора)?

Ответ. Runtime освобождает объекты в куче, на которые не осталось ссылок — Java, C#, Python, Go. Утечки возможны, если ссылки удерживают объект. Подробнее здесь — байт-код, архитектура.

Вопрос. Что такое segmentation fault (segfault)?

Ответ. Обращение к недопустимому адресу памяти — null pointer, выход за границы массива, use-after-free в C/C++. Подробнее здесь — неопределённое поведение, память.

Вопрос. Что такое undefined behavior в C и C++?

Ответ. Стандарт не гарантирует результат — программа может упасть, дать мусор или "работать" до смены компилятора. Опаснее обычного exception. Подробнее здесь — неопределённое поведение.

Вопрос. Что такое регистры процессора?

Ответ. Сверхбыстрая память внутри CPU для операндов, адресов и флагов — быстрее RAM и кэша. Подробнее здесь — регистры.

Вопрос. 32-bit или 64-bit программа — в чём разница?

Ответ. Размер машинного слова и адресного пространства — 32-bit до ~4 ГБ RAM на процесс, 64-bit — больше и другой ABI. Подробнее здесь — машинное слово.

Вопрос. Что такое виртуальная память?

Ответ. ОС даёт процессу своё адресное пространство; страницы в RAM или на диске (swap). Изоляция процессов и больше "логической" памяти, чем физической. Подробнее здесь — память процесса.

Вопрос. Что такое декомпиляция программы?

Ответ. Восстановление похожего на исходник текста из .exe или байт-кода — для анализа, не для точной копии. Подробнее здесь — дизассемблирование.

Вопрос. Что такое машинное слово?

Ответ. Единица данных, с которой CPU удобно работает за такт — 32 или 64 бита; влияет на адресацию и размер регистров. Подробнее здесь — машинное слово.

Вопрос. Что такое endianness (порядок байтов)?

Ответ. Порядок байтов в многобайтовом числе — little-endian или big-endian; важен при сетевых протоколах и бинарных файлах. Подробнее здесь — машинное слово.

Вопрос. Как работает return в функции?

Ответ. Завершает функцию, кладёт результат (если есть) и восстанавливает предыдущий кадр стека. Подробнее здесь — функции, процесс выполнения.

Вопрос. Что такое рекурсия и почему stack overflow?

Ответ. Функция вызывает сама себя; каждый вызов — новый кадр на стеке. Без базового случая стек переполняется. Подробнее здесь — функции, вызовы.

Вопрос. Как Python хранит переменные в памяти?

Ответ. Имена — ссылки на объекты в куче; присваивание меняет ссылку, immutable-типы создают новый объект. Подробнее здесь — переменные, Python.

Вопрос. Как цикл for работает на уровне процессора?

Ответ. Компилятор разворачивает в метки, сравнение и условный переход (jump) — счётчик в регистре, проверка флагом CPU. Подробнее здесь — циклы, условия.


Что запомнить

Выполнение программного кода — это многоуровневый процесс, начинающийся с абстрактного описания логики на языке высокого уровня и завершающийся физическими изменениями состояния транзисторов в микросхемах процессора. Этот путь проходит через этапы компиляции или интерпретации, загрузки в среду выполнения, управления памятью, работы с регистрами и кэшем, а также взаимодействия с операционной системой и аппаратным обеспечением.

Ключевыми элементами этого процесса являются:

  • Переменные — именованные ссылки на участки памяти, имеющие ограниченное время жизни и область видимости.
  • Функции — изолированные блоки кода, принимающие входные данные, обрабатывающие их и возвращающие результат, при этом управляя потоком выполнения через стек вызовов.
  • Циклы — механизмы повторного выполнения инструкций, реализуемые через условные переходы и изменение состояния переменных.
  • Условные операторы — точки ветвления, определяющие дальнейший путь выполнения программы на основе логических выражений.
  • Машинное слово — базовая единица данных, с которой эффективно работает процессор, определяющая разрядность адресов, регистров и вычислений.
  • Регистры — сверхбыстрые ячейки внутри CPU, используемые для временного хранения операндов, адресов и управляющей информации.
  • Память — иерархическая система, включающая регистры, кэш, оперативную память и внешние носители, где данные организованы в стек и кучу.
  • Байт-код и виртуальные машины — промежуточный уровень абстракции, обеспечивающий платформонезависимость и гибкость исполнения.
  • Дизассемблирование и декомпиляция — методы анализа исполняемых файлов, позволяющие восстановить логику программы без исходного кода.
  • Неопределённое поведение — ситуация, когда спецификация языка не гарантирует результат выполнения, что делает программу непредсказуемой и потенциально опасной.

Понимание этих механизмов позволяет писать не только корректный, но и эффективный, безопасный и переносимый код. Оно формирует основу системного мышления, необходимого для работы с производительностью, отладкой, низкоуровневыми API, встраиваемыми системами и критически важными приложениями.


Куда идти дальше

ТемаРаздел
"Код — о разделе""Код — о разделе"
"Проект, структура и фреймворки — о разделе""Проект, структура и фреймворки — о разделе"
"Алгоритмы — о разделе""Алгоритмы — о разделе"
"Асинхронность — о разделе""Асинхронность — о разделе"

Проверьте себя: Чек-лист самопроверки.