Форум программистов, компьютерный форум CyberForum.ru

Методы вызываются через указатель на объект класса; Как компилятор определяет, какой из методов надо вызвать? - C++

Войти
Регистрация
Восстановить пароль
 
kravam
быдлокодер
 Аватар для kravam
1513 / 873 / 44
Регистрация: 04.06.2008
Сообщений: 5,302
10.03.2013, 08:39     Методы вызываются через указатель на объект класса; Как компилятор определяет, какой из методов надо вызвать? #1
Ребята, если вникать, то вообще ничё непонятно. Вот смотрите: пусть у нас есть абстрактный класс abstr (abstr.h) и в нём определено, например 10 функций виртуальных функции. Эти функции реализованы в каком-то статическом классе stats (файлы stats.h и stats.cpp). И пусть мы вызываем например две из них в main.cpp, вот так:

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//main.cpp
#include <windows.h>
#include <stdio.h>
#include "abstr.h"
#include "stats.h"
 
 
int main() {
 SetConsoleCP(1251);
 SetConsoleOutputCP(1251);
 
 abstr* p= new stats; 
 
 p->foo_0();  
 p->foo_1();  
 delete p; 
 
 getchar ();
 return 0;
}
Теперь мы всё это дело компилим, то есть main.cpp отдельно, stats.cpp отдельно. И получаем 2 файла main.o и stats.o. Можно и нужно посмотреть их содержание утилитой nm:

Bash
1
2
nm main.o 
nm stats.o
Так, а теперь кропаем экзешник:

Bash
1
g++ -o main.exe main.o stats.o
И запускаем. Пока всё предсказуемо. А теперь внимание, фокус-покус. Измените main.cpp вот так (например):

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//main.cpp
#include <windows.h>
#include <stdio.h>
#include "abstr.h"
#include "stats.h"
 
int main() {
 SetConsoleCP(1251);
 SetConsoleOutputCP(1251);
 
 abstr* p= new stats; 
 
 p->foo_4();  
 p->foo_5();  
 delete p; 
 
 getchar ();
 return 0;
}
И снова скомпильте- вы получите *.o файлы, АБСОЛЮТНО ИДЕНТИЧНЫЕ тем, что были раньше (смотрим утилитой nm). Но экзешник у вас получится совершенно другой- а именно с функциями foo_4 и foo_5 вместо foo_0 и foo_1

Два абсолютно одинаковых набора сырцов (хотя сырцами называют исходники, но я щас говорю про *.o файлы) и два абсолютно разных экзешника!

+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Вы мне можете возразить- функции, которые вызовутся, определяются непосредственно во время исполнения. А вот ни фига подобного. Во время исполнения может быть определён тип статического классса, к которому будет неявно приведён указатель p- (тут, собсно, ничё нового. Но у меня для простоты один тип- stats). А вот функции, которые вызовутся, становятся известны только на этапе линковки и не позже! Хотя бы даже из того это следует, что: мы линкуем, допустим, второй пример и у нас в экзешнике оказываются ИМЕНО две нужных функции из 10-ти, foo_4 и foo_5, остальных и в помине там нет.

Так как компилятор из двух совершенно одинаковых наборов символов определяет- что в этот раз надо вставить в тело экзешника код функции такой-то, а в другой раз код функции такой-то. Где он в файле main.o видит имена этих функций?

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

В общем, ступор тот ещё.

Спасибо, кто откликнется. Файлы прилагаю.

это исходники будут
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
76
//main.cpp
#include <windows.h>
#include <stdio.h>
#include "abstr.h"
#include "stats.h"
 
 
int main() {
 SetConsoleCP(1251);
 SetConsoleOutputCP(1251);
 
 abstr* p= new stats; 
 
 p->foo_0();  
 p->foo_1();  
 delete p; 
 
 getchar ();
 return 0;
}
//++++++++++++++++++++++++++++++++++++++++++++++++++++=
 
