Содержание


Управление видимостью символических имен для общих библиотек: часть 2 – работа с компилятором IBM XL C/C++ V13

Comments

Компилятор IBM XL C/C++ V13: поддержка атрибутов видимости

Из предыдущей статьи вы узнали о различных методах управления видимостью символических имен. Основными из них являются использование атрибутов видимости компилятора GNU, файлов экспорта IBM AIX® и сценариев версий GNU. Компилятор GNU позволяет управлять видимостью на уровне исходного кода, а сценарий версий компоновщика больше подходит для управления видимостью на этапах окончательной сборки приложений.

Компилятор XL C/C++ для AIX и Linux® версии V13 поддерживает расширение GNU, позволяющее работать с атрибутами видимости. Кроме того, для этой цели можно использовать новую директиву pragma и новые глобальные опции компилятора. В этой статье мы рассмотрим параметры атрибутов видимости и расскажем о том, как определяется их итоговое значение. Также мы рассмотрим различия между подходами к управлению видимостью символов в AIX и Linux и затронем вопросы совместимости в AIX.

Спецификатор атрибута видимости

Так же как и в расширении GNU, видимость символов для компилятора XL C/C++ можно определять при помощи спецификатора атрибута, используя синтаксис GNU. Пример объявления функции и переменной представлен в листинге 1.

Листинг 1. Спецификатор атрибута видимости для переменной и функции
int __attribute__((visibility("hidden"))) m;  // переменная m определена как "hidden" (скрытая)
void __attribute__((visibility("protected"))) fun(); // функция fun() определена как "protected" (защищенная)

Атрибут видимости может принимать значения default, hidden, protected или internal. Это соответствует синтаксису GNU, который мы рассматривали в предыдущей статье. Однако существует тонкое различие между значениями, устанавливаемыми по умолчанию в AIX и Linux, о котором мы расскажем далее.

На первый взгляд атрибут видимости кажется очень простым. Однако на практике может возникнуть множество вопросов. Чаще всего программистов интересует, что произойдет, если для символа задать несколько атрибутов видимости с различными значениями. Наличие определений символов в нескольких разных заголовочных файлах не является редкостью, и обработка таких определений может доставить много неприятностей разработчику. С точки зрения компилятора причина этой проблемы заключается в человеческом факторе, а ответственность за ее решение ложится на плечи программиста. К счастью, компилятор придерживается определенных правил и предлагает ряд возможностей, помогающих избегать таких проблем. Компилятор XL C/C++ принимает тот атрибут видимости, который встречается ему первым, а при обнаружении остальных атрибутов выдает предупреждение, содержащее отклоненное значение атрибута. Компилятор игнорирует все атрибуты, которые встречаются ему позже. Пример представлен в листинге 2.

Листинг 2. Различные атрибуты видимости для одного символа
extern int __attribute__((visibility("hidden"))) m;  // переменная "m" является скрытой ("hidden")
int __attribute__((visibility("protected"))) m;       // компилятор выдаст предупреждение: атрибут "protected" игнорируется

В этом примере для переменной m первым установлен атрибут со значением hidden. Это означает, что символическое имя этой переменной не будет видимым во время динамической компоновки. Далее для этой же переменной снова установлен атрибут видимости со значением protected, т. е. символическое имя должно стать глобально видимым. В этой ситуации при обработке исходного кода компилятор выдаст предупреждение и проигнорирует атрибут protected.

Другой вопрос, касающийся видимости, связан с использованием типов. Так как в C++ разработчики могут создавать типы самостоятельно, то возникает вопрос: можно ли устанавливать для них атрибуты видимости. Это довольно интересный вопрос, который никогда не возникал в языке C, поскольку все встроенные типы не должны взаимодействовать с атрибутами видимости. Тем не менее в C++ это правило уже не выполняется. Это видно из следующего листинга.

Листинг 3. Атрибуты видимости, устанавливаемые для класса
class __attribute__((visibility("hidden"))) T  // класс T определен как "hidden"
{
public:
      static int gVal;  // T::gVal определен как "hidden"
      void Dosth();    // T::Dosth()определен как "hidden"
};  

T t1;  // t1 is "hidden".
T __attribute__((visibility("internal"))) t2; //t2 определен как "internal"

