PDA

Просмотр полной версии : программирование в linux урок 1!



Алексей Шарапов
03.06.2013, 14:52
Компилятор превращает код программы на "человеческом" языке в объектный код понятный компьютеру. Компиляторов под Linux существует много, практически для каждого распространенного языка. Большинство самых востребованных компиляторов входит в набор GNU Compiler Collection, известных под названием GCC .Изначально аббревиатура GCC имела смысл GNU C Compiler, но в апреле 1999 года сообщество GNU решило взять на себя более сложную миссию и начать создание компиляторов для новых языков с новыми методами оптимизации, поддержкой новых платформ, улучшенных runtime-библиотек и других изменений . Поэтому сегодня коллекция содержит в себе компиляторы для языков C, C++, Objective C, Chill, Fortran, Ada и Java, как библиотеки для этих языков (libstdc++, libgcj, ...).Компиляция программ производится командой:

gcc <имя_файла>
После этого, если процесс компиляции пройдет успешно, то вы получите загружаемый файл a.out, запустить который можно командой:
./a.out
Для примера давайте напишем маленькую простейшую программку:
#include <stdio.h>int main(){
printf("[http://linux.firststeps.ru]\n");
printf("Our first program for Linux.\n");
return 0;};
И запустим ее:
http://www.firststeps.ru/linux/1_1.gif

Ставте + и спасибки

Алексей Шарапов
03.06.2013, 15:05
Любой компилятор по умолчанию снабжает объектный файл отладочной информацией. Компилятор gcc также снабжает файл такой информацией и на результат вы можете посмотреть сами. При компиляции проекта из предыдущего шага (http://pawno-info.ru/threads/166106-%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D 0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5-%D0%B2-linux-%D1%83%D1%80%D0%BE%D0%BA-1%21) у нас появился файл a.out размером 11817 байт (возможно у вас он может быть другого размера).Вся эта отладочная информация предназначается для отладки программы отладчиком GNU Debugger. Запустить его вы можете командой:
gdb a.outПри этом запустится отладчик и наша скомпилированная программа. Об отладке мы с вами поговорим отдельно и более подробно.Компилятор gcc может создавать отладочную информацию в различных объемах и форматах, контролировать которые можно специальными ключами. Посмотреть их подробное описание можно командой man gcc:
Debugging Options -a -dletters -fpretend-float -g -glevel -gcoff -gxcoff -gxcoff+ -gdwarf -gdwarf+ -gstabs -gstabs+ -ggdb -p -pg -save-temps -print-file-name=library -print-libgcc-file-name -print-prog-name=programКлюч -g создает отладочню информацию в родном для операционной системы виде, он выбирает между несколькими форматами: stabs, COFF, XCOFF или DWARF. На многих системах данный ключ позволяет использовать специальную информацию, которую умеет использовать только отладчик gdb. Другие ключи позволяют более тонко контролировать процесс встраивания отладочной информации.Ключ -ggdb включает в исполняемый файл отладочную информацию в родном для ОС виде и дополняет ее специализированной информацией для отладчика gdb.Ключ -gstabs создает отладочную информацию в формате stabs без дополнительных расширений gdb. Данный формат используется отладчиком DBX на большинстве BSD систем. Ключ -gstabs+дополняет отладочную информацию расширенниями понятными отладчику gdb.Ключ -gcoff создает отладочную информацию в формате COFF, которая используется отладчиком SDB на большинстве систем System V до версии System V R4.Ключ -gxcoff снабжает файл информацией в формате XCOFF, который используется отладчиком DBX на системах IBM RS/6000. Использование -gxcoff+ влкючает использование дополнительной информации для gdb.Ключ -gdwarf добавляет инфомацию в формате DWARF приняотм в системе System V Release 4. Соответственно ключ -gdwarf+ прибавляет возможностей отладчику gdb.Добавление к этим ключам в конце цифры позволяет увеличить или уменьшить уровень отладки, т.е. управлять размером требуемой отладочной информации. Например ключ:



gcc -g3 ...Увеличит уровень отладки до 3, по умолчанию он равен 2. При первом уровне отладки компилятор включает в файл минимальное количество отладочной информации достаточное для отладки частей программы, которые вы не планировли отлаживать. В эту информацию входит описание функций и внешних переменных, но не включается информация об локальных переменных и номерах строк исходного текста. Второй уровень - это уровень по умолчанию, включает в файл большинство нужной отладочной информации. Третий уровень позволяет добавить экстра-информацию, такую как определения присутствующих в программе макросов.Выше вы можете увидеть остальные ключи, которые можно использовать. Большинство этих ключей предназначено для вывода дампа программы во время компиляции и служит не для отладки программы, а для отладки самого компилятора :)) Поэтому о них можно забыть и не вспоминать.Отладочная информация это конечно хорошо, но она может значительно увеличить объем вашего файла (в три-четыре раза). Для создания программ "релизов" существует отдельная программа, позволяющая удалить отладочную информацию из запускаемого файла. Называется эта программа strip. Для того, чтобы полностью очистить файл от отладочной информации, требуется вызвать ее с ключом -s:
strip -s a.outПосле обработки файла этой командой его размер уменьшился практически в три раза и стал 3156 байт. По сравнению с 11 Кб до этого это очень даже хорошо.
+ и спасибки жду

