Что такое код hw_modifyobject

Содержание

hw_Modifyobject

hw_Modifyobject — Modifies object record

Описание

This command allows to remove, add, or modify indiv >object_to_change. In order to modify an attribute one will have to remove the old one and add a new one. hw_modifyobject() will always remove the attributes before it adds attributes unless the value of the attribute to remove is not a string or array.

The keys of both arrays are the attributes name. The value of each array element can either be an array, a string or anything else. If it is an array each attribute value is constructed by the key of each element plus a colon and the value of each element. If it is a string it is taken as the attribute value. An empty string will result in a complete removal of that attribute. If the value is neither a string nor an array but something else, e.g. an integer, no operation at all will be performed on the attribute. This is necessary if you want to add a completely new attribute not just a new value for an existing attribute. If the remove array contained an empty string for that attribute, the attribute would be tried to be removed which would fail since it doesn’t exist. The following addition of a new value for that attribute would also fail. Setting the value for that attribute to e.g. 0 would not even try to remove it and the addition will work.

If you would like to change the attribute ‘Name’ with the current value ‘books’ into ‘articles’ you will have to create two arrays and call hw_modifyobject() .

Пример #1 modifying an attribute

Пример #2 adding a completely new attribute

Замечание: Multilingual attributes, e.g. ‘Title’, can be modified in two ways. Either by providing the attributes value in its native form ‘language’:’title’ or by providing an array with elements for each language as described above. The above example would than be:

Пример #3 modifying Title attribute

Пример #4 modifying Title attribute

Пример #5 removing attribute

Замечание: This will remove all attributes with the name ‘Title’ and adds a new ‘Title’ attribute. This comes in handy if you want to remove attributes recursively.

Замечание: If you need to delete all attributes with a certain name you will have to pass an empty string as the attribute value.

Замечание: Only the attributes ‘Title’, ‘Description’ and ‘Keyword’ will properly handle the language prefix. If those attributes don’t carry a language prefix, the prefix ‘xx’ will be assigned.

Замечание: The ‘Name’ attribute is somewhat special. In some cases it cannot be complete removed. You will get an error message ‘Change of base attribute’ (not clear when this happens). Therefore you will always have to add a new Name first and than remove the old one.

Замечание: You may not surround this function by calls to hw_getandlock() and hw_unlock() . hw_modifyobject() does this internally.

Список параметров

The connection identifier.

The object to be changed.

An array of attributes to remove.

An array of attributes to add.

The last parameter determines if the modification is performed recursively. 1 means recursive modification. If some of the objects cannot be modified they will be skipped without notice. hw_error() may not indicate an error though some of the objects could not be modified.

Возвращаемые значения

Возвращает TRUE в случае успешного завершения или FALSE в случае возникновения ошибки.

hw-modifyobject — функция PHP

(PHP 3 >= 3.0.7, PHP 4, PECL)

hw_Modifyobject — Modifies object record

Description

This command allows to remove, add, or modify indiv >object_to_change . The first array remove is a list of attributes to remove. The second array add is a list of attributes to add. In order to modify an attribute one will have to remove the old one and add a new one. hw_modifyobject() will always remove the attributes before it adds attributes unless the value of the attribute to remove is not a string or array.

The last parameter determines if the modification is performed recursively. 1 means recursive modification. If some of the objects cannot be modified they will be skipped without notice. hw_error() may not indicate an error though some of the objects could not be modified.

The keys of both arrays are the attributes name. The value of each array element can either be an array, a string or anything else. If it is an array each attribute value is constructed by the key of each element plus a colon and the value of each element. If it is a string it is taken as the attribute value. An empty string will result in a complete removal of that attribute. If the value is neither a string nor an array but something else, e.g. an integer, no operation at all will be performed on the attribute. This is necessary if you want to add a completely new attribute not just a new value for an existing attribute. If the remove array contained an empty string for that attribute, the attribute would be tried to be removed which would fail since it doesn’t exist. The following addition of a new value for that attribute would also fail. Setting the value for that attribute to e.g. 0 would not even try to remove it and the addition will work.

If you would like to change the attribute ‘Name’ with the current value ‘books’ into ‘articles’ you will have to create two arrays and call hw_modifyobject() .

Пример 1. modifying an attribute

// $connect is an existing connection to the Hyperwave server
// $objid is the ID of the object to modify
$remarr = array( «Name» => «books» );
$addarr = array( «Name» => «articles» );
$hw_modifyobject ( $connect , $objid , $remarr , $addarr );
?>

In order to delete/add a name=value pair from/to the object record just pass the remove/add array and set the last/third parameter to an empty array. If the attribute is the first one with that name to add, set attribute value in the remove array to an integer.

Пример 2. adding a completely new attribute

// $connect is an existing connection to the Hyperwave server
// $objid is the ID of the object to modify
$remarr = array( «Name» => 0 );
$addarr = array( «Name» => «articles» );
$hw_modifyobject ( $connect , $objid , $remarr , $addarr );
?>

Замечание: Multilingual attributes, e.g. ‘Title’, can be modified in two ways. Either by providing the attributes value in its native form ‘language’:’title’ or by providing an array with elements for each language as described above. The above example would than be:

Пример 3. modifying Title attribute

= array( «Title» => «en:Books» );
$addarr = array( «Title» => «en:Articles» );
$hw_modifyobject ( $connect , $objid , $remarr , $addarr );
?>

Пример 4. modifying Title attribute

= array( «Title» => array( «en» => «Books» ));
$addarr = array( «Title» => array( «en» => «Articles» , «ge» => «Artikel» ));
$hw_modifyobject ( $connect , $objid , $remarr , $addarr );
?>

This removes the English title ‘Books’ and adds the English title ‘Articles’ and the German title ‘Artikel’.

Пример 5. removing attribute

= array( «Title» => «» );
$addarr = array( «Title» => «en:Articles» );
$hw_modifyobject ( $connect , $objid , $remarr , $addarr );
?>

Замечание: This will remove all attributes with the name ‘Title’ and adds a new ‘Title’ attribute. This comes in handy if you want to remove attributes recursively.

Замечание: If you need to delete all attributes with a certain name you will have to pass an empty string as the attribute value.

Замечание: Only the attributes ‘Title’, ‘Description’ and ‘Keyword’ will properly handle the language prefix. If those attributes don’t carry a language prefix, the prefix ‘xx’ will be assigned.

Замечание: The ‘Name’ attribute is somewhat special. In some cases it cannot be complete removed. You will get an error message ‘Change of base attribute’ (not clear when this happens). Therefore you will always have to add a new Name first and than remove the old one.

Замечание: You may not surround this function by calls to hw_getandlock() and hw_unlock() . hw_modifyobject() does this internally.

Возвращает TRUE в случае успешного завершения или FALSE в случае возникновения ошибки.

Метод modify() (тип данных xml) modify() Method (xml Data Type)

ОБЛАСТЬ ПРИМЕНЕНИЯ: SQL Server База данных SQL Azure Azure Synapse Analytics (хранилище данных SQL) Parallel Data Warehouse APPLIES TO: SQL Server Azure SQL Database Azure Synapse Analytics (SQL DW) Parallel Data Warehouse

Изменяет содержимое XML-документа. Modifies the contents of an XML document. Данный метод служит для изменения содержимого переменной или столбца типа xml. Use this method to modify the content of an xml type variable or column. Этот метод использует DML-инструкцию языка XML для вставки, обновления и удаления узлов из данных XML. This method takes an XML DML statement to insert, update, or delete nodes from XML data. Метод modify() типа данных xml может быть использован только в предложении SET инструкции UPDATE. The modify() method of the xml data type can only be used in the SET clause of an UPDATE statement.

Синтаксис Syntax

Аргументы Arguments

XML_DML XML_DML
Строка языка обработки данных для XML. Is a string in XML Data Manipulation Language (DML). Обновление XML-документа выполняется в соответствии с этим выражением. The XML document is updated according to this expression.

Возвращается ошибка, если метод modify() вызван при значении NULL или приводит к значению NULL. An error is returned if the modify() method is called on a null value or results in a null value.

Примеры Examples

Поскольку метод modify() требует строку на XML-языке обработки данных (DML), образцы для метода modify() содержатся в разделах, описывающих DML-инструкции. Because the modify() method requires a string in the XML Data Manipulation Language (DML), the samples for modify() are contained in the topics that describe the XML DML statements. Эти примеры см. в разделах insert (XML DML), delete (XML DML) и replace value of (XML DML). For these examples, see insert (XML DML), delete (XML DML) and replace value of (XML DML).

Что такое код hw_modifyobject

(PHP 3 >= 3.0.7, PHP 4, PECL)

hw_Modifyobject — Modifies object record