В листинге 3 мы установили для класса T атрибут видимости hidden. В C++ атрибуты видимости для классов можно устанавливать точно так же, как и атрибуты видимости для функций и объектов. Однако, несмотря на аналогичный синтаксис, обработка атрибутов происходит иначе. В данном случае можно предполагать, что тип T является скрытым (hidden). Далее создаются два объекта типа T: t1 и t2. Объект t1 определен без использования спецификатора атрибута, поэтому параметры видимости объекта t1 наследуются от его типа. Для объекта t2 установлен атрибут видимости internal. Это значение имеет приоритет над значением, установленным для типа T, поэтому в результате атрибут видимости для t2 будет иметь значение internal. В этом заключается основное правило. При этом атрибуты видимости таких элементов класса, как функции-члены и статические элементы, не изменяются при переопределении атрибутов видимости другого объекта. Это означает, что объекты T::gVal и T::Dosth() из листинга 3 останутся скрытыми (hidden) в соответствии с атрибутом видимости, установленным для класса T. Кроме того, предполагается, что объекты одного типа (т. е. объекты t1 и t2) будут использовать одну и ту же таблицу функций независимо от установленных для них атрибутов видимости. Если же разработчик хочет изменить значение атрибута видимости для элемента класса (например, для функции-члена), то это значение должно быть задано явно. В листинге 4 показано, как установить для объекта T::Dosth() новый атрибут видимости protected.

Листинг 4. Изменение атрибута видимости для элемента класса
class __attribute__((visibility("hidden"))) T  // класс T определен как "hidden"
{
public:
      static int gVal;  // T::gVal определен как "hidden"
      void __attribute__((visibility("protected"))) Dosth();    // T::Dosth() теперь определен как "protected"
};  

T t1;  // t1 определен как "hidden"
T __attribute__((visibility("internal"))) t2; // t2 определен как "internal"

Атрибуты видимости можно также применять к объединениям, перечислениям, пространствам имен и шаблонам. Правила видимости, действующие в области пространства имен, будут такими же, что и правила для класса. Если атрибут видимости установлен для пространства имен, то он применяется ко всем символам в его области действия. Однако атрибут действует лишь в рамках области. Пример представлен в листинге 5.

Листинг 5. Атрибуты видимости для пространств имен
namespace std __attribute__((visibility("hidden"))) {
   void foo1() {};  // foo1()определен как "hidden" 
}

namespace std {
   void foo2() {}   // foo2()определен как "default"/"unspecified"
}

Как и предполагалось, в этом примере символ foo1() является скрытым (hidden). Однако символ foo2() уже не является таковым. Это означает, что атрибут видимости пространства имен std, определенного в одном файле, не будет действовать на пространство имен с тем же именем, определенное в другом файле. В результате такой подход предотвращает возможное искажение параметров видимости в различных фрагментах кода, которые могут содержать пространства имен с одинаковыми названиями.

Определение параметров видимости шаблонов представляет не меньший интерес. Разработчиков всегда интересует, какой будет результирующая видимость, если атрибуты видимости установлены как для шаблона, так и для типа. В двух словах, если исходное определение шаблона содержит атрибут видимости, то значение атрибута неявно распространяется на все экземпляры на основе этого шаблона. В противном случае атрибут видимости экземпляра на основе шаблона по умолчанию будет тем же, что и атрибут аргумента его шаблона. Пример представлен в листинге 6.

Листинг 6. Атрибуты видимости для пространств имен
class __attribute__((visibility("internal"))) A {};
template <typename T> void foo1() {};
template <typename T> void __attribute__((visibility("hidden"))) foo2() {};

foo1<A>();  // Видимость "foo1<A>()" определена как "internal", это значение унаследовано от его аргумента "A"
foo2<int>();  // Видимость "foo2<int>()" определена как "hidden", это значение унаследовано из шаблона
foo2<A>();   // Видимость "foo2<A>()" определена как "hidden"

В этом примере определены тип A с параметром internal, функция общего шаблона foo1, а также функция шаблона foo2 с атрибутом hidden. Поскольку для типа A был указан атрибут видимости, то функция foo1 <A> первого экземпляра унаследует его от аргумента шаблона. Функция foo2 <int> наследует атрибут видимости от шаблона. Это же касается и функции foo2 <A>. Экземпляр игнорирует атрибут видимости, установленный для типа A. В результате приоритетным является атрибут видимости, установленный для шаблона.

Директивы pragma

