Различия между версиями 3 и 4
Версия 3 от 2020-10-05 17:45:51
Размер: 5440
Редактор: FrBrGeorge
Комментарий:
Версия 4 от 2020-10-05 19:27:26
Размер: 18085
Редактор: FrBrGeorge
Комментарий:
Удаления помечены так. Добавления помечены так.
Строка 102: Строка 102:
|| <<MiniPage({{{#!highlight c\n#include <stdio.h>\n#include "outlib.h"\nint main(int argc, char *argv[]) {\n int i;\n if((Count = argc)>1) {\n output("<INIT>");\n for(i=1; i<argc; i++)\n output(argv[i]);\n output("<DONE>");\n }\n else\n usage(argv[0]);\n return 0;\n}\n}}})>> || <<MiniPage({{{#!highlight c\n#include <stdio.h>\n#include "outlib.h"\nvoid output(char *str) {\n printf("%d: %s\012", Count++, str);\n}\n\nvoid usage(char *prog) {\n fprintf(stderr, "%s: Print all arguments\012\t"\\n "Usage: %s arg1 [arg2 […]]\012", prog, prog);\n}\n}}})>> || || <<MiniPage({{{#!highlight c\n#include <stdio.h>\n#include "outlib.h"\nint main(int argc, char *argv[]) {\n int i;\n if((Count = argc)>1) {\n output("<INIT>");\n for(i=1; i<argc; i++)\n output(argv[i]);\n output("<DONE>");\n }\n else\n usage(argv[0]);\n return 0;\n}\n}}})>> || <<MiniPage({{{#!highlight c\n#include <stdio.h>\n#include "outlib.h"\nvoid output(char *str) {\n printf("%d: %s\012", Count++, str);\n}\n\nvoid usage(char *prog) {\n fprintf(stderr, "%s v%.2f: Print all arguments\012\t"\\n "Usage: %s arg1 [arg2 […]]\012", prog, VERSION, prog);\n}\n}}})>> ||
Строка 104: Строка 104:
|| <<MiniPage({{{#!highlight c\nint Count=0;\n}}})>> || <<MiniPage({{{#!highlight c\nvoid output(char *);\nvoid usage(char *);\nextern int Count;\n}}})>> || || <<MiniPage({{{#!highlight c\nint Count=0;\n}}})>> || <<MiniPage({{{#!highlight c\nvoid output(char *);\nvoid usage(char *);\nextern int Count;\n#define VERSION 0.0\n}}})>> ||
Строка 106: Строка 106:
|||| <<MiniPage( {{{#!highlight makefile\nGENERATES = prog README\nTRASH = *.o *~ o.*\n\n%.o: %.c\n cc $< -c -o $@\n\nall: README prog\n\nprog: const.o fun.o prog.o\n cc $^ -o $@\n\nREADME: prog\n ./$< 2> $@\n\nclean:\n rm -f $(TRASH)\n\ndistclean: clean\n rm -rf $(GENERATES)\n}}})>> ||
  * Следуя традиции, мы разделили генераты на совсем ненужные (`TRASH` и собственно целевые файлы проекта (`GENERATES`), которые всё равно стоит удалять, если в репозитории должны остаться только исходники
||||<(><<MiniPage( {{{#!highlight makefile\nGENERATES = prog README\nTRASH = *.o *~ o.*\n\n%.o: %.c\n cc $< -c -o $@\n\nall: README prog\n\nprog: const.o fun.o prog.o\n cc $^ -o $@\n\nREADME: prog\n ./$< 2> $@\n\nclean:\n rm -f $(TRASH)\n\ndistclean: clean\n rm -rf $(GENERATES)\n}}})>> ||
  * Следуя традиции, мы разделили генераты на совсем ненужные (`TRASH`) и собственно целевые файлы проекта (`GENERATES`), которые всё равно стоит удалять, если в репозитории должны остаться только исходники
  * Традиционно же цель `all:` в начале файла перечисляет всё, что должно быть собрано (цель в начале считается целью по умолчанию, если `make` запущен без явно заданной цели)
  * Теперь сама наша программа участвует в генерации файла `README`! Поэтому сборка начнётся не с `README`, а с `prog`, несмотря на то, что в списке целей `all` раньше стоит `README`:
  {{{#!highlight console
$ make
cc const.c -c -o const.o
cc fun.c -c -o fun.o
cc prog.c -c -o prog.o
cc const.o fun.o prog.o -o prog
./prog 2> README
}}}
 1. В нашем Makefile есть недочёт: не все зависимости учтены. Например, при изменении `outlib.h` надо перекомпилировать все файлы, который его включают. Как минимум, `fun.c`, потому что в нём используется константа `VERSION`. Но про это ничего не сказано:
 {{{#!highlight console
$ make

$ vim outlib.h
…hack-hack-hack…
$ make
make: Цель «all» не требует выполнения команд.
$ # :((
 }}}
  * Добавим частную зависимость `fun.o: outlib.h` (без рецепта) в `Makefile`, и задача решена:
  {{{#!highlight console
$ touch outlib.h
$ make
cc fun.c -c -o fun.o
cc const.o fun.o prog.o -o prog
./prog 2> README
}}}
 1. В действительности у `make` есть огромное количество правил по умолчанию, их можно посмотреть с помощью `make -p`
  * Для шаблонов компиляции используется слегка другой синтаксис (`.c.o:` вместо `%.o: %.c` (выдержка из `make -p`):
  {{{
.c.o:
 $(COMPILE.c) $(OUTPUT_OPTION) $<
  }}}
  * Действительная команда, которая приводит к компиляции, получается подстановкой переменных `COMPILE.c` и `OUTPUT_OPTION`, которые сами тоже суть результат подстановки… но если коротко, то всё смотрится неплохо:
  {{{#!highlight make
GENERATES = prog README
TRASH = *.o *~ o.*

all: README prog

prog: const.o fun.o prog.o

README: prog
        ./$< 2> $@

fun.o: outlib.h

clean:
        rm -f $(TRASH)

distclean: clean
        rm -rf $(GENERATES)
  
  }}}
  * Мы воспользовались не только шаблоном компиляции, но и шаблоном линковки (у цели `prog` нет рецепта, но `make` догадался, что `.o` надо сделать из `.c`, а `prog` — из этих `.o`):
  {{{#!highlight console
$ make distclean all
rm -f *.o *~ o.*
rm -rf prog README
cc -c -o prog.o prog.c
cc -c -o const.o const.c
cc -c -o fun.o fun.c
cc prog.o const.o fun.o -o prog
./prog 2> README
  }}}
 1. Использование шаблонов довольно гибко управляется: можно подменять флаги сборки на Си (`CFLAGS`) и других языках, флаги компоновки (`LDFLAGS`), название компилятора (`GCC`) и компоновщика, подключаемые библиотеки (`LDLIBS`) и т. п.
  * Добавим в `Makefile` строку `CFLAGS = -Wall` и пересоберём проект:
  {{{#!highlight console
$ make distclean all
rm -f *.o *~ o.*
rm -rf prog README
cc -Wall -c -o prog.o prog.c
cc -Wall -c -o const.o const.c
cc -Wall -c -o fun.o fun.c
cc prog.o const.o fun.o -o prog
./prog 2> README
}}}
  * Переменные `make` можно переопеределять из командной строки:
 {{{#!highlight console
$ touch fun.c
$ make CFLAGS=-Werror LDLIBS=-lm
cc -Werror -c -o fun.o fun.c
cc prog.o const.o fun.o -lm -o prog
./prog 2> README
}}}
 1. Статическая библиотека собирается с помощью ''архиватора'' (т. е. программы, которая складывает много файлов в один) `ar`. Так что собрать нашу программу можно и с помощью библиотеки:
 {{{#!highlight console
$ nm *.o
const.o:
0000000000000000 B Count
fun.o:
                 U Count
                 U fprintf
0000000000000000 T output
                 U printf
                 U stderr
0000000000000033 T usage
prog.o:
                 U Count
0000000000000000 T main
                 U output
                 U usage
$ ar -rcs libout.a const.o fun.o
$ ls -l libout.a
-rw-r--r-- 1 frbrgeorge frbrgeorge 3144 окт 5 15:52 libout.a
$ ar -tv libout.a
rw-r--r-- 500/500 976 Oct 5 15:52 2020 const.o
rw-r--r-- 500/500 1944 Oct 5 15:52 2020 fun.o
$ nm libout.a
const.o:
0000000000000000 B Count
fun.o:
                 U Count
                 U fprintf
0000000000000000 T output
                 U printf
                 U stderr
0000000000000033 T usage
$ cc -L. -lout prog.o -o prog
/usr/bin/ld.default: prog.o: in function `main':
prog.c:(.text+0x14): undefined reference to `Count'
/usr/bin/ld.default: prog.c:(.text+0x1a): undefined reference to `Count'
/usr/bin/ld.default: prog.c:(.text+0x29): undefined reference to `output'
/usr/bin/ld.default: prog.c:(.text+0x51): undefined reference to `output'
/usr/bin/ld.default: prog.c:(.text+0x67): undefined reference to `output'
/usr/bin/ld.default: prog.c:(.text+0x78): undefined reference to `usage'
collect2: error: ld returned 1 exit status
$ cc -L. prog.o -lout -o prog
frbrgeorge@linuxprac ~/LinuxDev2020/04_Multifile_class $ ./prog qwe ert
3: <INIT>
4: qwe
5: ert
6: <DONE>
$ nm prog | grep output
00000000004011a5 T output
 }}}
  * `.a` — это обычный архив, в который сложен объектники, однако `nm` (анализатор объектников) и `ld` (компоновщик) могут туда залезать!
   * Обратите внимание на то, какие символы в каком файле определены (`T`) или требуются, но не определены (`U`)
  * Ключ компоновщика `-lБИБЛИОТЕКА` заставляет искать файл вида `libБИБЛИОТЕКА.a` (или `.so`) в стандартных каталогах с библиотеками. А если каталог нестандартный (например, текущий)), его надо передать с ключом `-LПУТЬ`.
  * Порядок файлов в компоновке имеет значение: неопределённые символы компоновщик накапливает, а затем ищет в библиотеке, и если библиотека идёт в начала, она игнорируется
  * В файле `prog` присутствует функция `output`, которая попала туда вместе с `fun.o`
  * Таким образом у нас получилась '''статическая''' библиотека, которая целиком компонуется с кодом исходной программы
 1. Динамическая (`.so`) библиотека имеет совсем другой тип (в некоторых случаях её даже можно запустить), и она подгружется в память в момент запуска программы, а в самой программе не содержится. Собрать её можно, передав компоновщику ключ `-shared`:
  {{{#!highlight console
$ cc -shared fun.o const.o -o libout.so
/usr/bin/ld.default: fun.o: перемещение R_X86_64_PC32 для символ «Count» не может использоваться при создании общий объект; перекомпилируйте с параметром -fPIC
/usr/bin/ld.default: final link failed: раздел, непредставимый для вывода
collect2: error: ld returned 1 exit status
$ make distclean CFLAGS=-fPIC prog.o const.o fun.o
rm -f *.o *~ o.* *.a *.so
rm -rf prog README
cc -fPIC -c -o prog.o prog.c
cc -fPIC -c -o const.o const.c
cc -fPIC -c -o fun.o fun.c
$ cc -shared fun.o const.o -o libout.so
$ readelf --dyn-syms libout.so

Таблица символов «.dynsym» содержит 11 элементов:
   Чис: Знач Разм Тип Связ Vis Индекс имени
     0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
     1: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _ITM_deregisterTMCloneTab
     2: 0000000000000000 0 FUNC GLOBAL DEFAULT UND printf@GLIBC_2.2.5 (2)
     3: 0000000000000000 0 FUNC GLOBAL DEFAULT UND fprintf@GLIBC_2.2.5 (2)
     4: 0000000000000000 0 NOTYPE WEAK DEFAULT UND __gmon_start__
     5: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _ITM_registerTMCloneTable
     6: 0000000000000000 0 FUNC WEAK DEFAULT UND __cxa_finalize@GLIBC_2.2.5 (2)
     7: 0000000000000000 0 OBJECT GLOBAL DEFAULT UND stderr@GLIBC_2.2.5 (2)
     8: 0000000000001115 59 FUNC GLOBAL DEFAULT 12 output
     9: 000000000000402c 4 OBJECT GLOBAL DEFAULT 23 Count
    10: 0000000000001150 57 FUNC GLOBAL DEFAULT 12 usage
$ cc -L. prog.o -lout -o prog
$ ./prog
./prog: error while loading shared libraries: libout.so: cannot open shared object file: No such file or directory
 $ LD_DEBUG=libs ./prog
     19737: find library=libout.so [0]; searching
     19737: search cache=/etc/ld.so.cache
     19737: search path=/lib64/tls/haswell/x86_64:/lib64/tls/haswell:/lib64/tls/x86_64:/lib64/tls:/lib64/haswell/x86_64:/lib64/haswell:/lib64/x86_64:/lib64:/usr/lib64/tls/haswell/x86_64:/usr/lib64/tls/haswell:/usr/lib64/tls/x86_64:/usr/lib64/tls:/usr/lib64/haswell/x86_64:/usr/lib64/haswell:/usr/lib64/x86_64:/usr/lib64 (system search path)
     19737: trying file=/lib64/tls/haswell/x86_64/libout.so
     19737: trying file=/lib64/tls/haswell/libout.so
     19737: trying file=/lib64/tls/x86_64/libout.so
     19737: trying file=/lib64/tls/libout.so
     19737: trying file=/lib64/haswell/x86_64/libout.so
     19737: trying file=/lib64/haswell/libout.so
     19737: trying file=/lib64/x86_64/libout.so
     19737: trying file=/lib64/libout.so
     19737: trying file=/usr/lib64/tls/haswell/x86_64/libout.so
     19737: trying file=/usr/lib64/tls/haswell/libout.so
     19737: trying file=/usr/lib64/tls/x86_64/libout.so
     19737: trying file=/usr/lib64/tls/libout.so
     19737: trying file=/usr/lib64/haswell/x86_64/libout.so
     19737: trying file=/usr/lib64/haswell/libout.so
     19737: trying file=/usr/lib64/x86_64/libout.so
     19737: trying file=/usr/lib64/libout.so
     19737:
./prog: error while loading shared libraries: libout.so: cannot open shared object file: No such file or directory
$ LD_LIBRARY_PATH=`pwd` ./prog sdfsdfs sde
3: <INIT>
4: sdfsdfs
5: sde
6: <DONE>
}}}
  * Разделяемая библиотека получается не из всякого объектника: код должен быть скомпилирован так, чтобы его можно было загрузить в ''любое место'' памяти (статический код может доверять абсолютным адресам). Это касается всех `.o` файлов проекта
  * Разделяемая библиотека имеет сложную структуру [[WP:Executable_and_Linkable_Format]]
  * При запуске программы система ищет разделяемые библиотеки в стандартных каталогах (подробнее тут: [[man8:ld.so]]), и если место нестандартное, надо указывать `LD_LIBRARY_PATH`

Пример написания и использования Makefile

Начнём с таких вот файлов:

prog.c

fun.c

   1 #include "outlib.h"
   2 int main(int argc, char *argv[]) {
   3        Count = argc;
   4  output("<INIT>");
   5      output(argc>1 ? argv[1] : "<NOPE>");
   6   output("<DONE>");
   7      return 0;
   8 }

   1 #include <stdio.h>
   2 #include "outlib.h"
   3 void output(char *str) {
   4         printf("%d: %s\012", Count++, str);
   5 }

const.c

outlib.h

   1 int Count=0;

   1 void output(char *);
   2 extern int Count;

  1. Их можно собрать в один файл просто с помощью cc *.c -o prog

  2. Напишем простейший Makefile для этого:
       1 prog:   const.c fun.c prog.c
       2         cc const.c fun.c prog.c -o prog
       3 
       4 clean:
       5         rm -f prog a.out 
    
    • Помните о табах!
    • Заодно сделаем цель clean. Пробуем make, make clean.

  3. Так можно и скриптом было сделать. Обеспечим раздельную компиляцию и компоновку.
       1 prog:   const.o fun.o prog.o
       2         cc const.o fun.o prog.o -o prog
       3 
       4 fun.o:  fun.c 
       5         cc fun.c -c -o fun.o
       6 
       7 prog.o: prog.c 
       8         cc prog.c -c -o prog.o
       9 
      10 const.o:        const.c 
      11         cc const.c -c -o const.o
      12 
      13 clean:
      14         rm -f prog a.out *.o
      15  
    
    • Раздельная компиляция работает:
         1 $ make clean
         2 rm -f prog a.out *.o
         3 $ make prog
         4 cc const.c -c -o const.o
         5 cc fun.c -c -o fun.o
         6 cc prog.c -c -o prog.o
         7 cc const.o fun.o prog.o -o prog
         8 $ touch fun.c 
         9 $ make prog  
        10 cc fun.c -c -o fun.o
        11 cc const.o fun.o prog.o -o prog
        12 $ touch fun.o
        13 $ make prog  
        14 cc const.o fun.o prog.o -o prog
        15 
      
    • Кстати, вот альтернативная форма, в которой нет табуляций. Она малочитаема, не будем ей пользоваться:
         1 prog:   const.o fun.o prog.o ; cc const.o fun.o prog.o -o prog
         2 
         3 fun.o:  fun.c  ; cc fun.c -c -o fun.o
         4 
         5 prog.o: prog.c  ; cc prog.c -c -o prog.o
         6 
         7 const.o:        const.c  ; cc const.c -c -o const.o
         8 
         9 clean: ; rm -f prog a.out *. 
      
      • (а табуляции и пробелы становятся обычными разделителями)
    • И ещё одна. На этот раз полями рецепта определяются не табуляции, а тильды (а табуляции и пробелы становятся обычными разделителями):
         1 .RECIPEPREFIX = ~
         2 prog:   const.o fun.o prog.o
         3 ~       cc const.o fun.o prog.o -o prog
         4 
         5 fun.o:  fun.c
         6 ~       cc fun.c -c -o fun.o
         7 
         8 prog.o: prog.c
         9 ~       cc prog.c -c -o prog.o
        10 
        11 const.o:        const.c
        12 ~       cc const.c -c -o const.o
        13 
        14 clean:
        15 ~       rm -f prog a.out *.o
      
  4. Хорошо бы задать правило по умолчанию, как делать .o файлы из .c.

       1 %.o:    %.c
       2         cc $< -c -o $@
       3 
       4 prog:   const.o fun.o prog.o
       5         cc $^ -o $@
       6 
       7 clean:
       8         rm -f prog a.out *.o
    
    • Конструкции, начинающиеся с $ — подстановки значения некоторых переменных Make:

      • $@ означает цель (похожа на цель в тире)

      • $< означает первую из зависимостей

      • $^ означает список всех зависимостей

  5. В Make есть и нормальные переменные, только их подстановка должна заключаться в круглые скобки. Заодно усложним и нашу программу.

    prog.c

    fun.c

   1 #include <stdio.h>
   2 #include "outlib.h"
   3 int main(int argc, char *argv[]) {
   4    int i;
   5         if((Count = argc)>1) {
   6                 output("<INIT>");
   7              for(i=1; i<argc; i++)
   8                  output(argv[i]);
   9               output("<DONE>");
  10      }
  11      else
  12           usage(argv[0]);
  13        return 0;
  14 }

   1 #include <stdio.h>
   2 #include "outlib.h"
   3 void output(char *str) {
   4         printf("%d: %s\012", Count++, str);
   5 }
   6 
   7 void usage(char *prog) {
   8     fprintf(stderr, "%s v%.2f: Print all arguments\012\t"\
   9                 "Usage: %s arg1 [arg2 […]]\012", prog, VERSION, prog);
  10 }

const.c

outlib.h

   1 int Count=0;

   1 void output(char *);
   2 void usage(char *);
   3 extern int Count;
   4 #define VERSION 0.0
   5 

Makefile

  •    1 GENERATES = prog README
       2 TRASH = *.o *~ o.*
       3 
       4 %.o:    %.c
       5         cc $< -c -o $@
       6 
       7 all:    README prog
       8 
       9 prog:   const.o fun.o prog.o
      10  cc $^ -o $@
      11 
      12 README: prog
      13    ./$< 2> $@
      14 
      15 clean:
      16   rm -f $(TRASH)
      17 
      18 distclean:      clean
      19        rm -rf $(GENERATES)
    

  • Следуя традиции, мы разделили генераты на совсем ненужные (TRASH) и собственно целевые файлы проекта (GENERATES), которые всё равно стоит удалять, если в репозитории должны остаться только исходники

  • Традиционно же цель all: в начале файла перечисляет всё, что должно быть собрано (цель в начале считается целью по умолчанию, если make запущен без явно заданной цели)

  • Теперь сама наша программа участвует в генерации файла README! Поэтому сборка начнётся не с README, а с prog, несмотря на то, что в списке целей all раньше стоит README:

       1 $ make
       2 cc const.c -c -o const.o
       3 cc fun.c -c -o fun.o
       4 cc prog.c -c -o prog.o
       5 cc const.o fun.o prog.o -o prog
       6 ./prog 2> README
       7 
    
  1. В нашем Makefile есть недочёт: не все зависимости учтены. Например, при изменении outlib.h надо перекомпилировать все файлы, который его включают. Как минимум, fun.c, потому что в нём используется константа VERSION. Но про это ничего не сказано:

       1 $ make
       2 
       3 $ vim outlib.h 
       4 …hack-hack-hack…
       5 $ make          
       6 make: Цель «all» не требует выполнения команд.
       7 $ # :((  
       8 
    
    • Добавим частную зависимость fun.o:  outlib.h (без рецепта) в Makefile, и задача решена:

         1 $ touch outlib.h
         2 $ make          
         3 cc fun.c -c -o fun.o
         4 cc const.o fun.o prog.o -o prog
         5 ./prog 2> README
         6 
      
  2. В действительности у make есть огромное количество правил по умолчанию, их можно посмотреть с помощью make -p

    • Для шаблонов компиляции используется слегка другой синтаксис (.c.o: вместо %.o:  %.c (выдержка из make -p):

      .c.o:
              $(COMPILE.c) $(OUTPUT_OPTION) $<
    • Действительная команда, которая приводит к компиляции, получается подстановкой переменных COMPILE.c и OUTPUT_OPTION, которые сами тоже суть результат подстановки… но если коротко, то всё смотрится неплохо:

         1 GENERATES = prog README
         2 TRASH = *.o *~ o.*
         3 
         4 all:    README prog
         5 
         6 prog:   const.o fun.o prog.o
         7 
         8 README: prog
         9         ./$< 2> $@
        10 
        11 fun.o:  outlib.h
        12 
        13 clean:
        14         rm -f $(TRASH)
        15 
        16 distclean:      clean
        17         rm -rf $(GENERATES)
        18   
      
    • Мы воспользовались не только шаблоном компиляции, но и шаблоном линковки (у цели prog нет рецепта, но make догадался, что .o надо сделать из .c, а prog — из этих .o):

         1 $ make distclean all 
         2 rm -f *.o *~ o.*
         3 rm -rf prog README
         4 cc    -c -o prog.o prog.c
         5 cc    -c -o const.o const.c
         6 cc    -c -o fun.o fun.c
         7 cc   prog.o const.o fun.o   -o prog
         8 ./prog 2> README
         9 
      
  3. Использование шаблонов довольно гибко управляется: можно подменять флаги сборки на Си (CFLAGS) и других языках, флаги компоновки (LDFLAGS), название компилятора (GCC) и компоновщика, подключаемые библиотеки (LDLIBS) и т. п.

    • Добавим в Makefile строку CFLAGS = -Wall и пересоберём проект:

         1 $ make distclean all
         2 rm -f *.o *~ o.*
         3 rm -rf prog README
         4 cc -Wall   -c -o prog.o prog.c
         5 cc -Wall   -c -o const.o const.c
         6 cc -Wall   -c -o fun.o fun.c
         7 cc   prog.o const.o fun.o   -o prog
         8 ./prog 2> README
         9 
      
    • Переменные make можно переопеределять из командной строки:

       1 $ touch fun.c
       2 $ make CFLAGS=-Werror LDLIBS=-lm
       3 cc -Werror   -c -o fun.o fun.c
       4 cc   prog.o const.o fun.o  -lm -o prog
       5 ./prog 2> README
       6 
    
  4. Статическая библиотека собирается с помощью архиватора (т. е. программы, которая складывает много файлов в один) ar. Так что собрать нашу программу можно и с помощью библиотеки:

       1 $ nm *.o   
       2 const.o:
       3 0000000000000000 B Count
       4 fun.o:
       5                  U Count
       6                  U fprintf
       7 0000000000000000 T output
       8                  U printf
       9                  U stderr
      10 0000000000000033 T usage
      11 prog.o:
      12                  U Count
      13 0000000000000000 T main
      14                  U output
      15                  U usage 
      16 $ ar -rcs libout.a const.o fun.o
      17 $ ls -l libout.a 
      18 -rw-r--r-- 1 frbrgeorge frbrgeorge 3144 окт  5 15:52 libout.a
      19 $ ar -tv libout.a
      20 rw-r--r-- 500/500    976 Oct  5 15:52 2020 const.o
      21 rw-r--r-- 500/500   1944 Oct  5 15:52 2020 fun.o
      22 $ nm libout.a
      23 const.o:
      24 0000000000000000 B Count
      25 fun.o:
      26                  U Count
      27                  U fprintf
      28 0000000000000000 T output
      29                  U printf
      30                  U stderr
      31 0000000000000033 T usage
      32 $ cc -L. -lout prog.o -o prog
      33 /usr/bin/ld.default: prog.o: in function `main':
      34 prog.c:(.text+0x14): undefined reference to `Count'
      35 /usr/bin/ld.default: prog.c:(.text+0x1a): undefined reference to `Count'
      36 /usr/bin/ld.default: prog.c:(.text+0x29): undefined reference to `output'
      37 /usr/bin/ld.default: prog.c:(.text+0x51): undefined reference to `output'
      38 /usr/bin/ld.default: prog.c:(.text+0x67): undefined reference to `output'
      39 /usr/bin/ld.default: prog.c:(.text+0x78): undefined reference to `usage'
      40 collect2: error: ld returned 1 exit status
      41 $ cc -L. prog.o -lout -o prog
      42 frbrgeorge@linuxprac ~/LinuxDev2020/04_Multifile_class $ ./prog qwe ert
      43 3: <INIT>
      44 4: qwe
      45 5: ert
      46 6: <DONE>
      47 $ nm prog | grep output
      48 00000000004011a5 T output
      49 
    
    • .a — это обычный архив, в который сложен объектники, однако nm (анализатор объектников) и ld (компоновщик) могут туда залезать!

      • Обратите внимание на то, какие символы в каком файле определены (T) или требуются, но не определены (U)

    • Ключ компоновщика -lБИБЛИОТЕКА заставляет искать файл вида libБИБЛИОТЕКА.a (или .so) в стандартных каталогах с библиотеками. А если каталог нестандартный (например, текущий)), его надо передать с ключом -LПУТЬ.

    • Порядок файлов в компоновке имеет значение: неопределённые символы компоновщик накапливает, а затем ищет в библиотеке, и если библиотека идёт в начала, она игнорируется
    • В файле prog присутствует функция output, которая попала туда вместе с fun.o

    • Таким образом у нас получилась статическая библиотека, которая целиком компонуется с кодом исходной программы

  5. Динамическая (.so) библиотека имеет совсем другой тип (в некоторых случаях её даже можно запустить), и она подгружется в память в момент запуска программы, а в самой программе не содержится. Собрать её можно, передав компоновщику ключ -shared:

    •    1 $ cc -shared fun.o const.o -o libout.so
         2 /usr/bin/ld.default: fun.o: перемещение R_X86_64_PC32 для символ «Count» не может использоваться при создании общий объект; перекомпилируйте с параметром -fPIC
         3 /usr/bin/ld.default: final link failed: раздел, непредставимый для вывода
         4 collect2: error: ld returned 1 exit status
         5 $ make distclean CFLAGS=-fPIC prog.o const.o fun.o
         6 rm -f *.o *~ o.* *.a *.so
         7 rm -rf prog README
         8 cc -fPIC   -c -o prog.o prog.c
         9 cc -fPIC   -c -o const.o const.c
        10 cc -fPIC   -c -o fun.o fun.c
        11 $ cc -shared fun.o const.o -o libout.so
        12 $ readelf --dyn-syms libout.so
        13 
        14 Таблица символов «.dynsym» содержит 11 элементов:
        15    Чис:    Знач           Разм Тип     Связ   Vis      Индекс имени
        16      0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND 
        17      1: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND _ITM_deregisterTMCloneTab
        18      2: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND printf@GLIBC_2.2.5 (2)
        19      3: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND fprintf@GLIBC_2.2.5 (2)
        20      4: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND __gmon_start__
        21      5: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND _ITM_registerTMCloneTable
        22      6: 0000000000000000     0 FUNC    WEAK   DEFAULT  UND __cxa_finalize@GLIBC_2.2.5 (2)
        23      7: 0000000000000000     0 OBJECT  GLOBAL DEFAULT  UND stderr@GLIBC_2.2.5 (2)
        24      8: 0000000000001115    59 FUNC    GLOBAL DEFAULT   12 output
        25      9: 000000000000402c     4 OBJECT  GLOBAL DEFAULT   23 Count
        26     10: 0000000000001150    57 FUNC    GLOBAL DEFAULT   12 usage
        27 $ cc -L. prog.o -lout -o prog
        28 $ ./prog 
        29 ./prog: error while loading shared libraries: libout.so: cannot open shared object file: No such file or directory
        30  $ LD_DEBUG=libs ./prog 
        31      19737:     find library=libout.so [0]; searching
        32      19737:      search cache=/etc/ld.so.cache
        33      19737:      search path=/lib64/tls/haswell/x86_64:/lib64/tls/haswell:/lib64/tls/x86_64:/lib64/tls:/lib64/haswell/x86_64:/lib64/haswell:/lib64/x86_64:/lib64:/usr/lib64/tls/haswell/x86_64:/usr/lib64/tls/haswell:/usr/lib64/tls/x86_64:/usr/lib64/tls:/usr/lib64/haswell/x86_64:/usr/lib64/haswell:/usr/lib64/x86_64:/usr/lib64            (system search path)
        34      19737:       trying file=/lib64/tls/haswell/x86_64/libout.so
        35      19737:       trying file=/lib64/tls/haswell/libout.so
        36      19737:       trying file=/lib64/tls/x86_64/libout.so
        37      19737:       trying file=/lib64/tls/libout.so
        38      19737:       trying file=/lib64/haswell/x86_64/libout.so
        39      19737:       trying file=/lib64/haswell/libout.so
        40      19737:       trying file=/lib64/x86_64/libout.so
        41      19737:       trying file=/lib64/libout.so
        42      19737:       trying file=/usr/lib64/tls/haswell/x86_64/libout.so
        43      19737:       trying file=/usr/lib64/tls/haswell/libout.so
        44      19737:       trying file=/usr/lib64/tls/x86_64/libout.so
        45      19737:       trying file=/usr/lib64/tls/libout.so
        46      19737:       trying file=/usr/lib64/haswell/x86_64/libout.so
        47      19737:       trying file=/usr/lib64/haswell/libout.so
        48      19737:       trying file=/usr/lib64/x86_64/libout.so
        49      19737:       trying file=/usr/lib64/libout.so
        50      19737:
        51 ./prog: error while loading shared libraries: libout.so: cannot open shared object file: No such file or directory
        52 $ LD_LIBRARY_PATH=`pwd` ./prog sdfsdfs sde
        53 3: <INIT>
        54 4: sdfsdfs
        55 5: sde
        56 6: <DONE>
        57 
      
    • Разделяемая библиотека получается не из всякого объектника: код должен быть скомпилирован так, чтобы его можно было загрузить в любое место памяти (статический код может доверять абсолютным адресам). Это касается всех .o файлов проекта

    • Разделяемая библиотека имеет сложную структуру Executable_and_Linkable_Format

    • При запуске программы система ищет разделяемые библиотеки в стандартных каталогах (подробнее тут: ld.so), и если место нестандартное, надо указывать LD_LIBRARY_PATH

FrBrGeorge/MakefileExample (последним исправлял пользователь FrBrGeorge 2022-10-02 00:18:06)