функции с произвольным числом параметров c

Функции со списками аргументов переменных (C++)

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

Функции с переменными аргументами

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

Блок, относящийся только к системам Microsoft

Завершение блока, относящегося только к системам Майкрософт

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

Функции, которым необходимы списки с переменным количеством аргументов, объявляются с многоточием (. ) в списках аргументов. Используйте типы и макросы, описанные в включаемом файле, для доступа к аргументам, передаваемым из списка переменных. Дополнительные сведения об этих макросах см. в разделе va_arg, va_copy, va_end va_start. в документации по библиотеке времени выполнения C.

В следующем примере показано, как макросы работают вместе с типом (объявленным в ):

Приведенный выше пример иллюстрирует следующие важные правила:

Для завершения обработки списка с переменным количеством аргументов необходимо вызвать макрос. va_end

Источник

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

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

К ак уже обсуждалось ранее, по умолчанию параметры передаются функции через стек. Поэтому, технически, нет ограничения на количество передаваемых параметров – “запихать” можно сколько угодно. Проблема в том, как потом функция будет разбирать переданные параметры. Функции с переменным числом параметров объявляются как обычные функции, но вместо недостающих аргументов ставится многоточие. Пусть мы хотим сделать функцию, которая складывает переданные ей числа, чисел может быть произвольное количество. Необходимо каким-то образом передать функции число параметров. Во-первых, можно явно передать число параметров обязательным аргументом. Во-вторых, последний аргумент может иметь некоторое «терминальное» значение, наткнувшись на которое функция закончит выполнение.
Общий принцип работы следующий: внутри функции берём указатель на аргумент, далее двигаемся к следующему аргументу, увеличивая значение указателя.

OLD SCHOOL

Д елаем всё вручную. Функция, которая складывает переданные ей аргументы

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

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

Можно сделать первый аргумент необязательным и «перешагнуть» аргумент unsigned char num, но тогда возникнет большая проблема: аргументы располагаются друг за другом, но не факт, что непрерывно. Например, в нашем случае первый аргумент будет сдвинут не на один байт, а на 4 относительно num. Это сделано для повышения производительности. На другой платформе или с другим компилятором, или с другими настройками компилятора могут быть другие результаты.

Поэтому лучше число параметров, если это аргумент, сделать типом int или unsigned int.

Можно сделать по-другому: в качестве «терминального» элемента передавать ноль и считать, что если мы встретили ноль, то больше аргументов нет. Пример

Но теперь уже передавать нули в качестве аргументов нельзя. Здесь также есть один обязательный аргумент – первое переданное число. Если его не передавать, то мы не сможем найти адрес, по которому размещаются переменные в стеке. Некоторые компиляторы (Borland Turbo C) позволяют получить указатель на …, но такое поведение не является стандартным и его нужно избегать.

VA_ARG

М ожно воспользоваться макросом va_arg библиотеки stdarg.h. Он делает практически то же самое, что и мы: получает указатель на первый аргумент а затем двигается по стеку. Пример, та же функция, только с va_arg

Первый аргумент – число параметров – также лучше делать типа int, иначе получим проблему со сдвигом, кратным 4.

Макросы и типы для работы с переменным числом параметров

НазваниеОписание
va_listТип, который используется для извлечения дополнительных параметров функции с переменным числом параметров
void va_start(va_list ap, paramN)Макрос инициализирует ap для извлечения дополнительных аргументов, которые идут после переменной paramN. Параметр не должен быть объявлена как register, не может иметь типа массива или указателя на функцию.
void va_end(va_list ap)Макрос необходим для нормального завершения работы функции, работает в паре с макросом va_start.
void va_copy(va_list dest, va_list src)Макрос копирует src в dest. Поддерживается начиная со стандарта C++11

Неправильное использование

Если передано больше аргументов, то функция выведет только те, которые ожидала встретить

Так как очистку стека производит вызывающая функция, то стек не будет повреждён. Получается, что если изменить схему вызова и сделать так, чтобы вызываемый объект сам чистил стек после себя, то в случае неправильного количества аргументов стек будет повреждён. То есть, буде функция объявлена как __stdcall, в целях безопасности она не может иметь переменного числа аргументов.
Однако, если добавить спецификатор __stdcall к нашей функции summ она будет компилироваться. Это связано с тем, что компилятор автоматически заменит __stdcall на __cdecl.

