Форум программистов, компьютерный форум, киберфорум
Наши страницы

С++ для начинающих

Войти
Регистрация
Восстановить пароль
 
Haster
инженер-системотехник
110 / 109 / 2
Регистрация: 10.03.2009
Сообщений: 533
#1

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

27.10.2011, 12:00. Просмотров 800. Ответов 14
Метки нет (Все метки)

Здравствуйте, товарищи!

У меня возник вопрос, почему работает данный код:
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.
Вероятно, функции в объектах вызываются каким-то странным образом?
0
Надоела реклама? Зарегистрируйтесь и она исчезнет полностью.
Similar
Эксперт
41792 / 34177 / 6122
Регистрация: 12.04.2006
Сообщений: 57,940
27.10.2011, 12:00
Здравствуйте! Я подобрал для вас темы с ответами на вопрос О вызове функций классов (C++):

Множественное наследование, Перегрузка функций, Перегрузка операторов, Использование дружественных функций и классов, Использование шаблонов классов - C++
Здравствуйте!!! Я бы хотел попросить помоч решить...ну или скинуть примеры таких задач, если вдруг у вас они завалялись на компе или...

Несовпадение типов данных при вызове функций - C++
Код не компилируется из-за несовпадения типов переменных. Я уже запутался и не понимаю что не так) #include &lt;iostream&gt; #include...

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

Объясните, пожалуйста, как работает передача переменных при вызове функций? - C++
#include &lt;iostream&gt; #include &lt;conio.h&gt; using namespace std; void z(int *a,int lenght){ int *p; for...

Что конкретно означает оператор . (точка) при вызове каких либо функций? - C++
Например, функция s.size() вычисляет длину строки s. Так есть где-нибудь определение этой точки? В Википедии написано &quot;Обращение к члену...

Шаблоны классов и функций - C++
Помогите пожалуйста создать шаблон на класс list. Программу написал, только без шаблонов. //#include &quot;stdafx.h&quot; #include &lt;iostream&gt; ...

14
kazak
3050 / 2371 / 160
Регистрация: 11.03.2009
Сообщений: 5,436
Завершенные тесты: 1
27.10.2011, 12:43 #2
delete не удаляет объекты (переменные), а просто помечает занимаемую ими память как "свободно".
1
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
1
kazak
3050 / 2371 / 160
Регистрация: 11.03.2009
Сообщений: 5,436
Завершенные тесты: 1
27.10.2011, 12:59 #4
Цитата Сообщение от Haster Посмотреть сообщение
Вероятно, функции в объектах вызываются каким-то странным образом?
Код функций классов существует в единственном экземпляре, в этом плане они не отличаеются от обычных функций. Для их вызова в некоторых случаях не требуется даже создавать конкретный объект класса, достаточно сделать такой вызов <имя_класса>::<имя_функции>.
1
Haster
инженер-системотехник
110 / 109 / 2
Регистрация: 10.03.2009
Сообщений: 533
27.10.2011, 14:43  [ТС] #5
Я понимаю, что delete не удаляет объект,
мне интересно почему после изменения значения указателя (т.е. a указывает на другой адрес в памяти) все равно запись a->my(0) работает нормально.

