Версия для печати
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум на Исходниках.RU > C/C++: Общие вопросы > static_cast для получения значения адреса в числовом представлении


Автор: Eric-S 21.12.17, 08:38
Здравствуйте!

Несколько раз встречал рекомендацию, что указатель в число, можно приводить через reinterpret_cast, но рекомендуется через static_cast.
Решил поправить старый код. Но тут вылезли какие-то ошибки.

<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
          int a = 0;
          const int* const p = &a;
     
          /* проверка размерности  указателя */
          static_assert( sizeof( const unsigned long long int ) == sizeof( const void* const ), "check sizeof pointer" );
     
    // работает, но не рекомендуется
          const unsigned long long int intx = reinterpret_cast< const unsigned long long int >( p );
     
          /* так рекомендуется, но не работает */
          const void* const y1 = static_cast< const void* const >( p );
          const unsigned long long int y2 = static_cast< const unsigned long long int >( y1 );


Может быть я пропустил какой-то этап? Или это тот самый мифический глюк компилятора?

Автор: ЫукпШ 21.12.17, 10:21
Это ?
<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
      int a = 0;
      const int* const p = &a;
     
          /* проверка размерности  указателя */
    //  static_assert( sizeof( const unsigned long long int ) == sizeof( const void* const ), "check sizeof pointer" );
     
    // работает, но не рекомендуется
      //    const unsigned long long int intx = reinterpret_cast< const unsigned long long int >( p );
     
          /* так рекомендуется, но не работает */
     //     const void* const y1 = static_cast< const void* const >( p );
     //     const unsigned long long int y2 = reinterpret_cast< const unsigned long long int >( y1 );
     
     
      const unsigned long long int y2 = *static_cast<const unsigned long long int*>(static_cast<const void* const>(&p));

Автор: Qraizer 21.12.17, 10:36
Преобразование между целыми и указателями никогда не были стандартизованы. Любые такие касты определяются исключительно реализациями. Именно по этой причине они и осуществляются посредством reinterpret_cast<>, потому как непереносимы. Поэтому ты нигде не мог читать совет по замене их на static_cast<>.

Автор: Eric-S 21.12.17, 10:46
Цитата Qraizer @
Поэтому ты нигде не мог читать совет по замене их на static_cast<>.

Цитата
для преобразования указателей лучше использовать двойной static_cast через void* вместо reinterpret_cast, потому как такое преобразование позволяет быть уверенным в том, что только pointer-ы участвуют в приведении

https://habrahabr.ru/post/106294/

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

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

https://habrahabr.ru/company/pvs-studio/blog/340458/

Добавлено
Цитата ЫукпШ @
Это ?

Упс. А я и запамятовал о таком весёлом хаке. Иногда сам делал. А тут даже не подумал применить. Спасибо за идеию!

Автор: Qraizer 21.12.17, 11:57
Цитата Eric-S @
Цитата
для преобразования указателей лучше использовать двойной static_cast через void* вместо reinterpret_cast, потому как такое преобразование позволяет быть уверенным в том, что только pointer-ы участвуют в приведении

https://habrahabr.ru/post/106294/
Читай внимательно: "...для преобразования указателей...", а не между указателями и интегралами; и "...такое преобразование позволяет быть уверенным в том, что только pointer-ы участвуют в приведении" как раз исключает ситуацию, которую ты хочешь осуществить, а я соответственно комментирую.

Добавлено
Цитата Eric-S @
Упс. А я и запамятовал о таком весёлом хаке. Иногда сам делал. А тут даже не подумал применить. Спасибо за идеию!
С тем же успехом ты это можешь сделать через union. Этот ровно тот же самый reinterpret_cast<>.

Автор: Eric-S 21.12.17, 13:00
Цитата Qraizer @
С тем же успехом ты это можешь сделать через union. Этот ровно тот же самый reinterpret_cast<>.

Да, знаю. Но я хотел, чтоб входное значение, сразу возвращалось. Тест в первом сообщении только для наглядности.
У меня в реале шяблонная функция, которая принимает указатель произвольного типа и возвращает число.

Добавлено
Цитата Qraizer @
Читай внимательно: "...для преобразования указателей...", а не между указателями и интегралами; и "...такое преобразование позволяет быть уверенным в том, что только pointer-ы участвуют в приведении" как раз исключает ситуацию, которую ты хочешь осуществить, а я соответственно комментирую.

Понятно. Спасибо. Это я затупил.

Автор: ЫукпШ 21.12.17, 13:04
Цитата Qraizer @
С тем же успехом ты это можешь сделать через union. Этот ровно тот же самый reinterpret_cast<>.

