Две фазы компиляции

Общая схема процесса получения выполняемой программы

Компиляция программы

Язык C. Лекция 2

Способ передачи аргументов при вызове функции

В языке C имеется единственный способ передачи аргументов (параметров) при вызове функций — по значению. Это значит, что в момент вызова функции создается временная переменная и в нее копируется значение аргумента. Таким образом, функция имеет дело с копией, а не с аргументом-оригиналом.

Если нужно написать функцию, которая изменяет значение какой-либо переменной в вызывающей программе, ей следует передавать в качестве аргумента не саму переменную, а указатель на неё (т.е. адрес расположения переменной в памяти компьютера). Получив значение указателя (адреса), функция сможет читать содержимое памяти или записывать новое содержимое в память по указанному адресу (это называется косвенной адресацией, или косвенным доступом по указателю).

В языке C есть две специальных операции для работы с указателями:

&x — определить адрес (получить указатель) на переменную x

*p — обратиться к памяти по адресу p (т.е. получить доступ к переменной по указателю на нее).

4. Пример простой программы: решение квадратного уравнения

/* example1.c - Решение квадратного уравнения.

*/

#include <stdio.h>

#include <math.h>

void main (void)

{

double a, b, c, d, x1, x2;

printf ("Введи коэффициенты a, b, c: ");

scanf ("%lf%lf%lf", &a, &b, &c);

d = b*b - 4.0*a*c;

if (d < 0.0)

printf ("Уравнение имеет комплексные корни\n");

else {

x1 = (-b + sqrt(d))/(2.0*a);

x2 = (-b - sqrt(d))/(2.0*a);

printf ("x1 = %g\nx2 = %g\n", x1, x2);

}

}


[1] Имеются переводы на русский язык обеих изданий книги Кернигана и Ритчи. Второе английское издание вышло по меньшей мере в трех разных переводах:

– Перевод Вик. С. Штаркмана под ред. Вс. С. Штарк­ма­на, М.: «Финансы и статистика», 1992 г.

– Те же переводчик и редактор, исправленное издание (пересмотрен перевод терминологии, заново проведена сверка с оригиналом). СПб.: «Невский Диалект», 2001.

– Перевод В. Л. Бродового. М.: Издательский дом «Вильямс», 2006.

[2] В языке C говорят, что функция «возвращает» значение, т.е. программа обращается к функции («вызывает» ее), та вычисляет значение, соответствующее заданному аргументу (аргументам) и возвращает вычисленное значение вызвавшей программе, которая после этого продолжает свою работу.

Процесс создания выполняемого файла (т.е. программы в машинном коде) из исходного текста на языке C состоит из следующих шагов:

Зачем нужны два раздельных шага — компиляция и компоновка[1] программы?

1. Стандартные библиотечные функции скомпилированы заранее и прилагаются к компилятору в виде объектных библиотек. Требуемые функции отыскиваются в библиотеках и присоединяются к программе на этапе сборки (компоновки). Поручать эту работу компилятору нерационально — он ориентирован на обработку исходного текста, тогда как объектные файлы и библиотеки содержат не текст, а двоичный машинный код. У компилятора и компоновщика (линкера) разная специализация — они имеют дело с информацией, представленной в принципиально отличных формах, и выполняют работу, непохожую по содержанию.

2. Возможность раздельной компиляции. Программа на языке C обычно состоит из нескольких исходных файлов (у больших программ число файлов достигает нескольких десятков или даже сотен). Каждый из файлов можно компилировать отдельно и независимо от других. Когда в какой-то один файл вносятся изменения, нет необходимости компилировать всю программу заново (при условии, что сохранились все объектные файлы). Достаточно перекомпилировать только измененный файл, а затем заново скомпоновать программу из объектных файлов (компоновка выполняется гораздо быстрее, чем компиляция).

3. Программисты-профессионалы довольно часто пишут отдельные части программы на других языках (Фортране, Ассемблере и т.д.). Для каждого языка используется свой компилятор, а полученные объектные файлы связываются в единую исполняемую программу с помощью компоновщика. Объектный файл не содержит «языковой» специфики, поскольку он состоит в основном из машинных команд. Поэтому компоновщик (линкер) вполне может работать с объектными файлами от разных компиляторов.

Примечание. Строго говоря, не всякие объектные файлы совместимы между собой. Существуют несколько разных (более или менее стандартизованных) форматов объектных файлов и библиотек. Как минимум, необходимо, чтобы все используемые файлы имели одинаковый формат. Однако, и этого бывает еще недостаточно. Требуется, чтобы компиляторы придерживались согласованных правил в отношении передачи параметров, использования регистров процессора, распределения памяти и т. п. либо позволяли явным образом задавать (менять) эти правила. В полной мере совместимыми на уровне объектного кода обычно бывают компиляторы, выпускаемые одной фирмой либо созданные (разными разработчиками) в рамках общего проекта.

Компиляция программы на языке C происходит в два этапа:

1. Препроцессирование (предварительная обработка текста)

2. Собственно компиляция (вообще говоря, тоже делится на две стадии — грамматический анализ и генерацию машинного кода).

Первая фаза выполняется препроцессором. Вначале из текста удаляются все комментарии. Затем препроцессор обрабатывает строки, начинающиеся с символа # — директивы препроцессора. К ним, в частности, относятся:

#include< имя-файла >

или

#include " имя-файла "

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

#define имя подставляемый-текст

Это директива макроподстановки в простейшей форме (бывает еще макроподстановка с параметрами; мы не будем пока ее рассматривать). Здесь имя — любое имя (идентификатор), т.е. последовательность букв и цифр, начинающаяся с буквы, а подставляемый-текст — любой текст без каких-либо ограничений (все, что находится в директиве после имени до конца строки). Если препроцессор находит имя в лежащем ниже тексте программы, он заменяет его на подставляемый текст. (Имя должно входить в текст как самостоятельный лексический элемент. Если имя является частью более длинного имени, то замена не производится. Не анализируется также содержимое текстовых констант.)

Препроцессор не анализирует грамматику и семантику (смысловое содержание) текста, он работает на уровне лексических элементов (т.е. умеет выделять в тексте отдельные «слова», но не интересуется смыслом этих слов).

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


Понравилась статья? Добавь ее в закладку (CTRL+D) и не забудь поделиться с друзьями:  



double arrow
Сейчас читают про: