TITLE: variatic functions, args, and type punning (Newsgroups: comp.std.c++, 21 Jan 97) [ This picks up part of a discussion on the type required for each parameter to a variable length argument list. It is also in the context of an alternative definition for NULL (ie, (char*)0 or (int*)0 or 0). Near the end, the term "type pun" is introduced (I was not familiar with this term). -adc ] ???: |> >However... |> >|> 8) It solves the variadic function problem: |> >|> int varfunc(int a, ...); |> >|> ... |> >|> i = varfunc(3, "abc", NULL); // NULL is void*, not a class |> > KANZE: James Kanze |> >Not quite. If "varfunc" reads the pointer using "va_arg( char* )", then |> >you have undefined behavior. If "varfunc" reads the pointer using |> >"va_arg( int* )", then there are real machines on which it will fail. TRIBBLE: David R Tribble |> The rule to follow with variadic functions is to pass the right type for |> each argument as needed. If the function expects 'char *', then pass it |> a 'char *' (such as '(char *)NULL'), and not something else. |> |> On the other hand, according to ANSI C (which I assume C++ builds upon), a |> 'void *' pointer can hold a pointer of any other type. Casting a 'void *' |> to 'int *', for example, results in a valid integer pointer value. |> Similarly, passing 'NULL' as a 'void *' to a function should allow the |> function to treat it like any pointer type. KANZE: Correct, but irrelevant. The exact rule is that any pointer type can be CONVERTED to void* and back to the original type without loss of information. In the context of the C standard, "converted" means more or less a static_cast, and in any case, a cast or an implicit type conversion. It does not mean, for example, that I can do type punning on the pointer itself, and expect it to work, or that I can write a void* into a union, and read it as a T*, and expect it to work. In the case of stdarg.h, the C standard is quite explicit in saying that the type extracted must exactly match the type actually passed (after promotion, etc.). There are no conversions involved, and the guarantee concerning void* doesn't apply. The reason for this is precisely that most, if not all, implementations, use type punning somewhere in the va_arg macro to extract the argument; if you pun to the original type (about which all information has been lost), it will work, but nothing else is guaranteed. FWIW: a strict reading of the C standard would seem to imply that even a mismatch on cv-qualifiers will result in undefined behavior. Thus, if the function extracts a "char const*", and you pass it a string literal, you have undefined behavior. In fact, I doubt that this was intended, and I feel very sure that there will never be a real implementation where it will fail. [ The type of a string literal is "char * const", in case you were hazy on this. -adc ] Of course, on many implementations, all pointers have the same representation, and the null pointer has the same representation what is defined by the macro NULL (either 0, 0L or, in C, ((void*)0)). So broken code will appear to work. This is not guaranteed, however. IMHO: a quality implementation will pass additional hidden type information when passing an argument which matches a ..., and the va_arg macro will expand to invoke a compiler built-in which extracts and verifies this information. (I don't know of any implementation which actually does this, however.) Also, for those not familiar with the term, type punning means telling the compiler to access the actual bits as if they were a T, regardless of what it thinks they are. Thus, for example: void* p ; f( (char*)p ) ; // Conversion. f( *((char**)( &p ) ) ) ; // Type pun Note that the conversion may change the representation (except maybe in the case where only the cv-qualifiers are changed).