А я полагаю, что это всё-таки не совсем так.
reinterpret_cast или преобразование типов в стиле С это такой примерно приказ:
"Компилятор ! Приказываю считать жёлтое - зелёным ! Твоё мнение меня не интересует."
В данном случае Eric-S желает узнать значение указателя.
Указатель - это тоже участок памяти некоторого размера. Узнаем его адрес, преобразуем
тип адреса в адрес на переменную, равную по длине оригинальному указателю.
Разименуем указатель и, таким образом, прочитаем значение указателя.
---
Ещё можно использовать пару sprintf/sscanf.

Автор: Eric-S 21.12.17, 13:41
Цитата ЫукпШ @
В данном случае Eric-S желает узнать значение указателя.

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

Автор: Qraizer 21.12.17, 13:41
ЫукпШ, это работает ровно до тех пор, пока репрезентация указателей разных типов одинакова. Никто не гарантирует, что так оно и будет везде и всегда. И даже больше: кое-где это не так.
Философия кастов довольно проста, хотя и не очевидна. Когда программисту требуется некая сущность, ему в подавляющем большинстве случаев пофигу её репрезентация, но ему важны её свойства. Он декларирует как раз свойства этой сущности, назначая ей тип, а не способ её репрезентации в памяти. Когда программисту требуется сменить тип сущности, в подавляющем большинстве случаев ему нужно сменить набор её свойств, а не репрезентацию в памяти. Собственно поэтому считается, что если программа содержит много кастов, она плохо спроектирована, ибо ненормально, что когда свойства сущностей меняются часто.
Так вот. В подавляющем большинстве случаев программисту нужно сменить свойства без потери хранимой информации. Это static_cast<>. Зачастую для сохранения хранимого значения при смене типа требуется сменить репрезентацию в памяти. Но программиста это обычно не заботит. Ему важно, что он имел, например, значение 123 со свойствами целого, а теперь ему понадобилось значение со свойствами вещественного, но то же самое, т.е. 123. Но изредка ему нужно ту же, уже имеющуюся, репрезентацию считать как сущность с другими свойствами. Например, когда это значение собирается по кусочкам посредством сериализации/десериализации. Вот тогда reinterpret_cast<>.
Т.к. такая сборка по кусочкам крайне зависима от реализации, подобный код непортабелен, и должен быть хорошо различим в исходных текстах. Посему скрывать сие под парой static_cast<>-ов по меньшей мере зарывать грабли под траву.
Так что я вообще не понимаю, что за задача у Eric-S.

Автор: Eric-S 21.12.17, 13:45
Цитата Qraizer @
ЫукпШ, это работает ровно до тех пор, пока репрезентация указателей разных типов одинакова. Никто не гарантирует, что так оно и будет везде и всегда. И даже больше: кое-где это не так.

А можно поподробнее? И как такие ситуации узнать?

Добавлено
Цитата Qraizer @
Вот тогда reinterpret_cast<>.
Т.к. такая сборка по кусочкам крайне зависима от реализации, подобный код непортабелен, и должен быть хорошо различим в исходных текстах. Посему скрывать сие под парой static_cast<>-ов по меньшей мере зарывать грабли под траву.

Раньше, и довольно долго, у меня был именно reinterpret_cast.
Но несколько раз попадались утверждения, что компиляторы, сущности пропущенные через reinterpret_cast, как-то странно размещают.
Я так понял, что неоднозначно использовать такие значения.
Но возможно, я что-то криво понял.

Автор: Qraizer 21.12.17, 15:59
Цитата Eric-S @
Но несколько раз попадались утверждения, что компиляторы, сущности пропущенные через reinterpret_cast, как-то странно размещают.
Одно из двух: либо reinterpret_cast<> использовался не по назначению (как-то была тема, где автор пытался им перекрёстное приведение сделать, за которое вообще-то отвечает dynamic_cast<>, но он пытался static_cast<>-ом и естественно получал ошибку компиляции, а reinterpret_cast<> конечно же молча глотал, но вот в run-time... неудивительно, что эксепшн), либо он неверно использовался, например, к данным с разными требованиями к выравниванию.

Добавлено
Цитата Eric-S @
А можно поподробнее? И как такие ситуации узнать?
Ну как-как... по документации на исполнительную платформу. Был такой микропроцессор, который имел 4Гб адресное пространство, но только 1Гб физической памяти, при этом один и тот же один гигабайт отображался на все четыре, и номер гигабайта выбирал ширину данных: 1 байт, 2 байта, 4 байта или 8 байт.

