Страницы

Поиск по вопросам

вторник, 24 декабря 2019 г.

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

#c #функции #переменные


Навеяно вопросом Как работает execlp, что за последний аргумент NULL? Полистал ответы
на схожие вопросы, но подробного описания не нашёл:


Функции с переменным числом параметров
Как в C объявить функцию с переменным числом аргументов?
С и переменное число аргументов


И т.д.

P.S Предполагается, что читатель этого текста уже выпустился из детсада, и не задаёт
вопросов уровня "Мне тут какую-то ошибку выдали, чо это?". И не путает C с C++.

Пинки, подзатыльники и прочие указания на "ты неправ" приветствуются.
    


Ответы

Ответ 1



В языке C есть возможность объявлять и использовать функции с переменным числом аргументов. Возможность эта обеспечивается особенностями работы со стеком при вызове функций, но сейчас они подробно рассматриваться не будут, только практическая сторона вопроса. Магия, происходящая в тоже за рамками, любопытный да найдёт объяснение этому шаманству. Прототип такой функции может выглядеть так, например: void print_messages( const char * title, ... ); А вызов - так: print_messages( "Вот что имею сообщить", "это раз", "это два", "это три", NULL ); И печатать, соответственно: Вот что имею сообщить: - это раз. - это два. - это три. Ну и её реализация: void print_messages( const char * title, ... ) { va_list ap; const char * message; va_start( ap, title ); printf( "%s:\n", title ); message = va_arg( ap, const char * ); while( message ) { printf( " - %s.\n", message ); message = va_arg( ap, const char *); } va_end( ap ); } Самой важной проблемой тут является определение конца списка аргументов. В данном случае использовался NULL для его отметки. Но это не всегда приемлемо. Например, когда NULL является допустимым значением аргумента. Или, скажем, 0/-1 в случае целых чисел. Первое решение этой проблемы - передать количество аргументов первым параметром: void print_numbers( size_t amount, ... ) { va_list ap; int number; va_start( ap, amount ); printf( "Total numbers: %zu, let's go! ", amount ); while( amount-- ) { number = va_arg( ap, int ); printf( " [%d]", number ); } va_end( ap ); } Вызов: print_numbers( 3, 11, 22, 33 ); Этот метод может применяться только тогда, когда в функцию передаются аргументы одного типа. Того же можно добиться, передавая массив значений с его размером. Но это не всегда оправдано. Да и рассматривается сейчас технологическая демка, а не целесообразность (которая всегда на совести программиста, но на то к нему /dev/brain и прилагается). Шёпотом: ну и TMTOWTDI в сях тоже бывает, хи-хи... А что делать, если аргументы могут быть разных типов? Тут на помощь приходит метод под названием "формат". Любой сишник с ним знаком по *printf*. Но это не интересно. Придумаем своё, на том же принципе. Определимся: символ 'c' означает char 's' - short 'l' - long 'z' - char * Прототип: void print_something( const char * format, ... ); Реализация: void print_something(const char * fmt, ...) { va_list ap; va_start( ap, fmt ); /* Гусары, молчать! */ long l; int i; char c; char *z; while( *fmt ) { switch( *fmt ) { case 'c': /* см дальше */ c = (char)va_arg( ap, char ); printf( "char: '%c'\n", c ); break; case 's': /* см дальше */ s = (short)va_arg( ap, short ); printf( "short: '%d'\n", s ); break; case 'i': i = va_arg( ap, int ); printf( "int: '%d'\n", i ); break; case 'l': l = va_arg( ap, long ); printf( "long: '%lu'\n", l );; break; case 'z': z = va_arg( ap, char * ); printf( "char *: '%s'\n", z );; break; default: printf( "Хрен знает что передали: '%c'\n", *fmt ); break; } fmt++; } } Дальше: char, short и прочее, которое меньше int. Эти строчки возбуждают компилятор: c = (char)va_arg( ap, char ); /* 'char' is promoted to 'int' when passed through '...' */ s = (short)va_arg( ap, short ); /* 'short' is promoted to 'int' when passed through '...' */ Выводы делать не буду, оставлю на домашнее задание.

Комментариев нет:

Отправить комментарий