Помимо использования атрибутов видимости, существует способ управлять видимостью символов на уровне исходного кода. Компилятор XL C/C++ позволяет реализовать это при помощи директивы pragma visibility. Директива pragma имеет определенные преимущества по сравнению с атрибутами видимости, поскольку позволяет задавать параметры сразу для нескольких символов, а не указывать их отдельно для каждого символа. Пример синтаксиса директивы pragma visibility представлен в листинге 7.

Листинг 7. Синтаксис директивы pragma visibility
#pragma GCC visibility push(hidden)
#pragma GCC visibility pop

Директива включает в себя только две стековые операции. Вершина стека содержит текущий атрибут видимости, который может применяться ко всем переменным, определяемым после данной точки кода. Например, для текущего контекста в Linux по умолчанию определено значение default. Если разработчик использует директиву push (запись в стек) с параметром hidden, то все символы, объявленные после этой директивы, будут иметь атрибут hidden. Это значение будет действовать до тех пор, пока в коде не встретится следующая директива pragma visibility. Пример представлен в листинге 8.

Листинг 8. Атрибуты видимости для пространств имен
#pragma GCC visibility push(hidden)
int m; // атрибут видимости "hidden"

#pragma GCC visibility push(protected)
void foo() // атрибут видимости "protected"

#pragma GCC visibility pop
class A{}; // атрибут видимости "hidden" (атрибут "protected" был вытолкнут из стека)

#pragma GCC visibility pop
int n; // атрибут видимости "default"

#pragma GCC visibility pop  // Компилятор выдаст ошибку, поскольку невозможно вытолкнуть из стека какой-либо атрибут

Обратите внимание: если стек видимости пуст, то директива pop приведет к ошибке во время компиляции.

Опции компилятора

Иногда возникает необходимость задавать атрибуты видимости при помощи опций компилятора, которые позволяют изменять видимость символов, содержащихся в компилируемом файле. Компилятор XL C/C++ позволяет использовать следующие опции.

xlc -qvisibility=default | protected | hidden | internal | unspecified

Следует заметить, что опция unspecified доступна только в операционной системе AIX. В Linux опция -qvisibility эквивалентна опции –qvisibility=default. В AIX опция -qvisibility эквивалентна опции –qvisibility=unspecified.

Правила определения параметров видимости

Поскольку параметры видимости можно устанавливать разными способами (спецификаторы атрибута, pragma-директива visibility и опции компилятора), важно знать правила, которые использует компилятор для разрешения конфликтов.

Фактически компилятор устанавливает параметры видимости символов, выполняя следующую последовательность проверок:

  • Проверяется спецификатор атрибута видимости символа (при его наличии)
  • Проверяются параметры видимости исходного шаблона (для экземпляра, созданного на основе шаблона)
  • Если символ заключен внутри одного из таких объектов как структура, класс, объединение, перечисление или pragma, то проверяются параметры видимости этого объекта
  • В остальных случаях проверяются параметры видимости типа символа

Это жесткое правило. Наивысший приоритет имеет спецификатор атрибута видимости, если он указан при объявлении символа. Если же атрибут видимости не указан, а код наследуется из шаблона, то устанавливаются параметры видимости исходного шаблона (такой пример был представлен в листинге 6).

Если первые два условия не выполняются, то проверяются параметры видимости объекта, содержащего объявление символа. Например, это может быть область пространства имен глобальной переменной или область класса для его члена. Пример представлен в листинге 9.

Листинг 9. Атрибуты видимости символа, содержащегося внутри объекта
#pragma GCC visibility push(hidden)
class A   // определен как "hidden" на основе директивы pragma
{
  static int m; // определен как "hidden" на основе определения класса
   void __attribute__((visibility("protected")))  foo()  // определен как "protected"
};

Класс A из листинга 9 является скрытым (hidden), поскольку pragma-директива visibility устанавливает атрибут hidden для всего последующего кода. Поскольку члены класса A являются его содержимым, их атрибут видимости также имеет значение hidden. Тем не менее символ foo является защищенным (protected), поскольку для него явно указан спецификатор.

В последнюю очередь значение параметра видимости определяется на основе типа символа. Пример с использованием типа класса был представлен в листинге 4.

