Variable Argument List
От: Chez Россия  
Дата: 24.08.04 14:25
Оценка:
Здравствуйте!

Насколько я знаю, делается так:
void func(int n, ...)
{ va_list v; va_start(v, n); ... }
А как получить v в функции типа
void simplified_func(...)
{ va_list v; /* Чем заменить va_start ? */ ... }
Chez, ICQ# 161095094
Re: Variable Argument List
От: _nn_ www.nemerleweb.com
Дата: 24.08.04 14:32
Оценка:
Здравствуйте, Chez, Вы писали:

C>Здравствуйте!


C>Насколько я знаю, делается так:

C>
C>void func(int n, ...)
C>{ va_list v; va_start(v, n); ... }
C>
А как получить v в функции типа

C>
C>void simplified_func(...)
C>{ va_list v; /* Чем заменить va_start ? */ ... }


Никак.
Если используется VC то можно посмотреть эту ветку : http://rsdn.ru/Forum/Message.aspx?mid=637982&only=1
Автор: Denwer
Дата: 13.05.04
http://rsdn.nemerleweb.com
http://nemerleweb.com
Re: Variable Argument List
От: LaptevVV Россия  
Дата: 24.08.04 14:37
Оценка: -1
Здравствуйте, Chez, Вы писали:

C>Здравствуйте!


C>Насколько я знаю, делается так:

C>
C>void func(int n, ...)
C>{ va_list v; va_start(v, n); ... }
C>
А как получить v в функции типа

C>
C>void simplified_func(...)
C>{ va_list v; /* Чем заменить va_start ? */ ... }

Ничего не выйдет! Требуется хотя бы 1 обязательный параметр — это принципиальный момент.
Почитай вот это

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

int f(…)

Этот заголовок не вызывает у компилятора протестов. Такая запись означает, что при определении функции компилятору неизвестны ни количество параметров, ни их типы, и он, естественно, не может ничего проверить. Количество параметров и их типы становятся известными только при вызове функции.
Однако у программиста с написанием таких функций сразу возникают проблемы. Ведь имена параметров отсутствуют! Спрашивается, как же написать алгоритм обработки, не зная имен параметров? Естественно, только одним способом – использовать для доступа к параметрам указатель! Вспомним, что все параметры при вызове помещаются в стек. Если мы каким-то образом установим указатель на начало списка параметров в стеке, то манипулируя с указателем, мы, в принципе, можем «достать» все параметры!
Таким образом, совсем пустой список параметров мы написать не можем — должен быть прописан хотя бы один явный параметр, адрес которого мы собираемся получить при выполнении программы. Заголовок такой функции может выглядеть так:
int f(int k...)

Как мы видим, ни запятая, ни пробел после параметра не обязательны, хотя можно их и прописать.
При написании функции с переменным числом параметров помимо алгоритма обработки программист должен разрабатывать и алгоритм доступа к параметрам. Нам надо ответить на два вопроса:
    как «перебирать» параметры;
    как закончить перебор.
Ответ на первый вопрос очевиден – надо изменять значение указателя, чтобы переместиться на следующий параметр. Отсюда следует, что указатель должен быть типизированным, поскольку с бестиповым указателем нельзя выполнять арифметические операции. Это же означает, что программист при разработке функции с переменным числом параметров должен отчетливо себе представлять типы аргументов, которые будет обрабатывать функция. Кроме того, способ передачи параметров должен быть одинаковым для всех: либо все – по значению, либо все – по ссылке, либо все – по указателю.
Передача аргумента не того типа, который задумывался, или не тем способом, который подразумевался при разработке, приведет к катастрофическим последствиям – компилятор-то ничего не проверяет.
Ответ на второй вопрос не вызывает затруднений. Это можно сделать одним из двух способов:
* явно передать среди обязательных параметров количество аргументов;
* добавить в конец списка аргумент с уникальным значением, по которому будет определяться конец списка параметров;
И тот, и другой способ имеют право на жизнь — все определяется потребностями задачи и вкусами программиста.
Попробуем написать функцию, вычисляющую среднее арифметическое своих аргументов. Пусть наша функция обрабатывает последовательность чисел типа double. Для ограничения списка используем второй способ: последним значением списка параметров будет ноль. Это означает, что все числа должны быть положительными. Ограничение, конечно, слишком сильное, но мы не будем придавать этому большое значение, так как нам важнее разобраться с доступом к списку параметров.
Таким образом, определение функции будет выглядеть так:
double f(double n, ...)            //--заголовок с переменным числом параметров
{   double *p = &n;            //--установились на начало списка параметров
    double sum = 0, count = 0;    
    while (*p)                 //--пока аргумент не равен нулю
    { sum+=(*p);             //--суммируем аргумент
      p++;                 //--«перемещаемся на следующий аргумент
      count++;                 //--считаем  количество аргументов
    }
    return (sum/count);            //--вычисляем среднее
}

