Поиск…


Вступление

Язык C традиционно является скомпилированным языком (в отличие от интерпретации). Стандарт C определяет фазы перевода , а продукт их применения представляет собой программный образ (или скомпилированную программу). В фазы перечислены в п. 5.1.1.2.

замечания

Расширение имени файла Описание
.c Исходный файл. Обычно содержит определения и код.
.h Файл заголовка. Обычно содержит декларации.
.o Файл объекта. Скомпилированный код в машинном языке.
.obj Альтернативное расширение для объектных файлов.
.a Файл библиотеки. Пакет объектных файлов.
.dll Динамическая библиотека ссылок в Windows.
.so Общий объект (библиотека) во многих Unix-подобных системах.
.dylib Библиотека динамических ссылок в OSX (вариант Unix).
.exe , .com Исполняемый файл Windows. Создано путем связывания объектных файлов и файлов библиотек. В Unix-подобных системах для исполняемого файла нет специального расширения имени файла.
Флаги компилятора POSIX c99 Описание
-o filename Имя выходного файла, например. ( bin/program.exe , program )
-I directory поиск заголовков в direrctory .
-D name определить name макроса
-L directory поиск библиотек в directory .
-l name link library libname .

Компиляторы на платформах POSIX (Linux, мейнфреймы, Mac) обычно принимают эти параметры, даже если они не называются c99 .

Флаги GCC (сборник компиляторов GNU) Описание
-Wall Позволяет использовать все предупреждающие сообщения, которые обычно считаются полезными.
-Wextra Включает больше предупреждающих сообщений, может быть слишком шумным.
-pedantic Принудительные предупреждения, когда код нарушает выбранный стандарт.
-Wconversion Включить предупреждения о неявной конверсии, использовать с осторожностью.
-c Компилирует исходные файлы без ссылки.
-v Распечатывает информацию о компиляции.
  • gcc принимает флаги POSIX и многие другие.
  • Многие другие компиляторы на платформах POSIX ( clang , компиляторы конкретных поставщиков) также используют флаги, перечисленные выше.
  • См. Также Вызов GCC для многих других опций.
Флаги TCC (Tiny C Compiler) Описание
-Wimplicit-function-declaration Предупреждать о объявлении неявной функции.
-Wunsupported Предупредите о неподдерживаемых функциях GCC, которые TCC игнорируют.
-Wwrite-strings Сделать строковые константы типа const char * вместо char *.
-Werror Прерывание компиляции, если выдается предупреждение.
-Wall Активируйте все предупреждения, кроме -Werror , -Wunusupported и -Wwrite strings .

Линкер

Задача компоновщика заключается в том, чтобы связать кучу объектных файлов (файлы .o ) в двоичном исполняемом файле. Процесс связывания в основном включает в себя разрешение символических адресов на числовые адреса . Результатом процесса ссылки обычно является исполняемая программа.

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

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

Неявный вызов компоновщика

Подобно препроцессору, компоновщик представляет собой отдельную программу, часто называемую ld (но Linux использует collect2 , например). Также как и предварительный процессор, компоновщик автоматически запускается для вас, когда вы используете компилятор. Таким образом, обычным способом использования компоновщика является следующее:

% gcc foo.o bar.o baz.o -o myprog

Эта строка сообщает компилятору связать три объектных файла ( foo.o , bar.o и baz.o ) в двоичный исполняемый файл с именем myprog . Теперь у вас есть файл под названием myprog который вы можете запустить, и который, надеюсь, сделает что-нибудь классное и / или полезное.

Явный вызов компоновщика

Можно напрямую ссылаться на компоновщик, но это редко бывает целесообразным и, как правило, очень специфично для платформы. То есть опции, которые работают в Linux, не обязательно будут работать на Solaris, AIX, MacOS, Windows и аналогично для любой другой платформы. Если вы работаете с GCC, вы можете использовать gcc -v для просмотра того, что выполняется от вашего имени.

