stdarg и printf () в C

на <stdarg.h> файл заголовка используется для того, чтобы функции принимали неопределенное количество аргументов, верно?

Итак,printf() funtion <stdio.h> должны использовать <stdarg.h> чтобы принять avariable количество аргументов (пожалуйста, поправьте меня, если я ошибаюсь).

Я нашел следующие строки в студию.H файл gcc:

#if defined __USE_XOPEN || defined __USE_XOPEN2K8
# ifdef __GNUC__
#  ifndef _VA_LIST_DEFINED
typedef _G_va_list va_list;
#   define _VA_LIST_DEFINED
#  endif
# else
#  include <stdarg.h>//////////////////////stdarg.h IS INCLUDED!///////////
# endif
#endif

я не могу понять большую часть того, что в нем, но, похоже, в том числе <stdarg.h>

Итак, если printf() использует <stdarg.h> для принятия переменного числа аргументов и stdio.h и printf(), программа C использованием printf() не нужен <stdarg.h> не так ли?

я попробовал программу, которая была printf() и пользовательская функция, принимающая переменное количество аргументов.

программа, которую я пробовал:

#include<stdio.h>
//#include<stdarg.h>///If this is included, the program works fine.

void fn(int num, ...)
{
    va_list vlist;
    va_start(vlist, num);//initialising va_start (predefined)

    int i;

    for(i=0; i<num; ++i)
    {
        printf("%dn", va_arg(vlist, int));
    }

    va_end(vlist);//clean up memory
}

int main()
{
    fn(3, 18, 11, 12);
    printf("n");
    fn(6, 18, 11, 32, 46, 01, 12);

    return 0;
}

он отлично работает, если <stdarg.h> включен, но в противном случае генерирует следующую ошибку:

40484293.c:13:38: error: expected expression before ‘int’
         printf("%dn", va_arg(vlist, int));//////error: expected expression before 'int'/////////
                                      ^~~

как это?

или это printf() не использовать <stdarg.h> для принятия переменного числа аргументов? Если да, то как это делается?

8 ответов


считаем:

С stdio.h:

int my_printf(const char *s, ...); 

вам нужно <stdarg.h>? нет, вам не. ... является частью грамматики языка - это "встроенный". Однако, как только вы хотите сделать что-нибудь значимое и портативное с таким списком аргументов, вам нужно имена определено там:va_list, va_start и так далее.

С stdio.c:

#include "stdio.h"
#include "stdarg.h"

int my_printf(const char *s, ...)
{
   va_list va;
   va_start(va, s);

   /* and so on */
}

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

Итак, если printf () использует для принятия переменного числа Аргументы и stdio.h имеет printf (), программу C с использованием printf () не включить ли его?

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


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

нет, <stdarg.h> просто предоставляет API, который должен использоваться для доступа к дополнительным аргументам. Нет необходимости включать этот заголовок, Если вы хотите просто объявить функцию, которая принимает переменное количество аргументов, например:

int foo(int a, ...);

это языковая функция и не требует дополнительных объявлений / определений.

I нашел следующие строки в студию.H файл gcc:

#if defined __USE_XOPEN || defined __USE_XOPEN2K8
# ifdef __GNUC__
#  ifndef _VA_LIST_DEFINED
typedef _G_va_list va_list;
#   define _VA_LIST_DEFINED
#  endif
# else
#  include <stdarg.h>//////////////////////stdarg.h IS INCLUDED!///////////
# endif
#endif

Я думаю, что этот материал требуется только для объявления таких вещей, как vprintf() без внутреннего включения <stdarg.h>:

int vprintf(const char *format, va_list ap);

в довершение всего:

  • заголовок, объявляющий функцию с переменным числом аргументов, не должен включать <stdarg.h> внутренне.
  • реализация функции с переменным числом аргументов должна включать <stdarg.h> и использовать va_list API-интерфейс для доступа к дополнительным аргументам.

сначала я отвечу на ваш вопрос с точки зрения стандарта C, потому что это то, что говорит вам, как вы должны писать свой код.

стандарт C требует stdio.h чтобы "вести себя как-будто" она делает не включить stdarg.h. Другими словами, макросы va_start, va_arg, va_end и va_copy, типа va_list требуются не быть доступным путем включения stdio.h. Другими словами, эта программа необходима не для компиляции:

