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

О вызове функций классов - C++

Восстановить пароль Регистрация
 
Haster
инженер-системотехник
 Аватар для Haster
109 / 108 / 2
Регистрация: 10.03.2009
Сообщений: 533
27.10.2011, 12:00     О вызове функций классов #1
Здравствуйте, товарищи!

У меня возник вопрос, почему работает данный код:
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
#include "stdafx.h"
#include <conio.h>
#include <iostream>
 
using namespace std;
 
class A
{
public:
    int a;
    int b;
    A()
    {
        cout<<"Constr\r\n";
    }
    void my(int a)
    {
        cout<<"my\r\n";
    }
    ~A()
    {
        cout<<"Destr\r\n";
    }
};
 
int _tmain(int argc, _TCHAR* argv[])
{
    A *a = new A();
    a->a = 5;
    a->b = 10;
    cout<<"&a = "<<&a<<"\r\n";
    cout<<"a = "<<a<<"\r\n";
    delete a;
    cout<<"a->a = "<<a->a<<"\r\n";
    cout<<"a->b = "<<a->b<<"\r\n";
 
    a = reinterpret_cast<A*>(0x403450L);
    cout<<"&a = "<<&a<<"\r\n";
    cout<<"a = "<<a<<"\r\n";
    a->my(0);
    cout<<"a->a = "<<a->a<<"\r\n";
    cout<<"a->b = "<<a->b<<"\r\n";
    _getch();
    return 0;
}
Вывод на консоль
C++
1
2
3
4
5
6
7
8
9
10
11
Constr
&a = 0012FF54
a = 0034C3B8
Destr
a->a = -17891602
a->b = -17891602
&a = 0012FF54
a = 00403450
my
a->a = 0
a->b = 0
т.е. мне не понятно, почему после удаления объекта, и изменения значения указателя a
сохраняется возможность вызвать функцию my.
Вероятно, функции в объектах вызываются каким-то странным образом?
После регистрации реклама в сообщениях будет скрыта и будут доступны все возможности форума.
kazak
 Аватар для kazak
3029 / 2350 / 155
Регистрация: 11.03.2009
Сообщений: 5,401
27.10.2011, 12:43     О вызове функций классов #2
delete не удаляет объекты (переменные), а просто помечает занимаемую ими память как "свободно".
Revol'veR
 Аватар для Revol'veR
23 / 23 / 2
Регистрация: 05.11.2010
Сообщений: 134
27.10.2011, 12:45     О вызове функций классов #3
http://ru.wikipedia.org/wiki/Delete_%28C%2B%2B%29
kazak
 Аватар для kazak
3029 / 2350 / 155
Регистрация: 11.03.2009
Сообщений: 5,401
27.10.2011, 12:59     О вызове функций классов #4
Цитата Сообщение от Haster Посмотреть сообщение
Вероятно, функции в объектах вызываются каким-то странным образом?
Код функций классов существует в единственном экземпляре, в этом плане они не отличаеются от обычных функций. Для их вызова в некоторых случаях не требуется даже создавать конкретный объект класса, достаточно сделать такой вызов <имя_класса>::<имя_функции>.
Haster
инженер-системотехник
 Аватар для Haster
109 / 108 / 2
Регистрация: 10.03.2009
Сообщений: 533
27.10.2011, 14:43  [ТС]     О вызове функций классов #5
Я понимаю, что delete не удаляет объект,
мне интересно почему после изменения значения указателя (т.е. a указывает на другой адрес в памяти) все равно запись a->my(0) работает нормально.

Т.е. получается, что компилятор не обращает внимание на адреса, а тупо где видит конструкцию a->my(0) замещает ее на вызов нужной функции?
kazak
 Аватар для kazak
3029 / 2350 / 155
Регистрация: 11.03.2009
Сообщений: 5,401
27.10.2011, 15:23     О вызове функций классов #6
Цитата Сообщение от Haster Посмотреть сообщение
Т.е. получается, что компилятор не обращает внимание на адреса, а тупо где видит конструкцию a->my(0) замещает ее на вызов нужной функции?
Типа того. Адрес в данном случае должен ссылаться на участок памяти, где располагаются данные экземпляра класса. На вызов функций значение адреса никак не влияет. Единственное, если функция работает с данными экземпляра класса, то неверное значение адреса, в большинстве случаев, приведет к краху программы.
hoot
 Аватар для hoot