Т.е. получается, что компилятор не обращает внимание на адреса, а тупо где видит конструкцию a->my(0) замещает ее на вызов нужной функции?
0
kazak
3050 / 2371 / 160
Регистрация: 11.03.2009
Сообщений: 5,436
Завершенные тесты: 1
27.10.2011, 15:23 #6
Цитата Сообщение от Haster Посмотреть сообщение
Т.е. получается, что компилятор не обращает внимание на адреса, а тупо где видит конструкцию a->my(0) замещает ее на вызов нужной функции?
Типа того. Адрес в данном случае должен ссылаться на участок памяти, где располагаются данные экземпляра класса. На вызов функций значение адреса никак не влияет. Единственное, если функция работает с данными экземпляра класса, то неверное значение адреса, в большинстве случаев, приведет к краху программы.
1
hoot
101 / 22 / 4
Регистрация: 10.11.2010
Сообщений: 202
27.10.2011, 15:33 #7
и после delete, есле не ошибаюсь, желательно присвоить значение ноль. Так что б не появлялось мусора в памяти.
1
Haster
инженер-системотехник
110 / 109 / 2
Регистрация: 10.03.2009
Сообщений: 533
27.10.2011, 15:47  [ТС] #8
Ага, спасибо за пояснения! Посмотрел ассемблерный листинг - действительно компилятор подставляет адрес функции в место вызова (точнее адрес элемента таблицы, элемент которой содержит инструкцию jmp на начало функции).
0
Mr.X
Эксперт С++
3051 / 1696 / 265
Регистрация: 03.05.2010
Сообщений: 3,867
27.10.2011, 16:25 #9
Цитата Сообщение от Haster Посмотреть сообщение
Т.е. получается, что компилятор не обращает внимание на адреса, а тупо где видит конструкцию a->my(0) замещает ее на вызов нужной функции?
Ну почему же, очень даже обращает, ведь при вызове нестатической функции-члена в нее неявно передается адрес объекта под именем this.
В данном случае после присвоения указателю a нового значения при разыменовании этого указателя участок памяти, на который он указывает, интерпретируется как объект класса A, и, собственно, для этого объекта и вызывается функция my. Если тот мусор, который хранится на месте этого "объекта", способен выполнить его роль, то все пройдет нормально. Если же вы, например, объявите функцию my виртуальной, то все пройдет не так гладко.
2
Haster
инженер-системотехник
110 / 109 / 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; вызов метода
Для виртуальных функций все сложнее, ибо присутствует виртуальная таблица, которая должна адресоваться через адрес объекта. Разве не так?
0
Сыроежка
Заблокирован
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 не работаете. Поэтому и никаких ошибок и не возникает!
2
Mr.X
Эксперт С++
3051 / 1696 / 265
Регистрация: 03.05.2010
Сообщений: 3,867
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 Посмотреть сообщение
компилятор не использует адрес объекта для вычисления местоположения функции в памяти (это наглядно демонстрируется возможностью вызова функции после установки указателя на другой адрес.
Там же и данные-члены выводятся для нового адреса. Если так рассуждать, то и для их вывода адрес объекта не используется?
1
Haster
инженер-системотехник
110 / 109 / 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
0
Сыроежка
Заблокирован
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 раз записывается какое-то слово в область памяти
0
Haster
инженер-системотехник
110 / 109 / 2
Регистрация: 10.03.2009
Сообщений: 533
29.10.2011, 16:52  [ТС] #15
Сыроежка, функция та, не сомневайтесь.
Просто компилятор от Майкрософт вставляет такую вещь перед каждой функцией.
Мне тоже не совсем понятно зачем (кстати, копирует он инструкцию int 3), но скорей всего для защиты стека или что-то подобного.
Может кто-нибудь в курсе, зачем?
0
29.10.2011, 16:52
MoreAnswers
Эксперт
37091 / 29110 / 5898
Регистрация: 17.06.2006
Сообщений: 43,301
29.10.2011, 16:52
Привет! Вот еще темы с ответами:

Шаблоны функций и классов - C++
Нужносоздать шаблон функции возведения заданного числа в заданную степень (аналог функции pow() ). Шаблон класа Класс: матрица....

Шаблоны функций и классов - C++
Задание Создать шаблон класса для массивов целых и дробных чисел, а также символов и строк. Программа должна выполнять...

Шаблоны классов и функций - C++
Всем доброго времени суток =) Мне нужно создать класс для работы с целыми числами. Используя шаблоны, выполнить тоже задание для...

Группировка функций разных классов - C++
Всем привет! Возник спорный вопрос. Задача: Есть много классов, но у каждого из них может быть (! а может и нет) по методу,...


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

Или воспользуйтесь поиском по форуму:
15
Ответ Создать тему
Опции темы

КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin® Version 3.8.9
Copyright ©2000 - 2017, vBulletin Solutions, Inc.