Description bool hw_modifyobject ( int connection, int object_to_change, array remove, array add [, int mode] )

This command allows to remove, add, or modify indiv >object_to_change . The first array remove is a list of attributes to remove. The second array add is a list of attributes to add. In order to modify an attribute one will have to remove the old one and add a new one. hw_modifyobject() will always remove the attributes before it adds attributes unless the value of the attribute to remove is not a string or array.

The last parameter determines if the modification is performed recursively. 1 means recursive modification. If some of the objects cannot be modified they will be skipped without notice. hw_error() may not indicate an error though some of the objects could not be modified.

The keys of both arrays are the attributes name. The value of each array element can either be an array, a string or anything else. If it is an array each attribute value is constructed by the key of each element plus a colon and the value of each element. If it is a string it is taken as the attribute value. An empty string will result in a complete removal of that attribute. If the value is neither a string nor an array but something else, e.g. an integer, no operation at all will be performed on the attribute. This is necessary if you want to add a completely new attribute not just a new value for an existing attribute. If the remove array contained an empty string for that attribute, the attribute would be tried to be removed which would fail since it doesn’t exist. The following addition of a new value for that attribute would also fail. Setting the value for that attribute to e.g. 0 would not even try to remove it and the addition will work.

If you would like to change the attribute ‘Name’ with the current value ‘books’ into ‘articles’ you will have to create two arrays and call hw_modifyobject() .

Example 1. modifying an attribute

«books»); $addarr = array(«Name» => «articles»); $hw_modifyobject($connect, $objid, $remarr, $addarr); ?>

In order to delete/add a name=value pair from/to the object record just pass the remove/add array and set the last/third parameter to an empty array. If the attribute is the first one with that name to add, set attribute value in the remove array to an integer.

Example 2. adding a completely new attribute

0); $addarr = array(«Name» => «articles»); $hw_modifyobject($connect, $objid, $remarr, $addarr); ?>

Note: Multilingual attributes, e.g. ‘Title’, can be modified in two ways. Either by providing the attributes value in its native form ‘language’:’title’ or by providing an array with elements for each language as described above. The above example would than be:

Example 3. modifying Title attribute

«en:Books»); $addarr = array(«Title» => «en:Articles»); $hw_modifyobject($connect, $objid, $remarr, $addarr); ?>

Example 4. modifying Title attribute

array(«en» => «Books»)); $addarr = array(«Title» => array(«en» => «Articles», «ge»=>»Artikel»)); $hw_modifyobject($connect, $objid, $remarr, $addarr); ?>

This removes the English title ‘Books’ and adds the English title ‘Articles’ and the German title ‘Artikel’.

Example 5. removing attribute

«»); $addarr = array(«Title» => «en:Articles»); $hw_modifyobject($connect, $objid, $remarr, $addarr); ?>

Note: This will remove all attributes with the name ‘Title’ and adds a new ‘Title’ attribute. This comes in handy if you want to remove attributes recursively.

Note: If you need to delete all attributes with a certain name you will have to pass an empty string as the attribute value.

Note: Only the attributes ‘Title’, ‘Description’ and ‘Keyword’ will properly handle the language prefix. If those attributes don’t carry a language prefix, the prefix ‘xx’ will be assigned.

Note: The ‘Name’ attribute is somewhat special. In some cases it cannot be complete removed. You will get an error message ‘Change of base attribute’ (not clear when this happens). Therefore you will always have to add a new Name first and than remove the old one.

Returns TRUE on success or FALSE on failure.

hw_Modifyobject

hw_Modifyobject — Modifies object record

Description

This command allows to remove, add, or modify indiv >object_to_change . In order to modify an attribute one will have to remove the old one and add a new one. hw_modifyobject() will always remove the attributes before it adds attributes unless the value of the attribute to remove is not a string or array.

The keys of both arrays are the attributes name. The value of each array element can either be an array, a string or anything else. If it is an array each attribute value is constructed by the key of each element plus a colon and the value of each element. If it is a string it is taken as the attribute value. An empty string will result in a complete removal of that attribute. If the value is neither a string nor an array but something else, e.g. an integer, no operation at all will be performed on the attribute. This is necessary if you want to add a completely new attribute not just a new value for an existing attribute. If the remove array contained an empty string for that attribute, the attribute would be tried to be removed which would fail since it doesn’t exist. The following addition of a new value for that attribute would also fail. Setting the value for that attribute to e.g. 0 would not even try to remove it and the addition will work.

If you would like to change the attribute ‘Name’ with the current value ‘books’ into ‘articles’ you will have to create two arrays and call hw_modifyobject() .

Example #1 modifying an attribute

Example #2 adding a completely new attribute

Note: Multilingual attributes, e.g. ‘Title’, can be modified in two ways. Either by providing the attributes value in its native form ‘language’:’title’ or by providing an array with elements for each language as described above. The above example would than be:

Example #3 modifying Title attribute

Example #4 modifying Title attribute

Example #5 removing attribute

Note: This will remove all attributes with the name ‘Title’ and adds a new ‘Title’ attribute. This comes in handy if you want to remove attributes recursively.

Note: If you need to delete all attributes with a certain name you will have to pass an empty string as the attribute value.

Note: Only the attributes ‘Title’, ‘Description’ and ‘Keyword’ will properly handle the language prefix. If those attributes don’t carry a language prefix, the prefix ‘xx’ will be assigned.

Note: The ‘Name’ attribute is somewhat special. In some cases it cannot be complete removed. You will get an error message ‘Change of base attribute’ (not clear when this happens). Therefore you will always have to add a new Name first and than remove the old one.

Note: You may not surround this function by calls to hw_getandlock() and hw_unlock() . hw_modifyobject() does this internally.

Parameters

The connection identifier.

The object to be changed.

An array of attributes to remove.

An array of attributes to add.

The last parameter determines if the modification is performed recursively. 1 means recursive modification. If some of the objects cannot be modified they will be skipped without notice. hw_error() may not indicate an error though some of the objects could not be modified.

Return Values

Returns TRUE on success or FALSE on failure.

Что такое код hw_modifyobject

(PHP 3>= 3.0.7, PHP 4 )

hw_Modifyobject — modifies object record

Description int hw_modifyobject ( int connection, int object_to_change, array remove, array add, int mode)

This command allows to remove, add, or modify indiv > object_to_change . The first array remove is a list of attributes to remove. The second array add is a list of attributes to add. In order to modify an attribute one will have to remove the old one and add a new one. hw_modifyobject() will always remove the attributes before it adds attributes unless the value of the attribute to remove is not a string or array.

The last parameter determines if the modification is performed recursively. 1 means recurive modification. If some of the objects cannot be modified they will be skiped without notice. hw_error() may not indicate an error though some of the objects could not be modified.

The keys of both arrays are the attributes name. The value of each array element can either be an array, a string or anything else. If it is an array each attribute value is constructed by the key of each element plus a colon and the value of each element. If it is a string it is taken as the attribute value. An empty string will result in a complete removal of that attribute. If the value is neither a string nor an array but something else, e.g. an integer, no operation at all will be performed on the attribute. This is neccessary if you want to to add a completely new attribute not just a new value for an existing attribute. If the remove array contained an empty string for that attribute, the attribute would be tried to be removed which would fail since it doesn’t exist. The following addition of a new value for that attribute would also fail. Setting the value for that attribute to e.g. 0 would not even try to remove it and the addition will work.

If you would like to change the attribute ‘Name’ with the current value ‘books’ into ‘articles’ you will have to create two arrays and call hw_modifyobject() .

Example 1. modifying an attribute

// $connect is an existing connection to the Hyperwave server // $obj => «books»); $addarr = array(«Name» => «articles»); $hw_modifyobject($connect, $objid, $remarr, $addarr);

In order to delete/add a name=value pair from/to the object record just pass the remove/add array and set the last/third parameter to an empty array. If the attribute is the first one with that name to add, set attribute value in the remove array to an integer.

Example 2. adding a completely new attribute

// $connect is an existing connection to the Hyperwave server // $obj => 0); $addarr = array(«Name» => «articles»); $hw_modifyobject($connect, $objid, $remarr, $addarr);

Note: Multilingual attributes, e.g. ‘Title’, can be modified in two ways. Either by providing the attributes value in its native form ‘language’:’title’ or by providing an array with elements for each language as described above. The above example would than be:

Example 3. modifying Title attribute

$remarr = array(«Title» => «en:Books»); $addarr = array(«Title» => «en:Articles»); $hw_modifyobject($connect, $objid, $remarr, $addarr);

Example 4. modifying Title attribute

$remarr = array(«Title» => array(«en» => «Books»)); $addarr = array(«Title» => array(«en» => «Articles», «ge»=>»Artikel»)); $hw_modifyobject($connect, $objid, $remarr, $addarr);