//abstr.h
#ifndef _ABSTR_H
#define _ABSTR_H
class abstr {
 public:
  virtual void foo_0  ()= 0;
  virtual void foo_1  ()= 0;
  virtual void foo_2  ()= 0;
  virtual void foo_3  ()= 0;
  virtual void foo_4  ()= 0;
  virtual void foo_5  ()= 0;
  virtual void foo_6  ()= 0;
  virtual void foo_7  ()= 0;
  virtual void foo_8  ()= 0;
  virtual void foo_9  ()= 0;
};
#endif
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 
//stats.h
#ifndef _STATS_H
#define _STATS_H
#include "abstr.h"
class stats: public abstr {
 public:
  void foo_0  ();
  void foo_1  ();
  void foo_2  ();
  void foo_3  ();
  void foo_4  ();
  void foo_5  ();
  void foo_6  ();
  void foo_7  ();
  void foo_8  ();
  void foo_9  ();
};
#endif
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 
//stats.cpp
#include "stats.h"
#include <stdio.h>
 
void stats::foo_0 ()  {printf ("ноль__\n");};
void stats::foo_1 ()  {printf ("один__\n");};
void stats::foo_2 ()  {printf ("два___\n");};
void stats::foo_3 ()  {printf ("три___\n");};
void stats::foo_4 ()  {printf ("четыре\n");};
void stats::foo_5 ()  {printf ("пять__\n");};
void stats::foo_6 ()  {printf ("шесть_\n");};
void stats::foo_7 ()  {printf ("семь__\n");};
void stats::foo_8 ()  {printf ("восемь\n");};
void stats::foo_9 ()  {printf ("девять\n");};
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++

компилить:
Bash
1
2
3
4
5
6
7
8
9
10
11
rem кропаем stats.o
del stats.o
g++ -c stats.cpp
 
rem кропаем main.o
del main.o
g++ -c main.cpp
 
rem кропаем main.exe
del main.exe
g++ -o main.exe main.o stats.o
А тут на всякий случай АБСОЛЮТНО одинаковые файлы main.o в первом и во втором случаях

//m_01.txt
00000000 b .bss
00000000 d .data
00000000 r .eh_frame
00000000 r .eh_frame$_ZN5abstrC2Ev
00000000 r .eh_frame$_ZN5statsC1Ev
00000000 r .eh_frame$getchar
00000000 r .rdata$_ZTI5abstr
00000000 r .rdata$_ZTS5abstr
00000000 r .rdata$_ZTV5abstr
00000000 t .text
00000000 t .text$_ZN5abstrC2Ev
00000000 t .text$_ZN5statsC1Ev
00000000 t .text$getchar
U _SetConsoleCP@4
U _SetConsoleOutputCP@4
00000000 T __ZN5abstrC2Ev
00000000 T __ZN5statsC1Ev
00000000 R __ZTI5abstr
00000000 R __ZTS5abstr
00000000 R __ZTV5abstr
U __ZTV5stats
U __ZTVN10__cxxabiv117__class_type_infoE
U __ZdlPv
U __Znwj
U ___cxa_pure_virtual
U ___main
U __filbuf
U __imp___iob
00000000 T _getchar
00000000 T _main

+++++++++++++++++++++++++++++++++++++++=

//m_45.txt
00000000 b .bss
00000000 d .data
00000000 r .eh_frame
00000000 r .eh_frame$_ZN5abstrC2Ev
00000000 r .eh_frame$_ZN5statsC1Ev
00000000 r .eh_frame$getchar
00000000 r .rdata$_ZTI5abstr
00000000 r .rdata$_ZTS5abstr
00000000 r .rdata$_ZTV5abstr
00000000 t .text
00000000 t .text$_ZN5abstrC2Ev
00000000 t .text$_ZN5statsC1Ev
00000000 t .text$getchar
U _SetConsoleCP@4
U _SetConsoleOutputCP@4
00000000 T __ZN5abstrC2Ev
00000000 T __ZN5statsC1Ev
00000000 R __ZTI5abstr
00000000 R __ZTS5abstr
00000000 R __ZTV5abstr
U __ZTV5stats
U __ZTVN10__cxxabiv117__class_type_infoE
U __ZdlPv
U __Znwj
U ___cxa_pure_virtual
U ___main
U __filbuf
U __imp___iob
00000000 T _getchar
00000000 T _main

