Форум программистов, компьютерный форум CyberForum.ru

Как реализована функция printf - C++

Восстановить пароль Регистрация
 
Рейтинг: Рейтинг темы: голосов - 32, средняя оценка - 4.78
Avazart
 Аватар для Avazart
6893 / 5133 / 250
Регистрация: 10.12.2010
Сообщений: 22,561
Записей в блоге: 17
13.06.2012, 00:42     Как реализована функция printf #1
C++
1
int printf ( const char * format, ... );
http://www.cplusplus.com/reference/c...cstdio/printf/

Смотрю на эту ф-цию и думаю как же она реализована?
Как осуществляется то что ф-ция может иметь переменное кол-во параметров?
Не уже ли перегрузкой ф-ции?

И вообще можно ли реализавать такое (без перегрузки,передачи массива объектом, итп)?
После регистрации реклама в сообщениях будет скрыта и будут доступны все возможности форума.
Evg
Эксперт С++Автор FAQ
 Аватар для Evg
16821 / 5242 / 318
Регистрация: 30.03.2009
Сообщений: 14,118
Записей в блоге: 26
13.06.2012, 00:55     Как реализована функция printf #2
Упрощённая самодельная реализация printf'а
Как перенести параметры из ф-ции printf() в самодельную

Цитата Сообщение от Avazart Посмотреть сообщение
Как осуществляется то что ф-ция может иметь переменное кол-во параметров?
Читай про va_arg. Научное название "variable arguments" или "переменное число параметров"

Цитата Сообщение от Avazart Посмотреть сообщение
Не уже ли перегрузкой ф-ции?
printf - это функция по сути из стандарта языка Си, унаследованная в Си++. Ни о каких перегрузках не может быть и речи
Avazart
 Аватар для Avazart
6893 / 5133 / 250
Регистрация: 10.12.2010
Сообщений: 22,561
Записей в блоге: 17
13.06.2012, 01:39  [ТС]     Как реализована функция printf #3
Читаю
Для определения функции с переменным числом параметров необходимо знать две вещи: как задать переменный список в прототипе функции и как обрабатывать переменный список в теле функции. Переменный список параметров задается в заголовке функции многоточием:int f(…)

