Форум программистов, компьютерный форум CyberForum.ru

C++

Войти
Регистрация
Восстановить пароль
 
 
Рейтинг: Рейтинг темы: голосов - 65, средняя оценка - 4.88
Evg
Эксперт CАвтор FAQ
17537 / 5775 / 370
Регистрация: 30.03.2009
Сообщений: 15,904
Записей в блоге: 26
#1

Процесс компиляции - C++

18.06.2012, 10:32. Просмотров 8968. Ответов 16
Метки нет (Все метки)

Вопрос из лички перетащу на форум, т.к. другим, возможно, тоже будет интересно

Когда препроцессор вставляет включаемый хедерами код в исполнительный файл и компилятор транслирует код в объектный, как происходит линк функций (хедеры включает только заголовки функций), используемых из включенных хедеров?
Когда компилируется код используемых функций и как формируются ссылки?
1. Препроцессор ничего в "исполнительный" файл не вставляет. Препроцессор делает только текстовую подстановку и всё. Т.е. из 10 текстовых файлов делает один. И больше ничего
2. В любом случае я может неправильно понимаю, что означает "исполнительный"
3. Если под "линком" подразумевается линковка при помощи линкера (в русских книгах обычно это называют "связывание" и "редактор связей"), то препроцессор к этому отношения не имеет. Весь код функций находится в библиотеках. В языках Си и Си++ любая библиотека предоставляется в виде файла с бинарным кодом и набором инклюдов. Автор библиотеки должен гарантировать, что инклюды соответствуют бинарному файлу. Т.е. когда ты вызываешь printf, то в файле stdio.h есть только описание прототипа и больше ничего. Сам код функции printf находится в библиотеке в уже скомпилированном виде, а эта библиотека поставляется в комплекте вместе с компилятором. Компилятор по умолчанию линкуется с библиотекой, пользователю дополнительных действий делать не надо.
4. Что такое "ссылки" не понял

В среднем при плохой терминологии и кривой постановке вопроса я примерно понимаю, чего человек хочет узнать. Если получится придумать, каким образом аккуратно и понятно изложить мысли, то попробую отписаться попозже
Similar
Эксперт
41792 / 34177 / 6122
Регистрация: 12.04.2006
Сообщений: 57,940
18.06.2012, 10:32     Процесс компиляции
Посмотрите здесь:

Процесс - C++ Builder
Здравствуйте. У меня такая проблема: В чужом процессе с адреса 0х29ACF4 идет строка вида: e-mail, дальше 4 байта длины пароля, и...

Закрыть процесс по ID - C++ WinAPI
Нашел на этом форуме код, который закрывает процессы по имени. #include <stdio.h> #include <Windows.h> #include <iostream> #include...

Дочерний процесс - C++ Linux
Как передать аргумент size в дочерний процесс?) Как объявить массив??? На ubuntu компилятор gcc компилит под СИ и объявить массив с...

Процесс VS Потоки - C++ Linux
Добрый день. Встал вопрос: когда-нибудь в рамках одного приложения выгоднее использовать новый процесс вместо потока? Где читал, пишут...

Внедрение в процесс - C++ WinAPI
Добрый день. Я только недавно начал писать на С++, так что не ругайтесь сильно. Хочу внедрить поток в обычный процесс. Пытаюсь написать...

Хук на процесс - C++ WinAPI
Добрый день. Подскажите, как установить хук на отдельный процесс, к примеру на Mozilla Firefox. А если есть возможность, приведите...

