Форум программистов, компьютерный форум, киберфорум
C++
Войти
Регистрация
Восстановить пароль
Карта форума Темы раздела Блоги Сообщество Поиск Заказать работу  
 
 
Рейтинг 4.53/34: Рейтинг темы: голосов - 34, средняя оценка - 4.53
0 / 0 / 0
Регистрация: 11.11.2017
Сообщений: 50
1

Дорого ли приведение указателей?

14.11.2019, 18:19. Показов 6096. Ответов 55
Метки c++ (Все метки)

Author24 — интернет-сервис помощи студентам
Я не встретил эту тему в инете(именно про указатели, а не просто типы), но если она есть, дайте ссылку. На сколько дорога операция приведения указателя в стиле СИ? К примеру, есть два класса, один наследует другой. Дорого ли приведение указателя из указателя на родительский класс на указатель на дочерний? Я знаю, что указатель—это обычный int, который система принимает за адрес в памяти. Переменная, содержащая в себе указатель на объект представляет из себя int со значением вида 0x7f84a41000c0. Тогда, по идее, это не дорого для процессора. Я прав?
0
Лучшие ответы (1)
Programming
Эксперт
94731 / 64177 / 26122
Регистрация: 12.04.2006
Сообщений: 116,782
14.11.2019, 18:19
Ответы с готовыми решениями:

Приведение указателей
В функции в качестве параметра передаю указатель на один из самых базовых классов . Затем в функции...

Приведение указателей
Вопрос немного из другого раздела, но тем не менее, вопросы не по поводу WinApi, а поводу...

приведение типов указателей
Задача у меня простая. Нужно побитно оперировать с числом unsigned int и на каких-то этапах...

Приведение указателей в стиле си
Здравствуйте, это наверное самый дурацкий вопрос но что значит скобочки в c++ т.е вот например дан...

55
фрилансер
5497 / 5093 / 1047
Регистрация: 11.10.2019
Сообщений: 13,338
21.11.2019, 12:21 41
Author24 — интернет-сервис помощи студентам
COKPOWEHEU, IGPIGP, зачем спорить. Можно же просто провести нагрузочный тест для обоих вариантов. И проверить, сколько времени займёт миллион таких вызовов
0
18840 / 9839 / 2408
Регистрация: 30.01.2014
Сообщений: 17,280
21.11.2019, 12:32 42
Цитата Сообщение от IGPIGP Посмотреть сообщение
для запуска виртуального метода задействуется механизм выяснения (конкретизации) динамического типа.
На самом деле нет ни одной реализации, где было бы так, как вы описываете.
Виртуальность никогда не реализовывалась через RTTI и сама по себе виртуальность появилась исторически гораздо раньше, чем RTTI (разница в почти 13 лет).
2
Комп_Оратор)
Эксперт по математике/физике
8949 / 4703 / 629
Регистрация: 04.12.2011
Сообщений: 13,999
Записей в блоге: 16
21.11.2019, 12:36 43
Цитата Сообщение от Алексей1153 Посмотреть сообщение
COKPOWEHEU, IGPIGP, зачем спорить. Можно же просто провести нагрузочный тест для обоих вариантов. И проверить, сколько времени займёт миллион таких вызовов
Ok, - на каждом компиляторе, на каждой версии. Вопрос же топика о скорости приведений.
Можно попробовать. Но если не заставлять строки использовать придётся сравнивать два type_id? А указатель нужно лишь на на проверить. Вечером может поковыряю. А вам, как инициатору, предлагаю положить свою версию теста.

Добавлено через 3 минуты
Цитата Сообщение от DrOffset Посмотреть сообщение
На самом деле нет ни одной реализации, где было бы так, как вы описываете.
Виртуальность никогда не реализовывалась через RTTI и сама по себе виртуальность появилась исторически гораздо раньше, чем RTTI (разница в почти 13 лет).
Сейчас подумал и понял, что увлёкся.
То есть, вы полагаете, type_id и dynamic_cast реализованы по разному и type_id может быть быстрее? Спор об этом возник.
0
фрилансер
5497 / 5093 / 1047
Регистрация: 11.10.2019
Сообщений: 13,338
21.11.2019, 12:44 44
IGPIGP, я не знаю точно, как именно организовать тест, чтобы всякие там кеши процессора не повлияли. Вызовы в цикле без всяких заморочек подойдут? Или лучше пересоздавать объект на каждой итерации?
0
18840 / 9839 / 2408
Регистрация: 30.01.2014
Сообщений: 17,280
21.11.2019, 13:00 45
Цитата Сообщение от IGPIGP Посмотреть сообщение
То есть, вы полагаете, type_id и dynamic_cast реализованы по разному и type_id может быть быстрее? Спор об этом возник.
Нет, я такого не говорил.