This removes the english title ‘Books’ and adds the english title ‘Articles’ and the german title ‘Artikel’.

Example 5. removing attribute

$remarr = array(«Title» => «»); $addarr = array(«Title» => «en:Articles»); $hw_modifyobject($connect, $objid, $remarr, $addarr);

Note: This will remove all attributes with the name ‘Title’ and adds a new ‘Title’ attribute. This comes in handy if you want to remove attributes recursively.

Note: If you need to delete all attributes with a certain name you will have to pass an empty string as the attribute value.

Note: Only the attributes ‘Title’, ‘Description’ and ‘Keyword’ will properly handle the language prefix. If those attributes don’t carry a language prefix, the prefix ‘xx’ will be assigned.

Note: The ‘Name’ attribute is somewhat special. In some cases it cannot be complete removed. You will get an error message ‘Change of base attribute’ (not clear when this happens). Therefore you will always have to add a new Name first and than remove the old one.

Returns TRUE if no error occurs otherwise FALSE .

hw_Modifyobject

hw_Modifyobject — Modifies object record

Description

This command allows to remove, add, or modify indiv >object_to_change . In order to modify an attribute one will have to remove the old one and add a new one. hw_modifyobject() will always remove the attributes before it adds attributes unless the value of the attribute to remove is not a string or array.

The keys of both arrays are the attributes name. The value of each array element can either be an array, a string or anything else. If it is an array each attribute value is constructed by the key of each element plus a colon and the value of each element. If it is a string it is taken as the attribute value. An empty string will result in a complete removal of that attribute. If the value is neither a string nor an array but something else, e.g. an integer, no operation at all will be performed on the attribute. This is necessary if you want to add a completely new attribute not just a new value for an existing attribute. If the remove array contained an empty string for that attribute, the attribute would be tried to be removed which would fail since it doesn’t exist. The following addition of a new value for that attribute would also fail. Setting the value for that attribute to e.g. 0 would not even try to remove it and the addition will work.

If you would like to change the attribute ‘Name’ with the current value ‘books’ into ‘articles’ you will have to create two arrays and call hw_modifyobject().

Example #1 modifying an attribute

Example #2 adding a completely new attribute

Note: Multilingual attributes, e.g. ‘Title’, can be modified in two ways. Either by providing the attributes value in its native form ‘language’:’title’ or by providing an array with elements for each language as described above. The above example would than be:

Example #3 modifying Title attribute

Example #4 modifying Title attribute

Example #5 removing attribute

Note: This will remove all attributes with the name ‘Title’ and adds a new ‘Title’ attribute. This comes in handy if you want to remove attributes recursively.

Note: If you need to delete all attributes with a certain name you will have to pass an empty string as the attribute value.

Note: Only the attributes ‘Title’, ‘Description’ and ‘Keyword’ will properly handle the language prefix. If those attributes don’t carry a language prefix, the prefix ‘xx’ will be assigned.

Note: The ‘Name’ attribute is somewhat special. In some cases it cannot be complete removed. You will get an error message ‘Change of base attribute’ (not clear when this happens). Therefore you will always have to add a new Name first and than remove the old one.

Note: You may not surround this function by calls to hw_getandlock() and hw_unlock(). hw_modifyobject() does this internally.

Parameters

The connection identifier.

The object to be changed.

An array of attributes to remove.

An array of attributes to add.

The last parameter determines if the modification is performed recursively. 1 means recursive modification. If some of the objects cannot be modified they will be skipped without notice. hw_error() may not indicate an error though some of the objects could not be modified.

Return Values

Returns TRUE on success or FALSE on failure.

Может кому надо! сорри, если было. Инженерные коды для «Андроид»

Многие пользователи смартфонов и планшетов на Android даже не подозревают о существовании так называемых инженерных или сервисных кодов. Сервисные коды на смартфонах и на обычных телефонах появились намного раньше, чем вышла первая версия ОС Андроид. Они предназначены в основном для инженеров сервисных центров и продвинутых пользователей, поэтом сразу хотим предупредить читателей: если не знаете для чего этот код, то не стоит вводить, а если все же решили ввести код, то делаете на свой страх и риск. Стоит подумать прежде, чем вводить какой-либо код для «Андроид», т.к. это может привести к частичной или полной потери данных и вывести из строя само устройство. Если же Вы приняли решение использовать коды, давайте рассмотрим каждый инженерный код подробнее:

#06# — Узнать IMEI;

##4636## — Сведения и настройки;

##8351## — Voice Dialer Logging Enabled;

##4636## — Этот код может быть использован для получения интересной информации о телефоне и аккумулятора. Он показывает следующие 4 меню на экране:
— Информация о телефоне;
— Информация об аккумуляторах;
— Статистика аккумулятора;
— Статистика использования.

##7780## — Этот код сбрасывает следующие настройки на заводские:
— настройки Вашего Google-аккаунта, хранящиеся на Вашем смартфоне;
— данные и настройки системы и приложений;
— загруженные приложения.
Код НЕ удаляет:
— текущие системные приложения и приложения поставляющиеся со смарфоном;
— данные на SD карте (фото, видео и т.д.).
PS: Перед тем, как сбросить настройки смартфон запросит подтверждение, так что до самого последнего момента у Вас будет шанс передумать.

27673855# — Подумайте прежде, чем вводить этот код. Этот код используется для заводского форматирования, то есть, запускает удаление всех файлов и настроек включая те, которые хранятся во внутренней памяти. Так же он переустанавливает прошивку смартфона.
PS: После введения кода есть только один путь назад — быстро вытащить батарею и начать восстановление данных через ПК.

##34971539## — Этот код используется для получения информации о камере телефона. Он показывает следующие четыре параметра:
— Обновление прошивки камеры в образ (не пытайтесь повторить этот вариант);
— Обновление прошивки камеры на SD-карте;
— Получить версию прошивки камеры;
— Посмотреть сколько раз обновлялась прошивка.
ВНИМАНИЕ: Никогда не используйте первый вариант, в противном случае ваша камера телефона перестанет работать, и придется нести телефон в сервисный центр для переустановки прошивки камеры.

##7594## — Этот код может быть использован для изменения режима кнопки «Отбой / Вкл.Выкл.». По умолчанию, при длительном нажатии кнопки, будет показан экран с предложением выбрать любой вариант: «Переключиться в беззвучный режим», «Режим полета» или же «Выключить смартфон».
Вы можете изменить предложенные варианты используя этот код. К примеру, можно сделать так, чтобы телефон сразу же выключался, без выбора из меню нужного варианта.

##273283255663 282## — Код открывает экран копирования файлов, на котором можно сделать резервные копии своих данных (фото, аудио и т.п.)

##197328640## — Этот код можно использовать для входа в режим обслуживания. Вы можете запускать различные тесты и изменять настройки в сервисном режиме для WLAN, GPS и Bluetooth;

##232339## или ##526## или ##528## — WLAN (используйте кнопку «Меню» для запуска различных тестов);

##232338## — показывает MAC адрес WiFi;

##1575## — Еще один тест GPS;

##232337## — Показывает адрес устройства Bluetooth;

Коды для запуска различных заводских тестов:
##0283## — Пакетные Loopback;

##0842## — Устройство тест (тест вибрации и подсветки тест);

##2663## — Сенсорный экран, версия;

##2664## — Сенсорный экран, тест;

Это основные коды для «Андроид», которые могут понадобится продвинутому пользователю смартфона. Еще раз повторимся: не вводите коды, если не уверены в их предназначении! Но для решения некоторых проблем в работе устройства на «Андроид» эти коды крайне полезны.

Дубликаты не найдены

там немного опечатался по невнимательности, пикабу отказывает мне в редактировании!
Многие пользователи смартфонов и планшетов на Android даже не подозревают о существовании так называемых инженерных или сервисных кодов. Сервисные коды на смартфонах и на обычных телефонах появились намного раньше, чем вышла первая версия ОС Андроид. Они предназначены в основном для инженеров сервисных центров и продвинутых пользователей, поэтом сразу хотим предупредить читателей: если не знаете для чего этот код, то не стоит вводить, а если все же решили ввести код, то делаете на свой страх и риск. Стоит подумать прежде, чем вводить какой-либо код для «Андроид», т.к. это может привести к частичной или полной потери данных и вывести из строя само устройство. Если же Вы приняли решение использовать коды, давайте рассмотрим каждый инженерный код подробнее:

*#06# — Узнать IMEI;

*#*#4636#*#* — Сведения и настройки;

*#*#8351#*#* — Voice Dialer Logging Enabled;