100 / 21 / 3
Регистрация: 10.11.2010
Сообщений: 193
27.10.2011, 15:33     О вызове функций классов #7
и после delete, есле не ошибаюсь, желательно присвоить значение ноль. Так что б не появлялось мусора в памяти.
Haster
инженер-системотехник
 Аватар для Haster
109 / 108 / 2
Регистрация: 10.03.2009
Сообщений: 533
27.10.2011, 15:47  [ТС]     О вызове функций классов #8
Ага, спасибо за пояснения! Посмотрел ассемблерный листинг - действительно компилятор подставляет адрес функции в место вызова (точнее адрес элемента таблицы, элемент которой содержит инструкцию jmp на начало функции).
Mr.X
Эксперт С++
 Аватар для Mr.X
2798 / 1574 / 246
Регистрация: 03.05.2010
Сообщений: 3,651
27.10.2011, 16:25     О вызове функций классов #9
Цитата Сообщение от Haster Посмотреть сообщение
Т.е. получается, что компилятор не обращает внимание на адреса, а тупо где видит конструкцию a->my(0) замещает ее на вызов нужной функции?
Ну почему же, очень даже обращает, ведь при вызове нестатической функции-члена в нее неявно передается адрес объекта под именем this.
В данном случае после присвоения указателю a нового значения при разыменовании этого указателя участок памяти, на который он указывает, интерпретируется как объект класса A, и, собственно, для этого объекта и вызывается функция my. Если тот мусор, который хранится на месте этого "объекта", способен выполнить его роль, то все пройдет нормально. Если же вы, например, объявите функцию my виртуальной, то все пройдет не так гладко.
Haster
инженер-системотехник
 Аватар для Haster
