Слабый символ
Слабый символ обозначает специально аннотированный символ во время связывания исполняемого и связываемого формата (ELF) объектных файлов . По умолчанию, без каких-либо аннотаций, символ в объектном файле является строгим . Во время связывания сильный символ может переопределить слабый символ с тем же именем. Напротив, при наличии двух сильных символов с одинаковым именем компоновщик разрешает символ в пользу первого найденного. Такое поведение позволяет исполняемому файлу переопределять стандартные библиотечные функции, такие как malloc (3). При связывании двоичного исполняемого файла слабо объявленный символ не нуждается в определении. Для сравнения: (по умолчанию) объявленный строгий символ без определения вызывает ошибку ссылки на неопределенный символ.
Слабые символы не упоминаются стандартами языков C или C++; как таковые, вставка их в код не очень переносима. Даже если две платформы поддерживают один и тот же или похожий синтаксис для обозначения символов как слабых, семантика может различаться в тонких моментах, например, теряют ли слабые символы во время динамического связывания во время выполнения свою семантику или нет. [1]
Синтаксис
[ редактировать ]Коллекция компиляторов GNU и компилятор Solaris Studio C используют один и тот же синтаксис для аннотирования символов как слабых, а именно специальный #pragma , #pragma weak
и, альтернативно, атрибут функции и переменной, __attribute__((weak))
. [2] [3] [4] [5] [6] [7]
Прагма
[ редактировать ]// function declaration
#pragma weak power2
int power2(int x);
Атрибут
[ редактировать ]// function declaration
int __attribute__((weak)) power2(int x);
// or
int power2(int x) __attribute__((weak));
// variable declaration;
extern int __attribute__((weak)) global_var;
Поддержка инструментов
[ редактировать ]Команда nm идентифицирует слабые символы в объектных файлах, библиотеках и исполняемых файлах. В Linux символ слабой функции отмечается буквой «W», если доступно слабое определение по умолчанию, и буквой «w», если его нет. Слабо определенные символы переменных отмечаются буквами «V» и «v». В Solaris «nm» печатает «WEAK» вместо «GLOB» для слабого символа.
Примеры
[ редактировать ]Следующие примеры работают в Linux и Solaris с GCC и Solaris Studio.
Статический пример
[ редактировать ]основной.с :
#include <stdio.h>
#include <stdlib.h>
#include "power_slow.h"
int main(int argc, char **argv)
{
fprintf(stderr, "power3() = %d\n", power3(atoi(argv[1])));
return 0;
}
power_slow.h:
#ifndef POWER2_SLOW_H
#define POWER2_SLOW_H
// alternative syntax
// #pragma weak power2
int
__attribute__((weak))
power2(int x)
// alternatively after symbol
// __attribute__((weak))
;
int power3(int x);
#endif
power_slow.c :
#include <stdio.h>
#include "power_slow.h"
int power2(int x)
{
fprintf(stderr, "slow power2()\n");
return x*x;
}
int power3(int x)
{
return power2(x)*x;
}
мощность.с :
#include <stdio.h>
int power2(int x)
{
fprintf(stderr, "fast power2()\n");
return x*x;
}
Команды сборки:
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 slow cc main.o power_slow.o power.o -o fast
Выход:
$ ./slow 3
slow power2()
power3() = 27
$ ./fast 3
fast power2()
power3() = 27
При удалении слабого атрибута и повторном выполнении команд сборки последняя завершается с ошибкой со следующим сообщением об ошибке (в Linux):
multiple definition of `power2'
Предпоследний все еще преуспевает, и ./slow
имеет тот же выход.
Если в любом из объектных файлов, связанных вместе, нет определения функции слабого символа в качестве реализации по умолчанию или в качестве другого определения функции слабого символа или определения функции сильного символа, то связывание будет выполнено успешно без каких-либо неопределенных ошибок символа для этого слабого символа. символ, но выполнение может привести к сбою во время выполнения.
Общий пример
[ редактировать ]Взяв файл main.c из предыдущего примера и добавив:
#ifndef NO_USER_HOOK
void user_hook(void)
{
fprintf(stderr, "main: user_hook()\n");
}
#endif
Замена power_slow.c на:
#include <stdio.h>
#include "power_slow.h"
void __attribute__((weak)) user_hook(void);
#ifdef ENABLE_DEF
void user_hook(void)
{
fprintf(stderr, "power_slow: user_hook()\n");
}
#endif
int power2(int x)
{
if (user_hook) // only needed ifndef ENABLE_DEF
user_hook();
return x*x;
}
int power3(int x)
{
return power2(x)*x;
}
Команды сборки:
cc -g -c -o main.o main.c
cc -g -fpic -c -o power_slow.po power_slow.c
cc -shared -fpic -o libpowerslow.so power_slow.po
cc main.o -L`pwd` -Wl,-R`pwd` -lpowerslow -o main
cc -g -DENABLE_DEF -fpic -c -o power_slow.po power_slow.c
cc -shared -fpic -o libpowerslow.so power_slow.po
cc main.o -L`pwd` -Wl,-R`pwd` -lpowerslow -o main2
cc -g -DNO_USER_HOOK -c -o main.o main.c
cc -g -fpic -c -o power_slow.po power_slow.c
cc -shared -fpic -o libpowerslow.so power_slow.po
cc main.o -L`pwd` -Wl,-R`pwd` -lpowerslow -o main3
cc -g -DNO_USER_HOOK -c -o main.o main.c
cc -g -DENABLE_DEF -fpic -c -o power_slow.po power_slow.c
cc -shared -fpic -o libpowerslow.so power_slow.po
cc main.o -L`pwd` -Wl,-R`pwd` -lpowerslow -o main4
Выход:
$ ./main 3
main: user_hook()
power3() = 27
$ ./main2 3
main: user_hook()
power3() = 27
$ ./main3 3
power3() = 27
$ ./main4 3
power_slow: user_hook()
power3() = 27
Удаление слабого атрибута и повторное выполнение команд сборки не приводит к ошибкам сборки и приводит к тому же результату (в Linux) для main
и main2
. Команды сборки для main3
приведет к появлению следующих предупреждений и сообщений об ошибках (в Linux):
warning: the address of ‘user_hook’ will always evaluate as ‘true’ libpowerslow.so: undefined reference to `user_hook'
Предупреждение выдается компилятором, поскольку он может статически определить, что в if (user_hook)
выражение user_hook
всегда оценивается как true, поскольку содержит запись таблицы переходов ELF. Сообщение об ошибке выдается компоновщиком. Сборка для main4
включает то же предупреждение, но без ошибки связи.
Варианты использования
[ редактировать ]Слабые символы можно использовать в качестве механизма для предоставления реализаций функций по умолчанию, которые могут быть заменены более специализированными (например, оптимизированными) во время компоновки. Затем реализация по умолчанию объявляется слабой, и для определенных целей в командную строку компоновщика добавляются объектные файлы со строго объявленными символами.
Если библиотека определяет символ как слабый, программа, связывающая эту библиотеку, может предоставить сильный символ, скажем, в целях настройки.
Другой вариант использования слабых символов — обеспечение обратной двоичной совместимости .
Ограничения
[ редактировать ]В системах-потомках UNIX System V во время выполнения программы динамический компоновщик разрешает определения слабых символов как сильные. Например, двоичный файл динамически связывается с библиотеками libfoo.so и libbar.so. libfoo определяет символ f
и объявляет его слабым. libbar также определяет f
и объявляет его сильным. В зависимости от порядка библиотеки в командной строке ссылки (т.е. -lfoo -lbar
) динамический компоновщик использует слабую версию f из libfoo.so, хотя во время выполнения доступна сильная версия. ГНУ ld
предоставляет переменную среды LD_DYNAMIC_WEAK
чтобы обеспечить слабую семантику для динамического компоновщика. [1] [8]
При использовании таких конструкций, как
#pragma weak func
void func();
void bar()
{
if (func)
func();
}
, в зависимости от компилятора и используемого уровня оптимизации, компилятор может интерпретировать условие как всегда истинное (поскольку func
может рассматриваться как неопределенный с точки зрения стандартов). [7] Альтернативой приведенной выше конструкции является использование системного API для проверки того, func
определен (например, dlsym с RTLD_DEFAULT
). Вышеуказанная проверка может также завершиться неудачно по другим причинам, например, если func содержит запись таблицы переходов elf. [9]
Использование слабых символов в статических библиотеках имеет другую семантику, чем в общих, т.е. в статической библиотеке поиск символа останавливается на первом символе – даже если он просто слабый и в архив библиотеки также включен объектный файл с сильным символом. В Linux опция компоновщика --whole-archive
меняет это поведение. [10]
Предполагается, что атрибут слабой функции будет использоваться в объявлениях функций. Использование его в определении функции может привести к неожиданным результатам, в зависимости от компилятора и уровня оптимизации. [11]
В Solaris слабые символы также используются внутри ядра. Общая часть ядра (называемая genunix
) определяет слабые функции, которые переопределяются в специфичной для платформы части ядра (называемой unix
), такие как процедуры виртуальной памяти. Компоновщик времени выполнения ядра устанавливает адреса этих функций, когда ядро объединяется в памяти во время загрузки. Однако это не работает для загружаемых модулей ядра — слабый символ в ядре не заменяется символом модуля ядра при загрузке модуля.
Связанные методы
[ редактировать ]Условные конструкции препроцессора C (CPP) также можно использовать для переключения между различными версиями символа. Отличие от слабых символов состоит в том, что слабые символы интерпретируются компоновщиком. CPP запускается во время компиляции каждой единицы трансляции перед компилятором C.
Процесс сборки (например, make) может быть реализован условным образом, так что в зависимости от цели создаются просто разные версии символа или используются и компонуются разные (специализированные) библиотеки.
См. также
[ редактировать ]Ссылки
[ редактировать ]- ^ Jump up to: а б Дреппер, Ульрих (7 июня 2000 г.). «слабая управляемость» .
- ^ «Руководство GCC, 6.58.9 Слабые прагмы» .
- ^ «Руководство GCC, 6.30 Объявление атрибутов функций» . ГНУ . Проверено 29 мая 2013 г.
- ^ «Руководство GCC, 6.36. Указание атрибутов переменных» .
- ^ «Oracle Solaris Studio 12.3: Руководство пользователя C, 2.11.27 слабая» .
- ^ «Oracle Solaris Studio 12.3: Руководство пользователя C, 2.9 Поддерживаемые атрибуты» .
- ^ Jump up to: а б «Руководство по компоновщику и библиотекам Oracle Solaris 11 Express 11/10, 2.11 Слабые символы» .
- ^ Дреппер, Ульрих (октябрь 2011 г.). «Как писать общие библиотеки (версия 4.1.2), 1.5.2 Перемещение символов, страница 6» (PDF) .
- ^ «Слабое связывание и общие библиотеки Linux» .
- ^ «Страница руководства GNU LD» .
- ^ Кишка, Ян (23 мая 2006 г.). «Re: чрезмерная оптимизация слабых атрибутов в версии 4.1» .