#include <stdio.h>

unsigned sum(unsigned n, ...)
{
   unsigned total = 0;
   va_list ap;
   va_start(ap, n);
   while (n--) total += va_arg(ap, unsigned);
   va_end(ap);
   return total;
}

(это отличие от C++. В C++ все стандартные заголовки библиотек разрешены, но не обязательны для включения друг в друга.)

это правда реализация of printf (вероятно) использует stdarg.h механизм для доступа к его аргументам, но это просто означает, что некоторые файлы в исходном коде для библиотеки C ("printf.c", пожалуй) надо включить stdarg.h а также stdio.h; это не влияет на ваш код.

это правда stdio.h объявляет функции, которые принимают va_listтипизированных аргументов. Если вы посмотрите на эти объявления, вы увидите, что они фактически используют имя typedef, которое начинается с двух подчеркиваний или подчеркивания и заглавной буквы: например, с тем же stdio.h вы смотрите,

$ egrep '\<v(printf|scanf) *\(' /usr/include/stdio.h
extern int vprintf (const char *__restrict __format, _G_va_list __arg);
extern int vscanf (const char *__restrict __format, _G_va_list __arg);

все имена, начинающиеся с двух подчеркиваний или подчеркивания и заглавной буквы, зарезервированы для реализации - stdio.h разрешено объявлять столько таких имен, сколько он хочет. И наоборот, вам, программисту приложения, не разрешается объявлять любой такие имена или использовать те, которые объявляет реализация (за исключением подмножества, которые документированы, такие как _POSIX_C_SOURCE и __GNUC__). Компилятор позволит вам это сделать, но эффекты не определены.


теперь я собираюсь поговорить о том, что вы процитировали из stdio.h. Вот оно опять:

#if defined __USE_XOPEN || defined __USE_XOPEN2K8
# ifdef __GNUC__
#  ifndef _VA_LIST_DEFINED
typedef _G_va_list va_list;
#   define _VA_LIST_DEFINED
#  endif
# else
#  include <stdarg.h>
# endif
#endif

чтобы понять, что это делает, вы должны знать три вещи:

  1. последние "вопросы"в POSIX.1, официальная спецификация того, что значит быть операционной системой "Unix", добавьте va_list к множеству вещей stdio.h предполагается определить. (В частности, в выпуск 6, va_list определяется stdio.h как расширение " XSI " и в выпуск 7 это обязательно.) Этот код определяет va_list, но только если программа запросила выпуск 6+XSI или выпуск 7 функций; вот что #if defined __USE_XOPEN || defined __USE_XOPEN2K8 средства. Обратите внимание, что он использует _G_va_list определение va_list, как и в других местах, он использовал _G_va_list объявить vprintf. _G_va_list уже как-то доступен.

  2. вы не можете написать то же самое typedef дважды в одной и той же переводческой единице. Если stdio.h определена va_list как-то не уведомляя stdarg.h не делать опять,

    #include <stdio.h>
    #include <stdarg.h>
    

    не будет компилироваться.

  3. GCC поставляется с копией stdarg.h, но это не приходите с копией stdio.h. The stdio.h вы цитируете исходит из GNU libc, который является отдельным проектом под зонтиком GNU, поддерживаемым отдельной (но перекрывающейся) группой людей. Важно отметить, что заголовки GNU libc не могут предполагать, что они компилируются ССЗ.

Итак, код, который вы процитировали, определяет va_list. Если __GNUC__ определено, что означает, что компилятор является либо GCC, либо Quirk-совместимым клоном, он предполагает, что он может общаться с stdarg.h использование макроса с именем _VA_LIST_DEFINED, который определяется тогда и только тогда, когда va_list определено-но будучи макросом, вы можете проверить его с помощью #if. stdio.h определить va_list себя, а затем определить _VA_LIST_DEFINED, а потом stdarg.h не будет делать этого, и

#include <stdio.h>
#include <stdarg.h>

