Слабый символ - Weak symbol

А слабый символ обозначает специально аннотированный символ при связывании Исполняемый и связываемый формат (ELF) объектные файлы. По умолчанию без аннотации символ в объектном файле сильный. Во время связывания сильный символ может заменять слабый одноименный символ. Напротив, два сильных символа с одинаковым именем приводят к ошибке ссылки во время ссылки. При компоновке бинарного исполняемого файла слабая объявлен символ не требует определения. Для сравнения (по умолчанию) объявленный сильный символ без определения вызывает ошибку неопределенной символьной ссылки.

Слабые символы не упоминаются стандартами языка C или C ++; как таковые, вставка их в код не очень переносима. Даже если две платформы поддерживают одинаковый или похожий синтаксис для маркировки символов как слабых, семантика может отличаться в тонких точках, например теряют ли слабые символы во время динамической компоновки во время выполнения свою семантику или нет.[1]

Синтаксис

В Коллекция компиляторов GNU и Студия Solaris Компилятор C использует тот же синтаксис для аннотирования символов как слабый, а именно специальный #pragma, #pragma weak, и, в качестве альтернативы, функция и атрибут переменной, __attribute __ ((слабый)).[2][3][4][5][6][7]

Прагма

// объявление функции#pragma weak power2int мощность2(int Икс);

Атрибут

// объявление функцииint __атрибут__((слабый)) мощность2(int Икс);  // или жеint мощность2(int Икс) __атрибут__((слабый));// объявление переменной;внешний int __атрибут__((слабый)) global_var;

Поддержка инструментов

В нм Команда определяет слабые символы в объектных файлах, библиотеках и исполняемых файлах. В Linux символ слабой функции помечается буквой «W», если слабое определение по умолчанию доступно, и «w», если его нет. Слабо определенные символы переменных отмечены буквами «V» и «v». В Solaris "nm" печатает "WEAK" вместо "GLOB" для слабого символа.

Примеры

Следующие примеры работают на Linux и Солярис с GCC и Solaris Studio.

Статический пример

main.c:

#включают <stdio.h>#включают <stdlib.h>#включают "power_slow.h"int главный(int argc, char **argv){  fprintf(stderr, "power3 () =% d", мощность3(атой(argv[1])));  возвращаться 0;}

power_slow.h:

#ifndef POWER2_SLOW_H#define POWER2_SLOW_H// альтернативный синтаксис// #pragma weak power2int  __атрибут__((слабый))    мощность2(int Икс)      // альтернативно после символа      // __attribute __ ((слабый))  ;int мощность3(int Икс);#endif

power_slow.c:

#включают <stdio.h>#включают "power_slow.h"int мощность2(int Икс){  fprintf(stderr, "медленная мощность2 ()");  возвращаться Икс*Икс;}int мощность3(int Икс){  возвращаться мощность2(Икс)*Икс;}

power.c:

#включают <stdio.h>int мощность2(int Икс){  fprintf(stderr, "быстрая мощность2 ()");  возвращаться Икс*Икс;}

Команды сборки:

cc -g -c -o main.o main.c cc -g -c -o power_slow.o power_slow.c cc -g -c -o power.o power.c cc main.o power_slow.o -o медленныйcc main.o power_slow.o power.o -o быстро

Выход:

$ ./медленный 3медленная мощность2мощность3 () = 27$ ./быстрый 3быстрая мощность2мощность3 () = 27

При удалении слабого атрибута и повторном выполнении команд сборки последняя не работает со следующим сообщением об ошибке (в Linux):

