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

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

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

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

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

Например, определим функцию, которая вычисляет сумму чисел, количество чисел нефиксировано:

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

Первый и обязательный параметр функции sum — n — указывает на количество необязательных параметров. В цикле устанавливаем указатель ptr на адрес параметра n и последовательно перемещаем его. С помощью операции разыменования *(++ptr) после перемещения указателя на один элемент вперед получаем значение и выполняем сложение с переменной result.

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

И для упрощения работы с переменным количеством параметров неопределенных типов в языке Си в стандартом заголовочном файле stdarg.h определены специальные макрокоманды:

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

Макрос va_start имеет следующее определение:

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

Макрос va_arg имеет следующее определение:

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

Макрос позволяет выйти из функции с переменным списком параметров. Она имеет следующее определение:

В качестве параметра она принимает указатель va_start, который ранее был задействован в макросах va_start и va_arg.

Перепишем предыдущий пример с использованием этих макрокоманд:

В функции sum() вначале определяется указатель va_list factor; .

Далее связываем этот указатель с первым необязательным параметром: va_start(factor, n); .

В цикле пробегаемся по всем необязательным параметрам и их значение прибавляем к переменной result: result += va_arg(factor, int);

В конце завершаем обработку параметров: va_end(factor); .

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

Но стоит отметить, что используемые нами функции ввода-вывода printf() и scanf() то же имеют неопределенное число параметров, но их типы также неопределены:

Для идентификации типов аргументов параметр format использует спецификаторы %d, %c и так далее. Например, определим собстенную функцию, которая будет выводит текст на экран, принимая параметры разных типов:

Для упрощения примера здесь взяты только два спецификатора: d (для типа int) и f (для типа double). В самой функции display с помощью указателя char *c пробегаемся по всем символам переданной строки format, пока этот указатель не станет указывать на нулевой символ ( *c!=’’ ). Если символ не равен знаку %, то выводим этот символ. Иначе смотрим, какой символ идет после знака % — d или f. В зависимости от этого получаем либо объект int, либо объект double.

Читайте также:  Credssp windows server 2012

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

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

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

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

Ф ункции printf и scanf типичные примеры функций с переменным числом параметров. Они имеют один обязательный параметр типа const char* — строку формата и остальные необязательные. Пусть мы вызываем эти функции и передаём им неверное количество аргументов: Если аргументов меньше, то функция пойдёт дальше по стеку и покажет какое-то значение, которое лежит «ниже» последнего аргумента, например

Читайте также:  Ntldr is compressed windows 7

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

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

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

Теперь объявим явно функцию как stdcall. Так как мы не использовали символа . то не произойдёт автоподмены stdcall на cdecl. Функция отработает, но после завершения стек будет повреждён.

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

Функции, в объявлениях которых в качестве последнего члена указано многоточие (. ), могут принимать переменное число аргументов. Function declarations in which the last member of is the ellipsis (. ) can take a variable number of arguments. В таких случаях C++ обеспечивает проверку типа только для явно объявленных аргументов. In these cases, C++ provides type checking only for the explicitly declared arguments. Переменные списки аргументов можно использовать, если функция должна быть настолько универсальной, что могут изменяться даже количество и типы аргументов. You can use variable argument lists when you need to make a function so general that even the number and types of arguments can vary. Семейство функций является примером функций, которые используются переменные списки аргументов. printf список объявлений аргументов The family of functions is an example of functions that use variable argument lists. printf argument-declaration-list

Функции с переменными аргументами Functions with variable arguments

Для доступа к аргументам после объявленных, используйте макрос, содержащийся в стандартном включаемом файле как описано ниже. To access arguments after those declared, use the macros contained in the standard include file as described below.

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

Microsoft C++ допускает указывать многоточие в качестве аргумента, если это последний аргумент и перед многоточием стоит запятая. Microsoft C++ allows the ellipsis to be specified as an argument if the ellipsis is the last argument and the ellipsis is preceded by a comma. Поэтому объявление int Func( int i, . ); допускается, а объявление int Func( int i . ); — нет. Therefore, the declaration int Func( int i, . ); is legal, but int Func( int i . ); is not.

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