Если при объявлении функции для нее не указан спецификатор атрибута видимости, то итоговое значение атрибута определяется следующими четырьмя элементами.

  • Параметры функции
  • Тип возвращаемого значения
  • Аргументы шаблона функции (type или non-type)
  • Параметры видимости, передаваемые компилятором

Сравнивая эти четыре элемента, несложно определить, какой из них будет наименее приоритетным. Наибольшую доступность функции обеспечивает параметр со значением default, за ним следует параметр protected, далее hidden и, наконец, наименьшую доступность (максимальную защищенность) обеспечивает параметр со значением internal. Итоговый атрибут видимости функции будет таким же, как атрибут элемента, обеспечивающего наименьшую доступность. Пример представлен в листинге 10.

Листинг 10. Итоговый атрибут видимости для функции
class __attribute__((visibility("internal"))) A {};
A *foo (int a) {}; // даже не смотря на опцию -qvisibility=hidden, функция foo определена как "internal"

Из этого примера видно, что функция foo возвращает ссылочный тип на класс A, для которого задан атрибут видимости internal. Кроме того, для глобального контекста задан атрибут видимости hidden. Итоговым значением атрибута видимости для функции foo является internal, поскольку оно определяет наименьшую доступность (видимость).

Если к функции или переменной не применяется ни одно из вышеперечисленных правил, то для нее устанавливается атрибут видимости по умолчанию, который будет рассмотрен в следующем разделе.

Атрибуты видимости и обратная совместимость в AIX

Из предыдущей статьи вы узнали, что в операционной системе AIX по умолчанию символы не видимы до тех пор, пока они не будут экспортированы на этапе компоновки – либо вручную, либо с помощью команды CreateExportList. Однако в операционной системе Linux символы по умолчанию видимы (т. е. имеют параметр видимости default). Это приводит к несоответствию между атрибутами видимости ОС AIX и компилятора GNU. Для обеспечения обратной совместимости в AIX компилятор XL C/C++ не делает все символы экспортируемыми, как это происходит в Linux. Если для символа не указан атрибут видимости, то символ считается неопределенным (unspecific), что удовлетворяет условиям старой реализации AIX. Компилятор, компоновщик и соответствующие утилиты AIX обрабатывают такие символы так же, как и раньше. Однако символы с атрибутом видимости unspecific не являются внутренними или невидимыми – это специальное значение предназначено лишь для обеспечения совместимости.

Следовательно, как уже говорилось в начале статьи, если атрибут видимости символа не указан явно, то в Linux его значением будет являться default, а в AIX – unspecified.

Конфликты параметров видимости на этапе компоновки

Конфликты параметров видимости на этапе компоновки в AIX

Из предыдущей статьи вы узнали о том, что при помощи файла экспорта в AIX можно устанавливать признак экспортируемости символов, содержащихся в библиотеках и исполняемых файлах. Поскольку теперь компилятор IBM XL C/C++ V13 позволяет использовать атрибуты видимости, необходимо знать, как эта функциональность работает в связке с файлом экспорта.

Рассмотрим обычное определение символа в файле a.cpp, как показано в листинге 11.

Листинг 11. Файл a.cpp
int sym __attribute__((visibility("default")));

В исходном коде атрибут видимости переменной sym имеет значение default (переменная является экспортируемой). Если скомпилировать файл a.cpp в динамическую общую библиотеку с именем liba.so, то можно предположить, что символ sym в этой библиотеке будет являться экспортируемым. Однако если при компоновке библиотеки liba.so используется файл экспорта (листинг 12) и опция компоновщика -bE:exportlist (листинг 13), возникает вопрос: будет ли символ sym в общей библиотеке глобально видимым (экспортируемым) или нет?

Листинг 12. Файл exportlist
sym hidden
Листинг 13. Команда компиляции
xlC -G -o liba.so a.c -qpic -bE:exportlist

Ответ на этот вопрос связан с алгоритмом работы компилятора /компоновщика. Являясь их разработчиками, авторы статьи могут сказать следующее:

  • В компиляторе XL C/C++ V13 для AIX будет присутствовать возможность использования атрибутов видимости. Наиболее полезной она окажется при разработке нового и при адаптации старого кода (поддерживающего атрибуты видимости GNU).
  • Параметры видимости в таблицах экспорта предназначены для использования на этапе компоновки, которая обычно выполняется после компиляции.