множественное определение `power2 '

Второй-последний по-прежнему успешен, и ./медленный имеет такой же вывод.

Общий пример

Взяв main.c из предыдущего примера и добавив:

#ifndef NO_USER_HOOKпустота user_hook(пустота){  fprintf(stderr, "main: user_hook ()"");}#endif

Замена power_slow.c на:

#включают <stdio.h>#включают "power_slow.h"пустота __атрибут__((слабый)) user_hook(пустота);#ifdef ENABLE_DEFпустота user_hook(пустота){  fprintf(stderr, "power_slow: user_hook ()");}#endifint мощность2(int Икс){  если (user_hook) // нужен только ifndef ENABLE_DEF    user_hook();  возвращаться Икс*Икс;}int мощность3(int Икс){  возвращаться мощность2(Икс)*Икс;}

Команды сборки:

cc -g -c -o main.o main.ccc -g -fpic -c -o power_slow.po power_slow.ccc -shared -fpic -o libpowerslow.so power_slow.pocc main.o power_slow.po -L`pwd` -Wl, -R`pwd` -lpowerslow -o maincc -g -DENABLE_DEF -fpic -c -o power_slow.po power_slow.ccc -shared -fpic -o libpowerslow.so power_slow.pocc main.o power_slow.po -L`pwd` -Wl, -R`pwd` -lpowerslow -o main2cc -g -DNO_USER_HOOK -c -o main.o main.ccc -g -fpic -c -o power_slow.po power_slow.ccc -shared -fpic -o libpowerslow.so power_slow.pocc main.o power_slow. po -L`pwd` -Wl, -R`pwd` -lpowerslow -o main3cc -g -DNO_USER_HOOK -c -o main.o main.ccc -g -DENABLE_DEF -fpic -c -o power_slow.po power_slow.ccc -shared -fpic -o libpowerslow.so power_slow.pocc main.o power_slow.po -L`pwd` -Wl, -R`pwd` -lpowerslow -o main4

Выход:

$ ./главный 3основная: user_hook ()мощность3 () = 27$ ./main2 3основная: user_hook ()мощность3 () = 27$ ./main3 3мощность3 () = 27$ ./main4 3power_slow: user_hook ()мощность3 () = 27

Удаление атрибута weak и повторное выполнение команд сборки не приводит к ошибкам сборки и приводит к тому же результату (в Linux) для главный и main2. Команды сборки для main3 приводят к следующим предупреждениям и сообщениям об ошибках (в Linux):

предупреждение: адрес «user_hook» всегда будет оцениваться как «true». libpowerslow.so: undefined ссылка на «user_hook»

Предупреждение выдается компилятором, потому что он может статически определить, что в если (user_hook) выражение user_hook всегда оценивается как истина, поскольку содержит запись таблицы переходов ELF. Сообщение об ошибке выдает компоновщик. Сборка для main4 включает то же предупреждение, но без ошибки ссылки.

Сценарии использования

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

Если библиотека определяет символ как слабый, программа, которая связывает эту библиотеку, может предоставить сильный символ, скажем, для целей настройки.

Другой вариант использования слабых символов - поддержание двоичных Обратная совместимость.

Ограничения

На Система UNIX V в дочерних системах, во время выполнения программы динамический компоновщик решает слабые определения символов как сильные. Например, двоичный файл динамически связан с библиотеками libfoo.so и libbar.so. libfoo определяет символ ж и объявляет его слабым. libbar также определяет ж и объявляет его сильным. В зависимости от порядка библиотеки в командной строке ссылки (т.е. -lfoo -lbar) динамический компоновщик использует слабую f из libfoo.so, хотя сильная версия доступна во время выполнения. GNU ld предоставляет переменную среды LD_DYNAMIC_WEAK для обеспечения слабой семантики динамического компоновщика.[1][8]

При использовании таких конструкций, как

#pragma weak funcпустота func();пустота бар(){  если (func)    func();}

, в зависимости от компилятора и используемого уровня оптимизации, компилятор может интерпретировать условное выражение как всегда истинное (поскольку func может рассматриваться как неопределенное с точки зрения стандартов).[7] Альтернативой вышеуказанной конструкции является использование системного API, чтобы проверить, func определено (например, dlsym с RTLD_DEFAULT). Вышеупомянутая проверка также может завершиться неудачно по другим причинам, например: когда func содержит запись таблицы переходов elf.[9]

Использование слабых символов в статических библиотеках имеет другую семантику, чем в общих, то есть в статической библиотеке поиск символа останавливается на первом символе - даже если он просто слабый и объектный файл с сильным символом также включен в архив библиотеки. В Linux опция компоновщика - весь архив меняет это поведение.[10]

Атрибут weak функции должен использоваться в объявлениях функций. Использование его в определении функции может привести к неожиданным результатам в зависимости от компилятора и уровня оптимизации.[11]

В Solaris слабые символы также используются в ядре. Общая часть ядра (называемая Genunix) определяет слабые функции, которые переопределяются в конкретной части ядра платформы (называемой unix), например, подпрограммы виртуальной памяти. Компоновщик времени выполнения ядра устанавливает адреса этих функций, когда ядро ​​объединяется в памяти во время загрузки. Однако это не работает для загружаемых модулей ядра - слабый символ в ядре не заменяется символом модуля ядра при загрузке модуля.

Связанные методы

Препроцессор C (CPP) условные конструкции также могут использоваться для переключения между различными версиями символа. Отличие от слабых символов в том, что слабые символы интерпретируются компоновщиком. CPP запускается во время компиляции каждой единицы трансляции перед компилятором C.

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

Смотрите также

Рекомендации

  1. ^ а б Дреппер, Ульрих (2000-06-07). "слабая управляемость".
  2. ^ "Руководство GCC, 6.58.9 Слабые прагмы".
  3. ^ "Руководство GCC, 6.30 Объявление атрибутов функций". GNU. Получено 2013-05-29.
  4. ^ "Руководство GCC, 6.36 Определение атрибутов переменных".
  5. ^ "Oracle Solaris Studio 12.3: Руководство пользователя C, 2.11.27 слабая".
  6. ^ "Oracle Solaris Studio 12.3: Руководство пользователя C, 2.9 Поддерживаемые атрибуты".
  7. ^ а б "Руководство по компоновщику и библиотекам Oracle Solaris 11 Express 11/10, 2.11 Слабые символы".
  8. ^ Дреппер, Ульрих (Октябрь 2011 г.). «Как писать общие библиотеки (версия 4.1.2), 1.5.2 Перемещение символов, стр. 6» (PDF).
  9. ^ «Слабое связывание и общие библиотеки Linux».
  10. ^ "Справочная страница GNU LD".
  11. ^ Кишка, Ян (23 мая 2006 г.). "Re: чрезмерная оптимизация слабых атрибутов в 4.1".