*#*#4636#*#* — Этот код может быть использован для получения интересной информации о телефоне и аккумулятора. Он показывает следующие 4 меню на экране:
— Информация о телефоне;
— Информация об аккумуляторах;
— Статистика аккумулятора;
— Статистика использования.

*#*#7780#*#* — Этот код сбрасывает следующие настройки на заводские:
— настройки Вашего Google-аккаунта, хранящиеся на Вашем смартфоне;
— данные и настройки системы и приложений;
— загруженные приложения.
Код НЕ удаляет:
— текущие системные приложения и приложения поставляющиеся со смарфоном;
— данные на SD карте (фото, видео и т.д.).
PS: Перед тем, как сбросить настройки смартфон запросит подтверждение, так что до самого последнего момента у Вас будет шанс передумать.

*2767*3855# — Подумайте прежде, чем вводить этот код. Этот код используется для заводского форматирования, то есть, запускает удаление всех файлов и настроек включая те, которые хранятся во внутренней памяти. Так же он переустанавливает прошивку смартфона.
PS: После введения кода есть только один путь назад — быстро вытащить батарею и начать восстановление данных через ПК.

*#*#34971539#*#* — Этот код используется для получения информации о камере телефона. Он показывает следующие четыре параметра:
— Обновление прошивки камеры в образ (не пытайтесь повторить этот вариант);
— Обновление прошивки камеры на SD-карте;
— Получить версию прошивки камеры;
— Посмотреть сколько раз обновлялась прошивка.
ВНИМАНИЕ: Никогда не используйте первый вариант, в противном случае ваша камера телефона перестанет работать, и придется нести телефон в сервисный центр для переустановки прошивки камеры.

*#*#7594#*#* — Этот код может быть использован для изменения режима кнопки «Отбой / Вкл.Выкл.». По умолчанию, при длительном нажатии кнопки, будет показан экран с предложением выбрать любой вариант: «Переключиться в беззвучный режим», «Режим полета» или же «Выключить смартфон».
Вы можете изменить предложенные варианты используя этот код. К примеру, можно сделать так, чтобы телефон сразу же выключался, без выбора из меню нужного варианта.

*#*#273283*255*663 282*#*#* — Код открывает экран копирования файлов, на котором можно сделать резервные копии своих данных (фото, аудио и т.п.)

*#*#197328640#*#* — Этот код можно использовать для входа в режим обслуживания. Вы можете запускать различные тесты и изменять настройки в сервисном режиме для WLAN, GPS и Bluetooth;

*#*#232339#*#* или *#*#526#*#* или *#*#528#*#* — WLAN (используйте кнопку «Меню» для запуска различных тестов);

*#*#232338#*#* — показывает MAC адрес WiFi;

*#*#1575#*#* — Еще один тест GPS;

*#*#232331#*#* — Bluetooth- тест;

*#*#232337#*# — Показывает адрес устройства Bluetooth;

Коды для запуска различных заводских тестов:
*#*#0283#*#* — Пакетные Loopback;

*#*#0673#*#* или *#*#0289#*#* — Мелодия-тест;

*#*#0842#*#* — Устройство тест (тест вибрации и подсветки тест);

*#*#2663#*#* — Сенсорный экран, версия;

*#*#2664#*#* — Сенсорный экран, тест;

*#*#0588#*#* — Датчик движения;

*#*#3264#*#* — RAM версия.

Это основные коды для «Андроид», которые могут понадобится продвинутому пользователю смартфона. Еще раз повторимся: не вводите коды, если не уверены в их предназначении! Но для решения некоторых проблем в работе устройства на «Андроид» эти коды крайне полезны.

Исходный, Машинный и Объектный коды — Разница?

Исходный код или исходный текст — текст на языке программирования, который пишет человек (как правило, человек). Это обычный текстовый файл (как правило).

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

Объектный код — это промежуточное представление кода. Еще не машинный, но уже не исходный. Используется на этапе сборки программы из нескольких кусков (возможно, написанных в исходном коде разными людьми и в разное время).

Машинный код — уже готов для исполнения. То есть просто берете его, ничего не нужно более. Делаете двойной щелчок мыши, к примеру, и программа запускается.

Пример исходного кода — легко:

Пример объектного кода и машинного кода — бессмысленен. Что объектный, что машинный коды этой программы
не предназначены для чтения человеком.

Это для вас будет выглядеть как абракадабра.

И это еще не весь объектный код — там большой файл.

Машинный код — еще бОльшая абракадабра, я его сюда даже вставить не смог. Текстовый редактор не пропускает эту кучу спецсимволов и т.п.

Упомянутая вами цепочка (исходный — объектный — машинный) касается языков программирования, компилируемых в машинный код:
C/С++/Go/Rust/Pascal.

Некоторые языки предназначены для компиляции в байт-код. Его нельзя назвать ни машинный ни объектным. Это Java/Kotlin/C#. Если бы этот код выполнялся непосредственно процессором (CPU) его можно было бы назвать машинным. Но он выполняется только через специальную программу.

Языки Ruby/Python/JavaScript ничего похожего на машинный код не генерят.

Руководство новичка по эксплуатации компоновщика

