Как найти адрес функции

With the caveat that this method is utterly unportable as T.C. mentions, and the additional caveat that it almost certainly will not work if optimizations are turned on, you can read the return address from the stack by reading off the end of a buffer, as in the example below.

int main()
{
    f1();
    return 0;
}
void f1()
{
    f2();
}
void f2() {
    char buf[4];
    printf("%pn", *(void**)(buf + 8));
}

Note that the number 8 above will vary based on operating system, architecture, and compiler padding, so you will most likely have to try a variety of different numbers to make it work.
The choice of 8 for the example assumes padding up to a 4-byte boundary, and 4-byte pointers on a 32-bit system.

You also have to make sure optimizations are turned off.

The reason why this works at all is because the structure of the stack immediately after a function call looks kind of like this.

|Return Address|
|Saved Frame Pointer|
|Local Variables|

Observe that the return address is at a higher address than the Local Variables. That is the reason that reading past the end of a buffer will allow you to potentially read the return address.

The reason why this breaks with optimizations is that compiler might decide to inline one or both functions, or realize one of them is doing nothing at all and optimize the function call away entirely, which blows aside the assumptions about the stack.

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
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
unit Unit1;
 
{$mode objfpc}{$H+}
 
interface
 
uses
  Classes, SysUtils, Forms, Controls, Graphics, Dialogs,
  StdCtrls, windows;
 