Реезультаты сравнения stats.o самого с собой не выкладываю- и так понятно, он же не менялся от компиляции к компиляции, потому что не менялся stats.cpp
Similar
Эксперт
41792 / 34177 / 6122
Регистрация: 12.04.2006
Сообщений: 57,940
10.03.2013, 08:39     Методы вызываются через указатель на объект класса; Как компилятор определяет, какой из методов надо вызвать?
Посмотрите здесь:

C++ Как получить указатель на объект класса CDocument
Указатель на объект класса C++
C++ Как вызвать функцию через указатель?
C++ Как вызвать функцию через указатель?
C++ Как передать указатель на функцию через метод класса ?
C++ Вызвать из базового класса методы потомков
Как создать указатель на функцию-член класса и вызвать его? C++
После регистрации реклама в сообщениях будет скрыта и будут доступны все возможности форума.
Croessmah
Модератор
Эксперт С++
 Аватар для Croessmah
12286 / 6973 / 784
Регистрация: 27.09.2012
Сообщений: 17,286
Записей в блоге: 2
Завершенные тесты: 1
10.03.2013, 08:55     Методы вызываются через указатель на объект класса; Как компилятор определяет, какой из методов надо вызвать? #2
Например, компилятор может сгенерировать код:
посмотреть такую то память->вызвать функцию по адресу.

Добавлено через 9 минут
И еще почитайте про заглушку __cxa_pure_virtual
kravam
быдлокодер
 Аватар для kravam
1513 / 873 / 44
Регистрация: 04.06.2008
Сообщений: 5,302
10.03.2013, 08:56  [ТС]     Методы вызываются через указатель на объект класса; Как компилятор определяет, какой из методов надо вызвать? #3
Да он так и делает. Только почему-то по этим адресам кладутся нужные функции. Как он во время ЛИНКОВКИ (иначе мы бы имели в экзешнике все функции вместо фактически задействованных) определяет, какая функция нужная, а какая нет?
Kastaneda
Модератор
Эксперт С++
 Аватар для Kastaneda
4248 / 2780 / 219
Регистрация: 12.12.2009
Сообщений: 7,109
Записей в блоге: 1
Завершенные тесты: 1
10.03.2013, 10:24     Методы вызываются через указатель на объект класса; Как компилятор определяет, какой из методов надо вызвать? #4
Цитата Сообщение от kravam Посмотреть сообщение
Вы мне можете возразить- функции, которые вызовутся, определяются непосредственно во время исполнения. А вот ни фига подобного. Во время исполнения может быть определён тип статического классса, к которому будет неявно приведён указатель p- (тут, собсно, ничё нового. Но у меня для простоты один тип- stats). А вот функции, которые вызовутся, становятся известны только на этапе линковки и не позже!
Все не так. Адрес виртуальной функции определяется на этапе исполнения. Данная возможность реалиуется при помощи т.н. VPTR - указатель на таблицу виртуальных функций, который содержится в каждом объекте, если класс этого объекта содержит виртуальную функцию. Указатели ни к чему приводить не надо, просто смотриться VPTR по указателю и вызывается нужная функция.
Пример грязного использования VPTR - будет работать в MSVS, т.к. у них документировано, что VPTR располагается по нулевому смещение в объекте класса.
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
#include <iostream>
 
class A {
public:
    virtual void foo()
    {
        std::cout << "A::foo()" << std::endl;
    }
};
 
class B : public A
{
public:
    virtual void foo()
    {
        std::cout << "B::foo()" << std::endl;
    }
};
 
int main ()
{
    A *ob = new B;
 
    ((void(*)())(***((void(***)())ob)))(); // вызываем B::foo() через VPTR
}
вобщем можешь погуглить в эту сторону, чтоб лучше понять.

Добавлено через 3 минуты
вот нагуглил.

