***** 1 - Stillclouds

advertisement
Лекция 7
Модульность
Причины модульности
• Структурированность
Данные, разделенные по отдельным файлам проще поддаются
структурированию, анализу и навигации
• Абстракция и сокрытие реализации
Возможность использования функций без указания их реализации
Возможность создания библиотек
• Частичная компиляция
Наличие «промежуточного формата» и этапа «связывания» позволяет
проводить компиляцию только части проекта
Последовательность сборки
• Препроцессирование
Обработка включаемых файлов и
директив компилятора
Подготовка «чистого» кода
• Компиляция
Синтаксическая и семантическая
обработка
Трансляция реализаций в
машинные коды
• Компоновка
Связывание имен и реализаций
Подготовка конечного
исполнимого файла
Исходный код
*.c,*.h,*.cpp,*.hpp
Препроцессирование
Единица трансляции
*.i
Компиляция
Промежуточный файл
*.obj
Компоновка
Исполнимый файл
*.exe, *.dll, *.lib
Фактическая схема
file1.h
common.h
file1.cpp
file2.h
file2.cpp
file3.h
file3.cpp
Препроцессор + Компилятор (Compiler)
file1.obj
file2.obj
Компоновщик (Linker)
file.exe
file3.obj
Препроцессирование
Этапы препроцессирования
• Начальное процессирование
Подготовка текста программы к разбору
• Лексический разбор (токенизация)
Разбиение текста программы на лексические единицы (лексемы или
токены) – ключевые слова, имена, операторы.
• Подстановка
Включение заголовочных файлов, условная компиляция, раскрытие
макросов
Начальное процессирование
• Чтение файла в память
Файл прочитывается как последовательность байт
• Разбиение на строки
Последовательность байт разбивается на последовательность строк.
Разделителями являются принятые в ОС символы (CR,LF,CR LF)
• Подстановка триграфов и диграфов
Тройные символы типа ??/ заменяются на \
• Склеивание «разбитых» строк
Строка, заканчивающаяся символом \, присоединяет к себе следующую
строку
• Замена комментариев на пробелы
Однострочные комментарии от // до конца строки
Многострочные комментарии от /* до первого появления */
Скрытые ловушки
• Склеивание комментариев
int f = 9 ;
// What the f, David Blaine????????/
f = 12 ;
• Вложенные комментарии
/* I don’t need this
int f ()
{
/* Nobody needs this */
int a = 9 ;
}
*/
Лексический разбор (токенизация)
• Разделение по whitespace
Whitespace (пробельными) символами являются символы пробела,
табуляции и переноса строки
• «Жадный» разбор в неоднозначных случаях
В неоднозначных случаях выбирается допустимая лексема
максимальной длины. Например a+++b будет разобрано как a ++ + b
a
++
+
b
• Сообщение об ошибке, если лексема не
принадлежит языку
Например, в случае лексем @ ` и т.п.
Макроподстановка
•
•
•
•
Включение заголовочных файлов
Директивы (команды) комилятору
Подстановка макросов
Условная компиляция
#include
#pragma
#define
#undef
#error
#if
#else
#elif
#endif
#ifdef
#ifndef
Включение заголовочных файлов
• Полное включение
Директива #include полностью замещается текстом включаемого
файла в текущей единице трансляции.
• Поиск стандартных заголовочных файлов
#include <file.h>
Заголовочный файл ищется среди списка каталогов стандартных
библиотек путем добавления к ним пути в угловых скобках.
• Поиск локальных заголовочных файлов
#include "file.h"
Аналогично предыдущему, но в начало поиска добавлюятся каталоги
с файлом, в котором указана директива, а также «каталоги
дополнительного включения».
Специальные директивы
• Директива генерации ошибки
#error message
Генерация ошибки с текстовым сообщением. Используется при
условной компиляции (например, чтобы сообщить о
неподдерживаемых компиляторе или платформе).
• Директива компилятора
#pragma command parameters
Специфична для каждого компилятора. Используется для управления
поведением компилятора.
#pragma
#pragma
#pragma
#pragma
#pragma
once
warning ( 4290:disable )
pack (push,1)
pack (pop)
comment ( lib, “libpng.lib" )
Макросы
• Непараметризованный макрос
#define name text
Указывает препроцессору, что в дальнейшем коде следует заменять
лексему name на соответствующий текст text.
• Параметризованный макрос
#define name(x,y) x+y
Указывает препроцессору, что в дальнейшем коде следует заменять
последовательность лексем name(параметры) на текст text, в котором
лексемы-параметры заменяются аргументы макроса
• Отмена макроса
#undef name
Отменяет дальнейшую подстановку имени
Примеры
#define NULL 0
int * t = NULL
; // int * t = 0 ;
int BERNULLY = 8 ; // Нет подстановки
const char p[] = “NULL” ; // Нет подстановки
#define test() hell
test()o // hell o , а не hello
#define max(a,b) ((a)>(b)?(a):(b))
//директивы компилятора всегда однострочны!
#define max_fn(type) type max ( type x, type y )\
{ \
return max( x,y ); \
}
Макрооператоры # и ##
• Преобразование в строку
#define as_str(x) # x
printf ( as_str(c u l 8 r)); // printf("c u l 8 r");
Параметр шаблона преобразуется в строку (заключается в кавычки)
• Конкатенация лексем
Две лексемы-имени, между которыми находится символ ##,
соединяются в одну
#define infix(x) x ## _ ## x
infix(O)
// O_O
infix(o O) // o O_o O
#define as_instr(x) “Who “ #x “ are”
as_instr ( you ) // “Who you are”
Недостатки макросов
#define max(a,b) a > b ? a : b
• Отсутствие типизации
max (7,”Hello”)
max (7,int)
• Досинтаксическая подстановка
int a = max (7,2);
int b = max (7,2)+4;
// int a = 7 > 2 ? 7 : 2 ;
// int b = 7 > 2 ? 7 : 2 + 4 ;
#undef max
#define max(a,b) ((a) > (b) ? (a) : (b))
int c=max(a++,++b);//int c=(a++)>(++b)?(a++):(++b);
Условная компиляция
#define DEBUG
#define MSVC_VER 0500
#if 0
printf (“yet another commented line”);
#endif
#if defined(DEBUG)
#
define MODE “Debuggy”
#elif MSVC_VER < 0500
#
define MODE “Ye olde”
#else
#
define MODE “Whatever”
#endif
Условная компиляция
#define DEBUG
#if defined(DEBUG)
printf (“debug”);
#else
printf (“release”);
#endif
#ifdef DEBUG
printf (“debug”);
#endif
#ifndef DEBUG
printf (“release”);
#endif
Компиляция
Этапы компиляции
• Синтаксический анализ
Выделение инструкций, проверка их синтаксиса
• Семантический анализ
Разрешение имен, соответствие типов, приоритет операций и т.п.
• Трансляция в машинный код
Перевод содержимого функций единицы трансляции в машинные
коды с сохранением имен и прототипов. Все исользуемые имена на
этом этапе должны быть объявлены, но могут не быть определены.
Результатом компиляции является промежуточный («объектный»)
файл, содержащий откомпилированный код и таблицу имен всех
объектов, доступных в этой единице трансляции. Код может
содержать символические ссылки на неразрешенные имена
(определенные, например, в других единицах трансляции).
Компоновка
Этапы компоновки
• Разрешение имен
Поиск соответствий имен в разных единицах трансляции. На этом
этапе все имена должны быть разрешены
• Связывание
Подстановка адресов соответствующих объектов (переменных,
функций) вместо символических имен.
• Сборка
Подготовка финального исполнимого файла (добавление заголовка,
связывание с точкой входа, связывание с runtime-функциями и т.п.)
Внутренняя и внешняя компоновка
• Внешняя компоновка
Имя, которое может быть использовано в других единицах
трансляции, считается именем со внешней компоновкой
• Ко внешним данным относятся:
Глобальные переменные (объявленные вне функций)
Функции
Глобальные пользовательские типы
• Все остальное – внутренние данные
int a ;
void * b ;
enum S { A, B };
void foo () {
int c = 9 ; }
Ключевое слово extern
• Используется как модификатор при
объявлениях переменных
• Применяется только к глобальным
объектам
• Указывает, что объект определен в другой
единице компиляции
int a ; //
extern int
extern int
extern int
определена в этой единице
b ; // определена в другой единице
c = 9 ; // extern теряет смысл – c определено
sum(int,int); // необязательно
Ключевое слово static
В контексте глобальных объектов ключевое
слово static означает внутреннюю
компононовку объекта.
int a ; // определена в этой единице
extern int b ; // определена в другой единице
static int c = 9 ; // определена в этой единице
// допускает объявление глобального
// имени c в другой единице
static int max(int,int); // прототип функции с внутренней
// компоновкой
Правило одного определения
• ODR (One Definition Rule)
Каждый объект (тип, переменная, функция) должен быть определен
только один раз. При этом ему может предшествовать любое
количество идентичных объявлений.
• Решение при многократном включении
заголовочного файла
#ifndef __SOME_HEADER_H__
#define __SOME_HEADER_H__
// тело файла
#endif
#include “file.h”
#include “file.h”
#include “file.h”
file.h
file.cpp
Примеры
File1.cpp
int sum(int, int); // объявление ф-ции и другой единицы
extern int a ; // внешняя a (из file2.cpp)
extern int c ; // ошибка (с не определена)
int d ; // ошибка (d определена дважды)
int eight () { return sum(3,5); } // Ok
void bar () { foo(); } // Ошибка: foo не определена
File2.cpp
int a ; // внешняя линковка, используется в file1.cpp
extern int c ; // ошибка (с не определена)
int d ; // ошибка (d определена дважды)
double eight ; // ошибка – внешнее имя из file1.cpp
static int bar = 7 ; // все хорошо – внутренняя комп.
int sum (int a, int b) // функция с внешней компоновкой
{ return a + b ; }
void foo () {}; // функция с внешней компоновкой
Download