|
Что такое gcc и g++? Если задать этот вопрос
людям, которые хотя бы отдаленно знакомы с Linux, то
наверняка можно получить следующий ответ: «gcc — это
компилятор языка Си, а g++ — компилятор С++». В
каком-то смысле это будет правильный ответ. А если
этим же людям задать другой вопрос: «А вы знаете,
что на самом деле не существует компилятора g++, а
gcc — это не Си-компилятор?» Бьюсь об заклад, что в
99% случаев можно услышать в ответ обвинения в
ламерстве, дебилизме, параличе мозга, врожденной
болезнью Дауна и пр. Ты тоже входишь в эти 99%?
Тогда читай ниже..
Повсеместно читая мессаги в форумах или во
время личного общения, я встречаю полное
непонимание со стороны людей (среди которых
попадаются и уже состоявшиеся программисты) того,
как на самом деле работают gcc и g++. Этой статьей
я попытаюсь открыть глаза на многие не простые
вещи (а что вообще просто в мире *nix?) связанные
с этими «компиляторами». Кто бы ты ни был:
администратор, скрипт-кидди, кодер или простой
юзер никс-системы - ты используешь gcc/g++, а
значит должен понимать принципы их работы.
Несмотря на то, что на момент написания этой
статьи уже появилась версия GCC 3.3.2, речь однако
пойдет о версии 2.96. Именно эта версия
установлена на моем стареньком Red Hat 7.1 и меня
совершенно не втыкает выкачивать около 30 МБ (если
не ошибаюсь, именно столько весит архив GCC
3.3.2), т. к. 2.96 меня пока что полностью
устраивает. Я просмотрел список изменений и
дополнений, прошедших с версии 2.96 и не заметил
ни чего существенного, чтобы могло бы повлиять на
ход этой статьи. Но в качестве домашнего задания,
я оставляю тебе проверить все сказанное здесь для
других версий компилятора и сообщить о результатах
мне.Подопытная крыса
Создадим простейшую тестовую программку на С++ и
сохраним ее под именем hello.cpp:
#include <iostream>
int main() {
std::cout<<"Hello, World!\n";
return 0;
}
Выполним сначала компиляцию с помощью g++:
g++ -o hello hello.cpp
Все проходит без ошибок, в текущей директории
образуется выполняемый файл hello. Теперь
попробуем произвести компиляцию этой же
программы с помощью gcc:
gcc -o hello hello.cpp
Компиляция не проходит, на экран вываливаются
ошибки вроде тех, что показаны на скриншоте (см.
рис. 1).