Добавлено
Компилятор естественно это учитывал при всяких там char data[1024]; (int*)data[10], но вот ты поди с интами так просто сделай.

Автор: Eric-S 21.12.17, 17:27
Цитата Qraizer @
Одно из двух...

Возможно и так. Я глубоко не рыл.
Как-то даже слышал, что типы int и float могут размещать в разной памяти. В том смысле, что у них может быть одновременно один адрес, но там будут храниться два разных значения. И компилятор обязан это учитывать.
Но вспомнить могу лишь древнюю числодробилку Урал-1 т-ща Ромеева. Там вроде как было два барабана, на первый писались целые, а на второй дробные числа.

Добавлено
Цитата Qraizer @
Ну как-как... по документации на исполнительную платформу.

Документация это понятно. Я-то надеялся, что есть какой-то стандартный признак для переключения.

Вообщем, я откотил ревизию и вернулся к reinterpret_cast.
Ваша правда - нечего мудрить, если работает более простой и понятный вариант.

Автор: amk 21.12.17, 20:08
Цитата Qraizer @
Был такой микропроцессор, который имел 4Гб адресное пространство, но только 1Гб физической памяти
Какой-то из сигнальников Texas Instruments помнится так устроен.

Автор: Qraizer 21.12.17, 20:27
Я не помню, amk. Я с ним не работал. В КБ, до меня, работали и рассказывали. Я тогда, более 15 лет назад, только-только приобщался к понятию портабельного программирования. Поневоле, ибо приходилось писать под железку с крайне слабыми отладочными средствами, поэтому всё что можно, пытались писать и отлаживать на привычном окружении PC под Win98/XP. Рассказ оказался поучительным.

Добавлено
Ещё рассказывали про 32-битный DSP с CHAR_BIT, равным 32. Не менее весёлая архитектура. Зато sizeof чего угодно == 1.

Автор: amk 22.12.17, 01:07
Цитата Qraizer @
Ещё рассказывали про 32-битный DSP с CHAR_BIT, равным 32. Не менее весёлая архитектура. Зато sizeof чего угодно == 1.
Ну, остальные DSP от TI именно такие. Там только double имеет размер отличный от 1. По работе не так давно пришлось ознакомиться.

Автор: ЫукпШ 22.12.17, 09:48
Цитата Qraizer @
ЫукпШ, это работает ровно до тех пор, пока репрезентация указателей разных типов одинакова. Никто не гарантирует, что так оно и будет везде и всегда. И даже больше: кое-где это не так.

Qraizer, да.
У микроконтроллеров бывают самые неожиданные варианты адресации.
Общего решения нет.
Значит, подобные мероприятия в каждом конкретном случае решаются индивидуально.
Можно добавить, что подобная операция мало чем отличается от де-сериализации.
Есть некая память с содержимым, разбор данных которой целиком выполняется "вручную".
Выполняя её мы всю ответственность берём на себя.
---
Кроме того, мероприятия с чтением указателя можно сделать более "безопасными".
Сначала узнаем размер указателя. sizeof. Сравним его с размером
переменной, в которую собираемся его считывать. и.т.д.
Или выделим достаточную память для чтения содержимого указателя.
В данном случае нам заранее известно, что 64-битной переменной достаточно
для приёма любого адреса.

Автор: Dushevny 22.12.17, 10:05
Цитата ЫукпШ @
Сначала узнаем размер указателя. sizeof. Сравним его с размером переменной, в которую собираемся его считывать.
делаем #include <stdint.h>. Используем uintptr_t. Все уже придумано до нас.

Автор: Eric-S 22.12.17, 11:07
Цитата Dushevny @
делаем #include <stdint.h>. Используем uintptr_t. Все уже придумано до нас.

Угу. Но это уже другая тема.
Я написал нечто вроде:
typedef uintptr_t address_type;

Цитата ЫукпШ @
Сначала узнаем размер указателя. sizeof. Сравним его с размером
переменной, в которую собираемся его считывать.

Я там прописал unsigned long long int... Но на самом деле использовался адаптивный тип address_type, зависящий от размера указателя на платформе.
Ну и дополнительно, чтобы проверить совпадает ли размерность, вставил static_assert.

Автор: JoeUser 23.12.17, 11:43
Цитата Eric-S @
Ну и дополнительно, чтобы проверить совпадает ли размерность, вставил static_assert.

Ничего проверять не надо. Как писал Qraizer выше, делаем "union_cast", в котором два поля uintptr_t и uintmax_t. И можем считать указатели)

Powered by Invision Power Board (https://www.invisionboard.com)
© Invision Power Services (https://www.invisionpower.com)