Варианты компоновщика

Линкер также принимает некоторые аргументы, чтобы изменить его поведение. Следующая команда сообщила бы gcc, чтобы связать foo.o и bar.o , но также включить библиотеку ncurses .

% gcc foo.o bar.o -o foo -lncurses

Это фактически (более или менее) эквивалентно

% gcc foo.o bar.o /usr/lib/libncurses.so -o foo

(хотя libncurses.so может быть libncurses.a , который является просто архивом, созданным с помощью ar ). Обратите внимание, что вы должны перечислить библиотеки (либо по имени пути, либо через параметры -lname ) после файлов объектов. С статическими библиотеками имеет значение порядок, в котором они указаны; часто, с общими библиотеками, порядок не имеет значения.

Обратите внимание, что во многих системах, если вы используете математические функции (из <math.h> ), вам нужно указать -lm для загрузки библиотеки математики, но Mac OS X и macOS Sierra этого не требуют. Существуют и другие библиотеки, которые являются отдельными библиотеками в Linux и других Unix-системах, но не для потоков macOS-POSIX и POSIX в реальном времени, а также для сетевых библиотек. Следовательно, процесс связывания варьируется между платформами.

Другие варианты компиляции

Это все, что вам нужно знать, чтобы начать компиляцию ваших собственных программ на C. Как правило, мы также рекомендуем использовать -Wall командной строки -Wall :

% gcc -Wall -c foo.cc

Опция -Wall заставляет компилятор предупреждать вас о юридических, но сомнительных конструкциях кода, и поможет вам поймать множество ошибок очень рано.

Если вы хотите, чтобы компилятор выдавал вам больше предупреждений (включая переменные, объявленные, но не используемые, забывая вернуть значение и т. Д.), Вы можете использовать этот набор параметров, поскольку -Wall , несмотря на имя, не поворачивается все возможные предупреждения :

% gcc -Wall -Wextra -Wfloat-equal -Wundef -Wcast-align -Wwrite-strings -Wlogical-op \
>     -Wmissing-declarations -Wredundant-decls -Wshadow …

Обратите внимание, что у clang есть опция -Weverything которая действительно включает все предупреждения в clang .

Типы файлов