Во-первых я хотел бы сказать, что не любой typeid, также как и не любой dynamic_cast обязаны разрешаться на этапе исполнения. Если у компилятора есть вся необходимая информация, то runtime структуры с динамической информацией о типе даже не задействуются.

Во-вторых в общем смысле type_id и dynamic_cast - это части RTTI.
И тот и другой при условии динамического исполнения черпают информацию из одного источника - специальной структуры данных c динамической информацией о типе.
Вот ссылка на то, как это делается в Itanium ABI (в Windows ABI примерно так же): https://itanium-cxx-abi.github... .html#rtti

Оттуда:
Как сравниваются два typeid:
In a flat address space (such as that of the Itanium architecture), the operator==, operator!=, and before() members are easily implemented in terms of an address comparison of the name NTBS.
Как реализован dynamic_cast:
https://itanium-cxx-abi.github... -algorithm
Т.е. никакого сравнения строк нет, и нет даже поиска по хеш-таблице, как в более ранних реализациях.
2
фрилансер
5497 / 5093 / 1047
Регистрация: 11.10.2019
Сообщений: 13,338
21.11.2019, 13:20 46
в общем, насчёт корректности теста не уверен, критикуйте
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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
#include <chrono>
#include <iostream>
 
class A
{
    int f1=0;
public:
    virtual ~A()
    {
    }
};
 
class B:public A
{
public:
    virtual ~B()
    {
    }
};
 
int main()
{
    const int64_t iters=10000000;
    std::cout<<"iters: "<<iters<<std::endl;;
    {
        auto start = std::chrono::system_clock::now();
        for(int64_t i=0; i<iters; i++ )
        {
            A* a=new B();
 
            auto* b=dynamic_cast<B*>(a);
 
            delete a; a=0;
        }
        auto end = std::chrono::system_clock::now();
        auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(end-start).count();
        std::cout<<"dynamic_cast: "<<elapsed<<" ms"<<std::endl;
    }
 
    {
        auto start = std::chrono::system_clock::now();
        for(int64_t i=0; i<iters; i++ )
        {
            A* a=new B();
 
            const std::type_info& info=typeid(*a);
            //std::string s=info.name();
 
            delete a; a=0;
        }
        auto end = std::chrono::system_clock::now();
        auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(end-start).count();
        std::cout<<"typeid: "<<elapsed<<" ms"<<std::endl;;
    }
 
    system("pause");
    return 0;
}
MinGW 5.3.0 32bit , запускал в дебаге, результат:

iters: 10000000
dynamic_cast: 1288 ms
typeid : 1168 ms
0
18840 / 9839 / 2408
Регистрация: 30.01.2014
Сообщений: 17,280
21.11.2019, 13:36 47
Алексей1153, во-первых участие в замере динамической аллокации сводит на нет всю достоверность.

А что вы хотели этим тестом показать? Можете словами сформулировать?
0
фрилансер
5497 / 5093 / 1047
Регистрация: 11.10.2019
Сообщений: 13,338
21.11.2019, 13:41 48
DrOffset, лично я - ничего не собирался показывать https://www.cyberforum.ru/post14016822.html
Но разница между двумя интервалами показывает, что выполняется быстрее, поскольку всё остальное в циклах одинаковое.
0
18840 / 9839 / 2408
Регистрация: 30.01.2014
Сообщений: 17,280
21.11.2019, 13:52 49
Цитата Сообщение от Алексей1153 Посмотреть сообщение
лично я - ничего не собирался показывать
Ну как же, вы предложили разрешить спор через замер. Значит вы знаете что нужно замерить, чтобы склонить чашу весов в ту или иную сторону. Вот я и спросил, что именно вы меряете?

Цитата Сообщение от Алексей1153 Посмотреть сообщение
поскольку всё остальное в циклах одинаковое.
Это не так. У вас там аллокации. Которые никто не обязывал отрабатывать за одинаковое время. И замеряете вы в дебаге, а там могут быть всякие проверки, которые замедляют выполнение. Это безотносительно сути замера.
Но это даже не важно здесь.
Важнее понять что именно мы меряем.

Вот у нас есть typeid. Что это? Это получение указателя на type_info из VTBL.
Код с typeid условно такой:
C++
1
std::type_info * info = a->vptr->ptypeinfo;
И все. Вот именно это вы и меряете.