Программа завершится с ошибкой вроде The value of ESP was not properly saved across a function call.

Источник

Функции с произвольным числом параметров c

Здравствуйте, nap2k, Вы писали:

N>Как в С++ реализуется сабж (типа как в sprintf())
Почитайте это.

Язык C++ вслед за С позволяет писать функции с переменным числом параметров. Одним из простых примеров может служить функция, вычисляющая среднее арифметическое своих аргументов. Другой уже классический пример — функция сцепления произвольного количества строк, которая является естественным обобщением функции сцепления двух строк.
Переменный список параметров задается в заголовке функции многоточием:
int f(…)
Этот заголовок не вызывает у компилятора протестов. Такая запись означает, что при определении функции компилятору неизвестны ни количество параметров, ни их типы, и он, естественно, не может ничего проверить. Количество параметров и их типы становятся известными только при вызове функции.
Однако у программиста с написанием таких функций сразу возникают проблемы. Ведь имена параметров отсутствуют! Поэтому доступ можно осуществить только одним способом – косвенным, используя указатель. Вспомним, что все параметры при вызове помещаются в стек. Если мы каким-то образом установим указатель на начало списка параметров в стеке, то, манипулируя с указателем, мы, в принципе, можем «достать» все параметры!
Таким образом, список параметров совсем пустой быть не может, должен быть прописан хотя бы один явный параметр, адрес которого мы можем получить при выполнении программы. Заголовок такой функции может выглядеть так:
int f(int k. )
Ни запятая, ни пробел после параметра не обязательны, хотя можно их и прописать.
Есть одно обстоятельство, которое ограничивает применение таких функций: при написании функции с переменным числом параметров помимо алгоритма обработки программист должен разрабатывать и алгоритм доступа к параметрам. Так что список необъявленных параметров не может быть совсем уж произвольным – в языке C++ не существует универсальных средств распознавания элементов этого списка. Это же означает, что передача аргумента не того типа, который задумывался, или не тем способом, который подразумевался при разработке, приведет к катастрофическим последствиям – компилятор-то ничего не проверяет.
Попробуем написать функцию, вычисляющую среднее арифметическое своих аргументов. Для этого требуется решить несколько проблем
как установиться на список параметров в стеке;
как «перебирать» параметры;
как закончить перебор.
Для доступа к списку параметров нам потребуется указатель, значением которого будет адрес последнего явного параметра в списке. Ответ на второй вопрос очевиден – надо изменять значение этого указателя, чтобы переместиться на следующий параметр. Отсюда следует, что указатель должен быть типизированным, поскольку с бестиповым указателем нельзя выполнять арифметические операции. Это же означает, что программист при разработке функции с переменным числом параметров должен отчетливо себе представлять типы аргументов, которые будет обрабатывать функция. Кроме того, способ передачи параметров должен быть одинаковым для всех параметров: либо все – по значению, либо все – по ссылке, либо все – по указателю.
Ответ на последний вопрос не вызывает затруднений. Это можно сделать одним из двух способов:
явно передать среди обязательных параметров количество аргументов;
добавить в конец списка аргумент с уникальным значением, по которому будет определяться конец списка параметров;
И тот, и другой способ имеют право на жизнь — все определяется потребностями задачи и вкусами программиста. В данном случае сначала попробуем второй способ: последним значением списка параметров будет ноль (листинг 7.7).
Листинг 7.7. Вычисление среднего арифметического аргументов (ноль в конце)

Вызов такой функции может выглядеть таким образом:
double y = f(1.0, 2.0, 3.0, 4.0, 0.0);
Переменная y получит значение 2.5. Так как компилятор ничего не проверяет, то попытка вызвать такую функцию с целыми аргументами f(1,2,3,0) либо вызовет аварийный останов программы (это лучший вариант), либо в приводит к неверному (но правдоподобному — в этом главная опасность) результату.
Реализация функции, которая в качестве первого параметра получает количество аргументов, на первый взгляд, не вызывает затруднений. Однако, если первый аргумент – целое число, то требуется преобразование указателя. И тут не все варианты проходят. Не будет работать такой вариант:

