C++.Бархатный путь

       

Функции с изменяемым списком параметров


Для решения задачи передачи неопределённого количества параметров C++ располагает также средствами объявления переменных списков параметров.

Вспомним несколько форм Бэкуса-Наура, определяющих синтаксис списка параметров в определении и прототипе функции. СписокОбъявленийПараметров ::= [СписокОбъявленийПарам] [...] ::= СписокОбъявленийПарам, ... СписокОбъявленийПарам ::= ОбъявлениеПараметра

::= [СписокОбъявленийПарам,] ОбъявлениеПараметра

Таким образом, список объявлений параметров может завершаться многоточием, отделённым запятой от списка объявлений параметров, этого многоточия в списке параметров может не быть, а возможно также, что кроме многоточия в списке параметров вовсе ничего нет.

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

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

Рассмотрим прототип и определение функции с переменным количеством параметров. int PP(…); int PP(…) { return 100; }

Трансляция этого фрагмента кода не вызывает у транслятора никаких возражений. Многоточием в списке параметров он предупреждён о возможных неожиданностях.

Следующий фрагмент кода демонстрирует варианты выражений вызова функции PP(). int retVal; retVal = PP(); retVal = PP(1,2 + retVal,3,4,5,25*2); PP('z',25,17);

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

    в стековом сегменте выделяется запись активации. Теперь размер записи активации зависит от количества и типа параметров в выражении вызова, (а не прототипа). Так что сначала нужно определить и запомнить общее количество и тип выражений, которые образуют список параметров в выражении вызова функции. Как и раньше, вычисление значений производится в процессе выполнения программы. Как и раньше, значение параметра может быть представлено любыми выражениями; на основе анализа прототипов вызываемой функции, расположенных в области видимости вызывающей функции, определяются начальные значения параметров (если они имеются), которые и записываются в соответствующие области записи активации. Как мы увидим дальше, в функциях с переменным количеством параметров слева от многоточия всё же может находиться хотя бы один явным образом определённый параметр. В противном случае просто не будет возможности воспользоваться значениями параметров; вычисляются значения выражений, которые образуют список параметров в выражении вызова. Поскольку вычисление значений производится в ходе выполнения программы, здесь также нет никаких ограничений на процесс определения значения выражений. Можно использовать любые значения, а также вызовы ранее объявленных функций; элементам записи активации присваиваются вычисленные значения. При этом возможно, часть параметров, которым были присвоены значения умолчания (это всегда ближайшие к многоточию параметры), получит новые значения. В этом процессе не допускается неопределённых ситуаций. Либо элемент записи активации получает значение умолчания, либо ему присваивается значение при вызове. Нарушение порядка означивания, как и раньше, выявляется ещё на стадии трансляции программы; в вызываемой функции всем параметрам, которые были указаны в списке параметров, присваиваются значения из записи активации. Для остальных (непостоянных и, естественно, безымянных) параметров выделяется дополнительная память. Эти параметры также получают свои значения из записи активации; управление передаётся первому оператору функции. Означенные параметры используются как переменные с определёнными значениями. Доступ к безымянным параметрам, в силу того, что к ним невозможно обращение по имени, обеспечивается специальными алгоритмами.


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

Если же параметр был определён как параметр без имени, то существует единственный способ доступа к таким параметрам - доступ с помощью указателей.

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

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

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

При этом либо известен тип и количество передаваемых параметров, и процедура доступа к параметрам сводится к примитивному алгоритму, который воспроизводится в следующем примере: #include iostream.h long PP(int n, ...); void main (void) { long RR; RR = PP(5, 1, 2, 3, 4, 5 ); /* Вызвали функцию с 6 параметрами. Единственный обязательный параметр определяет количество передаваемых параметров. */ cout RR endl; } long PP(int n ...) { int *pPointer = n; // Настроились на область памяти с параметрами... int Sum = 0; for ( ; n; n--) Sum += *(++pPointer); return Sum; }

Либо известен тип элементов списка и признак завершения списка передаваемых параметров. Процедура доступа к параметрам также проста, как и в первом случае: #include iostream.h long PP(int par1 ...); void main (void) { long RR; RR = PP( 1, 2, 0, 4, 0 ); /* Вызвали функцию с 5 параметрами. Единственный обязательный параметр - первый параметр в списке параметров. */ cout RRR endl; } long PP(int par1 ...) { int *pPointer = par1; /* Настроились на область памяти с параметрами. Признак конца списка - параметр с нулевым значением. */ int Sum = 0; for ( ; *pPointer != 0; pPointer++) Sum += *pPointer; // Что-то здесь не так… Мы так и не обработали до конца весь список. return Sum; }


Содержание раздела