С Новым годом! Форум программистов, компьютерный форум, киберфорум
С++ для начинающих
Войти
Регистрация
Восстановить пароль
Карта форума Темы раздела Блоги Сообщество Поиск Заказать работу  
 
Рейтинг 5.00/7: Рейтинг темы: голосов - 7, средняя оценка - 5.00
14 / 14 / 8
Регистрация: 26.09.2007
Сообщений: 919
1

Преобразования указателей

27.04.2021, 18:35. Показов 1280. Ответов 17
Метки нет (Все метки)

Author24 — интернет-сервис помощи студентам
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#include <windows.h>
#include <iostream>
 
struct Mammal {
  Mammal() { std::cout << "Mammal::Mammal\n"; }
  virtual ~Mammal() { std::cout << "Mammal::~Mammal\n"; };
  virtual void run() = 0;
  virtual void walk() = 0;
};
 
struct Dog : Mammal {
  Dog() { std::cout << "Dog::Dog\n"; }
  virtual ~Dog() { std::cout << "Dog::~Dog\n"; }
  virtual void run() { std::cout << "Dog::run\n"; }
  virtual void walk() { std::cout << "Dog::walk\n"; }
};
 
typedef void (*FunctionPtr_t)();
 
int main() {
 
    Mammal *m;
    m = new Dog();
 
    DWORD* vtable = *(DWORD**)m;
    FunctionPtr_t func = (FunctionPtr_t)*(vtable+2);
    func();
 
    delete m;
 
    return 0;
}
Я знаю как работают указатели, но мне не ясно как устроено это преобразование в стоке

C++
1
DWORD* vtable = *(DWORD**)m;
То есть как я понял m хранит адрес таблицы виртуальных функций. Почему мы не можем сразу сделать m+2 и вызвать функцию а надо именно такое преобразование *(DWORD**)m? То есть что дают эти две звездочки перед DWORD? Заранее спасибо.
0
Programming
Эксперт
94731 / 64177 / 26122
Регистрация: 12.04.2006
Сообщений: 116,782
27.04.2021, 18:35
Ответы с готовыми решениями:

Правила преобразования указателей
Много искал информации по этому вопросу, слышал от одного, что в каких-то ситуациях квалификаторы...

Почему в сортировке указателей на объекты в вызове функции используются адреса объектов, а не указателей?
Доброго времени суток! Рассматриваю пример (из Лафоре) сортировки массива указателей на объекты,...

Объяснить различия в работе указателей на целое число и указателей на const char (строки в стиле Си)
Уважаемые программисты, возникло несколько вопросов касательно указателей. Почему при выводе...

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

17
"C with Classes"
2022 / 1404 / 523
Регистрация: 16.08.2014
Сообщений: 5,884
Записей в блоге: 1
27.04.2021, 18:56 2
Цитата Сообщение от kurlyak Посмотреть сообщение
а надо именно такое преобразование *(DWORD**)m
DWORD** это своего рода хак, который из m имеющий тип Mammal *, делает тип DWORD **, что бы потом можно было применить разыменование * и получит указатель на таблицу виртуальных функций.
То есть в реализации класса Mammal в начале объекта есть указатель на таблицу виртуальных функций, язык C++ не дает стандартных способов доступа к нему, по этому нужно сделать из m указатель на указатель, что бы получить из него указатель на таблицу.
1
14 / 14 / 8
Регистрация: 26.09.2007
Сообщений: 919
27.04.2021, 19:26  [ТС] 3
Спасибо за ответ. А m это в нем и хранится адрес таблицы виртуальных функций? То есть *m указывает на таблицу виртуальных функций, да? Что хранится в m- адрес памяти по которому лежит адрес таблицы вирт. функций, или сразу адрес таблицы вирт. функций?
0
зомбяк
1584 / 1218 / 345
Регистрация: 14.05.2017
Сообщений: 3,940
27.04.2021, 19:35 4
kurlyak, https://ru.wikipedia.org/wiki/... ых_методов