будет компилироваться нормально. (Если вы посмотрите на GCC stdarg.h, который, вероятно, скрывается в /usr/lib/gcc/something/something/include в вашей системе вы увидите зеркальное отражение этого кода вместе с веселым длинным списком другое макросы, которые также означают "не определяют va_list, я уже сделал это" для другое библиотеки C, с которыми GCC может или может использоваться один раз.)

но если __GNUC__ не определен, то stdio.h предполагает, что это так не знать, как общаться с stdarg.h. Но он знает, что безопасно включать stdarg.h дважды в одном файле, потому что стандарт C требует, чтобы это работало. Так для того, чтобы получить va_list определено, он просто идет вперед и включает в себя stdarg.h и va_* макросы,stdio.h не предполагается определить также будет определен.

это то, что люди HTML5 назвали бы "умышленным нарушением" стандарта C: это неправильно, на цель, потому что ошибаться таким образом менее вероятно, чтобы сломать реальный код, чем любая доступная альтернатива. В частности,

#include <stdio.h>
#include <stdarg.h>

в подавляющем большинстве случаев чаще появляется в реальном коде, чем

#include <stdio.h>
#define va_start(x, y) /* something unrelated to variadic functions */

таким образом, гораздо важнее заставить первый работать, чем второй, хотя оба они должны работать.


наконец, вы все еще можете задаваться вопросом, Где черт _G_va_list пришли. Это не определено нигде в stdio.h сам, поэтому он должен быть либо встроенным компилятором, либо определяться одним из заголовков stdio.h включает в себя. Вот как вы узнаете все, что системный заголовок включает в себя:

$ echo '#include <stdio.h>' | gcc -H -xc -std=c11 -fsyntax-only - 2>&1 | grep '^\.'
. /usr/include/stdio.h
.. /usr/include/features.h
... /usr/include/x86_64-linux-gnu/sys/cdefs.h
.... /usr/include/x86_64-linux-gnu/bits/wordsize.h
... /usr/include/x86_64-linux-gnu/gnu/stubs.h
.... /usr/include/x86_64-linux-gnu/gnu/stubs-64.h
.. /usr/lib/gcc/x86_64-linux-gnu/6/include/stddef.h
.. /usr/include/x86_64-linux-gnu/bits/types.h
... /usr/include/x86_64-linux-gnu/bits/wordsize.h
... /usr/include/x86_64-linux-gnu/bits/typesizes.h
.. /usr/include/libio.h
... /usr/include/_G_config.h
.... /usr/lib/gcc/x86_64-linux-gnu/6/include/stddef.h
.... /usr/include/wchar.h
... /usr/lib/gcc/x86_64-linux-gnu/6/include/stdarg.h
.. /usr/include/x86_64-linux-gnu/bits/stdio_lim.h
.. /usr/include/x86_64-linux-gnu/bits/sys_errlist.h

я -std=c11 чтобы убедиться, что я был не компиляция в POSIX выпуск 6+XSI или выпуск 7 режимов, и все же мы видим stdarg.h в этом списке в любом случае - не включены непосредственно stdio.h, а libio.h, который не является стандартным заголовком. Давай заглянем внутрь. там:

#include <_G_config.h>
/* ALL of these should be defined in _G_config.h */
/* ... */
#define _IO_va_list _G_va_list

/* This define avoids name pollution if we're using GNU stdarg.h */
#define __need___va_list
#include <stdarg.h>
#ifdef __GNUC_VA_LIST
# undef _IO_va_list
# define _IO_va_list __gnuc_va_list
#endif /* __GNUC_VA_LIST */

так libio.h включает в себя stdarg.h в специальном режиме (вот еще один случай, когда макросы реализации используются для связи между заголовками системы) и ожидает, что он определит __gnuc_va_list, но он использует его, чтобы определить _IO_va_list, а не _G_va_list. _G_va_list определяется _G_config.h...

/* These library features are always available in the GNU C library.  */
#define _G_va_list __gnuc_va_list

... в терминах __gnuc_va_list. это имя is определено stdarg.h:

/* Define __gnuc_va_list.  */
#ifndef __GNUC_VA_LIST
#define __GNUC_VA_LIST
typedef __builtin_va_list __gnuc_va_list;
#endif

и __builtin_va_list, наконец, является недокументированный GCC внутренний, что означает " любой тип подходит для va_list с текущим ABI".

