Мегаобучалка Главная | О нас | Обратная связь


Константы и макросы #define



2016-09-17 1410 Обсуждений (0)
Константы и макросы #define 0.00 из 5.00 0 оценок




STDAFX.H

// stdafx.h: включаемый файл для стандартных системных включаемых файлов

// или включаемых файлов для конкретного проекта, которые часто используются, но

// не часто изменяются

//

 

#pragma once

 

#include "targetver.h"

 

#include <stdio.h>

#include <tchar.h>

 

// TODO: Установите здесь ссылки на дополнительные заголовки, требующиеся для программы

 

// stdafx.cpp: исходный файл, содержащий только стандартные включаемые модули

// whatstdafx.pch будет предкомпилированным заголовком

// stdafx.obj будет содержать предварительно откомпилированные сведения о типе

 

#include "stdafx.h"

 

// TODO: Установите ссылки на любые требующиеся дополнительные заголовки в файле STDAFX.H

// , а не в данном файле

 

Смотрите, .h-файлы это хедеры. То есть там вроде как должны быть определения типов данных, структуры классов и т. п. А код по идее должен быть только в .cpp-файлах.

Хедеры подключаются в файлы — то есть прямиком вставляются в исходник во время компиляции.

Иногда хедеры содержат не только определения, но и код (например, когда там определяется шаблонный класс). В итоге весь этот код прямиком вставляется в .cpp-файл, который инклюдит хедер, и компилируется заново. Вроде бы можно вынести этот код в отдельный .cpp-файл, но иногда лень/нельзя/синтаксис не позволяет/и ещё куча причин.

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

Конкретно Visual Studio это делает с помощью этого самого stdafx. Все инклюды, что идут перед #include "stdafx.h", выносятся в этот отдельный файл, компилируются там, а интерфейс к этом файлу запихивается в этот самый stdafx.h, чтобы остальная часть исходника ни о чём не подозревала.

 

Препроцессор Си

Препроцессор С/С++ (англ. pre processor, предобработчик) — программа, подготавливающая код программы на языке C/C++ к компиляции.

Основные функции препроцессора

Препроцессором выполняются следующие действия:

· замена соответствующих диграфов и триграфов на эквивалентные символы «#» и «\»;

· удаление экранированных символов перевода строки;

· замена строчных и блочных комментариев пустыми строками (с удалением окружающих пробелов и символов табуляции);