В объявлении функции, которая принимает переменное число аргументов, требуется по крайней мере один аргумент-местозаполнитель, даже если он не используется. Declaration of a function that takes a variable number of arguments requires at least one placeholder argument, even if it is not used. Если этот аргумент-местозаполнитель не указан, доступ к остальным аргументам невозможен. If this placeholder argument is not supplied, there is no way to access the remaining arguments.

Читайте также:  Ssd kingston sata iii 240gb a400

Если аргументы типа char передаются как переменные аргументы, они преобразуются в тип int. Аналогичным образом, если аргументы типа float передаются как переменные аргументы, они преобразуются в тип двойные. When arguments of type char are passed as variable arguments, they are converted to type int. Similarly, when arguments of type float are passed as variable arguments, they are converted to type double. Для аргументов остальных типов могут выполняться обычные восходящие приведения целочисленных типов и типов с плавающей запятой. Arguments of other types are subject to the usual integral and floating-point promotions. См. в разделе стандартные преобразования Дополнительные сведения. See Standard Conversions for more information.

Функции, которым необходимы списки с переменным количеством аргументов, объявляются с многоточием (. ) в списках аргументов. Functions that require variable lists are declared by using the ellipsis (. ) in the argument list. Использование типов и макросы, описанные в включаемый файл для доступа к аргументам, которые передаются по списка переменных. Use the types and macros that are described in the include file to access arguments that are passed by a variable list. Дополнительные сведения об этих макросах см. в разделе va_arg, va_copy, va_end, va_start. For more information about these macros, see va_arg, va_copy, va_end, va_start. в документации по библиотеке времени выполнения C. in the documentation for the C Run-Time Library.

В следующем примере показано, каким образом макросы взаимодействуют с типом (объявлены в ): The following example shows how the macros work together with the type (declared in ):

Приведенный выше пример иллюстрирует следующие важные правила: The previous example illustrates these important concepts:

Перед тем как обращаться к аргументам из списка переменной длины, необходимо установить его маркер в качестве переменной типа va_list . You must establish a list marker as a variable of type va_list before any variable arguments are accessed. В приведенном выше примере этот маркер имеет имя vl . In the previous example, the marker is called vl .

К отдельным аргументам можно обращаться при помощи макроса va_arg . The individual arguments are accessed by using the va_arg macro. Макросу va_arg необходимо указать тип получаемых аргументов, чтобы он мог перенести из стека нужное количество байтов. You must tell the va_arg macro the type of argument to retrieve so that it can transfer the correct number of bytes from the stack. Если вы ошибетесь и укажете другой тип, а его размер не будет совпадать с типом, который вызывающая программа передает макросу va_arg , это может привести к непредсказуемым результатам. If you specify an incorrect type of a size different from that supplied by the calling program to va_arg , the results are unpredictable.

Результат, полученный при помощи макроса va_arg , необходимо явным образом преобразовывать в нужный вам тип. You should explicitly cast the result obtained by using the va_arg macro to the type that you want.

Для завершения обработки списка с переменным количеством аргументов необходимо вызвать макрос. va_end You must call the macro to terminate variable-argument processing. va_end

Ссылка на основную публикацию
Battery remaining time в биосе что это
Вчера я писал о ноутбуке Samsung серии 300E5 (http://ammo1.livejournal.com/262248.html). В этой серии применён очень простой и остроумный способ сохранения жизни...
Amd wraith spire характеристики
После выбора процессора, оперативной памяти и материнской платы можно обратить внимание на менее важные компоненты, например, на охлаждение системы. Охлаждение...
Amd x8 fx 8350 4ghz 8 ядер
Основные характеристики сокет SocketAM3+, ядро Piledriver Volan Vishera, ядер — 8, потоков — 8, L3 кэш 8Мб, частота 4 ГГц...
Battle eye не запускается
К любому онлайн-проекту нужен античит, иначе все баталии превратятся в вакханалию бесконечных патронов и бессмертных бойцов. Но иногда этот самый...
Adblock detector