$ echo 'void foo(__builtin_va_list x) {}' |
    gcc -xc -std=c11 -fsyntax-only -; echo $?
0

(да, реализация stdio GNU libc намного сложнее, чем это имеет какое-либо оправдание. The объяснение разве что еще в старину люди пытались сделать ее FILE объект непосредственно используется как C++ filebuf. Это не работало в течение десятилетий - на самом деле, я не уверен, что это когда-нибудь работал; он был заброшен раньше EGCS, который насколько я знаю историю - но есть много, много рудиментов попытка все еще висит, либо для двоичной обратной совместимости, либо потому, что никто не удосужился их очистить.)

(да, если я правильно читаю это, GNU libc stdio.h не будет работать правильно с компилятором C, чей stdarg.h не определяет __gnuc_va_list. Это абстрактно неправильно, но безвредно; любой, кто хочет блестящий новый не совместимый с GCC компилятор для работы с GNU libc будет иметь гораздо больше вещей, о которых нужно беспокоиться.)


нет, чтобы использовать printf() все, что вам нужно-это #include <stdio.h>. Нет необходимости в stdarg, потому что printf уже скомпилирован. Компилятору нужно только увидеть прототип для printf знать, что он является вариативным (производным от многоточия ... в прототипе). Если вы посмотрите на исходный код библиотеки stdio на printf вы увидите <stdarg.h> включается.

если вы хотите написать свой собственный функции с переменным числом аргументов, вы должны #include <stdarg.h> и используйте свои макросы соответственно. Как вы можете видеть, если вы забудете это сделать,va_start/list/end символы неизвестны компилятору.

если вы хотите увидеть реальную реализацию printf посмотри код в стандартном источнике ввода-вывода FreeBSD вместе с источник vfprintf.


основы разделения модуля на файл заголовка и исходный файл:

  • в файле заголовка вы помещаете только интерфейс вашего модуля
  • в исходном файле вы помещаете реализацию своего модуля

так что даже если реализация printf использует va_arg Как вы порассуждать:

  • на stdio.h автор только объявили int printf(const char* format, ...);
  • на stdio.c автор реализовал printf используя va_arg

реализация stdio.h не входит stdarg.h при компиляции с gcc. Это работает по волшебству, что авторы компиляторов всегда имеют в рукавах.

ваши исходные файлы C должны включать каждый системный заголовок, на который они ссылаются. Это требование стандарта C. То есть, если ваш исходный код требует определений, присутствующих в stdarg.ч, он должен содержать #include <stdarg.h> директива либо напрямую, либо в одном из код заголовочные файлы, которые он включает. Он не может полагаться на включаемом файле stdarg.h быть включенным в другое стандартный заголовки, даже если они действительно включают его.


на должен быть включен, только если вы собираетесь реализовать переменное количество аргументов функции. Это не обязательно, чтобы иметь возможность использовать printf(3) и друзей. Только если вы собираетесь обрабатывать аргументы на переменном числе функций args, вам понадобится va_list, и va_start, va_arg и va_end макросы. Поэтому, только тогда вам нужно будет принудительно включить этот файл.

в общем, вы не гарантированы, что <stdarg.h> будет включен только с включением <stdio.h> действительно, код, который вы цитируете только включается, если __GNU_C__ не определен (я подозреваю, что это так, поэтому он не включен в ваш случай), и этот макрос определяется, если вы используете gcc компилятора.

если вы собираетесь создавать функции передачи переменных аргументов в своем коде, лучший подход - не ожидать, что другой включенный файл включит его,но сделай это сам (как a клиент для запрошенной Вами функциональности) везде, где вы используете va_list тип, или va_start, va_arg или va_end макросы.

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


хорошо, есть" регулярное " семейство printf: printf, fprintf, dprintf, sprintf и snprintf. И тогда есть переменное количество аргументов семейства printf: vprintf, vfprintf, vdprintf, vsprintf и vsnprintf.

чтобы использовать список переменных аргументов с любым, вам нужно объявить stdarg.ч. включаемом файле stdarg.h определяет все макросы, которые вы используете: va_list, va_start, va_arg, va_end и va_copy.