Скажите, а код компилировался под Windows? Дело в том, что в Linux, va_list - это структура, в которой описываются смещения и границы параметров. Это сделано для защиты стека. Да и спасибо за труд.
Привет, у меня вопрос по варидическим функциям. Насколько я понимаю у нас есть указатель на самое начало стек фрейма и есть указатель на первый вариадический аргумент. Разве этого не достаточно для того что бы нам прочесть все переданные аргументы без явной передачи их кол-ва. Мы ведь можем читать начиная от первого аргумента до того момента пока мы не упремся в начало стек фрейма, всё что нам надо это знать тип аргументов. Или я чего не то не так понимаю?
если несколько функций вызвано, то мы "уткнемся" не в конец стека, а в некие данные, и как понять к чему они относятся, не зная числа параметров функции?
@selfedu_rus а почему упремся в конец стека? стек ведь растет в обратную сторону. Увеличивая адрес мы движемся по стеку к его началу. Имея ссылку на начало стек фрейма мы можем начать с первого аргумента и увеличивать адрес пока не уткнемся в начало этого же стек фрейма. Ссылка на начало и будет нашим ограничителем что бы не зайти в предыдущий стек фрейм
@@pewpewpew8613 Вообще говоря, да. В стековом фрейме хранятся параметры, локальные переменные, адрес возврата и может быть еще какая-то служебная информация, характерная для платформы, на которой работает программа. Функция должна иметь доступ к параметрам и локальным переменным, для этого у ней есть какой-то "якорь" - служебный указатель, отмечающий какое-то место в стеке. Относительно этого указателя и отсчитываются смещения к параметрам и локальным переменным. Например, для конкретной платформы в момент вызова функции состояние стека может быть таким (от дна стека к вершине): параметр_N, параметр_N-1, ...,параметр_1, адрес_возврата, старое_значение_EBP, локальные_переменные, вершина_стека. При этом значение регистра ЕВР указывает на "старое_значение_EBP" - точку, разделяющую параметры и локальные переменные. По значению EBP можно высчитать, где находится адрес возврата, и сравнивать указатель на очередной параметр с этим значением, чтобы понять , кончились параметры или нет. Но это более платформозависимый механизм, тут просто макросами препроцессора не обойтись, нужны вставки в машинных кодах (для доступа к ЕВР, для учета ближняя это или дальняя функция). Нужно учитывать разрядность ОС (8/16/32/64), процессор, тонкости передачи параметров. Теоретически возможно добавить к stdarg какую-нибудь функцию va_exist (есть ли еще параметры?), но для каждой платформы придется писать свой вариант этой функции.
А как в C++ передать в функцию произвольное число заранее определённых разных классов (но с некоторыми одноименными методами), например "init_interfaces(SPI1, UART2);" когда неизвестно в каком порядке они будут поданы или вовсе без них("init_interfaces();") ? У меня было такое задание на собеседовании, смог решить не все условия и теперь меня этот вопрос уже долго мучает
Чет вообще неудобно. А можно так же как и с main, которая через батник вызывается? int main(int argc, char** argv) и перебирать параметры через argv[1], argv[2]...argv[argc] ?
Можно моделировать параметры по умолчанию с помощью вариадических параметров. Создать в функции локальные переменные с начальным значением. При чтении вариадических параметров присвоить этим переменным прочитанные значения. А если параметров нет, то останутся первоначально присвоенные (по умолчанию).
А в чем проблема-то? Вы как человек, создающий логику программы, априори знаете количество передаваемых элементов, иначе Вы бы не вызвали функцию. Если Вам лень считать их, то заведите массив аргументов и передайте количество sizeofом. Если угодно, то можно это воспринимать как неудобный синтаксис, взамен которого Вы получаете мощный инструмент.
@@old_cucumber3805 то есть я создаю программу с вводом пользователя, для суммирования, и я заведомо должен знать кол-во аргументов или что? Я вообще не понял логики. Смысл мне городить велосипед, если в языке есть хороший оптимизированный для работы со стеком инструмент для этих вещей? Или вы думаете что в printf тоже надо было через мапу какую нибудь делать, указывая что я сейчас передал?
@@old_cucumber3805 ну то есть я пишу функцию для взаимодействия с пользователем и должен заведомо знать сколько он напишет символов для сложения? Потом хранить все эти параметры в динамическом массиве, постоянно либо аллоцируя сразу дофига, чтоб все введенное влезло, либо реаллоцируя и тратя куча времени? По моему все же проще пользоваться готовым оптимизированным для работы со стеком инструментом….
Не обязательно передавать именно количество параметров. Например ,в вариадической функции 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
Спасибо Вам огромное за ваш труд! Особенно за внимание к деталям каждой темы, которую Вы разбираете!!! Не останавливайтесь пожалуйста!!! ❤
отдельное спасибо за концовку, связанную с С++!
Спасибо. Все понятно.
вообще огонь уроки!🤙
Спасибо
Скажите, а код компилировался под Windows?
Дело в том, что в Linux, va_list - это структура, в которой описываются смещения и границы параметров. Это сделано для защиты стека.
Да и спасибо за труд.
да, под Windows
Привет, у меня вопрос по варидическим функциям. Насколько я понимаю у нас есть указатель на самое начало стек фрейма и есть указатель на первый вариадический аргумент. Разве этого не достаточно для того что бы нам прочесть все переданные аргументы без явной передачи их кол-ва. Мы ведь можем читать начиная от первого аргумента до того момента пока мы не упремся в начало стек фрейма, всё что нам надо это знать тип аргументов. Или я чего не то не так понимаю?
если несколько функций вызвано, то мы "уткнемся" не в конец стека, а в некие данные, и как понять к чему они относятся, не зная числа параметров функции?
@selfedu_rus а почему упремся в конец стека? стек ведь растет в обратную сторону. Увеличивая адрес мы движемся по стеку к его началу. Имея ссылку на начало стек фрейма мы можем начать с первого аргумента и увеличивать адрес пока не уткнемся в начало этого же стек фрейма. Ссылка на начало и будет нашим ограничителем что бы не зайти в предыдущий стек фрейм
@@pewpewpew8613 Вообще говоря, да. В стековом фрейме хранятся параметры, локальные переменные, адрес возврата и может быть еще какая-то служебная информация, характерная для платформы, на которой работает программа. Функция должна иметь доступ к параметрам и локальным переменным, для этого у ней есть какой-то "якорь" - служебный указатель, отмечающий какое-то место в стеке. Относительно этого указателя и отсчитываются смещения к параметрам и локальным переменным.
Например, для конкретной платформы в момент вызова функции состояние стека может быть таким (от дна стека к вершине): параметр_N, параметр_N-1, ...,параметр_1, адрес_возврата, старое_значение_EBP, локальные_переменные, вершина_стека.
При этом значение регистра ЕВР указывает на "старое_значение_EBP" - точку, разделяющую параметры и локальные переменные. По значению EBP можно высчитать, где находится адрес возврата, и сравнивать указатель на очередной параметр с этим значением, чтобы понять , кончились параметры или нет. Но это более платформозависимый механизм, тут просто макросами препроцессора не обойтись, нужны вставки в машинных кодах (для доступа к ЕВР, для учета ближняя это или дальняя функция). Нужно учитывать разрядность ОС (8/16/32/64), процессор, тонкости передачи параметров. Теоретически возможно добавить к stdarg какую-нибудь функцию va_exist (есть ли еще параметры?), но для каждой платформы придется писать свой вариант этой функции.
О, элипсис… например
void f()
{
printf(“hello”);
}
принимает сколько угодно аргументов. И получаем уб
А как в C++ передать в функцию произвольное число заранее определённых разных классов (но с некоторыми одноименными методами), например "init_interfaces(SPI1, UART2);" когда неизвестно в каком порядке они будут поданы или вовсе без них("init_interfaces();") ? У меня было такое задание на собеседовании, смог решить не все условия и теперь меня этот вопрос уже долго мучает
Через массив указателей на один единый базовый класс у всех этих классов.
Чет вообще неудобно.
А можно так же как и с main, которая через батник вызывается?
int main(int argc, char** argv)
и перебирать параметры через argv[1], argv[2]...argv[argc] ?
А вот va_arg принимает указатель типа va_list и дескриптор типа(int)...а как можно самому объявить такую функцию чтоб она принимала дескриптор типа?
Никак, потому что va_arg, va_copy, va_end, va_start это макросы, а не функции. Хотя Сергей почему то их функциями обзывает.
@@weerbox Но можно написать свои макросы по образцу stdarg.h
В C нет функций с параметрами по умолчанию? После Python я воспринимал функции с параметрами по умолчанию, как нечто что есть везде "по умолчанию"
раньше в Си цикла for не было
В стандарте C99 таких нет. Но в С++ есть.
Можно моделировать параметры по умолчанию с помощью вариадических параметров. Создать в функции локальные переменные с начальным значением. При чтении вариадических параметров присвоить этим переменным прочитанные значения. А если параметров нет, то останутся первоначально присвоенные (по умолчанию).
а зачем нам это, если по факту все равно нужно передавать в функцию количество поступаемых аргументов?
чтобы не писать sum_2, sum_3 и т д, а написать одну sum которая все проглотит если правильно передать
А в чем проблема-то? Вы как человек, создающий логику программы, априори знаете количество передаваемых элементов, иначе Вы бы не вызвали функцию. Если Вам лень считать их, то заведите массив аргументов и передайте количество sizeofом. Если угодно, то можно это воспринимать как неудобный синтаксис, взамен которого Вы получаете мощный инструмент.
@@old_cucumber3805 то есть я создаю программу с вводом пользователя, для суммирования, и я заведомо должен знать кол-во аргументов или что? Я вообще не понял логики. Смысл мне городить велосипед, если в языке есть хороший оптимизированный для работы со стеком инструмент для этих вещей? Или вы думаете что в printf тоже надо было через мапу какую нибудь делать, указывая что я сейчас передал?
@@old_cucumber3805 ну то есть я пишу функцию для взаимодействия с пользователем и должен заведомо знать сколько он напишет символов для сложения? Потом хранить все эти параметры в динамическом массиве, постоянно либо аллоцируя сразу дофига, чтоб все введенное влезло, либо реаллоцируя и тратя куча времени? По моему все же проще пользоваться готовым оптимизированным для работы со стеком инструментом….
Не обязательно передавать именно количество параметров. Например ,в вариадической функции 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