サーチ…


前書き

変数の引数はprintfファミリの関数( printffprintfなど)と他の関数で使用され、毎回異なる数の引数で関数を呼び出すことができるため、 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_startva_argva_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 )。
  • floatdouble昇格します。
  • signed charint昇格します。 unsigned charsizeof(unsigned char) == sizeof(int)でなければint昇格されます。
  • charは通常int昇格します。
  • uint8_tint16_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()関数をラップし、 charintdouble型(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);
}


Modified text is an extract of the original Stack Overflow Documentation
ライセンスを受けた CC BY-SA 3.0
所属していない Stack Overflow