Процесс CreateProcess - C++ Builder
int _tmain(int argc, _TCHAR* argv) { PROCESS_INFORMATION pi; STARTUPINFO si; ZeroMemory(&si, sizeof(STARTUPINFO)); ...

После регистрации реклама в сообщениях будет скрыта и будут доступны все возможности форума.
alexsvk
8 / 8 / 1
Регистрация: 15.07.2010
Сообщений: 255
18.06.2012, 12:21     Процесс компиляции #2
Цитата Сообщение от Evg Посмотреть сообщение
4. Что такое "ссылки" не понял
Под ссылкой подразумевается связывание используемых функций библиотеки в пользовательском коде.
Evg
Эксперт CАвтор FAQ
17537 / 5775 / 370
Регистрация: 30.03.2009
Сообщений: 15,904
Записей в блоге: 26
19.06.2012, 13:24  [ТС]     Процесс компиляции #3
Сообщение было отмечено автором темы, экспертом или модератором как ответ
Процесс компиляции состоит из нескольких стадий. Ко всему, что я буду объяснять, следует подходить условно и воспринимать как самое общее приблизительное пояснение. Просто в реальной жизни есть очень много тонкостей, которые начинающему понять будет сложно и такие объяснения будут лишь загромождением. Просто попытаюсь вкратце пояснить на пальцах суть происходящего.

Рассмотрим на простеньком примере

C
#include <stdio.h>
 
int main (void)
{
  printf ("Hello world\n");
  return 0;
}
Конкретные действия буду объяснять на примере компилятора gcc. Просто потому, что с ним я умею работать из консоли. Наверняка все эти действия можно выполнять и из-под IDE, но я с ними не работаю, а потому не знаю, как это делается

*** UPDATE ***
Пока писал, понял, что пример выбрал неудачно и методику изложения следует немного поменять. Но для этого нужно в очередной раз найти время и уже в законченном виде с нормальными пояснениями выложить в блог, где можно спокойно редактировать и исправлять

1 этап. Препроцессирование

На данном этапе работа идёт только с текстовыми файлами. Здесь препроцессор объединит наш исходник и все include-файлы в один большой текстовый файл. Для нашего случая это будет что-то типа:

C
/* Здесь идут потроха, подцепившиеся из stdio.h и прочих .h файлов,
 * которые могут подключаться внутри stdio.h. Касаемо функций или переменных
 * тут будут только описания, но никаких определений */
 
...
 
extern int printf (const char*, ...);
 
...
 
 
/* Текст из stdio.h закончился, далее идёт текст нашей программы */
 
int main (void)
{
  printf ("Hello world\n");
  return 0;
}
Во время работы препроцессора идёт работа со всякими внешними файлами (include'ами) и путями (каталоги, в которых компилятор ищет include-файлы)

Результат работы препроцессора можно посмотреть так:

Bash
$ gcc t.c -E -o t.i
Итоговый препроцессированный текст будет в файле t.i

2 этап. Трансляция

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

В нашем случае на выходе транслятора мы получим ассемблерный текст. Я привожу тот текст, который получен в результате работы компилятора gcc. Некоторые интересные места я отметил стрелочками и пронумеровал. О них пойдёт речь ниже.

Assembler
    .file   "t.c"
    .section    .rodata.str1.1,"aMS",@progbits,1
.LC0:                          <----- 1
    .string "Hello world\n"
    .text
    .p2align 4,,15
.globl main                    <----- 2
    .type   main, @function
main:                          <----- 3
    pushl   %ebp
    movl    %esp, %ebp
    andl    $-16, %esp
    subl    $16, %esp
    movl    $.LC0, 4(%esp)     <----- 4
    call    printf             <----- 5
    xorl    %eax, %eax
    leave
    ret
    .size   main, .-main
    .ident  "GCC: (Ubuntu 4.4.3-4ubuntu5) 4.4.3"
    .section    .note.GNU-stack,"",@progbits
Ассемблерный текст является не просто образом будущего кода, но ещё и образом будущего объектного файла, в котором будут символьные имена меток, которые в свою очередь будут использоваться при линковке.

В нашем случае мы видим, что в коде имеются две метки. Метка ".LC0" (стрелка 1), описывающая набор символов от строкового литерала и метка "main" (стрелка 3), описывающая начало функции main. У каждой метки есть так называемая область видимости: локальная или глобальная. Локальные метки видны только внутри данного модуля, глобальные метки видны из других модулей. Метка ".LC0" является локальной, т.к. строковой литерал - это внутренние данные нашего модуля. Метка main является глобальной, т.к. эта функция должна быть видна извне (в исходнике у main'а нет модификатора static), а потому этот факт подсвечивается специальной директивой (стрелка 2).

Помимо вхождения меток мы видим ещё и обращения к меткам: обращение к строковому литералу ".LC0" (стрелка 4) и обращение к внешней метке "printf" (стрелка 5).

Результат работы препроцессора можно посмотреть так:

Bash
$ gcc t.c -S -o t.s
Итоговый ассемблерный текст будет в файле t.s

3 этап. Ассемблирование

Полученный ассемблерный текст далее передаётся программе-ассемблеру, которая преобразует его в объектный файл. Объектный файл представляет собой бинарные коды целевой машины плюс дополнительная информация о метках и их использовании. Информация, содержащаяся в объектном файле принципиально ничем не отличается от информации, содержащейся в ассемблерном тексте. Только весь код вместо мнемоник, понятных человеку, содержит двоичный код, понятный машине. А вместо меток, расставленных по ассемблерному тексту, объектный файл содержит специальную таблицу символов (symbol table), описывающую все метки из нашего ассемблерного текста и таблицу перемещений (relocations table), описывающую точки, где метки использовались. Эти таблицы спроектированы таким образом, чтобы с ними было удобно работать линкеру и дизассемблеру.

Конкретно в нашем случае эти таблицы будут выглядеть примерно таким образом. В объектном файле определена локальная метка ".LC0", глобальная метка "main" и внешняя метка "printf", к которой в данном файле есть обращения (но определения метки нет). В объектном файле есть использование метки ".LC0" и использования метки "printf"

Важным моментом является то, что в объектном файле адреса ещё не настроены. Например, у нас в функции main есть обращение к метке ".LC0". но в том месте кода, где происходит это обращение, адрес метки ".LC0" ещё не проставлен, т.к. он будет известен только на этапе линковки

Объектный файл можно получить так:

Bash
$ gcc t.c -c -o t.o
В итоге создастся объектный файл с именем t.o. Просто так в него заглянуть уже не получится, т.к. это бинарный файл, но при помощи воспомогательных утилит можно выудить любую информацию.

Таблица символов (меток) - symbol table:

Bash
$ readelf --symbols t.o
 
Symbol table '.symtab' contains 10 entries:
   Num:    Value  Size Type    Bind   Vis      Ndx Name
...
     8: 00000000    41 FUNC    GLOBAL DEFAULT    1 main
     9: 00000000     0 NOTYPE  GLOBAL DEFAULT  UND printf
Здесь удалил целую портянку служебных фиктивных символов, о которых на данном этапе можно и не вспоминать. Обратите внимание, что среди таблицы символов отсутствует метка ".LC0". Для процесса линковки метка как таковая не нужна (ибо она локальная внутри модуля), а все обращения к метке ассемблер заменил на смещения внутри объектного файла (см. далее). Это некая оптимизация. При желании можно попросить ассемблер эту метку сохранить (но это надо почитать документацию)

Таблица перемещений (использований) - relocation table:

Bash
$ readelf --relocs t.o
 
Relocation section '.rel.text' at offset 0x360 contains 2 entries:
 Offset     Info    Type            Sym.Value  Sym. Name
00000015  00000501 R_386_32          00000000   .rodata.str1.1    <-----
00000021  00000902 R_386_PC32        00000000   printf
Место, помеченное стрелкой - это есть обращение к метке ".LC0", которое ассемблер заменил на обращение к секции данных с заданным смещением (правда для данного примера смещение оказалось нулевым)

4 этап. Линковка

Полученный объектный файл (а их может быть несколько) отдаётся линкеру. Линкер склеивает между собой все поданные ему файлы и формирует один большой исполняемый файл. Помимо объектных файлов компилятор подаёт в линкер ещё и библиотеки. Какие-то библиотеки компилятор подаёт невидимым для пользователя образом (т.е. пользователь непосредственно в этом процессе не участвует). Какие-то библиотеки пользователь сам просит компилятор передать линкеру. В первую группу, как правило, относятся библиотеки, отвечающие за run-time поддержку языка программирования и библиотеки, входящие в состав стандарта языка программирования или входящие в состав стандартной библиотечной поддержки на данной операционной системе. Библиотека, содержащая реализацию функции printf относится именно к этой группе. Ко второй группе относятся все пользовательские библиотеки (графические библиотеки, библиотеки для работы с криптографией и прочее)

В контексте данной статьи к библиотекам нужно относиться следующим образом. Где-то кто-то написал реализации всех стандартных функций, в том числе и printf. Далее все эти реализации откомпилированы до объектного файла. Дальше каким-то образом склеили эти объектные файлы в единый файл (или в небольшое количество раздельных файлов) и назвали эти файлы словом "библиотека". Т.е. библиотека - это набор уже откомпилированных кодов. Далее эту библиотеку и include-файлы к ним включили в состав компилятора или в состав операционной системы

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

================================================================

В общем, пояснение было несколько сумбурным, а потому лучше будет, если ты задашь конкретные вопросы по непонятным местам, чтобы было ясно, каким образом в дальнейшем аккуратно это дело переписать
alexsvk
8 / 8 / 1
Регистрация: 15.07.2010
Сообщений: 255
19.06.2012, 23:01     Процесс компиляции #4
Цитата Сообщение от Evg Посмотреть сообщение
Код Bash
$ readelf --symbols t.o
Symbol table '.symtab' contains 10 entries:
Num: Value Size Type Bind Vis Ndx Name
...
8: 00000000 41 FUNC GLOBAL DEFAULT 1 main
9: 00000000 0 NOTYPE GLOBAL DEFAULT UND printf
Value уставлено в ноль и у main, и у printf. У printf - ясно, почему, - значения будет установлено линкером. У main - потому что это будущая точка входа в программу?

Vis - область видимости, установлено в default, т.е. весь файл. Как интерпретируются остальные значения в таблице (блок, класс, пространство имён)?

Type printf будет установлен линкером?

Цитата Сообщение от Evg Посмотреть сообщение
Код Bash
$ readelf --relocs t.o
Relocation section '.rel.text' at offset 0x360 contains 2 entries:
Offset Info Type Sym.Value Sym. Name
00000015 00000501 R_386_32 00000000 .rodata.str1.1 <-----
00000021 00000902 R_386_PC32 00000000 printf

Type, тип процессора, под который скомпилирована функция?
Sym. Value для printf будет установлено позже линкером для printf?
Evg
Эксперт CАвтор FAQ
17537 / 5775 / 370
Регистрация: 30.03.2009
Сообщений: 15,904
Записей в блоге: 26
19.06.2012, 23:59  [ТС]     Процесс компиляции #5
Сообщение было отмечено автором темы, экспертом или модератором как ответ
Цитата Сообщение от alexsvk Посмотреть сообщение
У main - потому что это будущая точка входа в программу?
Хочется обратить внимание на то, что понятия "переменная" или "функция" - это понятия языка программирования. В коде этих понятий как таковых нет. Линкер оперирует понятием "секция". Грубо говоря, секция кода (обычно называется ".text") - это набор байтов, куда свалены все коды функций. Это сплошной набор байтов, которые машина будет трактовать как код. И в этом наборе байтов расставлены метки, являющиеся как бы началом функции (с точки зрения человеческого восприятия). Соответственно, секция с данными - это набор байтов, описывающий все переменные с расставленными метками, отвечающими за начало переменной. Но для линкера нет никаких понятий "переменная" или "функция". Для линкера есть некоторые точки в наборе байтов, помеченные символьными метками

value - это некое подобие адреса. В исполняемом файле это действительно адрес, а вот в объектном - это смещение внутри секции (в данном случае внутри секции кода). Мой пример неудачен тем, что там только одна функция. Сделай несколько функций и увидишь, что они имеют разные value (разные смещения внутри одной секции).

Поле Ndx - это номер секции, в которой расположена метка. Список секций можно посмотреть через

Bash
$ readelf --sections t.o
Цитата Сообщение от alexsvk Посмотреть сообщение
Vis - область видимости, установлено в default, т.е. весь файл. Как интерпретируются остальные значения в таблице (блок, класс, пространство имён)?
Vis = visibility - это вот такая штука: http://gcc.gnu.org/wiki/Visibility
На данном этапе в неё углубляться не стОит

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

Поле size оно описывает размер (функции или переменной). Поле для линкера тоже не нужно. Точно так же используется дизассемблером, отладчиком, всяческими распечатывалками кода

Имя метки - ну вроде бы и так понятно. По символьным именам и происходит линковка в том месте, где нужно расставлять адреса функций и переменных

Поле Sym - это порядковый номер символьной ссылки (метки) в таблице символом текущего модуля. По этому номеру другие таблицы (типа таблицы перемещений) ссылаются на метку (символ)

Цитата Сообщение от alexsvk Посмотреть сообщение
Type, тип процессора, под который скомпилирована функция?
Type - это тип перемещения. Он описывает, в какой форме нужно в данное место прописать адрес. Бинарный файл имеет формат, не зависящий от процессора, но типы перемещений от процессора зависят (т.к. по сути дела адреса вклеиваются прямо в код), а потому в типе перемещения для удобства засвечивают имя процессора, к которому относится перемещение. Тип R_386_32 означает, что адрес будет прописан в виде абсолютного 32-битного значения. Тип R_386_PC32 означает, что адрес будет прописан в виде относительного 32-битного значения (PC = PC relative)

Цитата Сообщение от alexsvk Посмотреть сообщение
Sym. Value для printf будет установлено позже линкером для printf?
Сейчас с ходу не вспомню, что в таблице перемещений означает поле "Sym. Value". Просто у меня сейчас нету линукса под рукой
alexsvk
8 / 8 / 1
Регистрация: 15.07.2010
Сообщений: 255
20.06.2012, 19:03     Процесс компиляции #6
C++
1
2
3
4
5
6
7
8
9
10
11
a.cpp
 
#include <iostream>
static int a = -1;
extern int a;
 
int main()
{
     std::cout << a;
     return 0;
}
C++
1
2
3
b.cpp
 
int a = 0;
Возможно ли получить доступ к значению внешнего определения переменной a в a.cpp?
Evg
Эксперт CАвтор FAQ
17537 / 5775 / 370
Регистрация: 30.03.2009
Сообщений: 15,904
Записей в блоге: 26
20.06.2012, 23:04  [ТС]     Процесс компиляции #7
Формат бинарного файла это допускает сделать. Но, судя по всему, этого не допускает стандарт языка. Но утверждать что-либо тут не берусь. Надо читать стандарт и экспериментировать
S9
Волшебник
645 / 248 / 38
Регистрация: 18.12.2010
Сообщений: 541
21.06.2012, 12:00     Процесс компиляции #8
А как можно сделать, чтобы ассемблерный текст был не AT&T-синтаксис а Intel-синтаксис? Вообще возможно это?
Evg
Эксперт CАвтор FAQ
17537 / 5775 / 370
Регистрация: 30.03.2009
Сообщений: 15,904
Записей в блоге: 26
21.06.2012, 12:07  [ТС]     Процесс компиляции #9
Цитата Сообщение от S9 Посмотреть сообщение
А как можно сделать, чтобы ассемблерный текст был не AT&T-синтаксис а Intel-синтаксис? Вообще возможно это?
Если ты спрашиваешь про компилятор gcc, то маловероятно. Для компилятора ассемблерный текст - это всего лишь промежуточное состояние, обусловленное технологическим построением компилятора (отделить транслятор от непосредственной работы с кодировками операций и форматом бинарного файла). Во всяком случае в gcc-2.95.3 абсолютно точно не было такой возможности. И сильно сомневаюсь, что кто-то её сделал (ибо незачем)
alexsvk
8 / 8 / 1
Регистрация: 15.07.2010
Сообщений: 255
24.06.2012, 20:22     Процесс компиляции #10
Как происходит весь процесс (компиляция + связывание), если используются виртуальные функции?
Evg
Эксперт CАвтор FAQ
17537 / 5775 / 370
Регистрация: 30.03.2009
Сообщений: 15,904
Записей в блоге: 26
24.06.2012, 22:32  [ТС]     Процесс компиляции #11
Сам процесс происходит точно так же (и не зависит от того, что в исходниках написано). О том, как устроена таблица виртуальных функций, написано в любом учебнике по Си++. Либо я вопроса не понял
alexsvk
8 / 8 / 1
Регистрация: 15.07.2010
Сообщений: 255
25.06.2012, 21:25     Процесс компиляции #12
Необходимость реализации чистых виртуальных функций (ЧВФ) есть из-за возможности ошибки при обращении к нереализованной ЧВФ через указатель таблицы виртуальных функций?
Evg
Эксперт CАвтор FAQ
17537 / 5775 / 370
Регистрация: 30.03.2009
Сообщений: 15,904
Записей в блоге: 26
26.06.2012, 09:19  [ТС]     Процесс компиляции #13
Вообще ничего не понял
alexsvk
8 / 8 / 1
Регистрация: 15.07.2010
Сообщений: 255
26.06.2012, 10:43     Процесс компиляции #14
Цитата Сообщение от Evg Посмотреть сообщение
Вообще ничего не понял
C++
1
2
3
4
5
6
7
8
9
class A
{
void f() = 0;
};
 
class B:public A
{
ЗДЕСЬ ДОЛЖНО БЫТЬ ПЕРЕОПРЕДЕЛЕНИЕ f
};
MoreAnswers
Эксперт
37091 / 29110 / 5898
Регистрация: 17.06.2006
Сообщений: 43,301
26.06.2012, 10:47     Процесс компиляции
Еще ссылки по теме:

Волновой процесс - C++
Помогите найти исходник алгоритма (волновой процесс) для нахождения минимального маршрута в графе

Не создается процесс - C++
Вот часть задания: 1. Создать дочерний процесс Child (добавить к рабочей области еще один консольный вид проекта, при создании дочернего...

Завершить процесс - C++ Builder
Как завершить процессор(которые обычно отображаются в диспетчере задач) например надо завершить процессор game.exe. П.С. Есть...

Внедренние в процесс - C++ Builder
Может кто дать пример кода программы, которые например внедряется в стандратный telnet(который идет в составе windows xp) и отсылает...

Независимый процесс - C++ WinAPI
Появилась необходимость сделать так, что бы функция выполнялась вне основного цикла,и желательно что бы на её выполнение было задействовано...


Искать еще темы с ответами

Или воспользуйтесь поиском по форуму:
Evg
Эксперт CАвтор FAQ
17537 / 5775 / 370
Регистрация: 30.03.2009
Сообщений: 15,904
Записей в блоге: 26
26.06.2012, 10:47  [ТС]     Процесс компиляции #15
А вопрос в чём состоит? Что-то всё равно не въезжаю
Yandex
Объявления
26.06.2012, 10:47     Процесс компиляции
Ответ Создать тему
Опции темы

КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin® Version 3.8.9
Copyright ©2000 - 2017, vBulletin Solutions, Inc.
Рейтинг@Mail.ru