Алексей Шарапов
03.06.2013, 15:09
Обычно простые программы состоят из одного исходного файла. Дело обстоит несколько сложнее, если эта программа становится большой. При работе с такой программой может возникнуть несколько достаточно серьезных проблем:

Файл, становясь большим, увеличивает время компиляции, и малейшие изменения в исходном тексте автоматически вынуждают тратить время программиста на перекомпиляцию программы.
Если над программой работает много человек, то практически невозможно отследить сделанные изменения.
Процесс правки и само ориентирование при большом исходном тексте становится сложным и поиск небольшой ошибки может повлечь за собой вынужденное "изучение" кода заново.

Это далеко не все пробемы, которые могут возникнуть при наличии программы "монстра". Поэтому при разработке программ рекомендуется их разбивать на куски, которые функционально ограничены и закончены. В этом значительно помогает сам язык C++, предоставляя свой богатый синтаксис.Для того, чтобы вынести функцию или переменную в отдельный файл надо перед ней поставить зарезервированное слово extern. Давайте для примера создадим программу из нескольких файлов. Сначала создадим главную программу, в которой будут две внешние процедуры. Назовем этот файл main.c:
#include <stdio.h>// описываем функцию f1() как внешнююextern int f1();// описываем функцию f2() как внешнююextern int f2();int main(){ int n1, n2; n1 = f1(); n2 = f2(); printf("f1() = %d\n",n1); printf("f2() = %d\n",n2); return 0;}Теперь создаем два файла, каждый из которых будет содержать полное определение внешней функции из главной программы. Файлы назовем f1.c и f2.c:
// файл f1.cint f1(){ return 2;}// файл f2.cint f2(){ return 10;}После этого процесс компиляции программы с помощью gcc будет выглядеть несколько иначе от описанного в 1 уроке.Компилировать можно все файлы одновременно одной командой, перечисляя составные файлы через пробел после ключа -c:
gcc -c main.c f1.c f2.cИли каждый файл в отдельности:
gcc -c f1.cgcc -c f2.cgcc -c main.cВ результате работы компилятора мы получим три отдельных объектных файла:
main.of1.of2.oЧтобы их собрать в один файл с помощью gcc надо использовать ключ -o, при этом линкер соберет все файлы в один:
gcc main.o f1.o f2.o -o rezultВ результате вызова полученной программы rezult командой:
./rezultНа экране появится результат работы:
dron:~# ./rezultf1() = 2f2() = 10dron:~#Теперь, если мы изменим какую-то из процедур, например f1():
int f1(){ return 25;}То компилировать заново все файлы не придется, а понадобится лишь скомпилировать измененный файл и собрать результирующий файл из кусков:
dron:~# gcc -c f1.cdron:~# gcc main.o f1.o f2.o -o rezult2dron:~# ./rezult2f1() = 25f2() = 10dron:~#Таким образом можно создавать большие проекты, которые больше не будут отнимать много времени на компиляцию и поиск ошибок. Однако помните, не стоит также черезчур разбивать программу, иначе у Вас получится несколько десятков файлов, в которых Вы сами рано или поздно запутаетесь. Найдите "золотую середину", например в отдельные файлы помещайте те функции или классы, с которыми Вам приходится больше всего работать при отладке. После того, как функция будет окончательно отлажена, ее вполне можно перенести в более крупный файл.

+ и спасибки жду