Добавлено через 31 минуту
Поковырялся в ассемблере (в листинге для кода выше). Инициализация VPTR происходит так - сначала VPTR настраивается на таблицу для класса А (все адреса вычисляются на этапе компиляции), потом в конструкторе В (до начала выполнения самомого конструктора) VPTR переписывается на указатель на таблицу для класса В.
Увидел еще одну интересную вещь - везде пишут что сначала вызывается конструктор базового класса, потом для наследника. Конечно это все так, но интересно было увидеть как это реально сделано (как то раньше не обращал на это внимания). Для примера выше - сначала вызывается конструктор В, в нем есть некий код, который будет выполнен до начала выполнения самого тела конструктора и, кроме всего прочего, там есть явный вызов конструктора А, после выполнения конструктора А есть еще какой-то код и только после всего этого начинается выполнение тело конструктора В. Т.е. можно умудриться поставить breakpoint в конструкторе В еще до вызова конструктора А.
Я к тому, что
везде пишут что сначала вызывается конструктор базового класса, потом для наследника.
технически получается, что сначала вызывается конструкотр наследника, из него уже конструктор базового класса, а после этого начинается выполнение конструктора наследника.
Я конечно примерно так это все и представлял, но почему удивился
kravam
быдлокодер
 Аватар для kravam
1513 / 873 / 44
Регистрация: 04.06.2008
Сообщений: 5,302
10.03.2013, 15:52  [ТС]     Методы вызываются через указатель на объект класса; Как компилятор определяет, какой из методов надо вызвать? #5
Цитата Сообщение от Kastaneda Посмотреть сообщение
Все не так
Естессно. Только и вы ре разобрамшись. А я разобрался и тоже всё, что я узнал вступает в противоречие доселе прочитанному, а именно- компилятор в экзешник кладёт тела ВСЕХ функций. И даже заботливо кладёт данные-строки, с которыми эти функции работают. То есть реально нужно 2, а кладёт все 10- проверено. Вот эти функции. Ненужные отмечены красным цветом. Искусственно прыгал на них отладчике и проверял- что выполнится. А куда прыгал, та функция выполнялась. Такие дела.

Это конечно на раз выяснялось, но я уж не полез в отладчик теорию опровергать. А полез бы- не было бы этой темы.

Не такой уж он и умный, получается, компилятор-то...
Миниатюры
Методы вызываются через указатель на объект класса; Как компилятор определяет, какой из методов надо вызвать?  
OhMyGodSoLong
~ Эврика! ~
 Аватар для OhMyGodSoLong
1238 / 987 / 42
Регистрация: 24.07.2012
Сообщений: 2,002
10.03.2013, 15:56     Методы вызываются через указатель на объект класса; Как компилятор определяет, какой из методов надо вызвать? #6
Мне вам рассказать про ключи и уровень оптимизации gcc по умолчанию или сами найдёте?
Jupiter
10.03.2013, 15:57
  #7

Не по теме:

Цитата Сообщение от kravam Посмотреть сообщение
эти функции реализованы в каком-то статическом классе
что такое статический класс в терминах с++?

Croessmah
Модератор
Эксперт С++
 Аватар для Croessmah
12286 / 6973 / 784
Регистрация: 27.09.2012
Сообщений: 17,286
Записей в блоге: 2
Завершенные тесты: 1
10.03.2013, 16:08     Методы вызываются через указатель на объект класса; Как компилятор определяет, какой из методов надо вызвать? #8
Цитата Сообщение от kravam Посмотреть сообщение
Не такой уж он и умный, получается, компилятор-то...
А может быть всё-таки не стоит вырезать функциональность и данные класса?
Как не крути класс - это целая сущность и не нужно урезать его функциональность.
kravam
быдлокодер
 Аватар для kravam
1513 / 873 / 44
Регистрация: 04.06.2008
Сообщений: 5,302
10.03.2013, 16:50  [ТС]     Методы вызываются через указатель на объект класса; Как компилятор определяет, какой из методов надо вызвать? #9
Цитата Сообщение от OhMyGodSoLong Посмотреть сообщение
Мне вам рассказать про ключи и уровень оптимизации gcc по умолчанию или сами найдёте?
Мы принцип разбираем, а не умничаем. Если компилятор в принципе не может не поместить реализацию всех функций, то он ВСЕГДА поместит реализацию всех 10-ти функций, хоть ты тресни. Сам-то пробовал оптимизировать или так, лишь бы засветиться? Я-то пробовал...