Цитата Сообщение от kurlyak Посмотреть сообщение
адрес памяти по которому лежит адрес таблицы вирт. функций
Оттуда же

После создания объекта указатель на эту виртуальную таблицу, называемый виртуальный табличный указатель или vpointer (также иногда называется vptr или vfptr), добавляется как скрытый член данного объекта (а зачастую как первый член).
Собственно на том, что на использующемся компиляторе он оказался первым, и используется. Но это может не сработать, и вообще данные трюки используются только для объяснения работы компилятора, не более.

Добавлено через 4 минуты
В нормальном применении нужно вместо


C++
1
2
3
    DWORD* vtable = *(DWORD**)m;
    FunctionPtr_t func = (FunctionPtr_t)*(vtable+2);
    func();
использовать

C++
1
m->walk();
1
14 / 14 / 8
Регистрация: 26.09.2007
Сообщений: 919
27.04.2021, 20:10  [ТС] 5
Да, я как раз и разбираюсь как работает компилятор, это у меня учебный пример. А можно по- подробнее что значит каждая звездочка в выражении:

C++
1
*(DWORD**)m
слева первая я так понял мы получаем значение т.е. разыменовываем, внутри скобок одна звездочка значит мы преобразовываем к указателю DWORD.

То есть по идее вот так должно быть

C++
1
2
3
    DWORD* vtable = (DWORD*)m;
    FunctionPtr_t func = (FunctionPtr_t)*(vtable+2);
    func();
0
зомбяк
1584 / 1218 / 345
Регистрация: 14.05.2017
Сообщений: 3,940
27.04.2021, 20:28 6
Цитата Сообщение от kurlyak Посмотреть сообщение
То есть по идее вот так должно быть
Нет, это значит несколько другое.

(DWORD*)m - просто меняем тип адреса с Mammal * на DWORD*
*(DWORD**)m - представляем, что по адресу m лежит значение с адресом, по которому нужно перейти. Соответственно на (DWORD**) говорим о том, что у нас по адресу лежит адрес, а * в начале говорим "перейти по этому адресу, который там лежит"

Добавлено через 3 минуты
Если не понятно, повтори реализацию односвязного списка, там как раз иллюстрируются "прыжки по адресам"
1
14 / 14 / 8
Регистрация: 26.09.2007
Сообщений: 919
27.04.2021, 20:34  [ТС] 7
А! Вот оно что! Я понял, спасибо. Значит m это указатель, те ячейка памяти, она хранит адрес другой ячейки памяти, а в этой другой ячейке- адрес таблицы вирт.функций. И две звездочки в скобках- это значит мы по цепочке указателей обращаемся ко второй ячейке памяти (которая хранит адрес таблицы вирт.функ.) и разыменовываем эту вторую ячейку памяти (то есть звездочка перед скобками) что бы получить из нее адрес таблицы вирт. функ. Вроде так.
0
"C with Classes"
2022 / 1404 / 523
Регистрация: 16.08.2014
Сообщений: 5,884
Записей в блоге: 1
27.04.2021, 20:43 8
Цитата Сообщение от kurlyak Посмотреть сообщение
То есть по идее вот так должно быть
оно было бы так если бы m не был указателем.
C++
1
2
3
4
5
6
7
8
9
10
int main() {
 
    Dog m;
 
    DWORD* vtable = (DWORD*)&m;
    FunctionPtr_t func = (FunctionPtr_t)*(vtable+2);
    func();
 
    return 0;
}
Добавлено через 1 минуту
Цитата Сообщение от kurlyak Посмотреть сообщение
И две звездочки в скобках- это значит мы по цепочке указателей обращаемся ко второй ячейке памяти
хорошая интерпретация!
1
14 / 14 / 8
Регистрация: 26.09.2007
Сообщений: 919
27.04.2021, 22:34  [ТС] 9
Я для наглядности сделал чуть чуть по другому, тоже работает, но теперь не ясно DWORD p1 это адрес второй ячейки памяти? Если да, то что значит (DWORD*)p1 - это значит что мы берем значение из второй ячейки памяти (то есть таблица виртуальных функций), или что? Что значит это преобразование из DWORD p1 в указатель (DWORD*)p1? Или это значит, что мы преобразовываем адрес p1 в указатель и извлекаем из него значение вроде разыменовываем?

