C中的stdarg和printf()

C中的stdarg和printf(),c,gcc,printf,variadic-functions,C,Gcc,Printf,Variadic Functions,头文件用于使函数接受未定义数量的参数,对吗? 因此,的printf()功能必须使用来接受不同数量的参数(如果我弄错了,请纠正我)。 我在gcc的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

头文件用于使函数接受未定义数量的参数,对吗?

因此,
printf()
功能必须使用
来接受不同数量的参数(如果我弄错了,请纠正我)。

我在gcc的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>//////////////////////stdarg.h IS INCLUDED!///////////
# endif
#endif
#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
如果包含
,则可以正常工作,否则会生成以下错误:

这是怎么回事

还是说
printf()
不使用
接受可变数量的参数? 如果是这样的话,它是如何完成的?

不,要使用
printf()
您只需要
\include
。不需要stdarg,因为
printf
已经编译。编译器只需要查看
printf
的原型,就知道它是可变的(源自原型中的省略号
)。如果查看
printf
的stdio库源代码,您将看到包含

如果您想编写自己的可变函数,则必须包含,并相应地使用其宏。如您所见,如果忘记执行此操作,编译器将不知道
va_start/list/end
符号


如果您想看到printf的真正实现,请查看将模块拆分为头文件和源文件的基本原理:

  • 在头文件中,只放置模块的接口
  • 在源文件中,放置模块的实现
因此,即使
printf
的实现按照您的推测使用了
va_arg

  • stdio.h
    中,作者只声明了
    int printf(const char*format,…)
  • stdio.c
    中,作者使用
    va_arg
stdarg头文件用于使函数接受未定义的数字 争论不休,对吗

不,
只是公开了一个API,该API应用于访问额外的参数。如果您只想声明接受可变数量参数的函数,则无需包含该标头,如下所示:

int foo(int a, ...);
这是一种语言特性,不需要额外的声明/定义

我在gcc的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>//////////////////////stdarg.h IS INCLUDED!///////////
# endif
#endif
#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
最重要的是:

  • 声明具有可变参数数的函数的标头不应在内部包含
  • 具有可变参数数的函数的实现必须包括
    ,并使用
    va_list
    API访问额外的参数
考虑:

stdio.h:

int my_printf(const char *s, ...); 
您需要
,你没有
是该语言语法的一部分-它是“内置的”。但是,只要您想用这样的参数列表做任何有意义和可移植的事情,您就需要在其中定义的名称:
va_列表
va_开始
,等等

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。取而代之的是libc共享库,它已经被编译成机器代码

因此,如果printf()用于接受 参数和stdio.h具有printf(),这是一个使用printf()的C程序 不包括吗


即使是这样,您也不能依赖于此,否则您的代码格式不正确:如果使用了属于它们的名称,您必须包含所有标题,无论实现是否已经这样做。

stdio.h
的实现在使用gcc编译时不包括
stdarg.h
。编译器编写者总是袖手旁观,这真是不可思议

您的C源文件必须包含它们引用的每个系统头。这是C标准的要求。也就是说,如果源代码需要stdarg.h中的定义,那么它必须直接包含
#include
指令,或者包含在它包含的一个头文件中。它不能依赖stdarg.h包含在其他标准头中,即使它们实际上包含它。

好的,有“常规”printf系列:printf、fprintf、dprintf、sprintf和snprintf。 然后是printf系列的可变参数数:vprintf、vfprintf、vdprintf、vsprintf和vsnprintf

要将参数的变量列表用于其中一个,您需要声明stdarg.h。 stdarg.h定义了您正在使用的所有宏:va_list、va_start、va_arg、va_end和va_copy。

只有在要实现变量数函数时,才需要包含
文件。它不需要能够使用
printf(3)
和friends。仅当您要处理变量args函数的参数时,才需要
va_列表
类型,以及
va_开始
va_arg
va_结束
宏。因此,只有这样,您才需要强制包含该文件

一般来说,您不保证将
包含在仅包含
中。事实上,您引用的代码只包含它,如果
\uuu GNU C\uuuu
没有定义(我怀疑是这样,所以它不包含在您的案例中)如果使用的是
gcc
编译器,则定义此宏

如果要在代码中创建变量参数传递函数,最好的方法不是期望其他包含的文件包含它,而是在使用
va_列表
类型或
va_开始
va_arg
va_结束
#include "stdio.h"
#include "stdarg.h"

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

   /* and so on */
}
#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;
}
$ 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);
#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
#include <stdio.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdarg.h>
#include <stdio.h>
#define va_start(x, y) /* something unrelated to variadic functions */
$ 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
#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 */
/* These library features are always available in the GNU C library.  */
#define _G_va_list __gnuc_va_list
/* Define __gnuc_va_list.  */
#ifndef __GNUC_VA_LIST
#define __GNUC_VA_LIST
typedef __builtin_va_list __gnuc_va_list;
#endif
$ echo 'void foo(__builtin_va_list x) {}' |
    gcc -xc -std=c11 -fsyntax-only -; echo $?
0