Алексей Шарапов
03.06.2013, 15:19
В прошлом шаге (http://pawno-info.ru/threads/166112-%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D 0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5-%D0%B2-linux-%D1%83%D1%80%D0%BE%D0%BA-3%21) мы научились создавать объектные файлы. Естественно, если каждая функция будет содержаться в отдельном файле, то таких файлов может оказаться десятки или даже сотни. Управлять таким количеством файлов очень сложно. Для этого был придуман механизм создания библиотек объектных файлов.Библиотека объектных файлов - это файл содержащий несколько объектных файлов, которые будут использоваться вместе в стадии линковки программы. Нормальная библиотека содержит символьный индекс, который состоит из названий функций и переменных и т.д., которые содержатся в библиотеке. Это позволяет ускорить процесс линковки программы, так как поиск функций и переменных в объектных файлах библиотеки происходит намного быстрее, чем поиск в наборе указанных объектных файлов. Поэтому использование библиотеки позволяет компактно хранить все требуемые объектные файлы в одном месте, и при этом значительно повысить скорость компиляции.Объектные библиотеки по способу использования разделяются на два вида:

Статические библиотеки
Динамические библиотеки

Статическая библиотека - это коллекция объектных файлов, которые присоединяются к программе во время линковки программы. Таким образом статические библиотеки используются только при созданиии программы. Потом в работе самой программы они не принимают участие, в отличие от динамических библиотек.Динамическая библиотека - это созданная специальным образом библиотека, которая присоединяется к результирующей программе в два этапа. Первый этап, это естественно этап компиляции. На этом этапе линковщик встраивает в программу описания требуемых функций и переменных, которые присутствуют в библиотеке. Сами объектные файлы из библиотеки не присоединяются к программе. Присоединение этих объектных файлов(кодов функций) осуществляет системный динамический загрузчик во время запуска программы. Загрузчик проверяет все библиотеки прилинкованные с программе на наличие требуемых объектных файлов, затем загружает их в память и присоединяет их в копии запущенной программы, находящейся в памяти.Сложный процесс загрузки динамических библиотек замедляет запуск программы, но у него есть существунный, даже можно сказать неоценимый плюс - если другая запускаемая программа линкована с этой же загруженной динамической библиотекой, то она использует туже копию библиотеки. Это означает, что требуется гораздо меньше памяти для запуска нескольких программ, сами загрузочные файлы меньше по размеру, что экономит место на дисках.Однако не все прекрасно в Linux королевстве, если Вы модифицируете динамическую библиотеку и попытаетесь ее использовать при запуске программы, то если загрузчик обнаружит уже загруженную старую библиотеку он будет упорно использовать ее функции. При этом Вы так и не сможете загрузить новую версию библиотеки. Но есть пути решения этой проблемы, и мы познакомимся с ними позже.

+ и спасибку жду

Алексей Шарапов
03.06.2013, 15:25
Для создания статических библиотек существует специальная простая программа называемая ar (сокр. от archiver - архиватор). Она используется для создания, модификации и просмотра объектных файлов в статических библиотеках, которые в действительности представляют из себя простые архивы.Давайте вернемся к проекту из шага "Шаг 3 - Компиляция нескольких файлов" (http://pawno-info.ru/threads/166112-%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D 0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5-%D0%B2-linux-%D1%83%D1%80%D0%BE%D0%BA-3%21) и создадим из файлов f1.c и f2.c отдельную библиотеку. Для начала компилируем эти файлы:
dron:~# gcc -c f1.c f2.cВ результате получим, как обычно, два файла - f1.o и f2.o. Для того, чтобы создать библиотеку из объектых файлов надо вызвать программу ar со следующими параметрами:
ar rc libимя_библиотеки.a [список_*.o_файлов]Допустим наша библиотека будет называться fs, тогда команда запишется в виде:
dron:~# ar rc libfs.a f1.o f2.oВ результате получим файл libfs.a, в котором будут лежать копии объектых файлов f1.o и f2.o. Если файл библиотеки уже существует, то архиватор будет анализировать содержимое архива, он добавит новые объектные файлы и заменит старые обновленными версиями. Опция c заставляет создавать (от create) библиотеку, если ее нет, а опция r (от replace) заменяет старые объектные файлы новыми версиями.Пока у нас есть лишь архивный файл libfs.a. Чтобы из него сделать полноценную библиотеку объектных файлов надо добавить к этому архиву индекс символов, т.е. список вложенных в библиотеку функций и переменных, чтобы линковка происходила быстрее. Далается это командой:
ranlib libимя_библиотеки.aПрограмма ranlib добавит индекс к архиву и получится полноценная статическая библиотека объектных файлов. Стоит отметить, что на некоторых системах программа ar автоматически создает индекс, и использование ranlib не имеет никакого эффекта. Но тут надо быть осторожным при атоматической компиляции библиотеки с помощью файлов makefile, если вы не будете использовать утилиту ranlib, то возможно на каких-то системах библиотеки будут создаваться не верно и потеряется независимость от платформы. Так что возьмем за правило тот факт, что утилиту ranlib надо запускать в любом случае, даже если он нее нет никакого эффекта.Для компиляции нашего основного файла main.c надо сообщить компилятору, что надо использовать библиотеки. Чтобы компилятор знал где искать библиотеки ему надо сообщить каталог, в котором они содержатся и список этих билиотек. Каталог с библиотеками указывается ключом -L, в нашем случае библиотека находится в текущем каталоге, значит путь до нее будет в виде точки (-L.). Используемые библиотеки перечисляются через ключ -l, после которого указывается название библиотеки без префикса lib и окончания .a. В нашем случае этот ключ будет выглядеть, как -lfs. Теперь все одной командой:
dron:~# gcc -c main.cdron:~# gcc main.o -L. -lfs -o rezultИли можно чуть короче:
dron:~# gcc main.c -L. -lfs -o rezultЗаметьте, что компилятору нужны библиотеки на этапе создания конечного файла, т.е. линковки. В первом случае процесс компиляции совершается первой командой, а сборка файла второй командой. Если же мы попытаемся подсунуть библиотеку на этапе компиляции, то получим вежливый ответ:
dron:~# gcc -c main.c -L. -lfsgcc: -lfs: linker input file unused since linking not doneЧто означает, что файлы библиотек не нужны, до процесса линковки. Данная команда создаст лишь файл main.o, который в итоге потом придется собирать отдельно.

+ и спасибку жду

Алексей Шарапов
03.06.2013, 15:43
Как мы уже говорили в шаге (http://pawno-info.ru/threads/166120-%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D 0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5-%D0%B2-linux-%D1%83%D1%80%D0%BE%D0%BA-4%21)"Шаг 4 - Библиотеки объектных файлов" (http://www.firststeps.ru/linux/r.php?4) динамические библиотеки немного лучше статических, но их использование более сложное. И не из-за того, что процесс загрузки программы замедляется. Проблемы начинаются уже на этапе компиляции :)Для начала стоит сказать, что объектный файл создаваемый нашим проверенным способом вовсе не подходит для динамических библиотек. Связано это с тем, что все объектные файлы создаваемые обычным образом не имеют представления о том в какие адреса памяти будет загружена использующая их программа. Несколько различных программ могут использовать одну библиотеку, и каждая из них располагается в различном адресном пространстве. Поэтому требуется, чтобы переходы в функциях библиотеки (операции goto на ассемблере) использовали не абсолютную адресацию, а относительную. То есть генерируемый компилятором код должен быть независимым от адресов, такая технология получила название PIC - Position Independent Code. В компиляторе gcc данная возможность включается ключом -fPIC.Теперь компилирование наших файлов будет иметь вид:
dron:~# gcc -fPIC -c f1.cdron:~# gcc -fPIC -c f2.cДинамическая библиотека это уже не архивный файл, а настоящая загружаемая программа, поэтому созданием динамических библиотек занимается сам компилятор gcc. Для того, чтобы создать динамическую библиотеку надо использовать ключ -shared:
dron:~# gcc -shared -o libfsdyn.so f1.o f2.oВ результате получим динамическую библиотеку libfsdyn.so, которая по моей задумке будет динамической версией библиотеки libfs.a, что видно из названия :) Теперь, чтобы компилировать результирующий файл с использованием динамической библиотеки нам надо собрать файл командой:
dron:~# gcc -с main.сdron:~# gcc main.o -L. -lfsdyn -o rezultdynЕсли теперь Вы сравните файлы, полученные при использовании статической и динамической библиотеки, то увидите, что их размеры отличаются. В данном случае файл созданный с динамической библиотекой занимает чуть больше места, но это лишь от того, что программа используемая нами совершенно примитивная и львиную долю там занимает специальный код для использования динамических возможностей. В реальных условиях, когда используются очень большие функции размер программы с использованием динамической библиотеки значительно меньше.На этом фокусы не кончаются, если Вы сейчас попробуете запустить файл rezultdyn, то получите ошибку:
dron:~# ./rezultdyn./rezultdyn: error in loading shared libraries: libfsdyn.so: cannot open shared object file: No such file or directorydron:~#Это сообщение выдает динамический линковщик. Он просто не может найти файл нашей динамической библиотеки. Дело в том, что загрузчик ищет файлы динамических библиотек в известных ему директориях, а наша директория ему не известна. Но это мы чуть отложим, потому что это достаточно сложный вопрос.А сейчас стоит поговорить еще об одном моменте использования библиотек. Я специально создал динамическую библиотеку с названием fsdyn, чтобы она отличалась от названия статической библиотеки fs. Дело в том, что если у Вас две библиотеки статическая и динамическая с одинаковыми названиями, то есть libfs.a и libfs.so, то компилятор всегда будет использовать динамическую библиотеку.Связано это с тем, что в ключе -l задается часть имени библиотеки, а префикс lib и окончание .a или .so приставляет сам компилятор. Так вот алгоритм работы компилятора таков, что если есть динамическая библиотека, то она используется по умолчанию. Статическая же библиотека используется когда компилятор не может обнаружить файл .so этой библиотеки. Во всей имеющейся у меня документации пишется, что если использовать ключ -static, то можно насильно заставить компилятор использовать статическую библиотеку. Отлично, попробуем...
dron:~# gcc -staticmain.o -L. -lfs -o rez1Как бы я не пробовал играть с позицией ключа -static, результирующий файл rez1 получается размером в 900 Кб. После применения программы strip размер ее уменьшается до 200 Кб, но это же не сравнить с тем, что наша первая статическая компиляция давала программу размером 10 Кб. А связано это с тем, что любая программа написанная на C/C++ в Linux использует стандартную библиотеку"C" library, которая содержит в себе определения таких функций, как printf(), write() и всех остальных. Эта библиотека линкуется к файлу как динамическая, чтобы все программы написанные на C++могли использовать единожды загруженные функции. Ну, а при указании ключа -static компилятор делает линковку libc статической, поэтому размер кода увеличивается на все 200 Кб.Этот эффект я не смог побороть. Поэтому я пришел к выводу, что статическую библиотеку и динамическую лучше всего создавать с разными именами. Надеюсь в будущем мы с Вами еще разберемся с этой загвоздкой, но если действительно все так плохо, то решение с различными именами будет единственно верным.

Алексей Шарапов
03.06.2013, 16:24
В прошлый раз (http://pawno-info.ru/threads/166142-%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D 0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5-%D0%B2-linux-%D1%83%D1%80%D0%BE%D0%BA-6%21) мы с Вами обнаружили, что запуск программ, скомпилированных вместе с динамическими библиотеками, вызывает ошибку:
dron:~# ./rezultdyn./rezultdyn: error in loading shared libraries: libfsdyn.so: cannot open shared object file: No such file or directorydron:/#Это сообщение выдает загрузчик динамических библиотек(динамический линковщик - dynamic linker), который в нашем случае не может обнаружить библиотеку libfsdyn.so. Для настройки динамического линковщика существует ряд программ.Первая программа называется ldd. Она выдает на экран список динамических библиотек используемых в программе и их местоположение. В качестве параметра ей сообщается название обследуемой программы. Давайте попробуем использовать ее для нашей программы rezultdyn:
dron:~# ldd rezultdyn libfsdyn.so => not found libc.so.6 => /lib/libc.so.6 (0x40016000) /lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0x40000000)dron:~#Как видите все правильно. Программа использует три библиотеки:

libc.so.6 - стандартную библиотеку функций языка C++.
ld-linux.so.2 - библиотеку динамической линковки программ ELF формата.
libfsdyn.so - нашу динамическую библиотеку функций.

Нашу библиотеку она найти не может. И правильно! Динамический линковщик ищет библиотеки только в известных ему каталогах, а каталог нашей программы ему явно не известен.Для того, чтобы добавить нашу директорию с библиотекой в список известных директорий надо подредактировать файл /etc/ld.so.conf. Например, у меня этот файл состоит из таких строк:
dron:~# cat /etc/ld.so.conf/usr/X11R6/lib/usr/i386-slackware-linux/lib/usr/i386-slackware-linux-gnulibc1/lib/usr/i386-slackware-linux-gnuaout/libdron:~#Во всех этих директории хранятся всеми используемые библиотеки. В этом списке нет лишь одной директории - /lib, которая сама по себе не нуждается в описании, так как она является главной. Получается, что наша библиотека станет "заметной", если поместить ее в один их этих каталогов, либо отдельно описать в отдельном каталоге. Давайте для теста опишем, добавим строку в конец файлаld.so.conf:
/rootУ меня этот файл валяется в домашнем каталога пользователя root, у Вас он может быть в другом месте. Теперь после этого динамический линковщик будет знать где можно найти наш файл, но после изменения конфигурационного файла ld.so.conf необходимо, чтобы система перечитала настройки заново. Это делает программа ldconfig. Пробуем запустить нашу программу:
dron:~# ldconfigdron:~# ./rezultdynf1() = 25f2() = 10dron:~#Как видите все заработало :) Если теперь Вы удалите добавленную нами строку и снова запустите ldconfig, то данные о расположении нашей библиотеки исчезнут и будет появляться таже самая ошибка.Но описанный метод влияет на всю систему в целом и требует доступа администратора системы, т.е. root. А если Вы простой пользователь без сверх возможностей ?!Для такого случая есть другое безболезненное решение. Это использование специальной переменной среды LD_LIBRARY_PATH, в которой перечисляются все каталоги содержащие пользовательские динамические библиотеки. Для того, чтобы установить эту переменную в командной среде bash надо набрать всего несколько команд. Для начала посмотрим есть ли у нас такая переменная среды:
dron:~# echo $LD_LIBRARY_PATHУ меня в ответ выводится пустая строка, означающая, что такой переменной среды нет. Устанавливается она следующим образом:
dron:~# LD_LIBRARY_PATH=/rootdron:~# export LD_LIBRARY_PATHПосле этого программа rezultdyn будет прекрасно работать. В случае, если у Вас в системе эта переменная среды уже уставновлена, то, чтобы не испортить ее значение, надо новый каталог прибавить к старому значению. Делается это другой командой:
dron:~# LD_LIBRARY_PATH=/root:${LD_LIBRARY_PATH}dron:~# export LD_LIBRARY_PATHЕсли Вы обнулите эту переменную, то снова библиотека перестанет работать:
dron:~# LD_LIBRARY_PATH=""dron:~# export LD_LIBRARY_PATHdron:~# ./rezultdyn./rezultdyn: error in loading shared libraries: libfsdyn.so: cannot open shared object file: No such file or directorydron:~#Вы также параллельно можете зайти в систему под другим пользователем или даже тем же самым, но если Вы захотите просмотреть значение LD_LIBRARY_PATH, то увидите ее прежнее значение. Это означает, что два разных пользователя Linux не могут влиять на работу друг друга, а это и есть самое главное хорошее отличие систем Unix от большинства других систем.

+ и спасибки жду

Алексей Шарапов
03.06.2013, 16:27
Если Вы подумали, что фокусы с динамическими библиотеками кончились, то Вы очень сильно ошиблись. До того были цветочки, а ягодки будут сейчас :)Оказывается, что использовать динамические библиотеки можно не только в начале загрузки, но и в процессе самой работы программы. Программа сама может вызывать любые функции из библиотеки, когда ей захочется. Для этого всего-лишь надо использовать библиотеку dl, которая позволяет линковать библиотеки "на лету". Она управляет загрузкой динамических библиотек, вызовом функций из них и выгрузкой после конца работы.Для использования функций программной работы с динамическими библиотеками необходимо подключить заголовочный файл:
#include <dlfcn.h>Чтобы вызывать какие-то функции из динамической библиотеки сначала надо открыть эту библиотеку (можно сказать "загрузить"). Открывается она функцией:
void *dlopen (const char *filename, int flag);Параметр filename содержит путь до требуемой библиотеки, а параметр flag задает некоторые специфические флаги для работы. Функция возвращает указатель на загруженную библиотеку. В случае любой ошибки возвращается указатель NULL. В таком случае тест ошибки понятный человеку можно получить с помощью функции dlerror(). Пока мы не будем задумываться над этим, и я приведу стандартный код для открытия библиотеки:
void *library_handler;//......//загрузка библиотекиlibrary_handler = dlopen("/path/to/the/library.so",RTLD_LAZY);if (!library_handler){ //если ошибка, то вывести ее на экран fprintf(stderr,"dlopen() error: %s\n", dlerror()); exit(1); // в случае ошибки можно, например, закончить работу программы};После этого можно работать с библиотекой. А работа эта заключается в получении адреса требуемой функции из библиотеки. Получить адрес функции или переменной можно по ее имени с помощью функции:
void *dlsym(void *handle, char *symbol);Для этой функции требуется адрес загруженной библиотеки handle, полученный при открытии функцией dlopen(). Требуемая функция или переменная задается своим именем в переменной symbol.Закрывается библиотека функцией:
dlclose(void *handle);При закрытии библиотеки динамический линковщик проверяет счетчик количества открытий библиотеки, и если она была открыта несколькими программами одновременно, то она не выгружается до тех пор, пока все программы не закроют эту библиотеку.Для примера создадим программу, которая в качестве параметра получает название функции, которую она будет использовать в работе. Например, это будут математические функции возведения в степень. Создадим сначала динамическую библиотеку. Пишем ее код:
double power2(double x){ return x*x;};double power3(double x){ return x*x*x;};double power4(double x){ return power2(x)*power2(x);};//......Сохраняем его в файл lib.c и создаем динамическую библиотеку libpowers.so следующими командами:
dron:~# gcc -fPIC -c lib.cdron:~# gcc -shared lib.o -o libpowers.soТеперь создаем основную программу в файле main.c:
#include <stdio.h>/* заголовочный файл для работы с динамическими библиотеками */#include <dlfcn.h>int main(int argc, char* argv[]){ void *ext_library; // хандлер внешней библиотеки double value=0; // значение для теста double (*powerfunc)(double x); // переменная для хранения адреса функции //загрузка библиотеки ext_library = dlopen("/root/libpowers.so",RTLD_LAZY); if (!ext_library){ //если ошибка, то вывести ее на экран fprintf(stderr,"dlopen() error: %s\n", dlerror()); return 1; }; //загружаем из библиотеки требуемую процедуру powerfunc = dlsym(ext_library, argv[1]); value=3.0; //выводим результат работы процедуры printf("%s(%f) = %f\n",argv[1],value,(*powerfunc)(value)); //закрываем библиотеку dlclose(ext_library);};Код главной программы готов. Требуется его откомпилировать с использованием библиотеки dl:
dron:~# gcc main.c -o main -ldlПолучим программный файл main, который можно тестировать. Наша программа должна возводить значение 3.0 в требуемую нами степень, которая задается названием функции. Давайте попробуем:
dron:~# ./main power2power2(3.000000) = 9.000000dron:~# ./main power3power3(3.000000) = 27.000000dron:~# ./main power4power4(3.000000) = 81.000000dron:~#Ну, как ?! Круто !!! Мы используем какие-то функции, зная лишь их название. Представьте открывающиеся возможности для программ, на основе этого метода можно создавать плагины для программ, модернизировать какие-то части, добавлять новые возможности и многое другое.

жду + и спасибку

Алексей Шарапов
03.06.2013, 16:29
До сих пор мы писали с Вами простые динамические библиотеки, поэтому у нас не болела голова об инициализации внутренних переменных. А представьте себе более сложную ситуацию, когда функции библиотеки для работы требуют правильно инициализированные переменные. Ну, например, для работы функции нужен буфер или массив.


Специально для таких случаев в библиотеках можно задавать инициализирующую и деинициализирующую функции:


void _init() - инициализация
void _fini() - деинициализация
Чтобы понять, что к чему, введем в нашей библиотеке lib.c переменную test и возвращающую ее функцию:


char *test;


char *ret_test(){
return test;
};
Пишем основную программу main.c. Она очень похожа на предыдущий наш проект, поэтому можете его модифицировать:


#include <stdio.h>
#include <dlfcn.h>


int main(){


void *ext_library;
double value=0;
char * (*ret_test)();


ext_library = dlopen("libtest.so",RTLD_LAZY);
if (!ext_library){
fprintf(stderr,"dlopen() error: %s\n", dlerror());
return 1;
};


ret_test = dlsym(ext_library,"ret_test");


printf("Return of ret_test: \"%s\" [%p]\n",(*ret_test)(),(*ret_test)());


dlclose(ext_library);
};
После компиляции всего этого хозяйства мы получим результат:


dron:~# gcc -c lib.c -fPIC
dron:~# gcc -shared lib.o -o libtest.so
dron:~# gcc -o main main.c -ldl
dron:~# ./main
Return of ret_test: "(null)" [(nil)]
dron:~#
Как видите переменная test оказалась равной NULL, а нам бы хотелось нечто другое. Ну, так давайте посмотрим как работают функции _init() и _fini(). Создадим вторую библиотеку lib1.c:


#include <stdlib.h>


char *test;


char *ret_test(){
return test;
};


void _init(){
test=(char *)malloc(6);
if (test!=NULL){
*(test+0)='d';
*(test+1)='r';
*(test+2)='o';
*(test+3)='n';
*(test+4)='!';
*(test+5)=0;
};
printf("_init() executed...\n");
};


void _fini(){
if (test!=NULL) free(test);
printf("_fini() executed...\n");
};
Теперь пробуем компилировать:


dron:~# gcc -c lib1.c -fPIC
dron:~# gcc -shared lib1.o -o libtest.so
lib1.o: In function `_init':
lib1.o(.text+0x24): multiple definition of `_init'
/usr/lib/crti.o(.init+0x0): first defined here
lib1.o: In function `_fini':
lib1.o(.text+0xc0): multiple definition of `_fini'
/usr/lib/crti.o(.fini+0x0): first defined here
collect2: ld returned 1 exit status
dron:~#
Опаньки... Облом. Что же это такое ?! Оказывается кто-то уже использовал эти функции до нас и программа не может слинковаться. После долгого копания в нескольких чужих исходниках я получил ответ на этот вопрос. Оказывается, чтобы избавиться от мешающей библиотеки надо использовать ключ компилятора -nostdlib. Попробуем:


dron:~# gcc -shared -nostdlib lib1.o -o libtest.so
dron:~#
Смотрите-ка, все прекрасно скомилировалось. Теперь попробуем запустить main:


dron:~# ./main
_init() executed...
Return of ret_test: "dron!" [0x8049c20]
_fini() executed...
dron:~#
Ну как ? Помоему классно. Теперь можно спокойно создавать для работы библиотеки правильно инициализированные переменные. Однако классные эти штуки - динамические библиотеки ! А Вы что хотели ? Тот же Windows только на своих DLL и живет. А Linux ничем не хуже..

ставим + и спасибки

Алексей Шарапов
03.06.2013, 16:36
Почти всем сложным программам для работы требуется входные параметры (опции, аргументы - называйте как хотите), от значения которых строится последовательность работы алгоритма заложенного в программе или используются различные источники данных.Вы наверняка знаете, что передача параметров в программу на C/C++ осуществляется через массив функции main(). Так повелось, что он называется argv (от arguments values - значения аргументов), но в принципе его можно назвать и по другому. Количество этих параметров передается через переменную argc (от arguments counter - счетчик аргументов).Программа, для работы которой требуется набор входных параметров задается при помощи специального определения функции main():
int main(int argc, char *argv[]{};int main(int argc, char **argv){};Давайте напишем маленькую программку, которая выводит значения переданных параметров:
// программа test.c#include <stdio.h>int main(int argc, char *argv[]){ int i=0; for (i=0;i<argc;i++){ printf("Argument %d: %s\n",i,argv[i]); };};Сохраняем в файл test.c и компилируем:
dron:~# gcc test.c -o testПосле этого попробуем запустить программу:
dron:~# ./testArgument 0: ./testПередадим несколько параметров:
dron:~# ./test qwe sdf fgh hjk kl 123 --helpArgument 0: ./testArgument 1: qweArgument 2: sdfArgument 3: fghArgument 4: hjkArgument 5: klArgument 6: 123Argument 7: --helpВ качестве первого параметра программе всегда передается ее имя и таким образом программа может узнать свое название, т.е. имя файла, в котором она содержится.Но моя цель не говорить о том, как передаются параметры, а как с ними работать. Для начала надо вспомнить, что в системе Linux существует два вида параметров: короткие и длинные. Короткие параметры начинаются с одного дефиса и имеют длину в один символ, их просто и быстро набирать в командной строке. Длинные параметры начинаются с двух дефисов и могут иметь длинное имя, которое целесообразно использовать в скриптах (чтобы потом можно было вспомнить, что и как происходит). Кроме этого любой параметр может иметь значение, а может и не иметь. Приведу для примера несколько параметров:
-h - короткий параметр--help - длинный параметр-s 10 - параметры со значениями--size 10--size=10Так вот, существует несколько специальных функций предназначенных для разбора списка переданных параметров:

int getopt(...) - Обрабатывает короткие параметры
int getopt_long(...) - Обрабатывает короткие и длинные параметры
int getopt_long_only(...) - Обрабатывает параметры только как длинные

Давайте разберемся с работой первой функции - getopt(...). Ее определение выглядит следующим образом:
#include <unistd.h>int getopt(int argc, char * const argv[], const char *optstring);extern char *optarg;extern int optind, opterr, optopt;Эта функция последовательно перебирает переданные параметры в программу. Для работы в функцию передается количество параметров argc, массив параметров argv[] и специальная строкаoptstring, в которой перечисляются названия коротких параметров и признаки того, что параметры должны иметь значение. Например, если программа должна воспринимать три параметра a, b, F , то такая строка бы выглядела как "abF". Если параметр должен иметь значение, то после буквы параметра ставится двоеточие, например параметр F и d имеют значения, а параметры e, a и b не имеют, тогда эта строка могла бы выглядеть как "eF:ad:b". Если параметр может иметь (т.е. может и не иметь) значение, то тогда ставится два знака двоеточия, например "a::" (это специальное расширениеGNU). Если optstring содержит "W:", то тогда параметр -W opt переданный в программу, будет восприниматься как длинный параметр --opt. Это связано с тем, что параметр W зарезервирован в POSIX.2для расширения возможностей.Для перебора параметров функцию getopt() надо вызывать в цикле. В качестве результата возвращется буква названия параметра, если же параметры кончились, то функция возвращает -1. Индекс текущего параметра хранится в optind, а значение параметра помещается в optarg (указатель просто указывает на элемент массива argv[]). Если функция находит параметр не перечисленный в списке, то выводится сообщение об ошибке в stderr и код ошибки сохраняется в opterr, при этом в качестве значения возврящается "?". Вывод ошибки можно запретить, если установить opterr в 0.
#include <stdio.h>#include <unistd.h>int main(int argc, char *argv[]){ int rez=0;// opterr=0; while ( (rez = getopt(argc,argv,"ab:C::d")) != -1){ switch (rez){ case 'a': printf("found argument \"a\".\n"); break; case 'b': printf("found argument \"b = %s\".\n",optarg); break; case 'C': printf("found argument \"C = %s\".\n",optarg); break; case 'd': printf("found argument \"d\"\n"); break; case '?': printf("Error found !\n");break; }; };};Попробуем скомпилировать данную программку и запустить:
dron:~# gcc test.c -o testdron:~# ./test -a -b -d -Cfound argument "a".found argument "b = -d".found argument "C = (null)".dron:~# ./test -a -b -C -dfound argument "a".found argument "b = -C".found argument "d"dron:~# ./test -a -b1 -C -dfound argument "a".found argument "b = 1".found argument "C = (null)".found argument "d"dron:~# ./test -b1 -b2 -b 15found argument "b = 1".found argument "b = 2".found argument "b = 15".Давайте посмотрим, как функция getopt вылавливает ошибки. Попробуем задать параметр, которого нет в списке:
dron:~# ./test -h -a./test: invalid option -- hError found !found argument "a".Как я и говорил, функция вывела сообщение об ошибке в stderr. Давайте выключим вывод сообщений, для этого надо где-то в программе перед вызовом функции вставить opterr=0;. Компилируем и запускаем:
dron:~# ./test -h -aError found !found argument "a".Теперь, как видите, сообщение больше не выдается, зато как и раньше можно обработать ошибку самому.

+ и спасибку жду