· вставка (включение) содержимого произвольного файла (#include);

· макроподстановки (#define);

· условная компиляция (#if, #ifdef, #elif, #else, #endif);

· вывод сообщений (#warning, #error).

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

· модели процессора (платформы);

· разрядности адресов;

· размерности типов;

· наличия/отсутствия поддержки расширений языка;

· наличия/отсутствия библиотек и/или функций;

· особенностей поведения конкретных функций;

· и другого.

Этапы работы препроцессора:

· лексический анализ кода C/C++ (синтаксический анализ не выполняется);

· обработка директив;

· выполнение подстановок:

· диграфов и триграфов;

· комментариев;

· директив;

· лексем, заданных директивами.

Язык препроцессора C/C++ не является полным по Тьюрингу хотя бы потому, что с помощью директив невозможно заставить препроцессор зависнуть. См. рекурсивная функция (теория вычислимости).

Синтаксис директив

Директивой (командной строкой) препроцессора называется строка в исходном коде, имеющая следующий формат: #ключевое_слово параметры:

· ноль или более символов пробелов и/или табуляции;

· символ #;

· одно из предопределённых ключевых слов;

· параметры, зависимые от ключевого слова.

Список ключевых слов:

· define — создание константы или макроса;

· undef — удаление константы или макроса;

· include — вставка содержимого указанного файла;

· if — проверка истинности выражения;

· ifdef — проверка существования константы или макроса;

· ifndef — проверка не существования константы или макроса;

· else — ветка условной компиляции при ложности выражения if;

· elif — проверка истинности другого выражения; краткая форма записи для комбинации else и if;

· endif — конец ветки условной компиляции;

· line — указание имени файла и номера текущей строки для компилятора;

· error — вывод сообщения и остановка компиляции;

· warning — вывод сообщения без остановки компиляции;

· pragma — указание действия, зависящего от реализации, для препроцессора или компилятора;

· если ключевое слово не указано, директива игнорируется;

· если указано несуществующее ключевое слово, выводится сообщение об ошибке и компиляция прерывается.

Описание директив

Вставка файлов (#include)[править | править вики-текст]

При обнаружении директив #include "..." и #include <...>, где «…» — имя файла, препроцессор читает содержимое указанного файла, выполняет директивы и замены (подстановки), заменяет директиву #include на директиву #line и обработанное содержимое файла.

Для #include "..." поиск файла выполняется в текущей папке и папках, указанных в командной строке компилятора. Для #include <...> поиск файла выполняется в папках, содержащих файлы стандартной библиотеки (пути к этим папкам зависят от реализации компилятора).

При обнаружении директивы #include последовательность-лексем не совпадающей ни с одной из предыдущих форм, рассматривает последовательность лексем как текст, который в результате всех макроподстановок должен дать #include <...> или #include "...". Сгенерированная таким образом директива далее будет интерпретироваться в соответствии с полученной формой.

Включаемые файлы обычно содержат:

· объявления функций;

· объявления глобальных переменных;

· определения интерфейсов;

· определения типов данных;

· и другое.

Директива #include обычно указывается в начале файла (в заголовке), поэтому включаемые файлы называются заголовочными.

Пример включения файлов из стандартной библиотеки языка C.

#include <math.h> // включение объявлений математических функций#include <stdio.h> // включение объявлений функций ввода-вывода

Использование препроцессора считается неэффективным по следующим причинам:

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

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

Начиная с 1970-х годов стали появляться способы, заменившие включение файлов. В языках Java и Common Lisp используются пакеты (ключевое слово package) (см.package в Java), в языке Паскаль — англ. units (ключевые слова unit и uses), в языках Modula, OCaml, Haskell и Python — модули. В языке D, разработанном для замены языков C и C++, используется ключевые слова module и import.

Константы и макросы #define

Константы и макросы препроцессора используются для определения небольших фрагментов кода.

// константа#define BUFFER_SIZE ( 1024 ) // макрос#define NUMBER_OF_ARRAY_ITEMS( array ) ( sizeof( array ) / sizeof( *(array) ) )

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

Пример. Определение макроса max, принимающего два аргумента: a и b.

#define max( a, b ) ( (a) > (b) ? (a) : (b) )

Макрос вызывается так же, как и любая функция.

z = max( x, y );

После замены макроса код будет выглядель следующим образом:

z = ( (x) > (y) ? (x) : (y) );

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

Например, если f и g — две функции, вызов

z = max( f(), g() );

не вычислит один раз f()и один раз g(), и поместит наибольшее значение в z, как этого можно было ожидать. Вместо этого одна из функций будет вычислена дважды. Если функция имеет побочные эффекты, то вероятно, что её поведение будет отличаться от ожидаемого.

Макросы Си могут походить на функции, создавая новый синтаксис в некоторых пределах, а также могут быть дополнены произвольным текстом (хотя компилятор Си требует, чтобы текст был без ошибок написанным Си-кодом или оформлен как комментарий), но у них есть некоторые ограничения как у программных конструкций. Макросы, схожие с функциями, например, могут быть вызваны как «настоящие» функции, но макрос не может быть передан другой функции при помощи указателя, по той причине, что макрос сам по себе не имеет адреса.

Некоторые современные языки обычно не используют такой способ метапрограммирования с использованием макросов как дополнений строк символов, в расчете или на автоматическое или на ручное подключение функций и методов, а вместо этого другие способы абстракции, такие как шаблоны, обобщённые функции илипараметрический полиморфизм. В частности, встраиваемые функции[en] позволяют избежать одного из главных недостатков макросов в современных версиях Си и C++, так как встроенная функция обеспечивает преимущество макросов в снижении накладных расходов при вызове функции, но её адрес можно передавать в указателе для косвенных вызовов или использовать в качестве параметра. Аналогично, проблема множественных вычислений, упомянутая выше в макросе max, для встроенных функций неактуальна.

Константы #define можно заменить на enum, а макросы — на функции inline.

Операторы # и ##[править | править вики-текст]

Эти операторы используются при создании макросов. Оператор # перед параметром макроса обрамляет его в двойные кавычки, например:

#define make_str( bar ) # barprintf( make_str( 42 ) );

препроцессор преобразует в:

printf( "42" );

Оператор ## в макросах объединяет две лексемы, например:

#define MakePosition( x ) x##X, x##Y, x##Width, x##Heightint MakePosition( Object );

препроцессор преобразует в:

int ObjectX, ObjectY, ObjectWidth, ObjectHeight;

Формальное описание макроподстановок[править | править вики-текст]

Этот раздел статьи следует викифицировать. Пожалуйста, оформите его согласно правилам оформления статей.  

1) Управляющая строка следующего вида заставляет препроцессор заменять идентификатор на последовательность лексем везде далее по тексту программы:

#define идентификатор последовательность_лексем

При этом символы пустого пространства в начале и в конце последовательности лексем выбрасываются. Повторная строка #define с тем же идентификатором считается ошибкой, если последовательности лексем не идентичны (несовпадения в символах пустого пространства не играют роли).

2) Строка следующего вида, где между первым идентификатором и открывающей круглой скобкой не должно быть символов пустого пространства, представляет собой макроопределение с параметрами, задаваемыми списком-идентификаторов.

#define идентификатор( список_идентификаторов ) последовательность_лексем

Как и в первой форме, символы пустого пространства в начале и в конце последовательности лексем выбрасываются, и макрос может быть повторно определен только с идентичным по количеству и именам списком параметров и с той же последовательностью лексем.

Управляющая строка следующего вида приказывает препроцессору «забыть» определение, данное идентификатору:

#undef идентификатор

Применение директивы #undef к не определенному ранее идентификатору не считается ошибкой.

{

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

· Аргументами вызова макроса являются лексемы, разделенные запятыми, причем запятые, взятые в кавычки или вложенные круглые скобки, в разделении аргументов не участвуют.

· (!)Во время группировки аргументов раскрытие макросов в них не выполняется.

· Количество аргументов в вызове макроса должно соответствовать количеству параметров макроопределения.

· После выделения аргументов из текста символы пустого пространства, окружающие их, отбрасываются.

· Затем в замещающей последовательности лексем макроса каждый идентификатор-параметр, не взятый в кавычки, заменяется на соответствующий ему фактический аргумент из текста.

· (!)Если в замещающей последовательности перед параметром не стоит знак #, если и ни перед ним, ни после него нет знака ##, то лексемы аргумента проверяются на наличие в них макровызовов; если таковые есть, то до подстановки аргумента в нём выполняется раскрытие соответствующих макросов.

На процесс подстановки влияют два специальных знака операций.

· Во-первых, если перед параметром в замещающей строке лексем вплотную стоит знак #, то вокруг соответствующего аргумента ставятся строковые кавычки ("), а потом идентификатор параметра вместе со знаком # заменяется получившимся строковым литералом.

· Перед каждым символом " или \, встречающимся вокруг или внутри строковой или символьной константы, автоматически вставляется обратная косая черта.

· Во-вторых, если последовательность лексем в макроопределении любого вида содержит знак ##, то сразу после подстановки параметров он вместе с окружающими его символами пустого пространства отбрасывается, благодаря чему сцепляются соседние лексемы, образуя тем самым новую лексему.

· Результат не определён при генерировании таким образом недопустимых лексем языка или в случае, когда получающийся текст зависит от порядка применения операции ##.

· Кроме того, знак ## не может стоять ни в начале, ни в конце замещающей последовательности лексем.

}

· (!)В макросах обоих видов замещающая последовательность лексем повторно просматривается в поиске новых define-идентификаторов.

· (!)Однако если какой-либо идентификатор уже был заменен в текущем процессе раскрытия, повторное появление такого идентификатора не вызовет его замены; он останется нетронутым.

· (!)Даже если развернутая строка макровызова начинается со знака #, она не будет воспринята как директива препроцессора.

Восклицательным знаком (!) отмечены правила, отвечающие за рекурсивные вызов и определения.

Пример раскрытия макроопределения[править | править вики-текст]

#define cat( x, y ) х ## у

Вызов макроса «cat(var, 123)» будет заменён на «var123». Однако вызов «cat(cat(1, 2), 3)» не даст желаемого результата. Рассмотрим шаги работы препроцессора:

0: cat( cat( 1, 2 ), 3 )1: cat( 1, 2 ) ## 32: cat( 1, 2 )3

Операция «##» помешала правильному раскрытию аргументов второго вызова «cat». В результате получилась следующая цепочка лексем:

cat ( 1 , 2 )3

где «)3» — результат сцепления последней лексемы первого аргумента с первой лексемой второго аргумента, не является допустимой лексемой.

Можно задать второй уровень макроопределения в таком виде:

#define xcat( x, y ) cat( x, y )

Вызов «xcat(xcat(1, 2), 3)» будет заменён на «123». Рассмотрим шаги работы препроцессора:

0: xcat( xcat( 1, 2 ), 3 )1: xcat_( xcat( 1, 2 ), 3 )2: xcat_( xcat_( 1, 2 ), 3 )3: xcat_( 1 ## 2, 3 )4: xcat_( 12, 3 )5: 12 ## 36: 123

Всё прошло благополучно, потому что в раскрытии макроса «xcat» не участвовал оператор «##».

Многие статические анализаторы не умеют правильно обрабатывать макросы, поэтому качество статического анализа снижается[источник не указан 66 дней].

Предопределённые константы #define[править | править вики-текст]

Константы, создаваемые препроцессором автоматически:

· __LINE__ заменяется на номер текущей строки; номер текущей строки может быть переопределен директивой #line; используется для отладки;

· __FILE__ заменяется на имя файла; имя файла тоже может быть переопределено с помощью директивы #line;

· __FUNCTION__ заменяется на имя текущей функции;

· __DATE__ заменяется на текущую дату (на момент обработки кода препроцессором);

· __TIME__ заменяется на текущее время (на момент обработки кода препроцессором);

· __TIMESTAMP__ заменяется на текущие дату и время (на момент обработки кода препроцессором);

· __COUNTER__ заменяется на уникальное число, начиная от 0; после каждой замены число увеличивается на единицу;

· __STDC__ заменяется на 1, если компиляция происходит в соответствии со стандартом языка C;

· __STDC_HOSTED__ определена в C99 и выше; заменяется на 1, если выполнение происходит под управлением ОС;

· __STDC_VERSION__ определена в C99 и выше; для C99 заменяется на число 199901, а для C11 — на число 201112;

· __STDC_IEC_559__ определена в C99 и выше; константа существует, если компилятор поддерживает операции с числами с плавающей точкой по стандарту IEC 60559;

· __STDC_IEC_559_COMPLEX__ определена в C99 и выше; константа существует, если компилятор поддерживает операции с комплексными числами по стандарту IEC 60559; стандарт C99 обязывает поддерживать операции с комплексными числами;

· __STDC_NO_COMPLEX__ определена в C11; заменяется на 1, если не поддерживаются операции с комплексными числами;

· __STDC_NO_VLA__ определена в C11; заменяется на 1, если не поддерживаются массивы переменной длины; в С99 массивы переменной длины обязательно должны поддерживаться;

· __VA_ARGS__ определена в C99 и позволяет создавать макросы с переменным числом аргументов.

Условная компиляция[править | править вики-текст]

Основная статья: Include guard

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

В общем случае, программисту необходимо использовать конструкцию типа:

# ifndef FOO_H# define FOO_H...(код заголовочного файла)...# endif

Такая «защита макросов» предотвращает двойное подключение заголовочного файла путём проверки существования этого макроса, который имеет то же самое имя, что и заголовочный файл. Определение макроса FOO_H происходит, когда заголовочный файл впервые обрабатывается препроцессором. Затем, если этот заголовочный файл вновь подключается, FOO_H уже определен, в результате чего препроцессор пропускает полностью текст этого заголовочного файла.

То же самое можно сделать, включив в заголовочный файл директиву:

# pragma once

Условия препроцессора можно задавать несколькими способами, например:

# ifdef x...# else...# endif

или

# if x...# else...# endif

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

Большинство современных языков программирования не используют такие возможности, больше полагаясь на традиционные операторы условияif...then...else..., оставляя компилятору задачу извлечения бесполезного кода из компилируемой программы.

Диграфы и триграфы[править | править вики-текст]

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

См. диграфы и триграфы в языках C/C++.

Препроцессор обрабатывает диграфы «%:» («#»), «%:%:» («##») и триграфы «??=» («#»), «??/» («\»).

Препроцессор считает последовательность «%:%:» двумя токенами при обработке кода C и одним токеном при обработке кода C++.

 

Компилятор

Компиля́тор — программа или техническое средство, выполняющее компиляцию[1][2][3].

Компиля́ция — трансляция программы, составленной на исходном языке высокого уровня, в эквивалентную программу на низкоуровневом языке, близком машинному коду (абсолютный код, объектный модуль, иногда на язык ассемблера)[2][3][4]. Входной информацией для компилятора (исходный код) является описание алгоритма или программа на предметно-ориентированном языке, а на выходе компилятора — эквивалентное описание алгоритма на машинно-ориентированном языке (объектный код)[5].

Компили́ровать — проводить трансляцию машинной программы с предметно-ориентированного языка на машинно-ориентированный язык[3].

Виды компиляции[2][править | править вики-текст]

· Пакетная. Компиляция нескольких исходных модулей в одном пункте задания.

· Построчная. То же, что и интерпретация.

· Условная. Компиляция, при которой транслируемый текст зависит от условий, заданных в исходной программе директивами компилятора. Так, в зависимости от значения некоторой константы, можно включать или выключать трансляцию части текста программы.

Структура компилятора[править | править вики-текст]

Процесс компиляции состоит из следующих этапов:

1. Лексический анализ. На этом этапе последовательность символов исходного файла преобразуется в последовательность лексем.

2. Синтаксический (грамматический) анализ. Последовательность лексем преобразуется в дерево разбора.

3. Семантический анализ. Дерево разбора обрабатывается с целью установления его семантики (смысла) — например, привязка идентификаторов к их декларациям, типам, проверка совместимости, определение типов выражений и т. д. Результат обычно называется «промежуточным представлением/кодом», и может быть дополненным деревом разбора, новым деревом, абстрактным набором команд или чем-то ещё, удобным для дальнейшей обработки.

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

5. Генерация кода. Из промежуточного представления порождается код на целевом языке.

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

Генерация кода[править | править вики-текст]

Генерация машинного кода[править | править вики-текст]

Большинство компиляторов переводит программу с некоторого высокоуровневого языка программирования в машинный код, который может быть непосредственно выполнен процессором. Как правило, этот код также ориентирован на исполнение в среде конкретной операционной системы, поскольку использует предоставляемые ею возможности (системные вызовы, библиотеки функций). Архитектура (набор программно-аппаратных средств), для которой производится компиляция, называетсяцелевой машиной.

Результат компиляции — исполнимый модуль — обладает максимальной возможной производительностью, однако привязан к определённой операционной системе и процессору (и не будет работать на других).

Для каждой целевой машины (IBM, Apple, Sun и т. д.) и каждой операционной системы или семейства операционных систем, работающих на целевой машине, требуется написание своего компилятора. Существуют также так называемые кросс-компиляторы, позволяющие на одной машине и в среде одной ОС генерировать код, предназначенный для выполнения на другой целевой машине и/или в среде другой ОС. Кроме того, компиляторы могут оптимизировать код под разные модели из одного семейства процессоров (путём поддержки специфичных для этих моделей особенностей или расширений наборов инструкций). Например, код, скомпилированный под процессоры семейства Pentium, может учитывать особенности распараллеливания инструкций и использовать их специфичные расширения —MMX, SSE и т. п.

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

Генерация байт-кода[править | править вики-текст]

Результатом работы компилятора может быть программа на специально созданном низкоуровневом языке, подлежащем интерпретации виртуальной машиной. Такой язык называется псевдокодом или байт-кодом. Как правило, он не является машинным кодом какого-либо компьютера и программы на нём могут исполняться на различных архитектурах, где имеется соответствующая виртуальная машина, но в некоторых случаях создаются аппаратные платформы, напрямую поддерживающие псевдокод какого-либо языка. Например, псевдокод языка Java называется байт-кодом Java и выполняется в Java Virtual Machine, для его прямого исполнения была создана спецификация процессора picoJava. Для платформы .NET Framework псевдокод называется Common Intermediate Language (CIL), а среда исполнения — Common Language Runtime (CLR).

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

Динамическая компиляция[править | править вики-текст]

Основная статья: JIT-компиляция

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

CIL-код также компилируется в код целевой машины JIT-компилятором, а библиотеки .NET Framework компилируются заранее.

Декомпиляция[править | править вики-текст]

Существуют программы, которые решают обратную задачу — перевод программы с низкоуровневого языка на высокоуровневый. Этот процесс называют декомпиляцией, а такие программы — декомпиляторами. Но поскольку компиляция — это процесс с потерями, точно восстановить исходный код, скажем, на C++, в общем случае невозможно. Более эффективно декомпилируются программы в байт-кодах — например, существует довольно надёжный декомпилятор для Flash. Разновидностью декомпилирования является дизассемблирование машинного кода в код на языке ассемблера, который почти всегда выполняется успешно (при этом сложность может представлять самомодифицирующийся код или код, в котором собственно код и данные не разделены). Связано это с тем, что между кодами машинных команд и командами ассемблера имеется практически взаимно-однозначное соответствие.

Раздельная компиляция[править | править вики-текст]

Раздельная компиляция (англ. separate compilation) — трансляция частей программы по отдельности с последующим объединением их компоновщиком в единый загрузочный модуль.[2]

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

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

Кстати C не намного лучще.Если вы хотите получить маленькую и шуструю программу-вам необходим Ассемблер.Вот для чего он используется:

  • все, что требует максимальной скорости выполнения: основные компоненты компьютерных игр, ядра операционных систем реального времени и просто критические участки программ;
  • все, что взаимодействует с внешними устройствами: драйверы, программы, работающие напрямую с портами, звуковыми и видеоплатами;
  • все, что использует полностью возможности процессора: ядра многозадачных операционных систем, DPMI-серверы и вообще любые программы, переводящие процессор в защищенный режим;
  • все, что полностью использует возможности операционной системы: вирусы и антивирусы, защиты от несанкционированного доступа, программы, обходящие эти защиты, и программы, защищающиеся от этих программ;
  • и многое другое.

Этапы компиляции

Процесс обработки текстовых файлов с кодом на языке C++, который упрощенно называют "компиляцией", на самом деле, состоит из четырех этапов.

1. Препроцессинг — обработка текстовых файлов утилитой препроцессора, который производит замены текстов согласно правилам языка препроцессора C/C++. После препроцессора, тексты компилируемых файлов, обычно, значительно вырастают в размерах, но теперь в них содержится все, что потребуется компилятору для создания объектного файла.

2. Ассемблирование — процесс превращения текста на языке C++ в текст на языке Ассемблера. Для компиляторов GNU используется синтаксис ассебмлера AT&T.

3. Компилирование — процесс превращения текстов на языке Ассемблера в объектные файлы. Это файлы состоящие из кодов целевого процессора, но в которых еще не проставлены адреса объектов, которые находятся в других объектных файлах или библиотеках.

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

Рассмотрим подробнее упомянутые выше стадии обработки текстовых файлов на языке C++.

Препроцессинг

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

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

На входе препроцессора мы имеем исходный файл с текстом на языке C++ включающим в себя элементы языка препроцессора.

На выходе препроцессора получаются так называемые компиляционные листы, состоящие исключительно из выражений языка C++, которых должно быть достаточно для создания объектных файлов на следующих этапах обработки. Последнее означает, что на момент использования каких-либо символов языка из других файлов, объявления этих символов должны присутствовать в компиляционном листе выше. Именно такие подстановки и призван осуществлять препроцессор. Часто, на вход препроцессора поступает файл размером в несколько десятков строк, а на выходе получается компиляционный лист из десятков тысяч строк.

Ассемблирование

Процесс ассемблирования с одной стороны достаточно прост для понимания и с другой стороны является наиболее сложным в реализации. По своей сути это процесс трансляции выражений одного языка в другой. Более конкретно, в данном случае, мы имеем на входе утилиты ассемблера файл с текстом на языке C++ (компиляционный лист), а на выходе мы получаем файл с текстом на языке Ассемблера. Язык Ассемблера это низкоуровневый язык который практически напрямую отображается на коды инструкций процессора целевой системы. Отличие только в том, что вместо числовых кодов инструкций используется англоязычная мнемоника и кроме непосредственно кодов инструкций присутствуют еще директивы описания сегментов и низкоуровневых данных, описываемых в терминологии байтов.

Ассемблирование не является обязательным процессом обработки файлов на языке C++. В данном случае, мы наблюдаем лишь общий подход в архитектуре проекта коллекции компиляторов GNU. Чтобы максимально объеденить разные языки в одну коллекцию, для каждого из языков реализуется свой транслятор на язык ассемблера и, при необходимости, препроцессор, а компилятор с языка ассемблера и линковщик делаются общими для всех языков коллекции.

Компиляция

В данном случае, мы имеем компилятор с языка ассемблера. Результатом его работы является объектный файл полученный на основе всего того текста, что был предоставлен в компиляционном листе. Поэтому можно говорить, что каждый объектный файл проекта соответствует одному компиляционному листу проекта.

Объектный файл — это бинарный файл, фактически состоящий из набора функций. Однако в исходном компиляционном листе не все вызываемые функции имели реализацию (или определение — definition). Не путайте с объявлением (declaration). Чтобы компиляционный лист можно было скомпилировать, необходимо, чтобы объявления всех вызываемых функций присутствовали в компиляционном листе до момента их использования. Однако, объявление, это не более чем имя функции и параметры ее вызова, которые позволяют во время компиляции правильно сформировать стек (передать переменные для вызова функции) и отметить, что тут надо вызвать функцию с указанным именем, адрес реализации которой пока не известен. Таким образом, объектные файлы сплошь состоят из таких «дыр» в которые надо прописать адреса из функций, которые реализованы в других объектных файлах или даже во внешних библиотеках.

Вообще, разница между объявлением (declaration) и определением (definition) состоит в том, что объявление (declaration) говорит об имени сущности и описывает ее внешний вид — например, тип объекта или параметры функции, в то время как определение (definition) описывает внутреннее устройство сущности: класс памяти и начальное значение объекта, тело функции и пр.

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

Линковка

На этапе линковки выполняется объ



2016-09-17 1410 Обсуждений (0)
Константы и макросы #define 0.00 из 5.00 0 оценок









Обсуждение в статье: Константы и макросы #define

Обсуждений еще не было, будьте первым... ↓↓↓

Отправить сообщение

Популярное:
Модели организации как закрытой, открытой, частично открытой системы: Закрытая система имеет жесткие фиксированные границы, ее действия относительно независимы...
Как выбрать специалиста по управлению гостиницей: Понятно, что управление гостиницей невозможно без специальных знаний. Соответственно, важна квалификация...



©2015-2024 megaobuchalka.ru Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав. (1410)

Почему 1285321 студент выбрали МегаОбучалку...

Система поиска информации

Мобильная версия сайта

Удобная навигация

Нет шокирующей рекламы



(0.011 сек.)