Таким образом, лучше всего указывать параметры видимости для компоновщика в файлах экспорта, поскольку они имеют приоритет. Фактически компоновщик может обрабатывать различные комбинации ключевых слов из различных файлов экспорта и модулей (например, объектных файлов), содержащих атрибуты видимости символов. Такая обработка называется разрешением конфликтов при определении параметров видимости. В таблице 1 показано, как работает компоновщик.

Таблица 1. Разрешение конфликтов при определении параметров видимости компоновщиком AIX
Ключевое слово в файле экспортаАтрибут видимости символа в объектном файле
ImportedExportedProtectedHiddenInternalНет (без ключевого слова)
Export EXP EXP EXP EXP error EXP
Protected PROT PROT PROT PROT error PROT
Hidden error No No No error No
Internal error No No No No No
Нет (без ключевого слова) No EXP PROT No No No
  • EXP: символ является экспортируемым
  • PROT: символ является экспортируемым и защищенным
  • No: символ не является экспортируемым
  • Error: символ не является экспортируемым, компоновщик завершает работу и сообщает об ошибке

В таблице 1 в заголовках столбцов указаны атрибуты видимости, определяемые в исходном коде модуля, а в названиях строк – атрибуты, определенные в файле экспорта. Обратите внимание на то, что в некоторых версиях AIX вместо ключевого слова exported может использоваться ключевое слово export. В таблице 1 приведены результирующие параметры, устанавливаемые компоновщиком. Они позволяют определить следующее:

  • Будет ли символ видимым в конечном модуле (например, это может быть окончательная версия библиотеки)
  • Может ли экспортируемый символ быть вытеснен или нет

Из таблицы 1 видно, что если в файле экспорта символ определен как exported или protected, то компоновщик сделает его глобально видимым (в последнем случае символ не сможет быть вытеснен). Аналогично, если в файле экспорта символ определен как hidden или internal, в итоге он будет невидимым. Если для символа не определено значение в файле экспорта и не используется опция компоновщика -bexpall/-bxpfull, то приоритет будет иметь атрибут видимости, определенный в объектном файле. В нестандартных ситуациях (например, если в объектном файле для символа задано значение internal, а в файле экспорта – значение exported) компоновщик выдает ошибку, которая указывает на то, что значение в файле экспорта может противоречить замыслу разработчика.

В примере с файлом a.cpp переменная sym будет в итоге определена как глобально невидимая.

Иногда для экспорта всех символов может использоваться опция компоновщика -bexpall/-bexpfull. В этом случае до тех пор, пока для символа не будет указан атрибут unspecific, приоритет будут иметь атрибуты, установленные в объектном файле.

Итак, теперь вы знаете, что файл экспорта в общем случае может переопределять параметры видимости на этапе компоновки. Тем не менее мы не рекомендуем использовать атрибуты, определяемые в исходном коде, совместно с файлами экспорта, поскольку это может привести к путанице при написании кода. Несмотря на это, иногда данная возможность может оказаться очень полезной, позволяя обеспечить бинарную совместимость разных версий общих библиотек.

Конфликты параметров видимости на этапе компоновки в Linux

Конфликты параметров видимости на этапе компоновке случаются также и в Linux. Однако с учетом различий между платформами работа компоновщика GNU слегка отличается от работы компоновщика AIX.

В листинге 14 представлен пример для платформы Linux, в котором переменная определена в файле c.c.

Листинг 14. Файл c.c
int bar __attribute__((visibility("protected")));

В файле экспорта exmap переменная bar объявлена как глобально видимая. Компоновщик GNU также называет этот файл сценарием версий (version script). Обратите внимание на то, что в файле экспорта разработчик может определить видимость символа как global (глобальная) или local (локальная).

Листинг 15. Файл exmap
{
  global:
  bar;
  local:
  *;
};

В листинге 16 представлена команда для создания динамического общего объекта (DSO) libc.so.

Листинг 16. Команда компиляции
xlC -qmkshrobj c.c -o libc.so -Wl,--version-script=exmap

Наконец, проверим раздел динамической таблицы символов (DYNSYM) файла libc.so (обычно этот раздел называется .dynsym).

Листинг 17. Выгрузка из таблицы символов
5 0: 0001100c  4 OBJECT  GLOBAL PROTECTED  21 bar