109 / 108 / 2
Регистрация: 10.03.2009
Сообщений: 533
27.10.2011, 17:28  [ТС]     О вызове функций классов #10
Mr.X, я имеел ввиду, что компилятор не использует адрес объекта для вычисления местоположения функции в памяти (это наглядно демонстрируется возможностью вызова функции после установки указателя на другой адрес.

В дизассемблере это выглядит примерно так:

Assembler
1
2
3
4
5
6
...
1000: ...
1001: ...
1002: jmp 3000 ; прыжок на метод класса
...
2000: call 1002; вызов метода
Для виртуальных функций все сложнее, ибо присутствует виртуальная таблица, которая должна адресоваться через адрес объекта. Разве не так?
Сыроежка
Заблокирован
27.10.2011, 19:10     О вызове функций классов #11
Цитата Сообщение от Haster Посмотреть сообщение
Здравствуйте, товарищи!

У меня возник вопрос, почему работает данный код:
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
#include "stdafx.h"
#include <conio.h>
#include <iostream>
 
using namespace std;
 
class A
{
public:
    int a;
    int b;
    A()
    {
        cout<<"Constr\r\n";
    }
    void my(int a)
    {
        cout<<"my\r\n";
    }
    ~A()
    {
        cout<<"Destr\r\n";
    }
};
 
int _tmain(int argc, _TCHAR* argv[])
{
    A *a = new A();
    a->a = 5;
    a->b = 10;
    cout<<"&a = "<<&a<<"\r\n";
    cout<<"a = "<<a<<"\r\n";
    delete a;
    cout<<"a->a = "<<a->a<<"\r\n";
    cout<<"a->b = "<<a->b<<"\r\n";
 
    a = reinterpret_cast<A*>(0x403450L);
    cout<<"&a = "<<&a<<"\r\n";
    cout<<"a = "<<a<<"\r\n";
    a->my(0);
    cout<<"a->a = "<<a->a<<"\r\n";
    cout<<"a->b = "<<a->b<<"\r\n";
    _getch();
    return 0;
}
Вывод на консоль
C++
1
2
3
4
5
6
7
8
9
10
11
Constr
&a = 0012FF54
a = 0034C3B8
Destr
a->a = -17891602
a->b = -17891602
&a = 0012FF54
a = 00403450
my
a->a = 0
a->b = 0
т.е. мне не понятно, почему после удаления объекта, и изменения значения указателя a
сохраняется возможность вызвать функцию my.
Вероятно, функции в объектах вызываются каким-то странным образом?
Чтобы ответить на ваш вопрос, нужно разобраться, а что из себя представляет ваш объект? А ваш объект хранится в памяти, как обычная структура, состоящая из двух полей int a и int b. Больше ничего в вашем объекте нет, и никакие функции в объекте не хранятся.
Для всех объектов вашего класса имеется лишь одно определение ФУНКЦИИ void my(int a). Причем, так как эта функция определяется внутри тела класса, то она является встраиваемой, и код ее определения компилятор поместил во все места, где вы эту функцию вызываете. То есть это сделано компилятором еще до выполнения вашей программы. В чем разница вызова этой функции -члена класса, от обычной функции? Разница состоит лишь в том, что функции - члену класса передается неявно дополнительный первый параметр, который является указателем на объект вашего класса и носит название this.

Так что компилятору совершенно без разницы, удалили вы объект, или нет, он вставляет в код вызов этой функции и передает ей в качестве первого параметра значение вашего указателя a.
Как вы сами можете видеть из вывода вашей программы, сам указатель a никуда не делся! Его время жизни соотвентсвует телу функции main, в которой он, как локальная переменная, объявлен. Так что компилятору всегда известен адрес a. В вашем случае, судя по выводу программы, его адрес равен 0012FF54. Поэтому компилятор просто при вызове функции my в качестве первого неявного параметра, соответствующего this передает то значение, которое хранится в a. С этим параметром, то есть со значением, хранящемся в a, вы в функции my не работаете. Поэтому и никаких ошибок и не возникает!
Mr.X
Эксперт С++
 Аватар для Mr.X
2798 / 1574 / 246
Регистрация: 03.05.2010
Сообщений: 3,651
27.10.2011, 19:45     О вызове функций классов #12
Цитата Сообщение от Haster Посмотреть сообщение
Mr.X, я имеел ввиду, что компилятор не использует адрес объекта для вычисления местоположения функции в памяти
Да, это верно, и из этого следует такой, например, курьез, что если в функции-члене класса есть статическая переменная, то она будет одна для всех объектов этого класса в текущем пространстве имен, следовательно никак не связанные объекты этого класса могут обмениваться через нее информацией.
Пример:
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
/////////////////////////////////////////////////////////////////////////////////////////
#include <iostream>
/////////////////////////////////////////////////////////////////////////////////////////
struct A
{
    int&  get_set_n()
    {
        static int n = 0;
        return n;
    }
};
/////////////////////////////////////////////////////////////////////////////////////////
int main()
{
    A  a;
    std::cout << "a.get_set_n() = "
              << a.get_set_n()
              << std::endl;
 
    a.get_set_n() = 5;
    A  b;
 
    std::cout << "b.get_set_n() = "
              << b.get_set_n()
              << std::endl;
}
С другой стороны не очень понятна ваша логика в высказывании
Цитата Сообщение от Haster Посмотреть сообщение
компилятор не использует адрес объекта для вычисления местоположения функции в памяти (это наглядно демонстрируется возможностью вызова функции после установки указателя на другой адрес.
Там же и данные-члены выводятся для нового адреса. Если так рассуждать, то и для их вывода адрес объекта не используется?
Haster
инженер-системотехник
 Аватар для Haster
109 / 108 / 2
Регистрация: 10.03.2009
Сообщений: 533
28.10.2011, 09:52  [ТС]     О вызове функций классов #13
Mr.X, логика в том, что если бы компилятор использовал адрес объекта при вызове функции, то при изменении указателя не удалось бы вызвать данную функцию.
Ну а выводы элементов остались после модификации примера (раньше там еще были куски кода)

Вообще, спасибо за объяснения!!! )

Добавлено через 13 часов 28 минут
Сыроежка, спасибо за объяснения. Только в данном случае компилятор почему-то не встроил функцию в место вызова (судя по сгенерированному ассемблерному листингу). Интересно, почему?
Assembler
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
_wmain  PROC                        ; COMDAT
 
; 27   : {
 
    push    ebp
    mov ebp, esp
...
; 28   :         A *a = new A();
 
...
 
; 40   :         a->my(0);
 
    push    0
    mov ecx, DWORD PTR _a$[ebp]
    call    ?my@A@@QAEXH@Z              ; A::my
 
; 41   :         cout<<"a->a = "<<a->a<<"\r\n";
 
...
 
; 44   :         return 0;
 
    xor eax, eax
 
; 45   : }
 
CONST   SEGMENT
??_C@_04LMHPCPNB@my?$AN?6?$AA@ DB 'my', 0dH, 0aH, 00H   ; `string'
; Function compile flags: /Odtp /RTCsu /ZI
CONST   ENDS
;   COMDAT ?my@A@@QAEXH@Z
_TEXT   SEGMENT
_this$ = -8                     ; size = 4
_a$ = 8                         ; size = 4
?my@A@@QAEXH@Z PROC                 ; A::my, COMDAT
; _this$ = ecx
 
; 17   :         {
 
    push    ebp
    mov ebp, esp
    sub esp, 204                ; 000000ccH
    push    ebx
    push    esi
    push    edi
    push    ecx
    lea edi, DWORD PTR [ebp-204]
    mov ecx, 51                 ; 00000033H
    mov eax, -858993460             ; ccccccccH
    rep stosd
    pop ecx
    mov DWORD PTR _this$[ebp], ecx
 
; 18   :                 cout<<"my\r\n";
 
...
Т.е. видно, что функция вызывается при помощи команды call
Сыроежка
Заблокирован
28.10.2011, 21:29     О вызове функций классов #14
Цитата Сообщение от Haster Посмотреть сообщение
Mr.X, логика в том, что если бы компилятор использовал адрес объекта при вызове функции, то при изменении указателя не удалось бы вызвать данную функцию.
Ну а выводы элементов остались после модификации примера (раньше там еще были куски кода)

Вообще, спасибо за объяснения!!! )

Добавлено через 13 часов 28 минут
Сыроежка, спасибо за объяснения. Только в данном случае компилятор почему-то не встроил функцию в место вызова (судя по сгенерированному ассемблерному листингу). Интересно, почему?
Assembler
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
_wmain  PROC                        ; COMDAT
 
; 27   : {
 
    push    ebp
    mov ebp, esp
...
; 28   :         A *a = new A();
 
...
 
; 40   :         a->my(0);
 
    push    0
    mov ecx, DWORD PTR _a$[ebp]
    call    ?my@A@@QAEXH@Z              ; A::my
 
; 41   :         cout<<"a->a = "<<a->a<<"\r\n";
 
...
 
; 44   :         return 0;
 
    xor eax, eax
 
; 45   : }
 
CONST   SEGMENT
??_C@_04LMHPCPNB@my?$AN?6?$AA@ DB 'my', 0dH, 0aH, 00H   ; `string'
; Function compile flags: /Odtp /RTCsu /ZI
CONST   ENDS
;   COMDAT ?my@A@@QAEXH@Z
_TEXT   SEGMENT
_this$ = -8                     ; size = 4
_a$ = 8                         ; size = 4
?my@A@@QAEXH@Z PROC                 ; A::my, COMDAT
; _this$ = ecx
 
; 17   :         {
 
    push    ebp
    mov ebp, esp
    sub esp, 204                ; 000000ccH
    push    ebx
    push    esi
    push    edi
    push    ecx
    lea edi, DWORD PTR [ebp-204]
    mov ecx, 51                 ; 00000033H
    mov eax, -858993460             ; ccccccccH
    rep stosd
    pop ecx
    mov DWORD PTR _this$[ebp], ecx
 
; 18   :                 cout<<"my\r\n";
 
...
Т.е. видно, что функция вызывается при помощи команды call
Откровенно говоря, пока не могу сказать. Это может быть связано с опциями компилятора, с режимом запуска (отладка или нет) и т.д.

Но я смотрю код и не могу понять, а та ли эта функция? То есть в теле функции в цикле 81 раз записывается какое-то слово в область памяти
MoreAnswers
Эксперт
37091 / 29110 / 5898
Регистрация: 17.06.2006
Сообщений: 43,301
29.10.2011, 16:52     О вызове функций классов
Еще ссылки по теме:

C++ Какие нюансы в вызове виртуальных функций из конструктора и из деструктора?
Объясните, пожалуйста, как работает передача переменных при вызове функций? C++
C++ Шаблоны функций и классов

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

Или воспользуйтесь поиском по форуму:
Haster
инженер-системотехник
 Аватар для Haster
109 / 108 / 2
Регистрация: 10.03.2009
Сообщений: 533
29.10.2011, 16:52  [ТС]     О вызове функций классов #15
Сыроежка, функция та, не сомневайтесь.
Просто компилятор от Майкрософт вставляет такую вещь перед каждой функцией.
Мне тоже не совсем понятно зачем (кстати, копирует он инструкцию int 3), но скорей всего для защиты стека или что-то подобного.
Может кто-нибудь в курсе, зачем?
Yandex
Объявления
29.10.2011, 16:52     О вызове функций классов
Ответ Создать тему
Опции темы

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