Вызов такой функции может выглядеть таким образом:
double y = f(1.0, 2.0, 3.0, 4.0, 0.0);

Переменная y получит значение 2.5. Интересно, что попытка вызвать такую функцию с целыми аргументами f(1,2,3,0) в Visual C++ 6 приводит к явно неверному результату, в то время как в системе Borland выдается неверный, но правдоподобный результат 0.5. Интересно, что такой же результат получается в Visual C++ 6, если завершить список целых параметров двумя нулями: f(1,2,3,0,0). Это потому, что типы параметров не проверяются.
Реализация функции, которая в качестве первого параметра получает количество аргументов, на первый взгляд, не вызывает затруднений. Однако, если первый аргумент – целое число, то требуется преобразование указателя. И тут не все варианты проходят. Не будет работать такой вариант:
double f(int n, ...)            //--количество элементов
{   int *p = &n;            //--указатель – «целый»
    double sum = 0, count = n;
    for (;n--;(double *)p++)         //--преобразование указателя в «double» 
      sum+=(*p); 
    return (sum/count);
}

Такой вариант тоже неработоспособен
double f(int n, ...)            //--количество элементов
{   double *p = (double *)&n;        //--преобразование адреса
    double sum = 0, count = n;
    for (;n--;p++)             //--изменение указателя 
      sum+=(*p); 
    return (sum/count);
}

Причина кроется в том, что изменение указателя производится на столько байт, сколько в памяти занимает базовый тип. В обоих случаях мы установились не на начало списка double-параметров, а на sizeof(int) байтов «раньше» — на целую переменную. И от этого адреса происходит изменение указателя на 8 (размер переменных типа double), что приводит к совершенно неверным результатам. Решение заключается в том, чтобы сначала изменить «целый» указатель, а потом уже его преобразовать в double*. Так всегда необходимо делать, если тип первого параметра отличается от типов отсутствующих параметров.
double f(int n, ...)            //--количество элементов
{   int *p = &n;
    p++;                //--изменение «целого» на sizeof(int)
    double *pp = (double *)p;        //--преобразование адреса
     double sum = 0, count = n;
     for (;n--;pp++)             //--правильное увеличение на 8
       sum+=(*pp); 
     return (sum/count);
}

Преобразование указателя написано в стиле С, но только потому, что так короче.

То же самое — стандартными средствами

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

double f(int n, double a, ...)
{   va_list p;                 //--объявление указателя
    double sum = 0, count = 0;
    va_start(p, n);            //--инициализация указателя
    while(n--)             
    {   sum+=va_arg(p,double);        //--перемещение указателя 
        count++; 
    }
    va_end(p);                //--«закрытие» указателя
    return (sum/count);
}

Очень похоже выглядит и вариант с нулем в конце списка.
double f(double a, ...)
{   va_list p;                 //--объявление указателя
    double sum = 0, count = 0;
    va_start(p, a);            //--инициализация указателя
    double k = a;                //--промежуточная переменная 
    do {
        sum+=k;
        count++;
    } while(k=va_arg(p,double));     //--пока не ноль в списке-передвигаемся
    va_end(p);                //--«закрыли» указатель
    return (sum/count);
}

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