Этот заголовок не вызывает у компилятора протестов. Такая запись означает, что при определении функции компилятору неизвестны ни количество параметров, ни их типы, и он, естественно, не может ничего проверить. Количество параметров и их типы становятся известными только при вызове функции.
Однако у программиста с написанием таких функций сразу возникают проблемы. Ведь имена параметров отсутствуют! Спрашивается, как же написать алгоритм обработки, не зная имен параметров? Естественно, только одним способом – использовать для доступа к параметрам указатель! Вспомним, что все параметры при вызове помещаются в стек. Если мы каким-то образом установим указатель на начало списка параметров в стеке, то манипулируя с указателем, мы, в принципе, можем «достать» все параметры!
Таким образом, совсем пустой список параметров мы написать не можем — должен быть прописан хотя бы один явный параметр, адрес которого мы собираемся получить при выполнении программы. Заголовок такой функции может выглядеть так:
C++
1
int f(int k...)
Как мы видим, ни запятая, ни пробел после параметра не обязательны, хотя можно их и прописать.
При написании функции с переменным числом параметров помимо алгоритма обработки программист должен разрабатывать и алгоритм доступа к параметрам. Нам надо ответить на два вопроса:
как «перебирать» параметры;
как закончить перебор.
Ответ на первый вопрос очевиден – надо изменять значение указателя, чтобы переместиться на следующий параметр. Отсюда следует, что указатель должен быть типизированным, поскольку с бестиповым указателем нельзя выполнять арифметические операции. Это же означает, что программист при разработке функции с переменным числом параметров должен отчетливо себе представлять типы аргументов, которые будет обрабатывать функция. Кроме того, способ передачи параметров должен быть одинаковым для всех: либо все – по значению, либо все – по ссылке, либо все – по указателю.
Передача аргумента не того типа, который задумывался, или не тем способом, который подразумевался при разработке, приведет к катастрофическим последствиям – компилятор-то ничего не проверяет.
Ответ на второй вопрос не вызывает затруднений. Это можно сделать одним из двух способов:
* явно передать среди обязательных параметров количество аргументов;
* добавить в конец списка аргумент с уникальным значением, по которому будет определяться конец списка параметров;
И тот, и другой способ имеют право на жизнь — все определяется потребностями задачи и вкусами программиста.
Попробуем написать функцию, вычисляющую среднее арифметическое своих аргументов. Пусть наша функция обрабатывает последовательность чисел типа double. Для ограничения списка используем второй способ: последним значением списка параметров будет ноль. Это означает, что все числа должны быть положительными. Ограничение, конечно, слишком сильное, но мы не будем придавать этому большое значение, так как нам важнее разобраться с доступом к списку параметров.
Таким образом, определение функции будет выглядеть так:double f(double n, ...) //--заголовок с переменным числом параметров
C++
1
2
3
4
5
6
7
8
9
{   double *p = &n;            //--установились на начало списка параметров
    double sum = 0, count = 0;    
    while (*p)                 //--пока аргумент не равен нулю
    { sum+=(*p);             //--суммируем аргумент
      p++;                 //--«перемещаемся на следующий аргумент
      count++;                 //--считаем  количество аргументов
    }
    return (sum/count);            //--вычисляем среднее
}
Вызов такой функции может выглядеть таким образом:
C++
1
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). Это потому, что типы параметров не проверяются.
Реализация функции, которая в качестве первого параметра получает количество аргументов, на первый взгляд, не вызывает затруднений. Однако, если первый аргумент – целое число, то требуется преобразование указателя. И тут не все варианты проходят. Не будет работать такой вариант:
C++
1
2
3
4
5
6
7
double f(int n, ...)            //--количество элементов
{   int *p = &n;            //--указатель – «целый»
    double sum = 0, count = n;
    for (;n--;(double *)p++)         //--преобразование указателя в «double» 
      sum+=(*p); 
    return (sum/count);
}
Такой вариант тоже неработоспособен
C++
1
2
3
4
5
6
7
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*. Так всегда необходимо делать, если тип первого параметра отличается от типов отсутствующих параметров.
C++
1
2
3
4
5
6
7
8
9
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);
}
Преобразование указателя написано в стиле С, но только потому, что так короче.

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

В качестве примера рассмотрим реализацию функции вычисления среднего арифметического (вариант с параметром-количеством аргументов) с использованием этих макросов.
C++
1
2
3
4
5
6
7
8
9
10
11
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);
}
Очень похоже выглядит и вариант с нулем в конце списка.
C++
1
2
3
4
5
6
7
8
9
10
11
12
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 сразу устанавливается на слагаемое. Передвижение по списку выполняется прямо в условии цикла, что обеспечивает одновременную проверку на ноль.
http://www.rsdn.ru/forum/cpp/777944.all.aspx

Добавлено через 17 минут


Ага вот нашел...кое что более нормальное
http://www.cplusplus.com/reference/clibrary/cstdarg/

А есть ли книжная литература по данному вопросу?
По тому как раньше не встречал....
Toshkarik
 Аватар для Toshkarik
1139 / 856 / 50
Регистрация: 03.08.2011
Сообщений: 2,381
Завершенные тесты: 1
13.06.2012, 02:08     Как реализована функция printf #4
У Дейтелов, в полном, 5 издании точно обсуждалось в конце книги.
Evg
Эксперт С++Автор FAQ
 Аватар для Evg
16821 / 5242 / 318
Регистрация: 30.03.2009
Сообщений: 14,118
Записей в блоге: 26
13.06.2012, 09:37     Как реализована функция printf #5
Цитата Сообщение от Avazart Посмотреть сообщение
А есть ли книжная литература по данному вопросу?
Это дело входит в базовую часть языка Си, а потому в абсолютно любой книге/учебнике по Си это есть
Yandex
Объявления
13.06.2012, 09:37     Как реализована функция printf
Ответ Создать тему
Опции темы

Текущее время: 13:04. Часовой пояс GMT +3.
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin® Version 3.8.9
Copyright ©2000 - 2016, vBulletin Solutions, Inc.
Рейтинг@Mail.ru