Рис.1. gcc отказывается компилить
CPP-программу
Между тем gcc отлично справляется с любой
Си-прогой. Так, значит, gcc не понимает программ
на C++, а только Си-программы? Значит g++ и gcc
два разных компилятора? Зачем же я тогда завел
весь этот разговор? Подожди, подожди, давай
обратимся к man.
Если ты не умеешь читать
Откроем man g++. Прямо с первых строк, в разделе
описания читаем (я привожу сразу перевод):
«Компиляторы Си и Си++ объединены; g++ — это
скрипт, который вызывает gcc с опциями языка С++.
gcc пропускает входные файлы через одну или
более из четырех стадий: препроцессирование,
компиляцию, ассемблирование и линковку».
Нечто подобное можно найти и в man gcc.
Продолжая дальше изучать эти маны, можно узнать,
что, например, расширения исходных файлов .cc, .cpp,
.C (прописная буква) и пр. зарезервированы для
языка С++, а .c (строчная буква) — для языка Си.
Именно по расширению компилятор определяет, на
каком языке составлена программа. К сожалению, у
меня man g++ датируется аж апрелем 1993 года
(спасибо компании Red Hat, чей дистрибутив я
купил в 2001 году)! Поэтому он может не отражать
современной действительности. Так g++ на самом
деле ни какой не скрипт и gcc он не вызывает.
Это легко проверить, удалив/переместив файл gcc
(у меня он расположен в папке /usr/bin/gcc) и
повторно скомпилировав hello.cpp с помощью g++.
Компиляция пройдет на ура, а это значит, что gcc
для его работы не требуется.
Поэтому обратимся лучше к самому свежему
мануалу, а именно к «GCC 3.3.2 Manual», который
можно найти на официальном сайте http://gcc.gnu.org/onlinedocs/.
Вот что там можно прочитать (далее перевод):
«Несколько версий компиляторов объединены (C,
C++, Objective-C, Ada, Fortran, Java). Вот
почему мы используем название «GNU Compiler
Collection». GCC может компилировать программы
написанные на любом из этих языков. Ada, Fortran,
Java описаны в отдельных мануалах.
«GCC» - общее стандартное обозначение от «GNU
Compiler Collection». Это как общее название
компилятора, так и название, используемое, когда
акцент делается на компиляции C программ (как
прежде это сокращение обозначало «GNU C Compiler»).
Когда ссылаются на C++ компиляцию, обычно
называют компилятор «G++». Поскольку есть только
один компилятор, будет точным называть его «GCC»
вне зависимости от языка; однако термин «G++»
более полезен, когда ударение стоит на
компиляции С++ программ».
Если ты так ничего и не понял
Как же так, мы ведь вначале статьи на
эксперименте убедились, что gcc и g++ работают
по-разному, а в мануале сказано, что есть только
один компилятор GCC? Давай не будем делать
поспешных выводов, а лучше снова обратимся к man.
В разделе FILES обоих манов («man gcc» и «man
g++») мы обнаружим интересную деталь (см. рис.
2), оказывается, что для компиляции Си-программ
используется компилятор cc1, а для программ на
C++ как gcc, так и g++ вызывают cc1plus! Так вот
они настоящие компиляторы! Там же мы увидим
имена файлов препроцессора, линкера и пр.,
причем все эти файлы являются составной частью
пакета GCC! Значит, gcc и g++ просто вызывают
указанные файлы, выполняя поочередно все этапы
создания бинарника (препроцессирование,
компиляцию, ассемблирование и линковку)!
Но почему gcc отказался компилировать
программу на C++? Дело в том, что gcc по
умолчанию подключает к программе стандартную
библиотеку языка C: libc, а g++ стандартную
библиотеку языка C++: lstdc++. Таким образом,
перед компиляцией программы на языке С++
достаточно принудительно указать gcc библиотеку
lstdc++ и он откомпилирует программу не хуже g++.
Т. е. для того чтобы gcc скомпилил нашу
программу hello.cpp достаточно указать в
командной строке следующее: gcc -o hello
hello.cpp -lstdc++.
Сравнение работы gcc и g++
Хорошо, мы убедились, что gcc и g++ одинаково
смогли справиться с задачей, но может быть на
внутреннем уровне они отличаются? Может быть
процесс создания исполняемого файла в них идет
по-разному, а ведь это в свою очередь может
свидетельствовать о том, что это совершенно
независимые компиляторы? Что ж попытаемся
выяснить и это. Естественно мы не будем рыться в
исходниках GCC, т. к. компилятор это не
программка в сотню строк и одной бутылочки,
чтобы разобраться, явно не хватит. Для того
чтобы увидеть, что происходит в
действительности, существует замечательный
ключик –v. Результат компиляции с использованием
этого ключа показан на рисунке 3.
Чтобы сравнить выходные данные gcc и g++, я
сохранил результаты вывода в текстовые файлы:
gcc –v –o hello hello.cpp 2>tmp_gcc.txt
g++ -v –o hello hello.cpp 2>tmp_g++.txt
И воспользовавшись утилитой diff, выяснил, что
выходные данные оказались практически
одинаковыми. Отличие заключается лишь в именах
темповых файлов, например в одном случае: /tmp/cczEKZcb.ii,
а в другом: /tmp/ccdVfS2M.ii — но это само собой
разумеющиеся отличия! Следовательно, процесс
создания исполняемого файла на «внутреннем»
уровне в обоих случаях протекает совершенно
одинаково. Давай рассмотрим этот процесс в
деталях.
gcc и g++ по косточкам
Далее я привожу пошаговый разбор той информации,
которую выводят gcc и g++ с использованием ключа
–v (tmp_gcc.txt или tmp_g++):
Reading specs from /usr/lib/gcc-lib/i386-redhat-linux/2.96/specs
gcc version 2.96 20000731 (Red Hat Linux 7.1
2.96-81)
Эти строки говорят о том, что происходит чтение
файла specs, на основании которого будет
строиться дальнейшая компиляция, а также
показывается номер версии компилятора gcc. Еще
раз отмечаю: gcc и g++ выводят совершенно
идентичную информацию!
/usr/lib/gcc-lib/i386-redhat-linux/2.96/cpp0 -lang-c++
-D__GNUG__=2 -D__EXCEPTIONS -v -D__GNUC__=2 -D__GNUC_MINOR__=96
-D__GNUC_PATCHLEVEL__=0 -D__ELF__ -Dunix -Dlinux
-D__ELF__ -D__unix__ -D__linux__ -D__unix -D__linux
-Asystem(posix) -Acpu(i386) -Amachine(i386)
-Di386 -D__i386 -D__i386__ -D__tune_i386__
hello.cpp /tmp/cczEKZcb.ii GNU CPP version 2.96
20000731 (Red Hat Linux 7.1 2.96-81) (cpplib)
(i386 Linux/ELF)
Первым вызывается препроцессор cpp0, он заменяет
в программе комментарии пробелами, вставляет
файлы #include, преобразует директивы #define, #ifdef,
#undef и пр. В результате препроцессирования
образуется темповый файл /tmp/cczEKZcb.ii,
необходимый для дальнейшей компиляции.
ignoring nonexistent directory "/usr/local/include"
ignoring nonexistent directory "/usr/i386-redhat-linux/include"
#include "..." search starts here:
#include <...> search starts here:
/usr/include/g++-3
/usr/lib/gcc-lib/i386-redhat-linux/2.96/include
/usr/include
End of search list.
Здесь мы видим, что препроцессор ищет
необходимые include-файлы, указанные в двойных
кавычках - в текущей директории, а заключенные в
угловые скобки - по указанным трем путям.
/usr/lib/gcc-lib/i386-redhat-linux/2.96/cc1plus
/tmp/cczEKZcb.ii -quiet -dumpbase hello.cpp -version
-o /tmp/ccw4l1Ud.s GNU C++ version 2.96 20000731
(Red Hat Linux 7.1 2.96-81) (i386-redhat-linux)
compiled by GNU C version 2.96 20000731 (Red Hat
Linux 7.1 2.96-81).
Теперь в работу вступает компилятор С++:
cc1plus, который на основании файла /tmp/cczEKZcb.ii,
полученного на этапе препроцессирования, создает
исходник на ассемблере /tmp/ccw4l1Ud.s.
as -V -Qy -o /tmp/cc5T4w7y.o /tmp/ccw4l1Ud.s GNU
assembler version 2.10.91 (i386-redhat-linux)
using BFD version 2.10.91.0.2
Далее наступает этап ассемблирования, для чего
вызывается стандартный ассемблер as, который
создает объектный файл /tmp/cc5T4w7y.o.
/usr/lib/gcc-lib/i386-redhat-linux/2.96/collect2
-m elf_i386 -dynamic-linker /lib/ld-linux.so.2 -o
hello /usr/lib/gcc-lib/i386-redhat-linux/2.96/../../../crt1.o
/usr/lib/gcc-lib/i386-redhat-linux/2.96/../../../crti.o
/usr/lib/gcc-lib/i386-redhat-linux/2.96/crtbegin.o
-L/usr/lib/gcc-lib/i386-redhat-linux/2.96 -L/usr/lib/gcc-lib/i386-redhat-linux/2.96/../../..
/tmp/cc5T4w7y.o -lstdc++ -lgcc -lc -lgcc /usr/lib/gcc-lib/i386-redhat-linux/2.96/crtend.o
/usr/lib/gcc-lib/i386-redhat-linux/2.96/../../../crtn.o
Завершается все линковкой. Линковщик collect2
объединяет объектные файлы с необходимыми
разделяемыми библиотеками и создает исполняемый
файл. Ключ –L показывает, в каких директориях
происходит поиск библиотек.
Ну и какие выводы?
Мы наглядно убедились, что gcc и g++ компилируют
CPP-программы совершено одинаково, причем их
основная роль в этом процессе заключается в
вызове необходимых утилит (препроцессора,
компилятора, ассемблера, линковщика). Точно
также, к примеру, работает Microsoft Visual C++,
который для того, чтобы создать исполняемый файл
вызывает cl.exe, link.exe, lib.exe и пр. Т. е.
gcc - это уже давно не Си-компилятор, а «GNU
Compiler Collection», а g++ лишь одна из его
составных частей, оставленная скорее для
удобства (чтобы не терзать gcc левыми ключами),
чем для каких-то реально необходимых целей. G++
как отдельный компилятор просто отсутствует в
природе! На сервере ftp://ftp.gnu.org/ можно
скачать лишь обновления библиотеки libstdc++. Но
как сказано в мануалах, gcc можно называть
С-компилятором, когда речь идет о компилировании
С-программ, а g++, соответственно, можно
называть CPP-компилятором, когда акцент ставится
на CPP-программах. Тут терминология не особенно
важна, главное понимать, что происходит в
действительности, но как раз этого понимания
большинству nix-men и не хватает.
История GCC
Первая версия gcc была создана основателем «Free
Software Foundation» Ричардом Столманом в 1987
году. Первоначально аббревиатура GCC означала «GNU
C Compiler», т. к. gcc тогда компилировал только
Си-программы. Теперь GCC расшифровывается, как «GNU
Compiler Collection», т. к. включает в себя
множество компиляторов: C, C++, Objective-C, Ada,
Java и пр. Огромный вклад в развитие GCC внесла
компания Cygnus под руководством Майкла Тиманна
(Michael Tiemann). В 1997 между Cygnus и «Free
Software Foundation», из-за некоторых
разногласий произошел раскол, и Cygnus создала
свою версию компилятора EGCS. Однако в 1999 году
конфликт был исчерпан и проект EGCS слился с GCC.
Последнюю версию компилятора GCC всегда можно
найти на официальном FTP-сервере: ftp://ftp.gnu.org/gnu/gcc/.
Особенность G++
В статье я не сказал об одной мелкой детали. G++
всегда по умолчанию линкует библиотеку
математических подпрограмм libm (по крайней
мере, версия GCC 2.96). По этой причине
программы скомпилированные g++ получаются всегда
немного большего размера, чем те же программы
полученные с помощью gcc. Если при компиляции с
помощью gcc в командной строке указать флаг –lm
будет получен тот же эффект.
Полезные ссылки:
http://gcc.gnu.org
http://www.cae.wisc.edu/~gerdts/how_gcc_works.html
Источник:

(Рисунки к статье немного изменены -- прим.
редакции)
|
|