type
 
  { TForm1 }
 
  TForm1 = class(TForm)
    Button_create_remote_thread: TButton;
    Button_suspend_thread: TButton;
    Button_terminate_process: TButton;
    Button_open_process: TButton;
    Button_terminate_thread: TButton;
    Button_resume_thread: TButton;
    Button_open_remote_thr: TButton;
    Edit_func_addr: TEdit;
    Edit_process_id: TEdit;
    Edit_remote_thr_id: TEdit;
    Label1: TLabel;
    Label2: TLabel;
    Label3: TLabel;
    Label4: TLabel;
    proc_info: TMemo;
    procedure Button_create_remote_threadClick(Sender: TObject);
    procedure Button_open_processClick(Sender: TObject);
    procedure Button_open_remote_thrClick(Sender: TObject);
    procedure Button_resume_threadClick(Sender: TObject);
    procedure Button_suspend_threadClick(Sender: TObject);
    procedure Button_terminate_processClick(Sender: TObject);
    procedure Button_terminate_threadClick(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure create_process(proc_name: string);
  private
 
  public
    hThread, hProcess: THANDLE;
    thr_id: LongInt;
  end;
 
var
  Form1: TForm1;
 
implementation
 
{$R *.lfm}
 
{ TForm1 }
 
procedure TForm1.FormCreate(Sender: TObject);
var
  proc_id, thread_id: LongWord;
begin
  proc_id:=GetCurrentProcessId();
  thread_id:=GetCurrentThreadId();
  proc_info.Text:=Format('ID Процесса: %d'+LineEnding+
  'ID Потока: %d'+LineEnding+
  'Адрес функции: %p'+LineEnding,[proc_id, thread_id, Addr(proc_id)]); // тут я хочу записать адрес главной функции в ТМемо
  create_process('A');
  //create_process('B');
end;
 
procedure TForm1.Button_open_remote_thrClick(Sender: TObject);
begin
  try
  thr_id:=StrToInt64(Edit_remote_thr_id.Text);
  hThread:=OpenThread(THREAD_ALL_ACCESS,False,thr_id);
    If (hThread <> 0) then
      ShowMessage(Format('Доступ к удаленному потоку %d получен!',[thr_id]))
        else ShowMessage('Ошибка доступа к удаленному потоку!');
  except
    on E: EConvertError do
      ShowMessage('Введено некорректное значение!');
  end;
end;
 
procedure TForm1.Button_open_processClick(Sender: TObject);
var
  pr_id: DWORD;
begin
  try
  pr_id:=StrToDWord(Edit_process_id.Text);
  hProcess:=OpenProcess(PROCESS_ALL_ACCESS,False,pr_id);
  if (hProcess <> 0) then
    ShowMessage(Format('Доступ к удаленному процессу %d получен!',[pr_id]))
       else ShowMessage('Ошибка доступа к удаленному процессу!');
  except
    on E: EConvertError do
      ShowMessage('Введено некорректное значение!');
  end;
end;
 
procedure TForm1.Button_create_remote_threadClick(Sender: TObject);
var
  r: LPTHREAD_START_ROUTINE;
  r3: Integer;
  ID: DWORD;
begin
  r3:=1;
  SScanf(Edit_func_addr.Text,'%d',[@r]);
  hThread:=CreateRemoteThread(hProcess,Nil,0,r,@r3,CREATE_SUSPENDED,@ID);
  if (hThread <> 0) then ShowMessage(Format('Поток %d в удаленном процессе создан!',[ID]))
  else ShowMessage('Ошибка создания потока!');
end;
 
procedure TForm1.Button_resume_threadClick(Sender: TObject);
var
  flok: DWORD;
begin
  flok:=ResumeThread(hThread);
  if (flok <> 0) then ShowMessage(Format('Удаленный поток %d запущен!',[thr_id]))
  else ShowMessage('Ошибка запуска потока');
end;
 
procedure TForm1.Button_suspend_threadClick(Sender: TObject);
var
  flok: DWORD;
begin
  flok:=SuspendThread(hThread);
  if (flok = 0) then ShowMessage(Format('Удаленный поток %d приостановлен',[thr_id]))
  else ShowMessage('Ошибка приостановки потока!');
end;
 
procedure TForm1.Button_terminate_processClick(Sender: TObject);
begin
  TerminateProcess(hProcess,1);
end;
 
procedure TForm1.Button_terminate_threadClick(Sender: TObject);
var
  flok: WINBOOL;
begin
  flok:=TerminateThread(hThread, 1);
  if (flok) then ShowMessage(Format('Поток %d уничтожен',[thr_id]))
  else ShowMessage('Ошибка уничтожения потока');
end;
 
procedure TForm1.create_process(proc_name: string);
var
  si: TSTARTUPINFO;
  ProcInfo: TPROCESSINFORMATION;
  cmdline: string;
begin
  si:=Default(TSTARTUPINFO);
  si.cb:=SizeOf(si);
  cmdline:=Format('rabit.exe %s',[proc_name]);
  CreateProcess(PChar(Nil),PChar(cmdline),Nil,Nil,False,0,Nil,PChar(Nil),si,ProcInfo);
end;
 
end.

Получить адрес переменной

int var;
&var; // возвращает адрес переменной var

Идентификатор должен быть переменной. Префиксный символ амперсанда (&), используемый совместно с именем переменной, возвращает адрес в памяти, где хранится значение этой переменной, в нашем случае, — для переменной var.

Получить адрес массива

array;

Чтобы получить адрес массива, мы просто используем имя массива, которое само по себе является указателем на адрес в памяти, в котором содержится первый элемент массива. Обратите внимание, что при использовании имени массива, С++ специфицирует его как указатель.

Получить адрес функции

int func(); 
int (*funcPtr)() = &func;

Чтобы получить адрес памяти для функции, просто используйте её имя без скобок с префиксным символом амперсанда, таким образом, компилятор будет знать, что вы  извлекаете адрес функции. Обратите внимание, что адрес присваивается указателю на функцию, то есть через этот указатель можно вызывать функцию, как мы делаем  это обычно, только использовать имя funcPtr, — это псевдоним функции func.

Получить адрес памяти и сохранить его в указателе

char *ptr = new char;
ptr; // вернуть адрес только что выделенной памяти

Указатель по умолчанию возвращает адрес памяти переменной, на которую он указывает. А в этом случае нет никакой переменной, мы просто связали ячейку памяти с указателем.

Разыменование указателей

int var = 4;
int *ptr = &var
*ptr; // возвращаемый результат - значение 4

Символ звездочки разыменовывает указатель, строка 3, таким образом, значение этого выражения является значение переменной, на адрес памяти которой ссылается указатель. ptr.

В четырёх последних уроках мы плотно занимаемся адресацией данных в языке C, изучили почти всё по указателям и адресам: разыменование, доступ к адресам массивов, поработали с указателями на структуры и их поля, с указателями в аргументах функций на различные данные, с указателями типа void.

Но оказалось, что это ещё не всё. Существуют ещё указатели на функции.

Я не хотел делать такой урок, но изучив конкретно данную тему, понял, что она не менее полезна, чем те, которые мы уже изучили про указатели. И указатели на функции приносят очень интересную и нужную пользу.

Давайте для начала зададимся вопросом, откуда вообще взялся указатель на функцию. Функция для процессора — это подпрограмма, она также, как и остальные данные, располагается где-то в памяти, соответственно, данная память только для чтения, то есть мы не можем изменить код подпрограммы во время её выполнения (хотя это тоже ещё не факт, есть некоторые костыли, но нам это сейчас и не нужно и урок не о том). Вот поэтому и вопрос. А обязательно ли мы должны вызывать функцию по её имени? Ведь получали же мы данные. используя адрес их нахождения в памяти. Оказывается, тоже не обязательно. Вполне мы можем использовать и указатели. И от этого есть тоже своя польза.

Например, в C++ кроме структур существуют классы, объекты или экземпляры которых мы потом используем в программе. Как и в структурах, в классах есть поля, только, правда там они именуются свойства, а есть ещё и методы. Это такие функции, которые принадлежат данному классу и доступны только из объектов, созданных на основе него.

А у нас в C существуют структуры, в которых есть только поля. Методов у нас нет. Но, используя указатели на функции, мы вполне можем эти методы эмулировать. Пусть у нас не будет спецификаторов доступа, конструкторов и деструкторов, но со своими функциями структура станет намного интереснее, чем только с полями.

Есть ещё некоторые полезные свойства указателей на функции, но мы их затрагивать не будем на данном уроке, да они и мной ещё до конца не изучены, поработаем мы лишь со структурами, тем самым мы также поймём, как можно объявить указатель на функцию и как им воспользоваться. Ну, вернее, мы конечно же, поиграемся с нашими функциями и без структур. Всё постигается постепенно. Оно так лучше усваивается.

Давайте вообще разберёмся, как мы можем получить указатель на функцию.

Как это не удивительно, самым распространённым указателем на функцию является её имя.

Получается, что мы уже и так пользуемся указателем при вызове функции. Да и если посмотреть в дизассемблере код, когда мы вызываем подпрограмму, у нас там и есть адрес функции, а никакого имени там и нет. Тогда зачем вообще этот урок, спросите вы.

А урок затем, что указателем функции можно воспользоваться для других видов доступа к функции, так сказать, нестандартных.

Кроме указателя в виде имени функции мы можем также объявить указатель на функцию отдельной переменной

Как мы можем использовать указатель на функцию?

Классический способ такой.

Представим, что у нас есть функция, вот её прототип

void print_str(const char *c_str);

Мы объявляем переменную-указатель на функцию, например с таким именем, при этом используя строго все те же параметры, как и у оригинальной функции, на адрес которой впоследствии наш указатель будет указывать

void (*print_str_new)(const char *c_str);

У нас появилось теперь новое имя print_str_new.

Теперь мы простейшим способом присваиваем адрес оригинальной функции нашему новому указателю (а мы помним, что имя функции — это и есть указатель)

print_str_new = print_str;

И далее этим указателем мы пользуемся также, как и оригинальным именем функции

print_str_new("Hello, World!!!");

Всё оказалось просто.

Давайте теперь поиграем с указателями на функции в практическом проекте, который мы также создадим из проекта прошлого урока с именем MYPROG29 и присвоим ему имя MYPROG30.

Откроем наш проект в Eclipse, произведём его первоначальную настройку и удалим весь наш код из функции main() за исключением возврата. Функция main() приобретёт вот такой вид

int main()

{

   return 0; //Return an integer from a function

}

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

Оказывается, вот как. Раз мы знаем то, что имя функции и есть указатель на неё, то мы этим и воспользуемся.

В функции main объявим обычный указатель на целочисленный 4-байтовый тип, так как у нас адреса памяти 32-разрядных приложений именно такие

int main()

{

  unsigned int *print_str_addr;

И, применив приведение типа, заберём адрес функции

  unsigned int *print_str_addr;

  print_str_addr = (unsigned int *)print_str;

И теперь мы спокойно выведем его в консоль

  print_str_addr = (unsigned int *)print_str;

  printf(«0x%08Xn»,(unsigned int)print_str_addr);

Проверим наш код в работе

Вот таким образом мы получили адрес функции (а точнее подпрограммы, которую она породила в результате компиляции).

Давайте проверим это в отладке. В любое место функции main() установим breakpoint и выполним программу до него, перейдём в окно дизассемблирования и докрутим вниз до функции print_str

У функции действительно такой адрес.

Давайте теперь, воспользовавшись нашим указателем, попробуем вызвать с помощью него оригинальную функцию без использования её имени.

Объявим указатель на функцию

  printf(«0x%08Xn»,(unsigned int)print_str_addr);

  void (*print_str_new)(const char *c_str);

Присвоим ему полученный адрес функции

  void (*print_str_new)(const char *c_str);

  print_str_new = (void*)print_str_addr;

И, воспользовавшись нашим указателем, вызовем функцию

  print_str_new = (void*)print_str_addr;

  print_str_new(«Hello, World!!!»);

Проверим, сработал ли данный код

Всё работает. Мы теперь можем пользоваться указателем на функцию, как самой функцией.

Закомментируем наш предыдущий код.

Оказывается, если нам не нужно узнавать значение адреса функции в числовом выражении, то можно всё проделать гораздо проще, вот таким вот образом

  */

  void (*print_str_new)(const char *c_str);

  print_str_new = print_str;

  print_str_new(«Hello, World!!!»);

Ну в принципе мы это же и видели в теоретической части.

Проверим работу этого кода

Отлично!

Оказывается, мы можем кроме просто указателей на функции объявлять массивы таких указателей.

Зачем это нужно? Согласен, нужно редко, но может пригодиться. Поэтому не познакомить вас с такой возможностью я не имею права.

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

Ну давайте даже не представим, а поработаем конкретно с такими функциями.

Для этого перейдём в файл arith.c и увидим, что у нас есть две функции my_sum_p и my_div_p. Давайте создадим ещё 2 подобные функции, которые будут, соответственно, умножать и вычитать переданные параметры

//———————————————-

void my_mul_p(float a_f, float b_f, float *mul_f)

{

  *mul_f = a_f * b_f;

}

//———————————————-

void my_sub_p(float a_f, float b_f, float *sub_f)

{

  *sub_f = a_f b_f;

}

//———————————————-

Создадим на данные функции прототипы, вернёмся в функцию main() файла main.c, закомментируем предыдущий участок кода и создадим массив указателей на тип, подобный типу наших всех четырёх функций

  */

  void (*arith_operations[4])(float, float, float *) = {my_sum_p, my_sub_p, my_mul_p, my_div_p};

Как мы знаем, в прототипах функций (при её объявлении) мы не обязаны в параметрах использовать имена, достаточно только типов. Это также касается и указателей на функции, в нашем случае массивов указателей. Вот таким образом мы и объявили массив указателей на функции одного и того же типа размером 4 элемента. В данном случае мы также используем имена функций в качестве указателей в выражениях справа.

Создадим три вещественных переменных, две — для данных, которые мы будем посылать в качестве параметров в наши арифметические функции и проинициализируем их сразу и одну для возвращения результата. Как известно, чтобы результат возвращался в аргументе, последний должен иметь тип указателя. У нас пока обычный тип, а при вызове функций мы передадим адрес данной переменной

  void (*arith_operations[4])(float, float, float *) = {my_sum_p, my_sub_p, my_mul_p, my_div_p};

  float a = 3., b = 5., res = .0;

Вызовем в цикле по очереди наши все 4 функции, указатели на которые у нас находятся в массиве в его элементах, и в этом же цикле мы выведем результаты операций в консоль

  float a = 3., b = 5., res = .0;

  for(unsigned char i=0; i<4; i++)

  {

    arith_operations[i](a, b, &res);

    printf(«Valye oeration %hd is %fn», i, res);

  }

Проверим, как это сработает

Поставим breakpoint здесь

И теперь посмотрим, что там у нас за массив, выполнив до данной точки нашу программу в отладке.

Вот они — наши все указатели в массиве

Мы знаем адреса наших всех функций.

А в дизассемблере всё это выглядит вот так

Мы просто-напросто сохранили в стек адреса функций. Вот и весь массив. А говорят, ассемблер тяжёлый.

А в цикле потом мы эти адреса забираем

И затем вызываем функцию, находящуюся по адресу, который мы вытащили из стека

Конечно, между этими инструкциями есть ещё ряд инструкций, который нам пока не интересен, там создание и запись чисел с плавающей точкой, в которой применяются специальные команды арифметического сопроцессора.

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

Предыдущий урок Программирование на C Следующая часть

Смотреть ВИДЕОУРОК (нажмите на картинку)

C Указатели на функции


Post Views:
2 394

Указатели на функции

Указатель на функцию ссылается на входную точку этой функции.
Указатель на функцию— это довольное сложное, но очень мощное средство C++.
Несмотря на то что функция не является переменной, она, тем не менее, занимает
физическую область памяти, некоторый адрес которой можно присвоить указателю. Адрес,
присваиваемый указателю, является входной точкой функции. (Именно этот адрес
используется при вызове функции.) Если некоторый указатель ссылается на функцию, то ее
(функцию) можно вызвать с помощью этого указателя.

Указатели на функции также позволяют передавать функции в качестве аргументов
другим функциям. Адрес функции можно получить, используя имя функции без круглых
скобок и аргументов. (Этот процесс подобен получению адреса массива, когда также
используется только его имя без индекса.) Если присвоить адрес функции указателю, то эту
функцию можно вызвать через указатель. Например, рассмотрим следующую программу.
Она содержит две функции, vline() и hline(), которые рисуют на экране вертикальные и
горизонтальные линии заданной длины.


#include <iostream>
using namespace std;
void vline(int i), hline(int i);
int main()
{
void (*p)(int i);
p = vline; // указатель на функцию vline()
(*p)(4); // вызов функции vline()
p = hline; // указатель на функцию hline()
(*p)(3); // вызов функции hline()
return 0;
}
void hline(int i)
{
for( ; i; i--) cout << "-";
cout << "n";
}
void vline(int i)
{
for( ; i; i--) cout << "|n";
}
Вот как выглядят результаты выполнения этой программы.
I
I
I
I
- - -

Рассмотрим эту программу в деталях. В первой строке тела функции main() объявляется
переменная р как указатель на функцию, которая принимает один целочисленный аргумент
и не возвращает никакого значения. Это объявление не определяет, какая функция имеется в
виду. Оно лишь создает указатель, который можно использовать для адресации любой
функции этого типа. Необходимость круглых скобок, в которые заключен указатель *р,
следует из С++-правил предшествования.

В следующей строке указателю р присваивается адрес функции vline(). Затем
выполняется вызов функции vline() с аргументом 4. После этого указателю р присваивается
адрес функции hline(), и с помощью этого указателя реализуется ее вызов.

В этой программе при вызове функций посредством указателя используется следующий
формат:


(*p) (4);

Однако функцию, адресуемую указателем р, можно вызвать с использованием более
простого синтаксиса:


p (4);

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

Несмотря на то что в предыдущем примере указатель на функцию используется только
ради иллюстрации, зачастую такое его применение играет очень важную роль. Указатель на
функцию позволяет передавать ее адрес другой функции. В качестве показательного
примера можно привести функцию qsort() из стандартной С++-библиотеки. Функция qsort()
— это функция быстрой сортировки, основанная на алгоритме Quicksort, который
упорядочивает содержимое массива. Вот как выглядит ее прототип.


void qsort(void * start, size_t length, size_t size, int
(*compare) (const void *, const void *));

Функция qsort() — это функция сортировки из стандартной С++-библиотеки.

Прототип функции qsort() «прописан» в заголовке <cstdlib>, в котором также определен
тип size_t (как тип unsigned int). Чтобы использовать функцию qsort(), необходимо передать
ей указатель на начало массива объектов, который вы хотите отсортировать (параметр start),
длину этого массива (параметр length), размер в байтах каждого элемента (параметр size) и
указатель на функцию сравнения элементов массива (параметр *compare).

Функция сравнения, используемая функцией qsort(), сопоставляя два элемента массива,
должна возвратить отрицательное значение, если ее первый аргумент указывает на
значение, которое меньше второго, нуль, если эти аргументы равны, и положительное
значение, если первый аргумент указывает на значение, которое больше второго.

Чтобы понять, как можно использовать функцию qsort(), рассмотрим следующую
программу.


#include <iostream>
#include <cstdlib>
#include <cstring>
using namespace std;
int comp(const void *a, const void *b);
int main()
{
char str[] = "Указатели на функции дают гибкость.";
qsort(str, strlen(str), 1, comp);
cout << "Отсортированная строка: " << str;
return 0;
}
int comp(const void *a, const void *b)
{
return * (char *) a - * (char *) b;
}
Вот как выглядят результаты выполнения этой программы.
Отсортированная строка: Уаааабгдезииииккклнностттуфцью

Эта программа сортирует строку str в возрастающем порядке. Поскольку функции qsort()
передается вся необходимая ей информация, включая указатель на функцию сравнения, ее
можно использовать для сортировки данных любого типа. Например, следующая программа
сортирует массив целых чисел. Для гарантии переносимости при определении размера
целочисленного значения в ней используется оператор sizeof.


#include <iostream>
#include <cstdlib>
using namespace std;
int comp(const void *a, const void *b);
int main()
{
int num[] = {10, 4, 3, 6, 5, 7, 8};
int i;
qsort(num, 7, sizeof(int), comp);
for(i=0; i < 7; i++)
cout << num[i] << ' ';
return 0;
}
int comp(const void *a, const void *b)
{
return * (int *) a - * (int *) b;
}

He стану утверждать, что указатели на функции не так просты для понимания, но
практика поможет и «с ними найти общий язык». В отношении указателей на функции
необходимо рассмотреть еще один аспект, связанный с перегруженными функциями.

Как найти адрес перегруженной функции

Получить адрес перегруженной функции немного сложнее, чем найти адрес обычной
«одиночной» функции. Если же существует несколько версий перегруженной функции, то
должен существовать механизм, который бы определял, адрес какой именно версии мы
получаем. При получении адреса перегруженной функции именно способ объявления
указателя определяет, адрес какой ее версии будет получен. По сути, объявление указателя в
этом случае сравнивается с соответствующими объявлениями указателей перегруженных
функций. Функция, объявление которой обнаружит совпадение, и будет той функцией,
адрес которой мы получили.

В следующем примере программы содержится две версии функции space(). Первая
версия выводит на экран count пробелов, а вторая— count символов, переданных в качестве
аргумента ch. В функции main() объявляются два указателя на функции. Первый задан как
указатель на функцию с одним целочисленным параметром, а второй — как указатель на
функцию с двумя параметрами.


/* Использование указателей на перегруженные функции.
*/
#include <iostream>
using namespace std;
// Вывод на экран count пробелов.
void space(int count)
{
for( ; count; count--) cout << ' ';
}
// Вывод на экран count символов, переданных в ch.
void space(int count, char ch)
{
for( ; count; count--) cout << ch;
}
int main()
{
/* Создание указателя на void-функцию с одним int-параметром.
*/
void (*fp1) (int);
/* Создание указателя на void-функцию с одним int-параметром и
одним параметром типа char. */
void (*fp2)(int, char);
fp1 = space; // получаем адрес функции space(int)
fp2 = space; // получаем адрес функции space(int, char)
fp1(22); // Выводим 22 пробела (этот вызов аналогичен вызову
(* fp1) (22)) .
cout << "|n";
fp2(30, 'х'); // Выводим 30 символов "х" (этот вызов
аналогичен вызову (*fp2) (30, 'x').
cout << "|n";
return 0;
}
Вот как выглядят результаты выполнения этой программы.
I
ХХХХХХХХХХХХХХХХХХХХХХХХХХХХХХ I

Как отмечено в комментариях, компилятор способен определить, адрес какой
перегруженной функции он получает, на основе того, как объявлены указатели fp1 и fp2.

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

Понравилась статья? Поделить с друзьями:
  • Как найти координаты середины отрезка в пространстве
  • Как найти сюжет для своей книги
  • Как найти вес одной части
  • Как найти рюкзак для школы
  • Как найти свой стим оффер