Цель данной статьи — помочь C и C++ программистам понять сущность того, чем занимается компоновщик. За последние несколько лет я объяснил это большому количеству коллег и наконец решил, что настало время перенести этот материал на бумагу, чтоб он стал более доступным (и чтоб мне не пришлось объяснять его снова). [Обновление в марте 2009: добавлена дополнительная информация об особенностях компоновки в Windows, а также более подробно расписано правило одного определения (one-definition rule).

Типичным примером того, почему ко мне обращались за помощью, служит следующая ошибка компоновки:

Если Ваша реакция — ‘наверняка забыл extern «C»’, то Вы скорее всего знаете всё, что приведено в этой статье.

Содержание

Определения: что находится в C файле?

Эта глава — краткое напоминание о различных составляющих C файла. Если всё в листинге, приведённом ниже, имеет для Вас смысл, то скорее всего Вы можете пропустить эту главу и сразу перейти к следующей.

Сперва надо понять разницу между объявлением и определением. Определение связывает имя с реализацией, что может быть либо кодом либо данными:

  • Определение переменной побуждает компилятор зарезервировать некоторую область памяти, возможно задав ей некоторое определённое значение.
  • Определение функции заставляет компилятор сгенерировать код для этой функции

Объявление говорит компилятору, что определение функции или переменной (с определённым именем) существует в другом месте программы, вероятно в другом C файле. (Заметьте, что определение также является объявлением — фактически это объявление, в котором «другое место» программы совпадает с текущим).

Для переменных существует определения двух видов:

  • глобальные переменные, которые существуют на протяжении всего жизненного цикла программы («статическое размещение») и которые доступны в различных функциях;
  • локальные переменные, которые существуют только в пределах некоторой исполняемой функции («локальное размещение») и которые доступны только внутри этой самой функции.

При этом под термином «доступны» следует понимать «можно обратиться по имени, ассоциированным с переменной в момент определения».

Существует пара частных случаев, которые с первого раза не кажутся очевидными:

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

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

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

И наконец, мы можем сохранять информацию в памяти, которая динамически выделена посредством malloc или new . В данном случае нет возможности обратиться к выделенной памяти по имени, поэтому необходимо использовать указатели — именованные переменные, содержащие адрес неименованной области памяти. Эта область памяти может быть также освобождена с помощью free или delete . В этом случае мы имеем дело с «динамическим размещением».

Подытожим:

Код Данные
Глобальные Локальные Динамические
Инициа-
лизиро-
ванные
Неинициа-
лизиро-
ванные
Инициа-
лизиро-
ванные
Неинициа-
лизиро-
ванные
Объяв-
ление
int fn(int x); extern int x; extern int x; N/A N/A N/A
Опреде-
ление
int fn(int x) int x = 1;
(область действия
— файл)
int x;
(область действия — файл)
int x = 1;
(область действия — функция)
int x;
(область действия — функция)
int* p = malloc(sizeof(int));

Вероятно более лёгкий путь усвоить — это просто посмотреть на пример программы.

Что делает C компилятор

Работа компилятора C заключается в конвертировании текста, (обычно) понятного человеку, в нечто, что понимает компьютер. На выходе компилятор выдаёт объектный файл. На платформах UNIX эти файлы имеют обычно суффикс .o; в Windows — суффикс .obj. Содержание объектного файла — в сущности две вещи:

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

Код и данные, в данном случае, будут иметь ассоциированные с ними имена — имена функций или переменных, с которыми они связаны определением.

Объектный код — это последовательность (подходящим образом составленных) машинных инструкций, которые соответствуют C инструкциям, написанных программистом: все эти if ‘ы и while ‘ы и даже goto . Эти заклинания должны манипулировать информацией определённого рода, а информация должна быть где-нибудь находится — для этого нам и нужны переменные. Код может также ссылаться на другой код (в частности на другие C функции в программе).

Где бы код ни ссылался на переменную или функцию, компилятор допускает это, только если он видел раньше объявление этой переменной или функции. Объявление — это обещание, что определение существует где-то в другом месте программы.

Работа компоновщика проверить эти обещания. Однако, что компилятор делает со всеми этими обещаниями, когда он генерирует объектный файл?

По существу компилятор оставляет пустые места. Пустое место (ссылка) имеет имя, но значение соответствующее этому имени пока не известно.

Учитывая это, мы можем изобразить объектный файл, соответствующей программе, приведённой выше, следующим образом:

Анализирование объектного файла

До сих пор мы рассматривали всё на высоком уровне. Однако полезно посмотреть, как это работает на практике. Основным инструментом для нас будет команда nm , которая выдаёт информацию о символах объектного файла на платформе UNIX. Для Windows команда dumpbin с опцией /symbols является приблизительным эквивалентом. Также есть портированные под Windows инструменты GNU binutils, которые включают nm.exe .

Давайте посмотрим, что выдаёт nm для объектного файла, полученного из нашего примера выше:

Результат может выглядеть немного по разному на разных платформах (обратитесь к man ‘ам, чтобы получить соответствующую информацию), но ключевыми сведениями являются класс каждого символа и его размер (если присутствует). Класс может иметь различны значения:

  • Класс U обозначает неопределённые ссылки, те самые «пустые места», упомянутые выше. Для этого класса существует два объекта: fn_a и z_global . (Некоторые версии nm могут выводить секцию, которая была бы *UND* или UNDEF в этом случае.)
  • Классы t и T указывают на код, который определён; различие между t и T заключается в том, является ли функция локальной (t) в файле или нет (T), т.е. была ли функция объявлена как static . Опять же в некоторых системах может быть показана секция, например .text .
  • Классы d и D содержат инициализированные глобальные переменные. При этом статичные переменные принадлежат классу d. Если присутствует информация о секции, то это будет .data.
  • Для неинициализированных глобальных переменных, мы получаем b, если они статичные и B или C иначе. Секцией в этом случае будет скорее всего .bss или *COM*.

Также можно увидеть символы, которые не являются частью исходного C кода. Мы не будем заострять наше внимание на этом, так как это обычно часть внутреннего механизма компилятора, для того чтобы Ваша программа всё-таки смогла быть потом скомпонована.

Что делает компоновщик: часть 1

Ранее мы обмолвились, что объявление функции или переменной — это обещание компилятору, что где-то в другом месте программы есть определение этой функции или переменной, и что работа компоновщика заключается в осуществлении этого обещания. Глядя на диаграмму объектного файла, мы можем описать этот процесс, как «заполнение пустых мест».

Проиллюстрируем это на примере, рассматривая ещё один C файл в дополнение к тому, что был приведён выше.

Исходя из обоих диаграмм, мы можем видеть, что все точки могут быть соединены (если нет, то компоновщик выдал бы сообщение об ошибке). Каждая вещь имеет своё место, и каждое место имеет свою вещь. Также компоновщик может заполнить все пустые места как показано здесь (на системах UNIX процесс компоновки обычно вызывается командой ld ).

Также как и для объектных файлов, мы можем использовать nm для исследования конечного исполняемого файла.

Он содержит символы обоих объектных файлов и все неопределённые ссылки исчезли. Символы переупорядочены так, что похожие типы находятся вместе. А также существует немного дополнений, чтобы помочь ОС иметь дело с такой штукой, как исполняемый файл.

Существует достаточное количество сложных деталей, загромождающих вывод, но если вы выкинете всё, что начинается с подчёркивания, то станет намного проще.

Повторяющиеся символы

В предыдущей главе было упомянуто, что компоновщик выдаёт сообщение об ошибке, если не может найти определение для символа, на который найдена ссылка. А что случится, если найдено два определения для символа во время компоновки?

В C++ решение прямолинейное. Язык имеет ограничение, известное как правило одного определения, которое гласит, что должно быть только одно определение для каждого символа, встречающегося во время компоновки, ни больше, ни меньше. (Соответствующей главой стандарта C++ является 3.2, которая также упоминает некоторые исключения, которые мы рассмотрим несколько позже.)

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

Однако, компоновщики должны уметь обходится также и с другими языками кроме C и C++, для которых правило одного определения не обязательно соблюдается. Например, для Fortran’а является нормальным иметь копию каждой глобальной переменной в каждом файле, который на неё ссылается. Компоновщику необходимо тогда убрать дубликаты, выбрав одну копию (самого большого представителя, если они отличаются в размере) и выбросить все остальные. Эта модель иногда называется «общей моделью» компоновки из-за ключевого слова COMMON (общий) языка Fortran.

Как результат, вполне распространённо для UNIX компоновщиков не ругаться на наличие повторяющихся символов, по крайней мере, если это повторяющиеся символы неинициализированных глобальных переменных (эта модель компоновки иногда называется «моделью с ослабленной связью» [прим. перев. это мой вольный перевод relaxed ref/def model. Более удачные предложения приветствуются]). Если это Вас волнует (вероятно и должно волновать), обратитесь к документации Вашего компоновщика, чтобы найти опцию —работай-правильно , которая усмиряет его поведение. Например, в GNU тулчейне опция компилятора -fno-common заставляет поместить неинициализированную переменную в сегмент BBS вместо генерирования общих (COMMON) блоков.

Что делает операционная система

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

Запуск программы разумеется влечёт за собой выполнение машинного кода, т.е. ОС очевидно должна перенести машинный код исполняемого файла с жёстокого диска в операционную память, откуда CPU сможет его забрать. Эти порции называются сегментом кода (code segment или text segment).

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

Для неинициализированных переменных ОС может предположить, что они все имеют 0 в качестве начального значения, т.е. нет надобности копировать какие-либо значения. Кусок памяти, который инициализируется нулями, известен как bss сегмент.

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

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

Эти данные не нуждаются во вмешательстве компоновщика, потому что время их жизни начинается и заканчивается во время исполнения программы — гораздо позже того, как компоновщик уже сделал своё дело. Однако, для полноты описания, мы коротко укажем, что:

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

Для полноты картины стоит добавить, как выглядит пространство памяти выполняемого процесса. Так как куча и стек могут изменять свои размеры динамически, вполне распространенным является факт, что стек растёт в одном направлении, а куча обратном. Таким образом, программа выдаст ошибку отсутствия свободной памяти, только если стек и куча встретятся где-нибудь в середине (в этом случае пространство памяти программы будет действительно заполнено).

Что делает компоновщик; часть 2

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

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

Одним из возможных решений было бы использование одних и тех же объектных файлов, однако было бы гораздо удобнее держать всю коллекцию… объектных файлов в одном легко доступном месте: библиотеке.

Техническое отступление: Эта глава полностью опускает важное свойство компоновщика: переадресация (relocation). Разные программы имеют различные размеры, т.е. если разделяемая библиотека отображается в адресное пространство различных программ, она будет иметь различные адреса. Это в свою очередь означает, что все функции и переменные в библиотеке будут на различных местах. Теперь, если все обращения к адресам относительные («значение +1020 байта отсюда») нежели абсолютные («значение в 0x102218BF»), то это не проблема, однако так бывает не всегда. В таких случаях всем абсолютным адресам необходимо прибавить подходящий офсет — это и есть relocation. Я не собираюсь возвращается к этой теме снова, однако добавлю, что так как это практически всегда скрыто от C/C++ программиста — очень редко проблемы компоновки вызваны трудностями переадресации.

Статические библиотеки

Самое простое воплощение библиотеки — это статическая библиотека. В предыдущей главе было упомянуто, что можно разделять (share), код просто повторно используя объектные файлы; это и есть суть статичных библиотек.

В системах UNIX командой для сборки статичной библиотеки обычно является ar, и библиотечный файл, который при этом получается, имеет расширение *.a. Также эти файлы обычно имеют префикс «lib» в своём названии и они передаются компоновщику с опцией «-l» с последующим именем библиотеки без префикса и расширения (т.е. «-lfred» подхватит файл «libfred.a»).
(Раньше программа, называемая ranlib , также была нужна для статических библиотек, чтобы сгенерировать список символов вначале библиотеки. В наши дни инструменты ar делают это сами.)

В системе Windows статические библиотеки имеют расширение .LIB и собираются инструментами LIB, однако этот факт может ввести в заблуждение, так как такое же расширение используется и для «import library», которая содержит в себе только список того, что имеется в DLL — смотрите главу о Windows DLL

По мере того как компоновщик перебирает коллекцию объектных файлов, чтобы объединить их вместе, он ведёт список символов, которые не могут быть пока реализованы. Как только все явно указанные объектные файлы обработаны, у компоновщика теперь есть новое место для поиска символов, которые остались в списке — в библиотеке. Если нереализованный символ определён в одном из объектов библиотеки, тогда объект добавляется, точно также как если бы он был бы добавлен в список объектных файлов пользователем, и компоновка продолжается.

Обратите внимание на гранулярность того, что добавляется из библиотеки: если необходимо определение некоторого символа, тогда весь объект, содержащий определение символа, будет включён. Это означает, что этот процесс может быть как шагом вперёд, так и шагом назад — свеже добавленный объект может как и разрешить неопределённую ссылку, так и привнести целую коллекцию новых неразрешённых ссылок.

Другая важная деталь — это порядок событий; библиотеки привлекаются только, когда нормальная компоновка завершена, и они обрабатываются в порядке слева на право. Это значит, что если объект, извлекаемый из библиотеки в последнюю очередь, требует наличие символа из библиотеки, стоящей раньше в строке команды компоновки, то компоновщик не найдёт его автоматически.

Приведём пример, чтоб прояснить ситуацию; предположим у нас есть следующие объектные файлы и строка команды компоновки, которая содержит a.o, b.o, -lx и -ly .

Файл a.o b.o libx.a liby.a
Объект a.o b.o x1.o x2.o x3.o y1.o y2.o y3.o
Опредe-
ления
a1, a2, a3 b1, b2 x11, x12, x13 x21, x22, x23 x31, x32 y11, y12 y21, y22 y31, y32
Неразре-
шённые
ссылки
b2, x12 a3, y22 x23, y12 y11 y21 x31

Как только компоновщик обработал a.o и b.o , ссылки на b2 и a3 будут разрешены, в то время как x12 и y22 будут всё ещё неразрешёнными. В этот момент компоновщик проверяет первую библиотеку libx.a на наличие недостающих символов и находит, что он может включить x1.o , чтобы компенсировать ссылку на x12 ; однако делая это, x23 и y12 добавляются в список неопределённых ссылок (теперь список выглядит как y22, x23, y12 ).

Компоновщик всё ещё имеет дело с libx.a , поэтому ссылка на x23 легко компенсируется, включая x2.o из libx.a . Однако это добавляет y11 к списку неопределённых (который стал y22, y12, y11 ). Ни одна из этих ссылок не может быть разрешена использованием libx.a , таким образом компоновщик принимается за liby.a .

Здесь происходит примерно тоже самое и компоновщик включает y1.o и y2.o . Первым объектом добавляется ссылка на y21 , но так как y2.o всё равно будет включено, эта ссылка разрешается просто. Результатом этого процесса является то, что все неопределённые ссылки разрешены, и некоторые (но не все) объекты библиотек включены в конечный исполняемый файл.

Заметьте, что ситуация несколько изменяется, если скажем b.o тоже имел бы ссылку на y32 . Если это было бы так, то компоновка libx.a происходила бы также, но обработка liby.a повлекла бы включение y3.o . Включением этого объекта мы добавим x31 к списку неразрешённых символов и эта ссылка останется неразрешённой — на этой стадии компоновщик уже завершил обработку libx.a и поэтому уже не найдёт определение этого символа (в x3.o ).

(Между прочим этот пример имеет циклическую зависимость между библиотеками libx.a и liby.a ; обычно это плохо особенно под Windows)

Динамические разделяемые библиотеки

Для популярных библиотек таких как стандартная библиотека C (обычно libc ) быть статичной библиотекой имеет явный недостаток — каждая исполняемая программа будет иметь копию одного и того же кода. Действительно, если каждый исполняемый файл будет иметь копию printf , fopen и тому подобных, то будет занято неоправданно много дискового пространства.

Менее очевидный недостаток это то, что в статически скомпонованной программе код фиксируется навсегда. Если кто-нибудь найдёт и исправит баг в printf , то каждая программа должна будет скомпонована заново, чтобы заполучить исправленный код.

Чтоб избавиться от этих и других проблем, были представлены динамически разделяемые библиотеки (обычно они имеют расширение .so или .dll в Windows и .dylib в Mac OS X). Для этого типа библиотек компоновщик не обязательно соединяет все точки. Вместо этого компоновщик выдаёт купон типа «IOU» (I owe you = я тебе должен) и откладывает обналичивание этого купона до момента запуска программы.

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

Когда программа вызывается на исполнение, ОС заботится о том, чтобы оставшиеся части процесса компоновки были выполнены вовремя до начала работы программы. Прежде чем будет вызвана функция main , малая версия компоновщика (часто называемая ld.so ) проходится по списку обещания и выполняет последний акт компоновки прямо на месте — помещает код библиотеки и соединяет все точки.

Это значит, что ни один выполняемый файл не содержит копии кода printf . Если новая версия printf будет доступна, то её можно использовать просто изменив libc.so — при следующем запуске программы вызовется новая printf .

Существует другое большое отличие между тем, как динамические библиотеки работают по сравнению со статическими и это проявляется в гранулярности компоновки. Если конкретный символ берётся из конкретной динамической библиотеки (скажем printf из libc.so ), то всё содержимое библиотеки помещается в адресное пространство программы. Это основное отличие от статических библиотек, где добавляются только конкретные объекты, относящиеся к неопределённому символу.

Сформулируем иначе, разделяемые библиотеки сами получаются как результат работы компоновщика (а не как формирование большой кучи объектов, как это делает ar ), содержащий ссылки между объектами в самой библиотеке. Повторю ещё, nm — полезный инструмент для иллюстрации происходящего: для приведённого выше примера он выдаст множество исходов для каждого объектного файла в отдельности, если этот инструмент запустить на статической версии библиотеки, но для разделяемой версии библиотеки liby.so имеет только один неопределённый символ x31 . Также в примере с порядком включения библиотек в конце предыдущей главы тоже никаких проблем не будет: добавление ссылки на y32 в b.c не повлечёт никаких изменений, так как всё содержимое y3.o и x3.o уже было задействовано.

Так между прочим, другой полезный инструмент — это ldd ; на платформе Unix он показывает все разделяемые библиотеки, от которых зависит исполняемый бинарник (или же другая разделяемая библиотека), вместе с указанием, где эти библиотеки можно найти. Для того чтобы программа удачно запустилась, загрузчику необходимо найти все эти библиотеки вместе со всеми их зависимостями. (Обычно загрузчик ищет библиотеки в списке директорий, указанных в переменной окружения LD_LIBRARY_PATH .)

Причина большей гранулярности заключается в том, что современные операционные системы достаточно интеллигентны, чтобы позволить делать больше, чем просто сэкономить сохранение повторяющихся элементов на диске, чем страдают статические библиотеки. Различные исполняемые процессы, которые используют одну и туже разделяемую библиотеку, также могут совместно использовать сегмент кода (но не сегмент данных или сегмент bss — например, два различных процесса могут находится в различных местах при использовании, скажем, strtok ). Чтобы этого достичь, вся библиотека должна быть адресована одним махом, чтобы все внутренние ссылки были выстроены однозначным образом. Действительно, если один процесс подхватывает a.o и c.o , а другой b.o и c.o , то ОС не сможет использовать никаких совпадений.

Windows DLL

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

Экспортируемые символы

Самое большое отличие заключается в том, что в библиотеках Windows символы не экспортируются автоматически. В Unix все символы всех объектных файлов, которые были подлинкованы к разделяемой библиотеке, видны пользователю этой библиотеки. В Windows, программист должен явно делать некоторые символы видимыми, т.е. экспортировать их.

Есть три способа как экспортировать символ и Windows DLL (и все эти три способа можно перемешивать в одной и той же библиотеке).

    В исходном коде объявить символ как __declspec(dllexport) , примерно так:

При выполнении команды компоновщика использовать опцию LINK.EXE export: symbol_to_export

Скормить компоновщику файл определения модуля (DEF) (используя опцию /DEF: def_file ), включив в этот файл секцию EXPORT , которая содержит символы, подлежащие экспортированию.

Как только к этой мешанине подключается C++, первая из этих опций становится самой простой, так как в этом случае компилятор берёт на себя обязательства позаботиться о декорировании имён

.LIB и другие относящиеся к библиотеке файлы

Мы подошли ко второй трудности, связанной с библиотеками Windows: информация об экспортируемых символах, которые компоновщик должен связать с остальными символам, не содержится в самом DLL . Вместо этого данная информация содержится в соответствующем .LIB файле.

.LIB файл, ассоциированный с DLL описывает какие (экспортируемые) символы находятся в DLL вместе с их расположением. Любой бинарник, который использует DLL , должен обращаться к .LIB файлу, чтобы связать символы корректно.

Чтобы сделать всё ещё более запутанным, расширение .LIB также используется для статических библиотек.

На самом деле существует целый ряд различных файлов, которые могут относиться каким-либо образом к библиотекам Windows. Наряду с .LIB файлом, а также (опциональным) .DEF файлом Вы можете увидеть все нижеперечисленные файлы, ассоциированные с Вашей Windows библиотекой.

  • Файлы на выходе компоновки
    • library .DLL : собственно код библиотеки; этот файл нужен (во время исполнения) любому бинарнику, использующему библиотеку.
    • library .LIB : файл «импортирования библиотеки», который описывает где и какой символ находится в результирующей DLL . Этот файл генерируется, если только DLL экспортирует некоторые её символы. Если символы не экспортируются, то смысла в .LIB файле нет. Этот файл нужен во время компоновки.
    • library .EXP : «Экспорт файл» компилируемой библиотеки, который нужен если имеет место компоновка бинарников с циклической зависимостью.
    • library .ILK : Если опция /INCREMENTAL была применена во время компоновки, которая активирует инкрементную компоновку, то этот файл содержит в себе статус инкрементной компоновки. Он нужен для будущих инкрементных компоновок с этой библиотекой.
    • library .PDB : Если опция /DEBUG была применена во время компоновки, то этот файл является программной базой данных, содержащей отладочную информацию для библиотеки.
    • library .MAP : Если опция /MAP была применена во время компоновки, то этот файл содержит описание внутреннего формата библиотеки.

  • Файлы на входе компоновки:
    • library .LIB : Файл «импорта библиотеки», которые описывает где и какие символы находятся в других DLL , которые нужны для компоновки.
    • library .LIB : Статическая библиотека, которая содержит коллекцию объектов, необходимых при компоновке. Обратите внимание на неоднозначное использование расширения .LIB
    • library .DEF : Файл «определений», который позволяет управлять различными деталями скомпонованной библиотеки, включая экспорт символов.
    • library .EXP : Файл экспорта компонуемой библиотеки, который может сигнализировать, что предыдущее выполнение LIB.EXE уже создало файл .LIB для библиотеки. Имеет значение при компоновке бинарников с циклическими зависимостями.
    • library .ILK : Файл состояния инкрементной компоновки; см. выше.
    • library .RES : Файл ресурсов, который содержит информацию о различных GUI виджетах, используемых исполняемым файлом. Эти ресурсы включаются в конечный бинарник.

Это является большим отличием к Unix, где почти вся информация, содержащаяся в этих всех дополнительных файлах, просто добавляется в саму библиотеку.

Импортируемые символы

Вместе с требованием к DLL явно объявлять экспортируемые символы, Windows также разрешает бинарникам, которые используют код библиотеки, явно объявлять символы, подлежащие импортированию. Это не является обязательным, но даёт некоторую оптимизацию по скорости, вызванную историческими свойствами 16-ти битных окон.

При этом индивидуальное объявление функций или глобальных переменных в одном заголовочном файле является хорошим тоном программирования на C. Это приводит к некоторому ребусу: код в DLL, содержащий определение функции/переменной должен экспортировать символ, но любой другой код, использующий DLL, должен импортировать символ.

Стандартный выход из этой ситуации — это использование макросов препроцессора.

Файл с исходниками в DLL, который определяет функцию и переменную гарантирует, что переменная препроцессора EXPORTING_XYZ_DLL_SYMS определена (по средством #define ) до включения соответствующего заголовочного файла и таким образом экспортирует символ. Любой другой код, который включает этот заголовочный файл не определяет этот символ и таким образом импортирует его.

Циклические зависимости

Ещё одной трудностью, связанной с использованием DLL, является тот факт, что Windows относится строже к требованию, что каждый символ должен быть разрешён во время компоновки. В Unix вполне возможно скомпоновать разделяемую библиотеку, которая содержит неразрешённые символы, т.е. символы, определение которых неведомо компоновщику В этой ситуации любой другой код, использующий эту разделяемую библиотеку, должен будет предоставить определение незразрешённых символов, иначе программа не будет запущена. Windows не допускает такой распущенности.

Для большинства систем — это не проблема. Выполняемые файлы зависят от высокоуровевых библиотек, высокоуровневые библиотеки зависят от библиотек низкого уровня, и всё компонуется в обратном порядке — сначала библиотеки низкого уровня, потом высокого, а затем и выполняемый файл, который зависит от всех остальных

Однако, если имеет место циклическая зависимость между бинарниками, тогда всё немного усложняется. Если X.DLL нуждается в символе из Y.DLL , а Y.DLL нуждается в символе из X.DLL , тогда необходимо решить задачу про курицу и яйцо: какая бы библиотека ни компоновалась бы первой, она не сможет найти разрешение ко всем символам.

Windows предоставил обходной приём примерно следующего содержания.

  • Сначала имитируем компоновку библиотеки X. Запускаем LIB.EXE (не LINK.EXE ), чтобы получить файл X.LIB точно такой же, какой был бы получен с LINK.EXE . При этом X.DLL не будет сгенерирован, но вместо него будет получен файл X.EXP .
  • Компонуем библиотеку Y как обычно, используя X.LIB , полученную на предыдущем шаге, и получаем на выходе как Y.DLL так и Y.LIB .
  • В конце концов компонуем библиотеку X теперь уже полноценно. Это происходит почти как обычно, используя дополнительно файл X.EXP , полученный на первом шаге. Обычное в этом шаге то, что компоновщик использует Y.LIB и производит X.DLL . Необычное — компоновщик пропускает шаг создания X.LIB , так как этот файл был уже создан на первом шаге, чему свидетельствует наличие .EXP файла.

Но несомненно лучше всё же реорганизовать библиотеки таким образом, чтоб избежать любых циклических зависимостей…

C++ для дополнения картины

C++ предлагает ряд дополнительных возможностей сверх того, что доступно в C, и часть этих возможностей влияет на работу компоновщика. Так было не всегда — первые реализации C++ появились в качестве внешнего интерфейса к компилятору C, поэтому о совместимости работы компоновщика не было нужды. Однако со временем были добавлены более продвинутые особенности языка, так что компоновщик уже должен был быть изменён, чтобы их поддерживать.

Перегрузка функций и декорирование имён

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

Такое положение вещей определённо затрудняет работу компоновщика: если какой-нибудь код обращается к функции max , какая именно имелась в виду?

Решение к этой проблеме названо декорированием имён (name mangling), потому что вся информация о сигнатуре функции переводится (to mangle = искажать, деформировать, прим.пер.) в текстовую форму, которая становится собственно именем символа с точки зрения компоновщика. Различные сигнатуры переводятся в различные имена. Таким образом проблема уникальности имён решена.

Я не собираюсь вдаваться в детали используемых схем декорирования (которые к тому же отличаются от платформы к платформе), но беглый взгляд на объектный файл, соответствующий коду выше, даст идею, как всё это понимать (запомните, nm — Ваш друг!):

Здесь мы видим три функции max , каждая из которых получила отличное имя в объектном файле, и мы можем проявить смекалку и предположить, что две следующие буквы после «max» обозначают типы входящих параметров — «i» как int , «f» как float и «d» как double (однако всё значительно усложняется, если классы, пространства имён, шаблоны и перегруженные операторы вступают в игру!).

Также стоит отметить, что обычно есть способ конвертирования между именами, видимых программисту и именами, видимых компоновщику. Это может быть и отдельная программа (например, c++filt ) или опция в командной строке (например —demangle для GNU nm), которая выдаёт что-то похожее на это:

Область, где схемы декорирования чаще всего заставляют ошибиться, находится в месте переплетения C и C++. Все символы, произведённые C++ компилятором, декорированы; все символы, произведённые C компилятором, выглядят так же, как и в исходном коде. Чтобы обойти это, язык C++ разрешает поместить extern «C» вокруг объявления и определения функций. По сути этим мы сообщаем C++ компилятору, что определённое имя не должно быть декорировано — либо потому что это определение C++ функции, которая будет вызываться кодом C, либо потом что это определение C функции, которая будет вызываться кодом C++.

Возвращаясь к примеру в самом начале статьи, можно легко заметить, что существует достаточно большая вероятность, что кто-то забыл использовать extern «C» при компоновке C и C++ объектов.

Большой подсказкой является то, что сообщение об ошибке содержит сигнатуру функции — это не просто сообщение о том, что findmax не найдено. Другими словами C++ код ищет что-то вроде «_Z7findmaxii» , а находит только «findmax» . Поэтому возникает ошибка компоновки.

Кстати заметьте, что объявление extern «C» игнорируется для функций-членов классов (§7.5.4 стандарта С++)

Инициализация статических объектов

Следующее выходящее за рамки С свойство C++, которое затрагивает работу компоновщика, — это существование конструкторов объектов. Конструктор — это кусок кода, который задаёт начальное состояние объекта. По сути его работа концептуально эквивалентна инициализации значения переменной, однако с той важной разницей, что речь идёт о произвольных фрагментах кода.

Вспомним из первой главы, что глобальные переменные могут начать своё существование уже с определённым значением. В C конструкция начального значения такой глобальной переменной — дело простое: определённое значение просто копируется из сегмента данных выполняемого файла в соответствующее место в памяти программы, которая вот-вот-начнёт-выполняться.

В C++ процесс инициализации может быть гораздо сложнее, чем просто копирование фиксированных значений; весь код в различных конструкторах по всей иерархии классов должен быть выполнен, прежде чем сама программа фактически начнёт выполняться.

Чтобы с этим справиться, компилятор помещает немного дополнительной информации в объектный файл для каждого C++ файла; а именно это список конструкторов, которые должны быть вызваны для конкретного файла. Во время компоновки компоновщик объединяет все эти списки в один большой список, а также помещает код, которые проходит через весь этот список, вызывая конструкторы всех глобальных объектов.

Обратим внимание, что порядок, в котором конструкторы глобальных объектов вызываются не определён — он полностью находится во власти того, что именно компоновщик намерен делать. (См. «Эффективный C++» Скотта Майерса для дальнейших деталей — заметка 47 во второй редакции, заметка 4 в третьей редакции)

Мы можем проследить за этими списками, опять же прибегнув к помощи nm . Рассмотрим следующий C++ файл:

Для этого кода (недекорированный) вывод nm выглядит так:

Как обычно, мы можем увидеть здесь кучу разных вещей, но одна из них наиболее интересна для нас это записи с классом W (что означает «слабый» символ («weak» symbol)) а также записи именем секции типа «.gnu.linkonce.t.stuff«. Это маркеры для конструкторов глобальных объектов и мы видим, что соответствующее поле «Name» показывает то, что мы собственно и могли там ожидать — каждый из двух конструкторов задействованы.

Шаблоны

Ранее мы приводили пример с тремя различными реализациями функции max , каждая из которых принимала аргументы различных типов. Однако, мы видим, что код тела функции во всех трёх случаях идентичен. А мы знаем, что дублировать один и тот же код — это дурной тон программирования.

C++ вводит понятия шаблона (templates), который позволяет использовать код, приведённый ниже, сразу для всех случаев. Мы можем создать заголовочный файл max_template.h с только одной копией кода функции max :

и включим этот файл в исходный файл, чтобы испробовать шаблонную функцию:

Этот написанный на C++ код использует max (int,int) и max (double,double) . Однако, какой-нибудь другой код мог бы использовать и другие инстанции этого шаблона. Ну, скажем, max (float,float) или даже max (MyFloatingPointClass,MyFloatingPointClass) .

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

Как же это делается? Обычно есть два пути действия: либо прореживание повторяющихся инстанций либо откладывание инстанциирования до стадии компоновки (я обычно называю эти подходы как разумный путь и путь компании Sun).

Способ прореживания повторяющихся инстанций подразумевает, что каждый объектный файл содержит код всех повстречавшихся шаблонов. Например, для приведённого выше файла, содержимое объектного файла выглядит так:

И мы видим присутствие обоих инстанций max (int,int) и max (double,double) .

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

Другой подход (который используется в Solaris C++) — это не включать шаблонные определения в объектные файлы вообще, а пометить их как неопределённые символы. Когда дело доходит до стадии компоновки, то компоновщик может собрать все неопределённые символы, которые собственно относятся к шаблонным инстанциям, и потом сгенерировать машинный код для каждой из них.

Это определённо редуцирует размер каждого объектного файла, однако минус этого подхода проявляется в том, что компоновщик должен отслеживать где исходной код находится и должен уметь запускать C++ компилятор во время компоновки (что может замедлить весь процесс)

Динамически загружаемые библиотеки

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

Это осуществляется парой системных вызовов dlopen и dlsym (примерные эквиваленты в Windows соответственно называются LoadLibrary и GetProcAddress ). Первый берёт имя разделяемой библиотеки и догружает её в адресное пространство запущенного процесса. Конечно, эта библиотека может также иметь неразрешённые символы, поэтому вызов dlopen может повлечь за собой подгрузку других разделяемых библиотек.

dlopen предлагает на выбор либо ликвидировать все неразрешённости сразу, как только библиотека загружена, ( RTLD_NOW ) либо разрешать символы по мере необходимости ( RTLD_LAZY ). Первый способ означает, что вызов dlopen может занять достаточно времени, однако второй способ закладывает определённый риск, что во время выполнения программы будет обнаружена неопределённая ссылка, которая не может быть разрешена — в этот момент программа будет завершена.

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

Взаимодействие с C++

Процесс динамической загрузки достаточно прямолинеен, но как он взаимодействует с различными особенностями C++, которые воздействуют на всё поведение компоновщика?

Первое наблюдение касается декорирования имён. При вызове dlsym , передаётся имя символа, который нужно найти. Значит это должно быть версия имени, видимая компоновщику, т.е. декорированное имя.

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

Подводя итог изложенному выше, отметим следующее: обычно лучше иметь одну заключённую в extern «C» точку вхождения, которая может быть найдена dlsym ‘ом. Эта точка вхождения может быть фабричным методом, который возвращает указатели на все инстанции C++ класса, разрешая доступ ко всем прелестям C++.

Компилятор вполне может разобраться с конструкторами глобальных объектов в библиотеке, подгружаемой dlopen , так как есть парочка специальных символов, которые могут быть добавлены в библиотеку, и которые будут вызваны компоновщиком (неважно во время загрузки или исполнения), если библиотека динамически догружается или выгружается — то есть необходимые вызовы конструкторов или деструкторов могут произойти здесь. В Unix это функции _init и _fini , или для более новых систем, использующих GNU инструментарий существуют функции, маркированные как __attribute__((constructor)) или __attribute__((destructor)) . В Windows соответствующая функция — DllMain с параметром DWORD fdwReason равным DLL_PROCESS_ATTACH или DLL_PROCESS_DETACH .

И в заключении добавим, что динамическая загрузка справляется отлично с «прореживанием повторяющихся инстанций», если речь идёт об инстанциировании шаблонов; и всё выглядит неоднозначно с «откладыванием инстанциирования», так как «стадия компоновки» наступает после того, как программа уже запущена (и вполне вероятно на другой машине, которая не хранит исходники). Обращайтесь к документации компилятора и компоновщика, чтобы найти выход из такой ситуации.

Дополнительно

В этой статье были намеренно пропущены многие детали о том, как компоновщик работает, потому что я считаю, что содержимое написанного покрывает 95% повседневных проблем, с которыми программист имеет дело при компоновке своей программы.

Если Вы хотите узнать больше, то можно почерпнуть информацию из ниже приведённых ссылок:

  • John Levine, Linkers and Loaders: содержит огромнейшее количество информации о тонкостях работы компоновщика и загрузчика, включая все вещи, пропущенные в этой статье. Также существует онлайн-версия этой книги (или её черновик) здесь.
  • Отличная ссылка на описание формата Mach-O для бинарников на Mac OS X.
  • Peter Van Der Linden, Expert C Programming: отличная книга, включающая больше информации о том, как код, написанный на C, трансформируется в запускаемую программу, чем любой другой труд о C, прочитанный мной.
  • Scott Meyers, More Effective C++: заметка 34 описывает ловушки, встречающиеся на пути комбинирования C и C++ в одной программе (касается не только работы компоновщика)
  • Bjarne Stroustrup, The Design and Evolution of C++: в главе 11.3 обсуждается компоновка в C++ и как это происходит.
  • Margaret A. Ellis & Bjarne Stroustrup, The Annotated C++ Reference Manual: глава 7.2c описывает конкретную схему декорирования имён
  • ELF format reference [PDF]
  • Две интересные статьи о создании легковесных выполняемых файлов в Linux и о минимальном Hello World в частности.
  • «How To Write Shared Libraries» [PDF] небезызвестного Ulrich Drepper содержит больше деталей о ELF и переадресации.

Many thanks to Mike Capp and Ed Wilson for useful suggestions about this page.

Copyright © 2004-2005,2009-2010 David Drysdale

Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.1 or any later version published by the Free Software Foundation; with no Invariant Sections, with no Front-Cover Texts, and with no Back-Cover Texts. A copy of the license is available here.

Илон Маск рекомендует:  Модуль kernel asm
Понравилась статья? Поделиться с друзьями:
Кодинг, CSS и SQL