C Language
可変の引数
サーチ…
前書き
変数の引数はprintfファミリの関数( printf
、 fprintf
など)と他の関数で使用され、毎回異なる数の引数で関数を呼び出すことができるため、 varargsという名前が使用されます 。
可変引数機能を使用して関数を実装するには、 #include <stdarg.h>
使用します。
可変数の引数を取る関数を呼び出すには、スコープ内に末尾の省略記号を含む完全なプロトタイプがあることを確認してvoid err_exit(const char *format, ...);
例えば。
構文
- void va_start(va_list ap、 last ); / *可変引数の処理を開始します。 lastは、省略記号( "...")の前の最後の関数パラメータです。* /
- 型のva_arg(va_listのAPを、 タイプ ); / *リスト内の次の可変引数を取得します。正しいプロモートタイプを渡すようにしてください* /
- void va_end(va_list ap); / *終了引数の処理* /
- void va_copy(va_list dst、va_list src); / * C99以降:引数のリスト、つまり引数の処理における現在の位置を別のリストにコピーする(例えば、引数を複数回渡す)* /
パラメーター
パラメータ | 詳細 |
---|---|
va_list ap | 引数ポインタ、可変引数のリスト内の現在の位置 |
最終 | 最後の非可変関数引数の名前です。したがって、可変引数の処理を開始する正しい場所がコンパイラによって検出されます。 register 変数、関数、または配列型として宣言することはできません |
タイプ | (例えば読み込むための可変長引数の昇格された型int のためにshort int の引数) |
va_list src | コピーする現在の引数ポインタ |
va_list dst | 記入される新しい引数リスト |
備考
va_start
、 va_arg
、 va_end
、およびva_copy
関数は実際にはマクロです。
必ず va_start
最初に呼び出し、一度だけ呼び出すようにしてください。最後にva_end
を呼び出し、 va_end
すべての終了点で1回だけ呼び出すようにしてください。そうしないと、 あなたのシステムではうまくいくかもしれませんが、確かに移植性がないので、バグを招きます。
プロトタイプを使って関数を正しく宣言し、 最後の非可変引数(関数ではなくregister
でも配列型でもない)の制限に注意してください。引数の処理を開始するには少なくとも1つの非可変引数が必要であるため、可変引数のみを取る関数を宣言することはできません。
va_arg
呼び出すときは、 昇格された引数型を要求する必要があります。
-
short
するように促進されint
(およびunsigned short
も促進され、int
がない限りsizeof(unsigned short) == sizeof(int)
それが促進される場合には、unsigned int
)。 -
float
はdouble
昇格します。 -
signed char
はint
昇格します。unsigned char
もsizeof(unsigned char) == sizeof(int)
でなければint
昇格されます。 -
char
は通常int
昇格します。 -
uint8_t
やint16_t
ようなC99型も同様にプロモートされます。
歴史的な(すなわちK&R)可変引数処理は<varargs.h>
宣言されていますが、廃止されたものとしては使用しないでください。標準的な可変引数処理(ここで記述され、 <stdarg.h>
宣言されたもの)がC89で導入されました。 va_copy
マクロはC99で導入されましたが、その前に多くのコンパイラによって提供されていました。
明示的なカウント引数を使用してva_listの長さを判断する
任意の可変関数では、関数は可変引数リストの解釈方法を知っていなければなりません。 printf()
関数またはscanf()
関数では、書式文字列は関数に何を期待するかを指示します。
最も単純な手法は、他の引数(通常はすべて同じ型)の明示的な数を渡すことです。これは以下のコードのvariadic関数で示されます。この関数は一連の整数の合計を計算します。整数の数は任意ですが、その数は可変引数リストの前に引数として指定されます。
#include <stdio.h>
#include <stdarg.h>
/* first arg is the number of following int args to sum. */
int sum(int n, ...) {
int sum = 0;
va_list it; /* hold information about the variadic argument list. */
va_start(it, n); /* start variadic argument processing */
while (n--)
sum += va_arg(it, int); /* get and sum the next variadic argument */
va_end(it); /* end variadic argument processing */
return sum;
}
int main(void)
{
printf("%d\n", sum(5, 1, 2, 3, 4, 5)); /* prints 15 */
printf("%d\n", sum(10, 5, 9, 2, 5, 111, 6666, 42, 1, 43, -6218)); /* prints 666 */
return 0;
}
ターミネーター値を使用してva_listの終わりを判別する
任意の可変関数では、関数は可変引数リストの解釈方法を知っていなければなりません。 "従来の"アプローチ( printf
例示)は、引数の数を前面に指定することです。しかし、これは常に良い考えではありません:
/* First argument specifies the number of parameters; the remainder are also int */
extern int sum(int n, ...);
/* But it's far from obvious from the code. */
sum(5, 2, 1, 4, 3, 6)
/* What happens if i.e. one argument is removed later on? */
sum(5, 2, 1, 3, 6) /* Disaster */
POSIXのexeclp()
関数のような明示的なターミネータを追加するほうが頑強になることがあります。ここでは、一連の合計を計算するために別の関数のdouble
番号は:
#include <stdarg.h>
#include <stdio.h>
#include <math.h>
/* Sums args up until the terminator NAN */
double sum (double x, ...) {
double sum = 0;
va_list va;
va_start(va, x);
for (; !isnan(x); x = va_arg(va, double)) {
sum += x;
}
va_end(va);
return sum;
}
int main (void) {
printf("%g\n", sum(5., 2., 1., 4., 3., 6., NAN));
printf("%g\n", sum(1, 0.5, 0.25, 0.125, 0.0625, 0.03125, NAN));
}
良好なターミネーター値:
- 整数(すべて正または非負) -
0
または-1
- 浮動小数点型 -
NAN
- ポインタ型 -
NULL
- 列挙型の型 - 何らかの特別な値
`printf()`のようなインタフェースを使って関数を実装する
可変長引数リストの一般的な使い方の1つは、 printf()
ファミリの関数の周りに薄いラッパーである関数を実装することです。 1つのそのような例は、エラー報告機能のセットである。
errmsg.h
#ifndef ERRMSG_H_INCLUDED
#define ERRMSG_H_INCLUDED
#include <stdarg.h>
#include <stdnoreturn.h> // C11
void verrmsg(int errnum, const char *fmt, va_list ap);
noreturn void errmsg(int exitcode, int errnum, const char *fmt, ...);
void warnmsg(int errnum, const char *fmt, ...);
#endif
これは裸の例です。そのようなパッケージは非常に精巧である可能性があります。通常、プログラマーはerrmsg()
またはwarnmsg()
を使用します。 verrmsg()
内部的に使用します。しかし、誰かがもっと多くのことをする必要が生じた場合、公開されたverrmsg()
関数が便利になります。 (あなたがそれを必要としているまで、あなたはそれを公開しない可能性が-あなたはそれを必要とするつもりはないYAGNIを ( - YAGNIそれを必要とするあなたがつもりです ))が、必要性は最終的に生じることになります。
errmsg.c
このコードは、標準エラーに出力するために、可変引数をvfprintf()
関数に転送するだけです。また、関数に渡されたシステム・エラー番号( errno
)に対応するシステム・エラー・メッセージも報告されます。
#include "errmsg.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void
verrmsg(int errnum, const char *fmt, va_list ap)
{
if (fmt)
vfprintf(stderr, fmt, ap);
if (errnum != 0)
fprintf(stderr, ": %s", strerror(errnum));
putc('\n', stderr);
}
void
errmsg(int exitcode, int errnum, const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
verrmsg(errnum, fmt, ap);
va_end(ap);
exit(exitcode);
}
void
warnmsg(int errnum, const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
verrmsg(errnum, fmt, ap);
va_end(ap);
}
errmsg.h
使用
これらの関数は、次のように使用できます。
#include "errmsg.h"
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(int argc, char **argv)
{
char buffer[BUFSIZ];
int fd;
if (argc != 2)
{
fprintf(stderr, "Usage: %s filename\n", argv[0]);
exit(EXIT_FAILURE);
}
const char *filename = argv[1];
if ((fd = open(filename, O_RDONLY)) == -1)
errmsg(EXIT_FAILURE, errno, "cannot open %s", filename);
if (read(fd, buffer, sizeof(buffer)) != sizeof(buffer))
errmsg(EXIT_FAILURE, errno, "cannot read %zu bytes from %s", sizeof(buffer), filename);
if (close(fd) == -1)
warnmsg(errno, "cannot close %s", filename);
/* continue the program */
return 0;
}
open()
またはread()
システムコールのいずれかが失敗すると、エラーが標準エラーに書き込まれ、プログラムは終了コード1で終了します。close close()
システムコールが失敗した場合、エラーは単に警告メッセージとして出力され、プログラムは続行されます。
printf()
形式の正しい使用のチェック
GCC(GNU Compiler Collectionの一部であるGNU Cコンパイラ)を使用している場合、またはClangを使用している場合、エラーメッセージ関数に渡す引数がprintf()
予期しているものと一致することをコンパイラに確認させることができます。すべてのコンパイラが拡張機能をサポートしているわけではないので、条件付きでコンパイルする必要があります。しかし、それが与える保護は努力の価値がある。
まず、コンパイラがGCCまたはGCCをエミュレートするClangであることを検出する方法を知る必要があります。答えはGCCがそのことを示すために__GNUC__
を定義していることです。
属性(具体的にはformat
属性)については、 共通関数の属性を参照してください。
書き換えられたerrmsg.h
#ifndef ERRMSG_H_INCLUDED
#define ERRMSG_H_INCLUDED
#include <stdarg.h>
#include <stdnoreturn.h> // C11
#if !defined(PRINTFLIKE)
#if defined(__GNUC__)
#define PRINTFLIKE(n,m) __attribute__((format(printf,n,m)))
#else
#define PRINTFLIKE(n,m) /* If only */
#endif /* __GNUC__ */
#endif /* PRINTFLIKE */
void verrmsg(int errnum, const char *fmt, va_list ap);
void noreturn errmsg(int exitcode, int errnum, const char *fmt, ...)
PRINTFLIKE(3, 4);
void warnmsg(int errnum, const char *fmt, ...)
PRINTFLIKE(2, 3);
#endif
さて、もしあなたが次のような間違いをしたら:
errmsg(EXIT_FAILURE, errno, "Failed to open file '%d' for reading", filename);
( %d
は%s
なければなりません)、コンパイラは次のように文句を言います:
$ gcc -O3 -g -std=c11 -Wall -Wextra -Werror -Wmissing-prototypes -Wstrict-prototypes \
> -Wold-style-definition -c erruse.c
erruse.c: In function ‘main’:
erruse.c:20:64: error: format ‘%d’ expects argument of type ‘int’, but argument 4 has type ‘const char *’ [-Werror=format=]
errmsg(EXIT_FAILURE, errno, "Failed to open file '%d' for reading", filename);
~^
%s
cc1: all warnings being treated as errors
$
書式文字列を使用する
書式文字列を使用すると、後続の可変引数の予想数と型に関する情報が明示的なcount引数または終端値の必要性を避けるように提供されます。
次の例は、標準のprintf()
関数をラップし、 char
、 int
、 double
型(10進浮動小数点形式)の可変引数を使用できるようにする関数を示しています。 printf()
と同様に、ラッピング関数の最初の引数は書式文字列です。書式文字列が解析されるとき、関数は期待される別の可変引数があるかどうか、そしてその型はどうあるべきかを判断することができます。
#include <stdio.h>
#include <stdarg.h>
int simple_printf(const char *format, ...)
{
va_list ap; /* hold information about the variadic argument list. */
int printed = 0; /* count of printed characters */
va_start(ap, format); /* start variadic argument processing */
while (*format != '\0') /* read format string until string terminator */
{
int f = 0;
if (*format == '%')
{
++format;
switch(*format)
{
case 'c' :
f = printf("%d", va_arg(ap, int)); /* print next variadic argument, note type promotion from char to int */
break;
case 'd' :
f = printf("%d", va_arg(ap, int)); /* print next variadic argument */
break;
case 'f' :
f = printf("%f", va_arg(ap, double)); /* print next variadic argument */
break;
default :
f = -1; /* invalid format specifier */
break;
}
}
else
{
f = printf("%c", *format); /* print any other characters */
}
if (f < 0) /* check for errors */
{
printed = f;
break;
}
else
{
printed += f;
}
++format; /* move on to next character in string */
}
va_end(ap); /* end variadic argument processing */
return printed;
}
int main (int argc, char *argv[])
{
int x = 40;
int y = 0;
y = simple_printf("There are %d characters in this sentence", x);
simple_printf("\n%d were printed\n", y);
}