Хочешь быть счастливым — будь им!
Без булдырабыз!!!
Re: Variable Argument List
От: dupamid Россия  
Дата: 24.08.04 14:42
Оценка:
Здравствуйте, Chez, Вы писали:

C>[/ccode]А как получить v в функции типа

C>
C>void simplified_func(...)
C>{ va_list v; /* Чем заменить va_start ? */ ... }


В ANSI C — никак. Но в UNIX стиле можно, вот посмотри ссылку http://msdn.microsoft.com/library/default.asp?url=/library/en-us/vclib/html/_crt_va_arg.2c_.va_end.2c_.va_start.asp смотри про две формы va_start из STDARG.H и VARARGS.H.
Re[2]: Variable Argument List
От: Chez Россия  
Дата: 24.08.04 14:49
Оценка:
__>Если используется VC то можно посмотреть эту ветку : http://rsdn.ru/Forum/Message.aspx?mid=637982&only=1
Автор: Denwer
Дата: 13.05.04

Там обсуждались безымянные параметры, а я спросил про VA.
Но всё равно там нет ответа, как получить указатель на первый параметр.

Кстати, там использовалась какая-то штука "__formal" — я попробовал написать именно так — не скомпилилось! (VC6).
— Подумал грешным делом что это резервное слово или макрос, который я ещё не знаю
Что это такое?
Chez, ICQ# 161095094
Re[2]: Variable Argument List
От: achp  
Дата: 24.08.04 14:51
Оценка:
Здравствуйте, LaptevVV, Вы писали:

LVV>
LVV>double f(double n, ...)            //--заголовок с переменным числом параметров
LVV>{   double *p = &n;            //--установились на начало списка параметров
LVV>    double sum = 0, count = 0;    
LVV>    while (*p)                 //--пока аргумент не равен нулю
LVV>    { sum+=(*p);             //--суммируем аргумент
LVV>      p++;                 //--«перемещаемся на следующий аргумент
LVV>      count++;                 //--считаем  количество аргументов
LVV>    }
LVV>    return (sum/count);            //--вычисляем среднее
LVV>}
LVV>


И это пишут в книжках? Кошмар!

Почему p++? Почему не p--? Почему не p = reinterpret_cast<double*>(reinterpret_cast<char*>(p) + 9)?
Re[3]: Variable Argument List
От: _nn_ www.nemerleweb.com
Дата: 24.08.04 14:54
Оценка:
Здравствуйте, Chez, Вы писали:

__>>Если используется VC то можно посмотреть эту ветку : http://rsdn.ru/Forum/Message.aspx?mid=637982&amp;only=1
Автор: Denwer
Дата: 13.05.04

C>Там обсуждались безымянные параметры, а я спросил про VA.
А почему бы не сделать так:
template<typename T>
void f(T t,...)

Есть параметр и перемнное количество параметров
C>Но всё равно там нет ответа, как получить указатель на первый параметр.
Потому что нет стандартного способа.

Можно спомощью ассемблера пройтись по стеку, однако код не будет переносим.

C>Кстати, там использовалась какая-то штука "__formal" — я попробовал написать именно так — не скомпилилось! (VC6).

Может этого нет в VC6.
C>- Подумал грешным делом что это резервное слово или макрос, который я ещё не знаю
C>Что это такое?
Специльное слово от MS
http://rsdn.nemerleweb.com
http://nemerleweb.com
Re: Variable Argument List
От: adontz Грузия http://adontz.wordpress.com/
Дата: 24.08.04 14:58
Оценка:
Здравствуйте, Chez, Вы писали:

Если всё делать по уму, то без ассемблера не обойтись, а если на Си++ то как-то так
template <typename T>
void simplified_func(T t, ...)
A journey of a thousand miles must begin with a single step © Lau Tsu
Re[2]: Variable Argument List
От: LaptevVV Россия  
Дата: 24.08.04 15:25
Оценка:
Здравствуйте, dupamid, Вы писали:

D>В ANSI C — никак. Но в UNIX стиле можно, вот посмотри ссылку http://msdn.microsoft.com/library/default.asp?url=/library/en-us/vclib/html/_crt_va_arg.2c_.va_end.2c_.va_start.asp смотри про две формы va_start из STDARG.H и VARARGS.H.

Имеется ввиду вот это?
int average( va_alist )
va_dcl
{
   int i, count, sum;
   va_list marker;

   va_start( marker );            /* Initialize variable arguments. */
   for( sum = count = 0; (i = va_arg( marker, int)) != -1; count++ )
      sum += i;
   va_end( marker );              /* Reset variable arguments.      */
   return( sum ? (sum / count) : 0 );
}
Хочешь быть счастливым — будь им!
Без булдырабыз!!!
Re: Variable Argument List
От: warhast Россия  
Дата: 24.08.04 15:26
Оценка: 1 (1)
Здравствуйте, Chez, Вы писали:

C>void simplified_func(...)

C>{ va_list v; /* Чем заменить va_start ? */ ... }

Если VC7+, ICL 8+, то пользуем _AddressOfReturnAddress и благодарим Микрософт за наше счастливое детство

extern "C" void * _AddressOfReturnAddress ();

void function(...)
{
    int * params = (int*)(((long)_AddressOfReturnAddress()) + 4);
    for(int i = 0; i < 0[params]; i++)
    {
        _tprintf("params[%i] = %i\n", i, (i+1)[params]);
    }
}

int _tmain(int argc, _TCHAR* argv[])
{
    function(4,1,2,3,9);
    return 0;
}
Re[3]: Variable Argument List
От: LaptevVV Россия  
Дата: 24.08.04 15:29
Оценка:
Здравствуйте, achp, Вы писали:

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


LVV>>
LVV>>double f(double n, ...)            //--заголовок с переменным числом параметров
LVV>>{   double *p = &n;            //--установились на начало списка параметров
LVV>>    double sum = 0, count = 0;    
LVV>>    while (*p)                 //--пока аргумент не равен нулю
LVV>>    { sum+=(*p);             //--суммируем аргумент
A>LVV>      p++;                 //--«перемещаемся на следующий аргумент
LVV>>      count++;                 //--считаем  количество аргументов
LVV>>    }
LVV>>    return (sum/count);            //--вычисляем среднее
LVV>>}
LVV>>


A>И это пишут в книжках? Кошмар!


A>Почему p++? Почему не p--? Почему не p = reinterpret_cast<double*>(reinterpret_cast<char*>(p) + 9)?

Именно р++. Все проверялось на VC6 и BCB6 — правильно работает. А пишут для объяснения реализации стандартных средств va_...
Хочешь быть счастливым — будь им!
Без булдырабыз!!!
Re[4]: Variable Argument List
От: achp  
Дата: 24.08.04 16:09
Оценка:
Здравствуйте, LaptevVV, Вы писали:

LVV>Именно р++. Все проверялось на VC6 и BCB6 — правильно работает.


А если проверить на машине, где стек растет вверх?
Re[5]: Variable Argument List
От: LaptevVV Россия  
Дата: 24.08.04 16:27
Оценка:
Здравствуйте, achp, Вы писали:

LVV>>Именно р++. Все проверялось на VC6 и BCB6 — правильно работает.


A>А если проверить на машине, где стек растет вверх?