C++
1
2
3
4
5
6
    DWORD *p = (DWORD*)m;
    DWORD p1 = *p;
    DWORD *vtable = (DWORD*)p1;
    
    FunctionPtr_t func = (FunctionPtr_t)*(vtable+2);
    func();
0
19409 / 10028 / 2443
Регистрация: 30.01.2014
Сообщений: 17,678
27.04.2021, 23:52 10
kurlyak,
C++
1
2
3
    DWORD *p = (DWORD*)m; // интерпретируем m как указатель на DWORD. 
    DWORD p1 = *p; // Берем некое значение типа DWORD - целое, используя разыменование p
    DWORD *vtable = (DWORD*)p1; // Интерпретируем целое число с предыдущего шага как указатель.
если по смещению относительно m реально лежит какой-то указатель, то мы получим его значение в vtable. Это если конечно в DWORD на данной платформе указатель поместится без потерь (что далеко не факт).

Вообще же использование DWORD для хранения значения указателя здесь не нужно. Зачем, если можно сразу использовать тип указателя, как сделано было в стартовом посте.

Добавлено через 13 минут
Цитата Сообщение от kurlyak Посмотреть сообщение
C++
1
2
3
4
5
6
    Mammal *m;
    m = new Dog();
 
    DWORD* vtable = *(DWORD**)m;
    FunctionPtr_t func = (FunctionPtr_t)*(vtable+2);
    func();
C++
1
2
3
typedef DWORD* vptr_type;
 
vptr_type vtable = *(vptr_type*)m;

Код
                  объект типа Mammal
---------------------------------------------
|  DWORD* vptr  |         ...               |
---------------------------------------------
|
|
V

m - указатель на Mammal (Mammal*)

|
|
V

p - указатель на DWORD* (DWORD**)

|
|
V

*p - значение vptr (DWORD*)
1
14 / 14 / 8
Регистрация: 26.09.2007
Сообщений: 919
28.04.2021, 12:31  [ТС] 11
C++
1
2
3
4
5
DWORD *p = (DWORD*)m; //преобразуем Mammal к DWORD
    DWORD p1 = *p; //разыменовываем указатель то есть получаем адрес второй ячейки памяти где таблица вирт.функ.
    DWORD *vtable = (DWORD*)p1; //адрес второй ячейки памяти p1 интерпретируется как адрес памяти? или как разыменованый указатель?
    
    FunctionPtr_t func = (FunctionPtr_t)*(vtable+2);
то есть тут

C++
1
DWORD *vtable = (DWORD*)p1;
vtable равно то что по адресу хранится на который указывает p1, или vtable равно p1 как адрес?
0
зомбяк
1584 / 1218 / 345
Регистрация: 14.05.2017
Сообщений: 3,940
28.04.2021, 12:39 12
Цитата Сообщение от kurlyak Посмотреть сообщение
vtable равно то что по адресу хранится на который указывает p1
Адресу, который содержится (записан) в p1
1
14 / 14 / 8
Регистрация: 26.09.2007
Сообщений: 919
28.04.2021, 13:38  [ТС] 13
А почему эти два кода работают одинаково, несмотря что разное количество звездочек в указателях?

C++
1
2
3
4
5
DWORD *p = (DWORD*)m;
DWORD *vtable = (DWORD*)*p;
    
FunctionPtr_t func = (FunctionPtr_t)*(vtable+2);
func();
C++
1
2
3
4
5
DWORD **p = (DWORD**)m;
DWORD *vtable = (DWORD*)*p;
    
