<stdarg.h>
头文件用于使函数接受未定义数量的参数,对吧?
因此,printf()
的{{1}}功能必须使用<stdio.h>
来接受大量的论点(如果我误会,请纠正我)。
我在gcc的stdio.h文件中找到了以下行:
<stdarg.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
因此,如果<stdarg.h>
使用printf()
接受可变数量的参数而<stdarg.h>
使用stdio.h
,则使用printf()
的C程序无需包含{{ 1}}做到了吗?
我尝试了一个程序,其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("%d\n", 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("%d\n", va_arg(vlist, int));//////error: expected expression before 'int'/////////
^~~
来接受可变数量的参数?
如果是这样,它是如何完成的?
答案 0 :(得分:13)
考虑:
stdio.h中:
Button
您需要<Button Background="Transparent" HorizontalAlignment="Stretch"
HorizontalContentAlignment="Left">
吗? 不,你没有。 int my_printf(const char *s, ...);
是语言语法的一部分 - 它是“内置的”。但是,只要您想使用此类参数列表执行任何有意义且可移植的操作,就需要在其中定义名称:<stdarg.h>
,...
等等。< / p>
stdio.c:
va_list
但实际上,这在libc的实现中是必要的,除非你自己编译库,否则你看不到它。你得到的是libc共享库,它已被编译为机器代码。
因此,如果printf()用于接受变量数 参数和stdio.h有printf(),一个使用printf()需要的C程序 不包括吗?
即使是这样,你也不能依赖它,否则你的代码就是格式错误:如果使用属于它们的名称,你必须包括所有的标题,无论是否实现已经这样做了。
答案 1 :(得分:10)
stdarg头文件用于使函数接受未定义的数字 争论,对吧?
不,<stdarg.h>
只是公开了一个应该用来访问额外参数的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
我想这些内容只需要声明像vprintf()
这样的内容而不包含内部<stdarg.h>
:
int vprintf(const char *format, va_list ap);
最重要的是:
<stdarg.h>
。<stdarg.h>
并使用va_list
API来访问额外的参数。答案 2 :(得分:10)
我首先要按照C标准回答您的问题,因为这就是告诉您应该如何编写代码的原因。
C标准要求stdio.h
至&#34;表现为 - 如果&#34; 不包括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 ++中,允许所有标准库头,但不是必需的,以包含彼此。)
printf
(可能)的实现确实使用stdarg.h
机制来访问它的参数,但这只是意味着某些文件 C库的源代码(&#34; printf.c
&#34;或许)需要包含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
和{{ 1}})。编译器可以让你这样做,但效果是未定义的。
现在我要谈谈你引用__GNUC__
的内容。这又是:
stdio.h
要了解这是做什么的,你需要知道三件事:
最近&#34;问题&#34; POSIX.1的正式规范,对于&#34; Unix&#34;在操作系统中,将#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
添加到应该定义的事物集va_list
中。 (具体来说,在Issue 6中,stdio.h
由va_list
定义为&#34; XSI&#34;扩展名,Issue 7是强制性的。{)此代码定义stdio.h
,但仅限于程序已请求问题6 + XSI或问题7功能;这是va_list
的含义。请注意,它使用#if defined __USE_XOPEN || defined __USE_XOPEN2K8
来定义_G_va_list
,就像在其他地方使用va_list
来声明_G_va_list
一样。 vprintf
已经以某种方式提供。
您不能在同一翻译单元中两次编写相同的_G_va_list
。如果typedef
定义stdio.h
但未通知va_list
不再执行此操作,
stdarg.h
无法编译。
GCC附带#include <stdio.h>
#include <stdarg.h>
的副本,但不附带stdarg.h
的副本。您引用的stdio.h
来自GNU libc,这是GNU保护伞下的一个单独项目,由一组独立(但重叠)的人员维护。至关重要的是,GNU libc的头文件不能假设它们是由GCC编译的。
因此,您引用的代码定义了stdio.h
。如果定义了va_list
,这意味着编译器是GCC或与quirk兼容的克隆,它假定它可以使用名为__GNUC__
的宏与stdarg.h
进行通信,该宏定义为和只有定义了_VA_LIST_DEFINED
- 但作为一个宏,您可以使用va_list
进行检查。 #if
可以自定义stdio.h
,然后定义va_list
,然后_VA_LIST_DEFINED
不会这样做,
stdarg.h
编译正常。 (如果您查看GCC的#include <stdio.h>
#include <stdarg.h>
,它可能隐藏在您系统的stdarg.h
中,您将看到此代码的镜像,以及一个非常长的列表其他宏也意味着“不要定义/usr/lib/gcc/something/something/include
,我已经为#em>其他 C库提供了GCC可以或者可以一次,与。一起使用。)
但如果未定义va_list
,则__GNUC__
会假定不知道如何与stdio.h
进行通信。但它确实知道在同一个文件中包含stdarg.h
两次是安全的,因为C标准要求它能够工作。因此,为了获得stdarg.h
定义,它只是继续并包含va_list
,因此,stdarg.h
不是va_*
的宏1 应该定义也将被定义。
这就是HTML5用户所谓的“故意违规行为”#34; C标准:故意是错误的,因为以这种方式出错是不可能破坏现实世界的代码而不是任何可用的替代方案。特别是,
stdio.h
绝对比实际代码更容易出现
#include <stdio.h>
#include <stdarg.h>
所以让第一个工作比第二个工作更重要,即使两者都应该工作。
最后,你可能仍然想知道he #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
来确保我不在POSIX Issue 6 + XSI和Issue 7模式下进行编译,但我们仍然在此列表中看到-std=c11
- 不是由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
而言。 名称由<{1}}定义 :
stdarg.h
最后,/* Define __gnuc_va_list. */
#ifndef __GNUC_VA_LIST
#define __GNUC_VA_LIST
typedef __builtin_va_list __gnuc_va_list;
#endif
是一个未记录的GCC内在词,意思是&#34;任何类型适用于__builtin_va_list
当前的ABI&#34;。
va_list
(是的,GNU libc的stdio实现方式比没有任何理由更复杂。解释就是在人们试图使其成为{{1>对象可以直接用作C ++ $ echo 'void foo(__builtin_va_list x) {}' |
gcc -xc -std=c11 -fsyntax-only -; echo $?
0
。这几十年没有用过 - 实际上,我不确定它是否曾经工作;它已经被放弃了在EGCS之前,这可以追溯到我所知道的历史 - 但是尝试还有很多很多痕迹仍然存在,无论是为了二进制向后兼容还是因为没有人到处都是清理它们。)
(是的,如果我正确地阅读此内容,GNU libc&#39; FILE
无法使用filebuf
无法定义的C编译器stdio.h
。这是抽象的错误,但无害;任何想要一个闪亮的新的非GCC兼容编译器与GNU libc一起工作的人都会有更多的事情需要担心。)
答案 3 :(得分:9)
不,使用printf()
只需要#include <stdio.h>
。不需要stdarg因为已经编译了printf
。编译器只需要查看printf
的原型就知道它是variadic(从原型中的省略号...
派生)。如果您查看printf
的 stdio库源代码,您会看到<stdarg.h>
被包含在内。
如果您想编写自己的可变参数函数,必须 #include <stdarg.h>
并相应地使用其宏。如您所见,如果您忘记这样做,编译器将不知道va_start/list/end
符号。
如果您想查看printf
的真实实现,请查看the code in FreeBSD's standard I/O source以及source for vfprintf
。
答案 4 :(得分:2)
将模块拆分为头文件和源文件的基础知识:
因此,即使printf
的实施在您推测时使用了va_arg
:
stdio.h
中,作者仅声明int printf(const char* format, ...);
stdio.c
中,作者使用printf
va_arg
答案 5 :(得分:2)
使用gcc编译时,Start_Course()
的此实现不包括stdio.h
。它的工作原理是编译器编写者总是袖手旁观。
您的C源文件必须包含它们引用的每个系统标头。这是C标准的要求。也就是说,如果您的源代码需要stdarg.h中存在的定义,则它必须直接包含stdarg.h
指令,或者包含在您的头文件中。它不能依赖stdarg.h包含在其他标准标题中,即使它们确实包含它。
答案 6 :(得分:1)
仅当要实现可变数量的参数函数时,才需要包含<stdarg.h>
文件。它不需要能够使用printf(3)
和朋友。仅当您要处理可变数量的args函数的参数时,您才需要va_list
类型以及va_start
,va_arg
和va_end
宏。所以,只有这样你才需要强行包含该文件。
一般情况下,我们不保证<stdarg.h>
仅包括<stdio.h>
,__GNU_C__
实际上,只引用 的代码包括gcc
未定义(我怀疑是这种情况,所以它不包含在您的情况下)如果您使用的是va_list
编译器,则会定义此宏。
如果要在代码中创建变量参数传递函数,最好的方法是不要期望另一个包含的文件包含它,但是自己动手(作为所请求功能的客户端)您在任何地方使用va_start
类型,或va_arg
,va_end
或<ul>
{{for row in rows:}}
<li>{{=row}}</li>
{{pass}}
</ul>
宏。
在过去,有一些关于双重包含的混淆,因为一些头文件没有受到双重包含的保护(包括两次或更多次相同的包含文件产生的关于双重定义的宏或类似的错误,你必须小心谨慎)但今天,这不是一个问题,通常所有标准的标题字段都不受双重包含的影响。
答案 7 :(得分:-1)
好的,有#34;常规&#34; 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。