Если в Linux символ является экспортируемым, то он так же попадает в раздел DYNSYM таблицы DSO-объекта. В таблице 2 показано, как определяется итоговый параметр видимости при совместном использовании файла экспорта и атрибута видимости. Таблица 2 показывает, будет ли символ экспортирован и занесен в раздел DYNSYM или нет.

Таблица 2. Разрешение конфликтов при определении параметров видимости компоновщиком Linux linker
Ключевое слово в файле экспортаАтрибут видимости символа в объектном файле
DefaultProtectedHiddenInternal
Global Да Да Нет Нет
Local Нет Нет Нет Нет

Как и в таблице 1, здесь в заголовках столбцов указаны атрибуты видимости, определяемые в исходном коде модуля. По сути эта таблица аналогична таблице для AIX, поскольку приоритет имеют ключевые слова. Однако в Linux невозможно экспортировать скрытые символы при помощи файла экспорта из-за различий между платформами.

Другое различие между AIX и Linux заключается в том, что в Linux информация об атрибутах видимости символов содержится в разделах DYNSYM и SYMTAB. В AIX такая информация хранится только в таблице символов и отсутствует в таблице символов загрузчика. Таблица символов загрузчика содержит лишь итоговое значение параметров видимости. Если символ в ней присутствует, то он является экспортируемым, в противном случае символ является невидимым. В этом заключается еще одно различие между платформами AIX и Linux.

Утилита CreateExportList

В предыдущей статье мы упоминали об утилите CreateExportList, которая помогает создавать файлы экспорта автоматически. При этом программисты могут управлять взаимодействием этой утилиты с атрибутами видимости. Утилита CreateExportList работает следующим образом: она помещает в файл экспорта все глобально видимые символы и добавляет для них ключевые слова, соответствующие атрибутам видимости. В таблице 3 показано, в каких случаях символ var попадет в файл экспорта и какое ключевое слово при этом будет сгенерировано.

Таблица 3. Новое рабочее правило для утилиты CreateExportList
Атрибут видимости, указанный в объектном файле .o Представление в файле экспорта Комментарий
(unspecified) var Значение не меняется в целях обеспечения совместимости со старым кодом
exported var exported Добавляется ключевое слово exported
protected var protected Добавляется ключевое слово protected
hidden - Символ с этим атрибутом видимости не попадает в файл экспорта
internal - Символ с этим атрибутом видимости не попадает в файл экспорта

Таблица 1 позволяет понять, что представленный в ней способ обеспечивает хорошие результаты, поскольку компоновщик умеет корректно разрешать конфликты. В то же время утилита CreateExportList может быть полезна при создании модулей (библиотек или исполняемых файлов) из смешанных объектов (например, это могут быть старые объекты и новые объекты с поддержкой видимости символов). Кроме того, утилита CreateExportList гарантирует, что сценарий компоновки будет продолжать работать без ошибок после обновления версии компилятора.

Синтаксис на ассемблере

Программисты, пишущие код на ассемблере, также могут определять различные атрибуты видимости символов. В листинге 14 представлен простой синтаксис для псевдоопераций globl, .comm, .extern и .weak.

Листинг 14. Синтаксис для определения атрибутов видимости на ассемблере
   .extern Name [ , Visibility ]
   .globl  Name [ , Visibility ]
   .weak   Name [ , Visibility ]
   .comm   Name, Expression [, Number [, Visibility ] ]

Если переменной "Visibility" присваивается значение при выполнении операции .comm, то значение переменной "Number" (выравнивание) можно не указывать:
   .comm   Name, Expression , , Visibility

Переменная Visibility из листинга 14 может принимать одно из четырех значений: internal, hidden, protected и exported. Если значение этой переменной не задано, то атрибут видимости символу не назначается (т. е. принимает значение "unspecified"). Это правило соответствует правилу в языке C/C++.

Для того чтобы ассемблер начал понимать этот синтаксис, необходимо обновить его до последней версии. Это же касается и компоновщика AIX. Этим требованиям соответствуют платформы AIX 6.1 TL8/AIX 7.1 TL2 и более поздние. Начиная с версии V13.1 компилятор XL C/C++ для AIX также может работать с параметрами видимости C/C++.

Ресурсы


Ресурсы для скачивания


Комментарии

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

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=AIX и UNIX
ArticleID=1002502
ArticleTitle=Управление видимостью символических имен для общих библиотек: часть 2 – работа с компилятором IBM XL C/C++ V13
publish-date=04032015