Код с dynamic_cast условно такой:
C++
1
2
std::type_info * info = a->vptr->ptypeinfo;
b = someoffsetalgo(info, a); // см. ссылку выше
Ну так и давайте спросим, как можно сравнивать получение указателя с какой-то выполняемой логикой с использованием данных по этому казателю? Что это дает-то? Это разные же действия, с разной семантикой, с разным результатом.
1
3881 / 2479 / 418
Регистрация: 09.09.2017
Сообщений: 10,879
21.11.2019, 14:05 50
Цитата Сообщение от IGPIGP Посмотреть сообщение
COKPOWEHEU, боюсь, по какой-то фундаментальной причине мы не можем тут друг друга понять.
Для работы механизма таблицы виртуальных функций нет нужды в знании типа. Меня самого заинтересовала эта задача, так что набросал демонстрационный код (АХТУНГ! код кишит неопределенным поведением и старательно использует внутренности g++. Он может вызвать сегфолт, поломать чужую память и погрызть тапки!)
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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
#include <stdio.h>
#include <string.h>
 
//объявляем родительский класс с виртуальными полями
class cCl{
public:
  int field;
  cCl(){field = 1;}
  virtual void func(){printf("cCl\n");}
  virtual int func2(int a, int b){printf("cCl(%i, %i) ", a, b); return a+b+field;}
};
 
//объявляем дочерний класс с не менее виртуальными полями
class cChild:public cCl{
public:
  cChild(){field = 2;}
  virtual void func(){printf("cChild\n");}
  virtual int func2(int a, int b){printf("cChild(%i, %i) ", a, b); return a-b+field;}
};
 
//прототипы "методов"
typedef void (cCl_func)(cCl*);
typedef int (cCl_func2)(cCl*, int, int);
 
//хакерская структура для получения доступа к внутренностям классов
struct sCl{
  void **fn; //таблица виртуальных функций!
  int field; //поля класса (для демонстрации не обязательны)
};
 
void test(cCl *self){printf("test\n");}
 
int main(){
  cCl *sample = new cCl; //создаем "чистый" экземпляр родителя
  cCl *virt = new cChild; //создаем потомка через наследование
  cChild *child = new cChild; //создаем "чистого" потомка
  sCl *str;
  int res;
  
  //влезаем во внутренности родителя
  str = (sCl*)sample;
  printf("Sample: ");
  ((cCl_func*)str->fn[0])(sample); //ТАДАМ! Мы вызвали виртуальный метод func
  res = ((cCl_func2*)str->fn[1])(sample, 2, 1); //ТАДАМ! Мы вызвали виртуальный метод func2 даже с параметрами
  printf("%i\n", res);
  printf("\n");
  
  str = (sCl*)virt;
  printf("Virt  : ");
  ((cCl_func*)str->fn[0])(virt);
  res = ((cCl_func2*)str->fn[1])(virt, 2, 1);
  printf("%i\n", res);
  printf("\n");
  
  str = (sCl*)child;
  printf("Child : ");
  ((cCl_func*)str->fn[0])(child);
  res = ((cCl_func2*)str->fn[1])(child, 2, 1);
  printf("%i\n", res);
  printf("\n");
  
  
  str->fn = ((sCl*)sample)->fn;
  printf("Hacked: ");
  child->func();
  res = child->func2(2,1);
  printf("%i\n", res);
  str->fn = ((sCl*)child)->fn;
  
  
  //не забываем прибраться за собой!
  delete child;
  delete virt;
  delete sample;
}
Код
$ g++ main.c
$ ./a.out 
Sample: cCl
cCl(2, 1) 4

Virt  : cChild
cChild(2, 1) 3

Child : cChild
cChild(2, 1) 3

