Страницы

Поиск по вопросам

пятница, 31 января 2020 г.

Как работает компилятор языка C?

#c #tcl


Мне нужно описание работы компилятора на естествeннoм языке составленное для того
кто этого языка не знает. То есть учебник в духе "представь что ты компилятор, от этого
двигаемся дальше."

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


Ответы

Ответ 1



Вы должны думать следующим образом. Общая картина. Компилируются C-файлы по отдельности, компилятор не знает ничего о других файлах, если это не указано в файле явно. Другие файлы «втягиваются» препроцессором. (Для пуристов, да, я могу скормить и .h компилятору через Makefile, не будем усложнять картину без надобности.) Препроцессор. Он проходится по коду и производит тупые текстовые макроподстановки. #define X(Y, Z) for (int i = 0; i < Y; i = i * Z) заставляет X(10, 2 + 1) превращаться в for (int i = 0; i < 10; i = i * 2 + 1). Препроцессор, однако, знает о строках, и не проводит макроподстановки внутри них. Также он применяет #include путём механического включения в это место файла. Препроцессор строк. Внутри строковых и символьных литералов некоторые последовательности символов заменяются на другие. Например, \n заменяется на символ с кодом 10. Также, для литералов широких строк (wchar_t*) может применяться перекодировка из character set'а исходного файла в UCS-2 или UCS-4, в зависимости от компилятора. Собственно компилятор. Никакой магии у компилятора нет. Есть ключевые слова (for, if, etc.) и функции. Например, printf — это функция (из стандартной библиотеки), запись printf("%d\n", 15); производит в скомпилированном коде вызов функции printf и передачу ей параметров "%d\n" и 15. Точно так же вызов printf("%d\n", ""); производит в вызов функции printf с параметрами "%d\n" и "" (этот вызов завершится с ошибкой времени выполнения). Компилятор знает точную семантику форматной строки printf и имеет право выдать подсказку, если он видит, что типы параметров не подходят к форматной строке. Оптимизатор. Он имеет право внутри заменить любую конструкцию на более эффективную, пользуясь правилом as if: если с точки зрения конечного вывода и видимых пользователю значений это не меняет результат, преобразование допустимо. Пример: если у вас есть длинное вычисление без побочных эффектов, результатом которого вы не пользуетесь (то есть, не выводите его), оптимизатор имеет право выкинуть его. И также имеет право и не выкидывать. Например, порядок вычисления слагаемых в выражении A() + B() не определён, и даже если функции A и B имеют побочные эффекты, оптимизатор имеет право вычислять их в любом порядке, может быть даже вперемешку. Если вы хотите гарантировать, что A() вычислится строго перед B(), пользуйтесь явной дополнительной переменной. Undefined behaviour. Here be dragons. Существует достаточно большой набор рантайм-ситуаций (например: разыменовние нулевого указателя, выход за границу массива (!) или знаковое переполнение), когда компилятор перестаёт нести ответственность за результат. Компилятор имеет право предполагать, что такого никогда не случится, делать из этого нетривиальные умозаключения, и применять их для упрощения кода. Например: для кода int m[1]; if (cond) { printf("Хе-хе\n"); return; } for (int i = 0; i < 2; i++) { m[i] = 0; } компилятор имеет право предположить, что обращение m[1] никогда не происходит, поэтому цикл не выполняется, поэтому код должен выйти на раннем return, поэтому cond обязательно равно true, значит, его можно не вычислять, и упростить всю функцию до printf("Хе-хе\n");

Комментариев нет:

Отправить комментарий