FunctionPtr_t func = (FunctionPtr_t)*(vtable+2);
func();
Добавлено через 25 минут
Так вы мне скажите, m хранит адрес таблицы вирт.функ. или m хранит адрес другой ячейки памяти, и эта другая ячейка памяти хранит адрес табл. вирт. функ.?
0
зомбяк
1584 / 1218 / 345
Регистрация: 14.05.2017
Сообщений: 3,940
28.04.2021, 13:40 14
Не совсем. Адрес может не поместиться в DWORD при компиляции в 64-битной системе, в результате при выполнении *p у тебя часть адреса потеряется из-за переполнения. И первый вариант не сработает.
0
19409 / 10028 / 2443
Регистрация: 30.01.2014
Сообщений: 17,678
28.04.2021, 14:06 15
Цитата Сообщение от kurlyak Посмотреть сообщение
m хранит адрес таблицы вирт.функ. или m хранит адрес другой ячейки памяти, и эта другая ячейка памяти хранит адрес табл. вирт. функ.?
Как я уже выше сказал, m хранит адрес объекта типа Mammal. У этого объекта в составе есть указатель (на таблицу виртуальных функций), он располагается первым относительно начала объекта. Таким образом получается, что адрес всего объекта и адрес этого указателя численно совпадают. Поэтому мы можем сделать приведение m к адресу указателя (адрес указателя - это указатель на указатель, т.е. две звездочки). Когда мы разыменуем получившийся двойной указатель после этого приведения, мы получим значение адреса таблицы виртуальных функций и сможем использовать его для доступа к функциям.
Если вы внимательно перечитаете что выше было написано, то вы увидите, что вся эта информация там уже содержится.
1
14 / 14 / 8
Регистрация: 26.09.2007
Сообщений: 919
28.04.2021, 18:01  [ТС] 16
То есть мы указатель m приводим к (DWORD*) потом разыменовываем и еще раз приводим к (DWORD*) и получаем указатель на таблицу вирт.функ. как строчка кода ниже:

C++
1
DWORD* vtable = (DWORD*)*(DWORD*)m;
А вот в этом случае звездочка разыменования относится к первому указателю который хранит указатель на таб.вирт.функ. Как разобраться к чему относится звездочка разыменования тут?

C++
1
DWORD* vtable = *(DWORD**)m;
0
фрилансер
5848 / 5379 / 1103
Регистрация: 11.10.2019
Сообщений: 14,380
28.04.2021, 18:05 17
kurlyak, * разыменовывает то, что справа, с учётом приоритета операторов

если приоритеты не помнишь (я вот не всегда помню), его всегда можно точно задать при помощи скобок
1
зомбяк
1584 / 1218 / 345
Регистрация: 14.05.2017
Сообщений: 3,940
28.04.2021, 18:07 18
Цитата Сообщение от kurlyak Посмотреть сообщение
Как разобраться к чему относится звездочка разыменования тут?
К разыменовыванию указателя на указатель. По смыслу ровно то же самое что первое, только после разыменования адрес помещается не в переменную типа DWORD, а сразу в переменную типа DWORD *
1
28.04.2021, 18:07
IT_Exp
Эксперт
87844 / 49110 / 22898
Регистрация: 17.06.2006
Сообщений: 92,604
28.04.2021, 18:07
Помогаю со студенческими работами здесь

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

Как обойтись без указателей и указателей на указатель?
Ибо не совсем выходит понять,что на что тут указывает #include &quot;stdafx.h&quot; #include &lt;iostream&gt;...

Различия указателей char* от указателей других типов
Помогите пожалуйста разобраться! Прочитал раздел про указатели и даже вроде бы понял. Что...

Создание массивов указателей на массивы указателей
Помогите в решении задачи: создал массив указателей на массивы указателей на строки, но компилятор...

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

Массив указателей на массив строк и сортировка массива указателей
Добрый день. Поступил вопрос. Есть задача. У нас встроенный массив char mass;.Мы вводим строки до...


Искать еще темы с ответами

Или воспользуйтесь поиском по форуму:
18
Ответ Создать тему
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2024, CyberForum.ru