Для компиляции программ C вам необходимо работать с пятью типами файлов:

  1. Исходные файлы : эти файлы содержат определения функций и имеют имена, которые заканчиваются на .c по соглашению. Примечание: .cc и .cpp - файлы C ++; а не файлы C.
    например, foo.c

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

  3. Объектные файлы : эти файлы создаются как выходные данные компилятора. Они состоят из определений функций в двоичной форме, но сами по себе они не исполняются. Файлы объектов заканчиваются на .o по соглашению, хотя в некоторых операционных системах (например, Windows, MS-DOS) они часто заканчиваются на .obj .
    например, foo.o foo.obj

  4. Бинарные исполняемые файлы : они создаются в качестве выхода программы, называемой «компоновщик». Компонент связывает вместе множество объектных файлов для создания двоичного файла, который может быть выполнен непосредственно. Бинарные исполняемые файлы не имеют специального суффикса в операционных системах Unix, хотя обычно они заканчиваются на .exe в Windows.
    например foo foo.exe

  5. Библиотеки : библиотека является скомпилированным двоичным кодом, но сама по себе не является исполняемым (т. Е. В библиотеке нет функции main() ). Библиотека содержит функции, которые могут использоваться более чем одной программой. Библиотека должна поставляться с файлами заголовков, которые содержат прототипы для всех функций в библиотеке; эти файлы заголовков должны быть указаны (например, #include <library.h> ) в любом исходном файле, который использует библиотеку. Затем компоновщик необходимо передать в библиотеку, чтобы программа могла успешно скомпилироваться. Существует два типа библиотек: статический и динамический.

    • Статическая библиотека : статическая библиотека ( .a файлы для POSIX-систем и .lib файлов для Windows - не путать с файлами библиотеки DLL-импорта , которые также используют расширение .lib ) статически встроена в программу. Статические библиотеки имеют то преимущество, что программа точно знает, какая версия библиотеки используется. С другой стороны, размеры исполняемых файлов больше, поскольку все используемые функции библиотеки включены.
      например, libfoo.a foo.lib
    • Динамическая библиотека : динамическая библиотека ( .so файлы для большинства POSIX-систем, .dylib для OSX и .dll файлов для Windows) динамически связана во время выполнения программой. Они также иногда называются разделяемыми библиотеками, потому что одно из изображений библиотеки может использоваться многими программами. У динамических библиотек преимущество состоит в том, что они занимают меньше места на диске, если несколько библиотек используют более одного приложения. Кроме того, они позволяют обновлять библиотеки (исправления ошибок) без необходимости пересоздавать исполняемые файлы.
      например foo.so foo.dylib foo.dll

Препроцессор

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

Команды препроцессора начинаются с знака фунта («#»). Существует несколько команд препроцессора; два из наиболее важных:

  1. Определяет :

    #define в основном используется для определения констант. Например,

    #define BIGNUM 1000000
    int a = BIGNUM; 
    

    становится

    int a = 1000000;
    

    #define используется таким образом, чтобы избежать необходимости явно выписывать некоторое постоянное значение во многих разных местах файла исходного кода. Это важно, если вам нужно впоследствии изменить значение константы; он гораздо менее подвержен ошибкам, чтобы изменить его один раз в #define , чем изменять его в нескольких местах, разбросанных по всему коду.

    Поскольку #define просто выполняет расширенный поиск и заменяет, вы также можете объявить макросы. Например:

    #define ISTRUE(stm) do{stm = stm ? 1 : 0;}while(0)
    // in the function:
    a = x;
    ISTRUE(a);
    

    будет выглядеть так:

    // in the function:
    a = x;
    do {
        a = a ? 1 : 0;
    } while(0);
    

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

    Также обратите внимание на то, что препроцессор также заменит комментарии пробелами, как описано ниже.

  2. Включает :

    #include используется для доступа к определениям функций, определенным за пределами файла исходного кода. Например:

     #include <stdio.h> 
    

    заставляет препроцессор вставлять содержимое <stdio.h> в файл исходного кода по адресу оператора #include перед его компиляцией. #include почти всегда используется для включения файлов заголовков, которые являются файлами, которые в основном содержат объявления функций и инструкции #define . В этом случае мы используем #include , чтобы иметь возможность использовать такие функции, как printf и scanf , чьи объявления находятся в файле stdio.h . Компиляторы C не позволяют использовать функцию, если она ранее не была объявлена ​​или не определена в этом файле; Операторы #include , таким образом, являются способом повторного использования ранее написанного кода в ваших программах на C.

  3. Логические операции :

    #if defined A || defined B
    variable = another_variable + 1;
    #else
    variable = another_variable * 2;
    #endif
    

    будет изменено на:

    variable = another_variable + 1;
    

    если A или B были определены где-то в проекте раньше. Если это не так, конечно, препроцессор сделает это:

    variable = another_variable * 2;
    

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

  4. Комментарии

    Препроцессор заменяет все комментарии в исходном файле на отдельные пробелы. Комментарии обозначаются // до конца строки или комбинация открывания /* и закрытия */ комментариев.

Компилятор

После того, как предварительный процессор C включил все файлы заголовков и расширил все макросы, компилятор может скомпилировать программу. Он делает это, превращая исходный код C в файл объектного кода, который является файлом, заканчивающимся на .o который содержит двоичную версию исходного кода. Тем не менее, код объекта не является исполняемым. Чтобы сделать исполняемый файл, вам также нужно добавить код для всех функций библиотеки, которые были #include d в файл (это не то же самое, что и включение объявлений, что и означает #include ). Это работа линкера .

В общем, точная последовательность вызова C-компилятора во многом зависит от используемой вами системы. Здесь мы используем компилятор GCC, хотя следует отметить, что существует еще много компиляторов:

% gcc -Wall -c foo.c

% - командная строка ОС. Это говорит компилятору запустить предварительный процессор в файле foo.c а затем скомпилировать его в файл объектного кода foo.o Параметр -c означает компиляцию файла исходного кода в объектный файл, но не для вызова компоновщика. Эта опция -c доступна в системах POSIX, таких как Linux или macOS; другие системы могут использовать различный синтаксис.

Если вся ваша программа находится в одном исходном коде, вы можете сделать это:

% gcc -Wall foo.c -o foo

Это говорит компилятору, чтобы запустить предварительный процессор на foo.c , скомпилировать его, а затем связать его, чтобы создать исполняемый файл с именем foo . Опция -o указывает, что следующее слово в строке - это имя исполняемого бинарного файла (программы). Если вы не укажете -o , (если вы просто gcc foo.c ), исполняемый файл будет называться a.out по историческим причинам.

В общем случае компилятор выполняет четыре шага при преобразовании файла .c в исполняемый файл:

  1. pre-processing - текстовое расширение директив #include и макросов #define в вашем .c файле
  2. compilation - преобразует программу в сборку (вы можете остановить компилятор на этом шаге, добавив параметр -S )
  3. сборка - преобразует сборку в машинный код
  4. linkage - связывает объектный код с внешними библиотеками для создания исполняемого файла

Также обратите внимание, что имя используемого компилятора - это GCC, что означает «компилятор GNU C» и «сборник компилятора GNU», в зависимости от контекста. Существуют другие компиляторы C. Для Unix-подобных операционных систем многие из них имеют имя cc для «компилятора C», что часто является символической ссылкой на какой-либо другой компилятор. В системах Linux cc часто является псевдонимом для GCC. На macOS или OS-X он указывает на clang.

Стандарты POSIX в настоящее время определяют c99 как имя компилятора C - он по умолчанию поддерживает стандарт C99. Более ранние версии POSIX были c89 как c89 компилятором. POSIX также требует, чтобы этот компилятор понимал опции -c и -o которые мы использовали выше.


Примечание. Параметр -Wall присутствующий в обоих примерах gcc указывает компилятору печатать предупреждения о сомнительных конструкциях, что настоятельно рекомендуется. Также неплохо добавить другие варианты предупреждений , например -Wextra .

Фазы перевода

В соответствии со стандартом C 2011, перечисленным в разделе 5.1.1.2 «Фазы перевода» , перевод исходного кода на программный образ (например, исполняемый файл) перечислены в 8 упорядоченных шагах.

  1. Ввод исходного файла сопоставляется с исходным набором символов (при необходимости). На этом шаге заменены триграфы.
  2. Строки продолжения (строки, заканчивающиеся на \ ) соединяются следующей строкой.
  3. Исходный код анализируется на пробельные и препроцессорные маркеры.
  4. Препроцессор применяется, который выполняет директивы, расширяет макросы и применяет прагмы. Каждый исходный файл, набранный с помощью #include претерпевает фазы перевода с 1 по 4 (при необходимости рекурсивно). Затем все директивы, связанные с препроцессором, удаляются.
  5. Значения значений исходного символа в символьных константах и ​​строковых литералах сопоставляются с набором символов выполнения.
  6. Строковые литералы, смежные друг с другом, объединяются.
  7. Исходный код анализируется на токены, которые составляют блок перевода.
  8. Внешние ссылки разрешаются, и формируется изображение программы.

Реализация компилятора C может объединять несколько шагов вместе, но результирующее изображение должно вести себя так, как если бы вышеупомянутые шаги выполнялись отдельно в порядке, указанном выше.



Modified text is an extract of the original Stack Overflow Documentation
Лицензировано согласно CC BY-SA 3.0
Не связан с Stack Overflow