Причина кроется в том, что изменение указателя производится на столько байт, сколько в памяти занимает базовый тип. В обоих случаях мы установились не на начало списка double-параметров, а на sizeof(int) байтов «раньше» — на целую переменную. И от этого адреса происходит изменение указателя на 8 байт (sizeof(double)), что приводит к совершенно неверным результатам. Решение заключается в том, чтобы сначала изменить «целый» указатель, а потом уже его преобразовать в double *. Так всегда необходимо делать, если тип первого параметра отличается от типов отсутствующих параметров (листинг 7.8).
Листинг 7.8. Вычисление среднего арифметического аргументов (количество)

Источник

С++ для начинающих Функции с переменным числом параметров

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

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

Должен возникнуть вопрос как же получить доступ к каждому из этих параметров. Для того, чтоб хорошо понять то что будет написано, требуется знание указателей. Чтобы иметь доступ к каждому из параметров, нужно знать адрес первого параметра, а чтобы взять этот адрес, как раз и требуется указатель

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

Таким же образом можно привести классический пример суммирования элементов

while (* P ) //Пока встречаются параметры
<
sum = sum +(* P ) ” “ ; //Прибавляем к сумме то что взяли по адресу P
P ++; //Адресная арифметика. Смена текущего адреса на следующий
>


cout sum endl ; //Вывод результата на экран
>

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

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

Эти макроопределения обеспечивают простой и стандартный (независящий от реализации) способ доступа к спискам параметров переменной длины. Я не буду расписывать, так как сам плохо все это представляю, но приведу пример, содранный с учебника Марченко А.Л. Бархатный путь (2005). Там более менее расписано что к чему и нарушать его авторство я не хочу. Но кое что оттуда вынесу

Источник

Зона кода

Однако для создания функций с переменным числом аргументов требуются определённые представления о том, как в языке C организован механизм передачи параметров в функцию и доступ к ним из её тела. Также понадобится умение использовать макросы, определённые в стандартном заголовочном файле stdarg.h. Обо всём этом мы подробно поговорим в данной статье.

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

Знакомство с функциями с переменным числом аргументов

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

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

Вот пример заголовка функции с переменным числом аргументов:

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

Доступ к необязательным параметрам в теле функции

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

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

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

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

Например, в теле функции fun() вызов макроса должен выглядеть так:

Таким же образом можно получать значения остальных необязательных параметров. После получения значения последнего из них, содержимое argptr считается неопределённым. Впрочем, мы не обязаны обрабатывать с помощью макроса va_arg() все необязательные параметры. Всё зависит от наших целей.

После того, как макрос va_arg() вызван нужное количество раз, необходимо вызвать макрос va_end() :

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

Ближе к делу

А теперь давайте обсудим особенности реализаций типа va_list и макросов, предназначенных для создания функций с переменным числом аргументов, в компиляторе MinGW64, работающем под управлением операционной системы Windows 10. Именно это программное обеспечение установлено на моём ноутбуке и именно его я буду использовать для запуска и компиляции программ, рассмотренных далее.

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

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

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

Последние две версии можно, кстати, объединить в третью.

А теперь рассмотрим два примера функций с переменным числом аргументов. При их создании будем использовать макросы. Всё будем делать «по науке», как положено. Нужно использовать макрос va_end() — значит будем использовать.

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

Создадим функцию print() с переменным числом аргументов. Единственный обязательный формальный параметр str будет иметь тип char * и содержать адрес C-строки. Все остальные параметры будут необязательными. Предполагается, что значение каждого необязательного аргумента — это либо целое число, либо вещественное число, либо символ, либо адрес C-строки. Функция будет выводить на консоль значения необязательных параметров в том порядке, в котором они были переданы функции (с тем очевидным исключением, что вместо адресов C-строк будут выводиться сами C-строки).

Как мы видим, к программе подключён заголовок (стр. 2).

Не забываем напоследок вызвать макрос va_end() (стр. 34).

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

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

