变量函数和常量
可变函数究竟如何处理数字常量? 例如,考虑以下代码:
myfunc(5, 0, 1, 2, 3, 4);
该函数如下所示:
void myfunc(int count, ...) { }
现在,为了用va_arg
迭代单个参数,我需要知道它们的大小,例如int
, short
, char
, float
等。但是我应该假设数字常量的大小,就像我在上面的代码中使用的那样?
测试表明,假设为它们的int
似乎工作正常,所以编译器似乎将它们推送为int
即使这些常量也可以用单个char
表示或者每个short
。
不过,我正在寻找我所看到的行为的解释。 C中用于将数字常量传递给可变参数函数的标准类型是什么? 这是明确定义还是依赖于编译器? 32位和64位架构之间有区别吗?
谢谢!
我喜欢Jonathan Leffler的回答 ,但我认为我已经掌握了一些技术细节,对于那些打算编写可移植库或提供具有可变函数的API的人来说,因此需要深入研究细节。
变量参数受默认参数提升 (C11草案N1570为PDF;第6.5.2.2节函数调用,第6段):
..对每个参数执行整数提升,并将类型为float的参数提升为double。 这些被称为默认参数促销 。
[If] ..促销后的参数类型与促销后的参数类型不兼容,行为未定义,但以下情况除外:
一个提升类型是有符号整数类型,另一个提升类型是相应的无符号整数类型,并且该值可在两种类型中表示;
这两种类型都是指向字符类型或void的限定或非限定版本的指针
浮点常量的类型为double
,除非它们以f
或F
为后缀(如1.0f
),在这种情况下它们的类型为float
。
在C99和C11中,如果整数常量适合于一个整数常量,则它们的类型为int
; long
(AKA long int
)如果它们适合另一个; 否则long long
( long long int
) 由于许多编译器假设没有大小后缀的整数常量是人为错误或拼写错误,因此如果整数常量不是int
类型,则始终包含后缀是一个好习惯。
整数常量也可以有一个字母后缀来表示它们的类型:
-
u
或U
表示unsigned int
-
l
或L
表示long int
-
对于
unsigned long int
lu
或ul
或LU
或UL
或lU
或Lu
或uL
或Ul
-
对于
long long int
ll
或LL
或Ll
或lL
-
llu
或LLU
(或ULL
或其任何大写或小写变体)用于unsigned long long int
整数提升规则在6.3.1.1节中。
总结C11的默认参数提升规则(与C89和C99相比有一些增加,但没有重大变化):
-
float
升级为double
-
其值可由
int
表示的所有整数类型都将提升为int
。 (这包括无符号和有符号的char
和short
,以及类型为_Bool
,int
和较小的unsigned int
位域的位域。) -
可以用
unsigned int
(但不是int
)表示其值的所有整数类型都将提升为unsigned int
。 (这包括无法用int
表示的unsigned int
位字段CHAR_BIT * sizeof (unsigned int)
换句话说,是CHAR_BIT * sizeof (unsigned int)
位),以及unsigned int
typedef’d别名,但我认为就是这样。) -
至少与
int
一样大的整数类型不变。 例如,这包括long
/long int
类型,long long
/long long int
和size_t
。
规则中有一个“问题”我想指出: “签名为无符号是好的,未签名签名是iffy” :
-
如果参数被提升为有符号整数类型,但函数使用相应的无符号整数类型获取值,则函数使用模运算获得正确的值。
也就是说,负值就好像它们增加(1 +无符号整数类型中的最大可表示值),使它们为正。
-
如果参数被提升为无符号整数类型,但函数使用相应的有符号整数类型获取值,并且该值可在两者中表示,则函数将获得正确的值。 如果该值在两者中都不可表示,则行为是实现定义的。
在实践中,几乎所有体系结构都与上面相反,即获得的有符号整数值与减去的无符号值匹配(1 +无符号整数类型的最大可表示值)。 我听说有些奇怪的可能会发出整数溢出或类似奇怪的东西,但我从未在这些机器上得到过我的手套。
如果将上述规则与printf说明符进行比较,那么man 3 printf手册页(由Linux手册页项目提供)非常有用。 最后的make_message()
示例函数( vsnprintf()
所需的C99,C11或POSIX)也应该很有趣。
当你写1
,这是一个int
常量。 没有其他类型允许编译器使用。 如果函数的非变量原型需要不同的类型,编译器会将整数1
转换为适当的类型,但就其本身而言, 1
是一个int
常量。 因此,在您的示例中,所有6个参数都是int
。
在调用variadic函数处理它们之前,你必须以某种方式知道参数的类型。 使用printf()
系列函数,格式字符串告诉它期待什么; 类似于scanf()
系列函数。
请注意,默认转换适用于与可变参数函数的省略号对应的参数。 例如,给定:
char c = '\007'; short s = 0xB0hD; float f = 3.1415927;
致电:
int variadic_function(const char *, ...);
使用:
int rc = variadic_function("csf", c, s, f);
实际上将c
和s
都转换为int
,将f
为double
。