Добавлено через 1 минуту
Цитата Сообщение от Jupiter Посмотреть сообщение
что такое статический класс в терминах с++?
Вот так я и знал что кто-нибудь придерётся к терминам. Всё уже, решили вопрос.

Добавлено через 5 минут
Цитата Сообщение от Croessmah Посмотреть сообщение
А может быть всё-таки не стоит вырезать функциональность и данные класса?
Как не крути класс - это целая сущность и не нужно урезать его функциональность.
Да не то, чтобы не нужно- по-другому никак просто. Но на фига они в учебниках-то пишут ложь? Хотя может и не ложь. Читаешь же как обычно- есть класс, есть методы класса. Какие методы используются, те и будут в экзешнике и не методом больше (что правда). Так, а потом начинается тема про абстрактный класс, виртуальные функции и прочая. И дальше ситуация меняется- теперь в экзешнике будут присутствовать тела ВСЕХ функций! Только об этом в учебнике- молчок. Про таблицу виртуальных функций упомянут, а про то, что там будут тела всех функций- ни слова...
Croessmah
Модератор
Эксперт С++
 Аватар для Croessmah
12286 / 6973 / 784
Регистрация: 27.09.2012
Сообщений: 17,286
Записей в блоге: 2
Завершенные тесты: 1
10.03.2013, 16:53     Методы вызываются через указатель на объект класса; Как компилятор определяет, какой из методов надо вызвать? #10

Не по теме:

Цитата Сообщение от kravam Посмотреть сообщение
Но на фига они в учебниках-то пишут ложь?
Ну вот в школе учат, что на ноль делить нельзя, корень из отрицательных чисел находить... кто учился дальше школы, тот поймет о чем я



Цитата Сообщение от kravam Посмотреть сообщение
Только об этом- молчок в учебнике- молчок.
По-моему, везде пишут, что компилятор оптимизирует лишь то, что возможно.
kravam
быдлокодер
 Аватар для kravam
1513 / 873 / 44
Регистрация: 04.06.2008
Сообщений: 5,302
10.03.2013, 16:57  [ТС]     Методы вызываются через указатель на объект класса; Как компилятор определяет, какой из методов надо вызвать? #11
Угу. То есть я правильно вас понял, что вы всё это время знали в чём дело, что вот это вот заблуждение:
Хотя бы даже из того это следует, что: мы линкуем, допустим, второй пример и у нас в экзешнике оказываются ИМЕНО две нужных функции из 10-ти, foo_4 и foo_5, остальных и в помине там нет.
но стояли и снисходительно ждали, пока я сам дойду?
Croessmah
10.03.2013, 17:05
  #12

Не по теме:

Цитата Сообщение от kravam Посмотреть сообщение
но стояли и снисходительно ждали, пока я сам дойду?
Честно?
Написал пару простых прог, скомпилировал, дизасемблировал, потом отошел покушать и забыл про тему совершенно

MoreAnswers
Эксперт
37091 / 29110 / 5898
Регистрация: 17.06.2006
Сообщений: 43,301
10.03.2013, 17:08     Методы вызываются через указатель на объект класса; Как компилятор определяет, какой из методов надо вызвать?
Еще ссылки по теме:

C++ Не удается разыменовать указатель на объект класса
C++ Двойной указатель на объект класса
Спрятать указатель на функцию класса в map и вызвать из другого объекта C++
C++ Как получить указатель на объект класса из списка std::list?
C++ Родительский объект по отношению к дочерним. Реализация методов класса

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

Или воспользуйтесь поиском по форуму:
kravam
быдлокодер
 Аватар для kravam
1513 / 873 / 44
Регистрация: 04.06.2008
Сообщений: 5,302
10.03.2013, 17:08  [ТС]     Методы вызываются через указатель на объект класса; Как компилятор определяет, какой из методов надо вызвать? #13
ну хорошо что честно хоть
Yandex
Объявления
10.03.2013, 17:08     Методы вызываются через указатель на объект класса; Как компилятор определяет, какой из методов надо вызвать?
Ответ Создать тему
Опции темы

Текущее время: 06:06. Часовой пояс GMT +3.
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin® Version 3.8.9
Copyright ©2000 - 2017, vBulletin Solutions, Inc.
Рейтинг@Mail.ru