#47. Функции с произвольным числом параметров | Язык C для начинающих

Поделиться
HTML-код
  • Опубликовано: 18 окт 2024
  • Практический курс по C/C++: stepik.org/cou...
    Телеграм-канал: t.me/java_and_c
    Инфо-сайт: proproprogs.ru...
    Объявление и использование функций с произвольным числом параметров (вариадические функции). Дополнение для языка С++: перегрузка функций и функции с параметрами по умолчанию.

Комментарии • 28

  • @andrey_sautenko
    @andrey_sautenko Год назад +3

    отдельное спасибо за концовку, связанную с С++!

  • @Michael-Solo
    @Michael-Solo Год назад +1

    Спасибо Вам огромное за ваш труд! Особенно за внимание к деталям каждой темы, которую Вы разбираете!!! Не останавливайтесь пожалуйста!!! ❤

  • @andredru4278
    @andredru4278 9 месяцев назад +1

    Спасибо. Все понятно.

  • @СаняСанин-ш6у
    @СаняСанин-ш6у Год назад +1

    вообще огонь уроки!🤙

  • @РусланКарнеенко
    @РусланКарнеенко 8 месяцев назад +1

    Спасибо

  • @AlekzzzR
    @AlekzzzR Год назад +1

    Скажите, а код компилировался под Windows?
    Дело в том, что в Linux, va_list - это структура, в которой описываются смещения и границы параметров. Это сделано для защиты стека.
    Да и спасибо за труд.

  • @pewpewpew8613
    @pewpewpew8613 10 месяцев назад +1

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

    • @selfedu_rus
      @selfedu_rus  10 месяцев назад

      если несколько функций вызвано, то мы "уткнемся" не в конец стека, а в некие данные, и как понять к чему они относятся, не зная числа параметров функции?

    • @pewpewpew8613
      @pewpewpew8613 10 месяцев назад

      ​ @selfedu_rus а почему упремся в конец стека? стек ведь растет в обратную сторону. Увеличивая адрес мы движемся по стеку к его началу. Имея ссылку на начало стек фрейма мы можем начать с первого аргумента и увеличивать адрес пока не уткнемся в начало этого же стек фрейма. Ссылка на начало и будет нашим ограничителем что бы не зайти в предыдущий стек фрейм

    • @olegkomlev
      @olegkomlev Месяц назад

      @@pewpewpew8613 Вообще говоря, да. В стековом фрейме хранятся параметры, локальные переменные, адрес возврата и может быть еще какая-то служебная информация, характерная для платформы, на которой работает программа. Функция должна иметь доступ к параметрам и локальным переменным, для этого у ней есть какой-то "якорь" - служебный указатель, отмечающий какое-то место в стеке. Относительно этого указателя и отсчитываются смещения к параметрам и локальным переменным.
      Например, для конкретной платформы в момент вызова функции состояние стека может быть таким (от дна стека к вершине): параметр_N, параметр_N-1, ...,параметр_1, адрес_возврата, старое_значение_EBP, локальные_переменные, вершина_стека.
      При этом значение регистра ЕВР указывает на "старое_значение_EBP" - точку, разделяющую параметры и локальные переменные. По значению EBP можно высчитать, где находится адрес возврата, и сравнивать указатель на очередной параметр с этим значением, чтобы понять , кончились параметры или нет. Но это более платформозависимый механизм, тут просто макросами препроцессора не обойтись, нужны вставки в машинных кодах (для доступа к ЕВР, для учета ближняя это или дальняя функция). Нужно учитывать разрядность ОС (8/16/32/64), процессор, тонкости передачи параметров. Теоретически возможно добавить к stdarg какую-нибудь функцию va_exist (есть ли еще параметры?), но для каждой платформы придется писать свой вариант этой функции.

  • @MrLeyt1125
    @MrLeyt1125 7 месяцев назад +1

    Чет вообще неудобно.
    А можно так же как и с main, которая через батник вызывается?
    int main(int argc, char** argv)
    и перебирать параметры через argv[1], argv[2]...argv[argc] ?

  • @КириллЧе-я5ы
    @КириллЧе-я5ы 8 месяцев назад +1

    О, элипсис… например
    void f()
    {
    printf(“hello”);
    }
    принимает сколько угодно аргументов. И получаем уб

  • @lego6757
    @lego6757 Год назад +2

    А как в C++ передать в функцию произвольное число заранее определённых разных классов (но с некоторыми одноименными методами), например "init_interfaces(SPI1, UART2);" когда неизвестно в каком порядке они будут поданы или вовсе без них("init_interfaces();") ? У меня было такое задание на собеседовании, смог решить не все условия и теперь меня этот вопрос уже долго мучает

    • @selfedu_rus
      @selfedu_rus  Год назад +2

      Через массив указателей на один единый базовый класс у всех этих классов.

  • @Hippan_
    @Hippan_ Год назад +1

    А вот va_arg принимает указатель типа va_list и дескриптор типа(int)...а как можно самому объявить такую функцию чтоб она принимала дескриптор типа?

    • @weerbox
      @weerbox Год назад

      Никак, потому что va_arg, va_copy, va_end, va_start это макросы, а не функции. Хотя Сергей почему то их функциями обзывает.

    • @olegkomlev
      @olegkomlev Месяц назад

      @@weerbox Но можно написать свои макросы по образцу stdarg.h

  • @ГубкаБоб-р8ъ
    @ГубкаБоб-р8ъ Год назад +1

    В C нет функций с параметрами по умолчанию? После Python я воспринимал функции с параметрами по умолчанию, как нечто что есть везде "по умолчанию"

    • @Hippan_
      @Hippan_ Год назад

      раньше в Си цикла for не было

    • @selfedu_rus
      @selfedu_rus  Год назад

      В стандарте C99 таких нет. Но в С++ есть.

    • @olegkomlev
      @olegkomlev Месяц назад

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

  • @begidurak
    @begidurak Год назад

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

    • @kotifnat
      @kotifnat 4 месяца назад +1

      чтобы не писать sum_2, sum_3 и т д, а написать одну sum которая все проглотит если правильно передать

    • @old_cucumber3805
      @old_cucumber3805 2 месяца назад

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

    • @kotifnat
      @kotifnat 2 месяца назад

      @@old_cucumber3805 то есть я создаю программу с вводом пользователя, для суммирования, и я заведомо должен знать кол-во аргументов или что? Я вообще не понял логики. Смысл мне городить велосипед, если в языке есть хороший оптимизированный для работы со стеком инструмент для этих вещей? Или вы думаете что в printf тоже надо было через мапу какую нибудь делать, указывая что я сейчас передал?

    • @kotifnat
      @kotifnat 2 месяца назад

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

    • @olegkomlev
      @olegkomlev Месяц назад

      Не обязательно передавать именно количество параметров. Например ,в вариадической функции printf первым параметром передается не количество, а форматная строка, где есть символы % c последующей буквой. Каждый % означает параметр, а буква задает тип параметра.
      Или можно так задать функцию, которая печатает список ненулевых целых чисел:
      void print_num(int n,...){
      va_list param;
      va_start(param,n);
      while(n!=0){
      printf("%d ",n);
      n=va_arg(param,int);
      }
      va_end(param);
      printf("
      ");
      }
      Здесь первый обязательный параметр передает не количество параметров, а является первым параметром. А последним параметром нужно указывать 0, который не будет выведен. Т.е. можно вызывать функцию:
      print_num(3,5,7,-4,0); // напечатает 3 5 7 -4
      print_num(0); //будет выведена пустая строка
      print_num(6,0); //напечатает 6
      print_num(13,55,0);// напечатает 13 55