_<stdarg.h>
_ヘッダーファイルは、関数が未定義の数の引数を受け入れるようにするために使用されますよね?
したがって、_<stdio.h>
_のprintf()
関数は、可変数の引数を受け入れるために_<stdarg.h>
_を使用している必要があります(間違っている場合は修正してください)。
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
_
何が入っているのかほとんどわかりませんが、_<stdarg.h>
_が含まれているようです
したがって、printf()
が可変数の引数を受け入れるために_<stdarg.h>
_を使用し、_stdio.h
_がprintf()
を持っている場合、printf()
を使用するCプログラムは必要ありません。 _<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'///////// ^~~
_
これはどのように?
それとも、printf()
が可変数の引数を受け入れるために_<stdarg.h>
_を使用しないということですか?もしそうなら、それはどのように行われますか?
考えてみましょう:
stdio.h:
int my_printf(const char *s, ...);
<stdarg.h>
が必要ですか? いいえ、あなたはしません。 ...
は、言語の文法の一部であり、「組み込み」です。ただし、このような引数のリストを使用して意味のある移植可能なものを実行したい場合は、va_list
、va_start
などで定義されているnamesが必要です。
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のimplementationで必要になります。これは、ライブラリを自分でコンパイルしない限り表示されないものです。代わりに取得するのは、すでにマシンコードにコンパイルされているlibc共有ライブラリです。
したがって、printf()が可変数の引数を受け入れるために使用し、stdio.hにprintf()がある場合、printf()を使用するCプログラムはそれを含める必要はありませんか?
たとえそうだったとしても、あなたはできませんそれに依存します。そうでなければ、コードの形式が正しくありません。実装がすでにそれを行っているかどうかに関係なく、ヘッダーに属する名前を使用する場合は、とにかくすべてのヘッダーを含める必要がありますか否か。
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
_
このようなものは、_<stdarg.h>
_を内部に含めずにvprintf()
のようなものを宣言するためにのみ必要だと思います。
_int vprintf(const char *format, va_list ap);
_
あげくの果てに:
<stdarg.h>
_を含めないでください。<stdarg.h>
_を含め、_va_list
_ APIを使用して追加の引数にアクセスする必要があります。最初に、C標準の観点からあなたの質問に答えます。それが、コードの書き方を教えてくれるからです。
C標準では、stdio.h
が「あたかも動作する」必要があります not includestdarg.h
。言い換えると、マクロva_start
、va_arg
、va_end
、およびva_copy
、およびタイプva_list
は、stdio.h
を含めることによって使用可能にする必要があります not 。言い換えれば、このプログラムはコンパイルするために not が必要です:
#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
の implementation が(おそらく)stdarg.h
メカニズムを使用して引数にアクセスするのは事実ですが、それは単に、のソースコード内のいくつかのファイルを意味します。 Cライブラリ( "printf.c
"、おそらく)には、stdarg.h
とstdio.h
を含める必要があります。それはあなたのコードに影響を与えません。
stdio.h
がva_list
型の引数を取る関数を宣言していることも事実です。これらの宣言を見ると、実際には2つのアンダースコア、またはアンダースコアと大文字で始まる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);
2つのアンダースコア、またはアンダースコアと大文字で始まるすべての名前は、実装用に予約されています-stdio.h
は、必要な数の名前を宣言できます。逆に、アプリケーションプログラマーは、 any のような名前を宣言したり、実装で宣言された名前を使用したりすることはできません(_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
これが何をしているのかを理解するには、次の3つのことを知る必要があります。
POSIX.1 の最近の「問題」、「Unix」オペレーティングシステムの意味の公式仕様では、va_list
が定義することになっているもののセットにstdio.h
を追加します。 (具体的には、 Issue 6 では、va_list
はstdio.h
によって「XSI」拡張として定義され、 Issue 7 では必須です。)このコードはva_list
を定義しますが、プログラムがIssue6 + XSIまたはIssue7の機能を要求しました。それが#if defined __USE_XOPEN || defined __USE_XOPEN2K8
の意味です。 _G_va_list
を使用してva_list
を定義していることに注意してください。他の場所では、_G_va_list
を使用してvprintf
を宣言しているのと同じです。 _G_va_list
はすでに何らかの形で利用可能です。
同じ変換単位で同じtypedef
を2回書き込むことはできません。 stdio.h
がva_list
に再度実行しないように通知せずに、stdarg.h
を定義した場合、
#include <stdio.h>
#include <stdarg.h>
コンパイルされません。
GCCにはstdarg.h
のコピーが付属していますが、 not にはstdio.h
のコピーが付属しています。あなたが引用しているstdio.h
は GNU libc から来ています。これは、GNU傘下の別のプロジェクトであり、別の(しかし重複している)人々のグループによって維持されています。重要なのは、 GNU libcのヘッダーは、GCCによってコンパイルされていると想定することはできません。
したがって、引用したコードは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>
正常にコンパイルされます。 (おそらくシステムのstdarg.h
に隠れているGCCの/usr/lib/gcc/something/something/include
を見ると、このコードの鏡像と、 other マクロの非常に長いリストが表示されます。 other GCCで使用できる、または一度使用できるCライブラリの場合、「va_list
を定義しないでください。すでに定義しています。)
ただし、__GNUC__
が定義されていない場合、stdio.h
は、stdarg.h
との通信方法を not 知っていると見なします。ただし、C標準では動作する必要があるため、同じファイルにstdarg.h
を2回含めても安全であることはわかっています。したがって、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 */
したがって、両方が機能することになっている場合でも、最初の1つを2番目よりも機能させることがはるかに重要です。
最後に、あなたはまだ一体_G_va_list
がどこから来たのか疑問に思うかもしれません。 stdio.h
自体のどこにも定義されていないため、コンパイラ組み込み関数であるか、stdio.h
に含まれるヘッダーの1つで定義されている必要があります。システムヘッダーに含まれるすべてのものを見つける方法は次のとおりです。
$ 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 Issue 6 + XSIモードまたはIssue7モードで not コンパイルされていないことを確認しましたが、とにかくこのリストに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
に関して。 その名前は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固有のものであり、「現在のABIでva_list
に適切なタイプは何でも」を意味します。
$ echo 'void foo(__builtin_va_list x) {}' |
gcc -xc -std=c11 -fsyntax-only -; echo $?
0
(はい、GNU libcのstdioの実装は、それが存在するための言い訳よりもはるかに複雑です。説明は、昔の人々が試したことですFILE
オブジェクトをC++ filebuf
として直接使用できるようにするため。これは何十年も機能していません。実際、これまでが機能したかどうかはわかりません。以前は放棄されていました- [〜#〜] egcs [〜#〜] 、これは私が歴史を知っている限りさかのぼります—しかし、試行の痕跡はたくさんあります。バイナリの後方互換性のため、または誰もそれらをクリーンアップすることに慣れていないために、まだぶらぶらしています。)
(はい、これを正しく読んでいると、GNU libcのstdio.h
は、stdarg.h
が__gnuc_va_list
を定義していないCコンパイラでは正しく機能しません。これは抽象的に間違っていますが、無害です。 GNU libcには、さらに多くの心配事があります。)で動作する光沢のある新しい非GCC互換コンパイラ。
いいえ、printf()
を使用するために必要なのは#include <stdio.h>
だけです。 printf
はすでにコンパイルされているため、stdargは必要ありません。コンパイラーは、printf
のプロトタイプを見て、それが可変個引数であることを知る必要があります(プロトタイプの省略記号...
から派生)。 printf
のstdioライブラリのソースコードを見ると、<stdarg.h>
が含まれていることがわかります。
独自の可変個引数関数を作成する可変個引数関数を使用する場合は、必須#include <stdarg.h>
し、それに応じてマクロを使用します。ご覧のとおり、これを忘れると、va_start/list/end
シンボルはコンパイラーに認識されません。
printf
の実際の実装を見たい場合は、 FreeBSDの標準I/Oソースのコード と vfprintf
のソース)を見てください。 。
モジュールをヘッダーファイルとソースファイルに分割するための基本:
したがって、printf
の実装が_va_arg
_を利用している場合でも、次のように推測します。
stdio.h
_では、作成者はint printf(const char* format, ...);
のみを宣言しましたstdio.c
_では、作成者は_va_arg
_を使用してprintf
を実装しましたこのstdio.h
の実装には、gccでコンパイルした場合のstdarg.h
は含まれません。コンパイラの作成者が常に気を配っているのは魔法のように機能します。
Cソースファイルには、とにかく参照するすべてのシステムヘッダーが含まれている必要があります。これはC標準の要件です。つまり、ソースコードでstdarg.hに存在する定義が必要な場合は、直接、または含まれているyourヘッダーファイルのいずれかに#include <stdarg.h>
ディレクティブを含める必要があります。 stdarg.hが実際に含まれている場合でも、他のstandardヘッダーに含まれていることに依存することはできません。
_<stdarg.h>
_ファイルは、可変数の引数関数を実装するの場合にのみ含める必要があります。 printf(3)
やその仲間を使用できる必要はありません。可変数のargs関数で引数を処理する場合にのみ、_va_list
_タイプと、_va_start
_、_va_arg
_、および_va_end
_マクロが必要になります。したがって、その場合にのみ、そのファイルを強制的に含める必要があります。
一般に、_<stdarg.h>
_が_<stdio.h>
_を含めるだけで含まれることは保証されません。実際、引用するコードonlyには___GNU_C__
_ 定義されていない(これは事実であると思われるため、ケースには含まれていません)の場合、このマクロはgcc
コンパイラを使用している場合に定義されます。
コードで可変引数受け渡し関数を作成する場合、最善のアプローチは、別のインクルードファイルにそれが含まれることを期待しないことですが、自分で行います (要求された機能のクライアントとして)_va_list
_タイプ、または_va_start
_、_va_arg
_、または_va_end
_マクロを使用しているすべての場所。
以前は、一部のヘッダーファイルが二重インクルージョンから保護されていなかったため(同じインクルードファイルが二重に定義されたマクロなどに関するエラーを2回以上生成し、注意が必要だったため)、二重インクルージョンについて混乱がありましたが、今日は、これは問題ではなく、通常、すべての標準ヘッダーフィールドが二重インクルードから保護されています。