В ближайших от меня окрестностях ничего кроме Интела нету. ((
Хочешь быть счастливым — будь им!
Без булдырабыз!!!
Re[2]: Variable Argument List
От: Chez Россия  
Дата: 25.08.04 06:36
Оценка:
Здравствуйте, warhast, Вы писали:

_AddressOfReturnAddress!!!!
ЕЕЕЕЕ!
Класс! Работает даже в VC6!!!

Спасибо большое!
Chez, ICQ# 161095094
Re[2]: Variable Argument List
От: Chez Россия  
Дата: 25.08.04 06:37
Оценка:
А за что минусик то?
Chez, ICQ# 161095094
Re[3]: Variable Argument List
От: dupamid Россия  
Дата: 25.08.04 06:38
Оценка:
Здравствуйте, LaptevVV, Вы писали:

LVV>Имеется ввиду вот это?


Очень верный вопрос. Да, именно так:
//Компилировать нужно в режиме С, а не С++.
#include <varargs.h>
#include <stdio.h>

void f(va_alist)
va_dcl
{
    va_list marker;
    va_start(marker);
    printf(va_arg(marker, char*));
    va_end(marker);
}

int main()
{
    f("Hello, world!\n");
}


На самом деле в большинстве случаев компилятору не нужен последний именованный аргумент функции (разве он его сам не знает ). Более того, часто не нужен вообще ни один именованный аргумент, как лежат аргументы в стеке компилятору известно, откуда они начинаются тоже. Так что реализовать built-in, который все достает без лишний разговоров труда не составляет.
Re[3]: Variable Argument List
От: dupamid Россия  
Дата: 25.08.04 06:52
Оценка: +1
Здравствуйте, Chez, Вы писали:

C>А за что минусик то?


За без апелляционные заявления, что это не возможно более или менее переносимыми средствами (смотри здесь
Автор: dupamid
Дата: 25.08.04
) и за вот это
Автор: achp
Дата: 24.08.04
(achp успел написать раньше меня ). Писать "p++" не написав большого жирного предупреждения, что так никогда-никогда делать не нельзя недопустимо, код совершенно не переносим, даже если с ростом стека все в порядке, так как именованные аргументы могут передаваться на регистрах, а остальные в стеке. "Компиляторская магия" может спокойно это скрыть за va_start/va_arg.
Re[4]: Variable Argument List
От: Chez Россия  
Дата: 25.08.04 07:14
Оценка:
D>Очень верный вопрос. Да, именно так:
D>
D>void f(va_alist)
D>va_dcl
D>{
D>    va_list marker;
D>    va_start(marker);
D>    printf(va_arg(marker, char*));
D>    va_end(marker);
D>}
D>


Не работает в VC6... Не находит макросы va_alist и va_dcl. Когда подключаю <varargs.h>, кричит на то, что va_list, va_start — переопределены.
Chez, ICQ# 161095094
Re[5]: Variable Argument List
От: dupamid Россия  
Дата: 25.08.04 07:32
Оценка:
Здравствуйте, Chez, Вы писали:

D>>Очень верный вопрос. Да, именно так:

D>>
D>>void f(va_alist)
D>>va_dcl
D>>{
D>>    va_list marker;
D>>    va_start(marker);
D>>    printf(va_arg(marker, char*));
D>>    va_end(marker);
D>>}
D>>


C>Не работает в VC6... Не находит макросы va_alist и va_dcl. Когда подключаю <varargs.h>, кричит на то, что va_list, va_start — переопределены.


Там же написано компилировать в режиме С (опция /Tc), если расширениея файла .cpp.
>cl /Tc test152.cpp
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 12.00.8804 for 80x86
Copyright (C) Microsoft Corp 1984-1998. All rights reserved.

test152.cpp
Microsoft (R) Incremental Linker Version 6.00.8447
Copyright (C) Microsoft Corp 1992-1998. All rights reserved.

/out:test152.exe
test152.obj
Re: Variable Argument List
От: Nuald Россия http://nuald.blogspot.com
Дата: 25.08.04 07:46
Оценка: 1 (1)
Здравствуйте, Chez, Вы писали:

<skiped>

Если не заморачиваться, и не обожествлять MS, и думать о переносимости, есть также другой класс решений:
1) определяем класс T, который будет отрабатывать Variable Argument List.
2) определить в нем функции типа T& func(A a); (это может быть и операторы, скажем запятая, или << как в STL-потоках).

В итоге имеем:
T formatter;
formatter.add("Vasya is ").add(age).add(" years old.");


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