то в ходе компиляции мы получили бы предупреждение:

In function ‘print’:
[Warning] ‘char’ is promoted to ‘int’ when passed through ‘…’
[Note] (so you should pass ‘int’ not ‘char’ to ‘va_arg’)
[Note] if this code is reached, the program will abort

Программа скомпилировалась бы, но её запуск привёл бы к аварийному завершению.

Второй пример функции с переменным числом аргументов

В некоторых языках программирования, например, в C++ и Java поддерживается перегрузка функций. Под этим понимается сосуществование в одной области видимости функций, имеющих одинаковые имена, но различающихся количеством или (и) типами формальных параметров. Обычно одноимённые функции выполняют, по существу, близкие действия.

Язык C не поддерживает перегрузку функций. Однако, используя функцию с переменным числом аргументов, можно в какой-то степени сымитировать вызовы функций, имеющих одинаковые имена, но принимающих параметры разных типов. Разумеется, при этом каждый раз вызываться будет одна и та же функция.

Второй обязательный формальный параметр num имеет тип int и содержит число необязательных параметров.

Если необязательные параметры — это целые или вещественные числа, то функция вычисляет их сумму и получает результат в виде значения типа int в первом случае или типа double во втором. А если необязательные параметры — это адреса C-строк, то функция выполняет конкатенацию всех C-строк, получая новую строку. Теперь уже результатом будет адрес новой строки (т. е. значение типа char * ).

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

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

А можно обойтись без макросов?

Да, можно, поскольку мы имеем представление о том, как «работают» макросы в нашем случае. Но нам потребуется некоторая дополнительная информация. А именно, нам нужно получить ответы на 2 вопроса:

Заметим, что, вообще говоря, размер памяти, выделяемой в стеке под значение параметра, необязательно совпадает с размером значения, а может его превосходить.

Заметим, что размер переменной типа int составляет 4 байта. Таким образом, при хранении значения этого типа используется только половина отведённой памяти. Оказалось, что задействованы младшие 4 байта из 8 (под «младшими» понимаются имеющие меньшие адреса).

Этой информации вполне достаточно, чтобы написать новую версию функции sum() первой программы. Приводим код новой версии программы целиком:

Поскольку после получения значения последнего необязательного параметра значение p уже не изменяется, по окончании цикла p содержит адрес последнего параметра. А в случае использования va_arg() по окончании цикла в argptr находился адрес первого байта, следующего за 8-байтовой ячейкой стека, содержащей значение последнего необязательного параметра.

Макросу va_end никакой код в новой версии функции не соответствует.

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

Преимущество кода, не использующего макросы, определённые в файле stdarg.h, заключается в том, что он очень гибок. При желании мы можем «перепрыгивать» с одного значения необязательного параметра на другое, двигаться по стеку в любом направлении любое количество раз. В случае использования макросов наши возможности несколько ограничены: мы можем двигаться по стеку лишь в одном направлении. Чтобы вернуться к какому-либо уже пройденному значению, необходимо начинать весь путь заново.

Зато код, основанный на макросах, универсален. Разработчикам компиляторов даётся большая свобода выбора как способа передачи значений необязательных параметров в функцию, так и способа реализации механизма доступа к ним. Так что макросы с одними и теми же названиями в разных системах могут быть реализованы по-разному.

Кстати, мы так и не установили, в каком порядке значения параметров заталкиваются в стек в нашем случае. Мы лишь выяснили, что перебор значений идёт в сторону увеличения адресов. Но адреса могут возрастать как в направлении от вершины стека к его дну, так и в обратном направлении.

Меры предосторожности

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

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

А вот предумышленный обман может произойти со стороны пользователя программы, при условии, что информация, полученная от него, без проверок передаётся функции с переменным числом аргументов. Такие рода ситуации описаны в статье хорошо известного Криса Касперски «Неизвестная уязвимость функции printf».

Но на самом деле печататься будет содержимое стека, которое вовсе не предназначено для чужих глаз. И вполне может оказаться так, что напечатан будет верный пароль. Описанный случай, как мне кажется, достаточно искусственный. Однако, теоретически такое случиться может. Статья интересная, советую её прочитать.

Источник

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *