默认参数提升对wchar_t的影响:va_arg参数类型合规性咨询
我对默认参数提升如何作用于wchar_t存在困惑。我知道char会被提升为int,因此在va_arg的第二个参数中必须传入int,否则可能(GCC)或不会(MSVC)报错,如下方的%c示例所示。
于是我类比推测,wchar_t可能存在类似的提升规则,查阅了C99标准中相关类型的定义:
7.17 wchar_t……是一种整数类型,其取值范围可表示支持的区域设置中指定的最大扩展字符集的所有成员的不同编码;空字符的编码值应为0。若实现未定义__STDC_MB_MIGHT_NEQ_WC__,则基本字符集的每个成员的编码值应等于其作为整数字符常量中唯一字符时的值。
7.24.1 wint_t……是一种不受默认参数提升影响的整数类型,可容纳对应扩展字符集成员的任意值,以及至少一个不对应扩展字符集任何成员的值(见下文的WEOF)。
我清楚wint_t不会被提升,但不确定wchar_t是否也不会被提升。我尝试用va_arg分别以wchar_t、wint_t和int类型获取参数,三者都能运行,但这可能只是巧合:
#include <stdarg.h> #include <stdio.h> #include <string.h> #include <wchar.h> void print( char const* format, ... ); int main() { printf( "char == %zu, int == %zu, wchar_t == %zu, wint_t == %zu.\n", sizeof( char ), sizeof( int ), sizeof( wchar_t ), sizeof( wint_t ) ); // MSVC x86: char == 1, int == 4, wchar_t == 2, wint_t == 2. // MSVC x64: char == 1, int == 4, wchar_t == 2, wint_t == 2. // GCC x64: char == 1, int == 4, wchar_t == 4, wint_t == 4. char charA = 'A'; print( "%c", charA ); wchar_t wchar_tA = L'A'; print( "%lc", wchar_tA ); printf( "\n" ); } void print( char const* format, ... ) { va_list arguments; va_start( arguments, format ); if( strcmp( format, "%c" ) == 0 ) { // char c = va_arg( arguments, char ); // -> Bad (1) char c = va_arg( arguments, int ); // -> Good putchar( ( int ) c ); } else if( strcmp( format, "%lc" ) == 0 ) { wchar_t w = va_arg( arguments, wchar_t ); // -> Good // wint_t w = va_arg( arguments, wint_t ); // -> Good // int w = va_arg( arguments, int ); // -> Good putwchar( ( wchar_t ) w ); } va_end( arguments ); } // (1) GCC prints: // warning: 'char' is promoted to 'int' when passed through '...' // note: (so you should pass 'int' not 'char' to 'va_arg') // Running the program prints: // Illegal instruction
问题:else if块中的三条va_arg语句,哪一条符合C语言标准?
答案
首先明确C语言的默认参数提升规则:对于可变参数(...)中的实参,若其类型是小于int的整数类型(包括char、signed char、unsigned char、short、unsigned short),会被提升为int;如果该类型的取值范围超出int的容纳范围,则提升为unsigned int。而wchar_t本质是整数类型,具体大小由实现决定:
wchar_t w = va_arg( arguments, wchar_t );
只有当wchar_t的大小等于int时才符合标准,否则属于未定义行为——因为实参已经被提升为int,用更小的类型读取会导致参数栈的读取错位。int w = va_arg( arguments, int );
在大部分常见实现中有效(比如MSVC下wchar_t是2字节,会被提升为int),但存在极端场景的未定义行为:如果wchar_t是无符号类型且大小等于int,提升后的类型是unsigned int,此时用int读取会触发未定义行为(这种情况非常罕见)。wint_t w = va_arg( arguments, wint_t );
这是完全符合C标准的写法。C标准明确规定wint_t是为了处理宽字符的可变参数场景设计的,它可以容纳wchar_t提升后的所有可能值,无论wchar_t在当前实现中是否会被提升,用wint_t读取都能保证行为符合标准。
总结:
- 最安全、完全合规的写法是使用
wint_t作为va_arg的类型; - 使用
int在多数场景下可行,但存在潜在的未定义行为; - 使用
wchar_t仅在特定实现(wchar_t与int大小相同)下合规,否则属于未定义行为。
内容的提问来源于stack exchange,提问作者z32a7ul

