下記コードではvoid*型をもつマクロNULLの値*1を、関数f実装ではchar*型として展開するが、両ポインタ型は互換型(compatible type)とみなされない*2。関数呼び出し側における実引数の型と、マクロva_arg呼び出しで指定する型は、互換型でないと未定義動作(undefined behavior)を引き起こす。ただし、特例によりvoid*char*(およびその互換型)同士はマクロva_argによる変換が許容されている。

#include <stdarg.h>
#include <stdio.h>

void f(char *s, ...)
  va_list ap;
  va_start(ap, s);
  do {
    printf("%s\n", s);
    s = va_arg(ap, char*);
    // 3回目のva_arg呼び出しはヌルポインタ(NULL)へ展開されるが、
    // void*型をchar*型として展開するのは特例によりwell-defined
  } while (s);

f("abc", "123", "xyz", NULL);  // OK


struct X { /*...*/ };
void h(X* p, ...) {
  // va_arg(ap, X*)を用いた実装
X x1, x2;

h(&x1, &x2, NULL);  // NG: void*とX*は非互換型のため未定義動作!
h(&x1, &x2, (X*)NULL);  // OK: X*型へのキャスト明示が必要


2 The va_arg macro expands to an expression that has the specified type and the value of the next argument in the call. The parameter ap shall have been initialized by the va_start or va_copy macro (without an intervening invocation of the va_end macro for the same ap). Each invocation of the va_arg macro modifies ap so that the values of successive arguments are returned in turn. The parameter type shall be a type name specified such that the type of a pointer to an object that has the specified type can be obtained simply by postfixing a * to type. If there is no actual next argument, or if type is not compatible with the type of the actual next argument (as promoted according to the default argument promotions), the behavior is undefined, except for the following cases:

  • one type is a signed integer type, the other type is the corresponding unsigned integer type, and the value is representable in both types;
  • one type is pointer to void and the other is a pointer to a character type.



*1:厳密にはマクロ NULL は処理系定義(implementation defined)のヌルポインタ定数へ展開される(C99 7.17/p3)。C標準規格の範囲内だけで解釈した場合、マクロ NULL は事実上 (void*)0 へと展開される。id:yohhoy:20120503 も参照のこと。

*2:C99 "For two pointer types to be compatible, both shall be identically qualified and both shall be pointers to compatible types."

*3:C++11 2.14.7/p1: "It is a prvalue of type std::nullptr_t."

*4:C++11 5.2.2/p7: "An argument that has (possibly cv-qualified) type std::nullptr_t is converted to type void*."