Hacked: cCl
cCl(2, 1) 5
TL; DR: перед явными полями классов есть еще указатель на массив виртуальных функций, из которого при некотором извращении их можно вызвать просто по индексам. Примерно так же это работает и в штатном режиме: если функция не-виртуальная, компилятор тупо подставляет фиксированный адрес, если же виртуальная - вызывает по номеру из массива. Более того, адрес этой таблицы можно даже подменить! Это и продемонстрировано в последнем вызове, когда объект cChild вдруг начинает считать себя cCl'ом (но вот поле field у него сохраняется).
Собственно, именно отсюда берется "лишний переход по указателю", о котором я говорил с самого начала.
1
фрилансер
5497 / 5093 / 1047
Регистрация: 11.10.2019
Сообщений: 13,338
21.11.2019, 14:12 51
DrOffset, про корректность теста я выше сразу написал - что я не уверен в ней.
0
18840 / 9839 / 2408
Регистрация: 30.01.2014
Сообщений: 17,280
21.11.2019, 14:14 52
Цитата Сообщение от Алексей1153 Посмотреть сообщение
про корректность теста я выше сразу написал - что я не уверен в ней.
Ну вот я вам показал, надеюсь, в чем именно проблема.
Просто сначала я хотел чтобы вы сами к этому пришли. Если бы вы начали формулировать словами достаточно строго смысл ваших замеров, то сами столкнулись бы с противоречием.
0
фрилансер
5497 / 5093 / 1047
Регистрация: 11.10.2019
Сообщений: 13,338
21.11.2019, 14:26 53
DrOffset, ну так в итоге то что, получается, нужно оставить циклы вот в таком виде и запускать в релизе?


C++
1
2
3
4
5
6
7
8
9
        for(int64_t i=0; i<iters; i++ )
        {
             auto* b=dynamic_cast<B*>(a);
        }
 
        for(int64_t i=0; i<iters; i++ )
        {
            const std::type_info& info=typeid(*a);
        }
0
18840 / 9839 / 2408
Регистрация: 30.01.2014
Сообщений: 17,280
21.11.2019, 15:54 54
Цитата Сообщение от Алексей1153 Посмотреть сообщение
нужно оставить циклы вот в таком виде и запускать в релизе?
Нет. Это же разные действия.
Сравнивать имеет смысл только эквивалентные по результату действия. Например два алгоритма сортировки. Результат одинаковый, процессы разные. Вот можно их сравнить. А тут?

Добавлено через 3 минуты
Ну хорошо, допустим мы сравнили, избавились от излишней оптимизации, оставили нужную - получили результат.
Как это докажет или опровергнет позицию одной из сторон?

Не по теме:

В вашей текущей реализации просто исходя из количества действий первый цикл всегда будет медленнее, чем второй. Можно даже не мерить ничего. Но что это значит в контексте спора? Мне вот ответ на этот вопрос не очевиден совсем.

0
фрилансер
5497 / 5093 / 1047
Регистрация: 11.10.2019
Сообщений: 13,338
21.11.2019, 16:07 55
DrOffset, в общем, понятно, работает вечное правило оптимизации - сначала нужно доказать, что оптимизация нужна, только потом начинать замерять и оптимизировать
0
Вездепух
Эксперт CЭксперт С++
11694 / 6373 / 1723
Регистрация: 18.10.2014
Сообщений: 16,057
22.11.2019, 00:02 56
Цитата Сообщение от Dimgo2 Посмотреть сообщение
На сколько дорога операция приведения указателя в стиле СИ?
Преобразование указателей в стиле С в языке С++ - слишком универсальная операция. Она может быть

1. Чисто концептуальной: не порождать кода вообще, т.е. быть бесплатной

2. Производить сдвиг указателя на значение времени компиляции. Так как язык требует, чтобы null-указатель превращался именно null-указатель, такой сдвиг должен еще сопровождаться проверкой на null. Это уже дороже, но все равно относительно дешево.

3. Производить модификацию значения указателя на значение времени выполнения. Такое возможно при преобразованиях указателей на члены класса (хотя они формально не являются "указателями").

4. Выполнять относительно "тяжелые" обращения к структурам в памяти, например, при преобразовании указателей в иерархии с виртуальным наследованием. Опять же, оговорка про null имеет место и здесь.

Так что все зависит от контекста.
0
22.11.2019, 00:02
IT_Exp
Эксперт
87844 / 49110 / 22898
Регистрация: 17.06.2006
Сообщений: 92,604
22.11.2019, 00:02
Помогаю со студенческими работами здесь

Неявное приведение указателей на классы
Всем привет! Обнаружилась вот такая нестыковочка: имеем интерфейсный класс IIn. И имеем...

Приведение типов умных указателей
Добрый день. Реализовал простенький умный указатель с подсчетом ссылок. template&lt;typename...

Отличие приведение типов указателей
Чем отличаются при Base* a_ptr = new Derivered(); следующие строки: A) auto ptr =...

Объяснить, что происходит в коде (приведение одного типа указателей к другому?)
char* a = &quot;Hell&quot;; int* b =(int*) a; cout &lt;&lt; *b; Как я понимаю в этом кусочке кода происходит...


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

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