检索传递给可变参数函数的 int32_t 的便携式方法
Portable way to retrieve a int32_t passed to variadic function
7.16.1.1 2 描述 va_arg
如下(强调我的):
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.
根据我的理解,6.5.2.2(函数调用)似乎与我并不矛盾,虽然我可能是错的,默认的促销是:
char
到 int
或 unsigned
(指定的实现)
signed char
到 int
unsigned char
至 unsigned
short
到 int
unsigned short
到 unsigned
float
到 double
当您知道传递给 va_list
的确切基础类型(char
除外,AFAIK 不可能可移植地检索它时,这一切都很好,因为它的签名是实现指定的)。
当您希望将来自 <stdint.h>
的类型传递给您的 va_list
时,情况会变得更加复杂。
int8_t
和int16_t
,通过逻辑极限观察推演,保证提升或已经是int
类型。然而,依靠我的 原始 "logical" 极限观察 是非常可疑的,所以我正在寻求你(和标准)对这个推论的确认(我可能会遗漏一些极端情况我什至不知道)。
- 同样适用于
uint8_t
和 uint16_t
,除了基础类型是 unsigned
int32_t
可能会也可能不会 晋升为 int
。它可能大于、小于或完全等于 int
。同样适用于 uint32_t
,但适用于 unsigned
。 如何可移植地检索传递给va_list
的int32_t
和uint32_t
?换句话说,如何确定int32_t
(uint32_t
) 已经提升为 int
(unsigned
)? 也就是说,如何判断我是否应该使用 va_arg(va, int)
或 va_arg(va, int32_t)
来检索传递给可变参数函数的 int32_t
而不会在任何平台上调用未定义的行为?
- 我相信同样的问题也适用于
int64_t
和 uint64_t
。
这是一个理论问题(仅涉及标准),假设 <stdint.h>
中的所有精确宽度类型都存在。我对 "what's true in practice" 类型的答案不感兴趣,因为我相信我已经知道了。
编辑
我想到的一个想法是使用_Generic
来确定int32_t
的基础类型。我不确定你会如何使用它。我正在寻找更好(更简单)的解决方案。
确实没有什么好的办法。我认为规范的答案是 "don't do this"。除了不将此类类型作为参数传递给可变函数之外,甚至避免将它们用作 "variables" 并且仅将它们用作 "storage" (在大量存在的数组和结构中)。当然,很容易犯错误并将这样的 element/member 作为参数传递给可变参数函数,所以这不是很令人满意。
只有当这些类型不是用您的代码不知道的特定于实现的扩展整数类型定义时,您对 _Generic
的想法才有效。
有一种糟糕但有效的方法,涉及使用正确的 "PRI*" 宏将 va_list
传递给 vsnprintf
,然后从字符串中解析整数,但这样做之后列表是在无法再次使用它的状态下,因此 if 仅适用于最终参数。
您最好的选择可能是尝试找到 "does this type get promoted by default promotions?" 的公式您可以轻松查询类型的最大值是否超过 INT_MAX
或 UINT_MAX
但这仍然没有如果存在具有相同范围的虚假扩展整数类型,则无助于形式正确性。
关于 #if
和 <limits.h>
解决方案,我发现了这个 (6.2.5.8):
For any two integer types with the same signedness and different integer conversion rank
(see 6.3.1.1), the range of values of the type with smaller integer conversion rank is a
subrange of the values of the other type.
和 6.3.3.1 声明(强调我的):
Every integer type has an integer conversion rank defined as follows:
- No two signed integer types shall have the same rank, even if they have the same
representation.
- The rank of a signed integer type shall be greater than the rank of any signed integer
type with less precision.
- The rank of long long int shall be greater than the rank of long int, which
shall be greater than the rank of int, which shall be greater than the rank of short
int, which shall be greater than the rank of signed char.
- The rank of any unsigned integer type shall equal the rank of the corresponding
signed integer type, if any.
- The rank of any standard integer type shall be greater than the rank of any extended
integer type with the same width.
- The rank of char shall equal the rank of signed char and unsigned char.
- The rank of _Bool shall be less than the rank of all other standard integer types.
- The rank of any enumerated type shall equal the rank of the compatible integer type
(see 6.7.2.2).
- The rank of any extended signed integer type relative to another extended signed
integer type with the same precision is implementation-defined, but still subject to the
other rules for determining the integer conversion rank.
- For all integer types T1, T2, and T3, if T1 has greater rank than T2 and T2 has
greater rank than T3, then T1 has greater rank than T3.
这就是 6.5.2.2 6 所说的(强调我的):
If the expression that denotes the called function has a type that does not include a
prototype, the integer promotions are performed on each argument, and arguments that
have type float are promoted to double. These are called the default argument
promotions. If the number of arguments does not equal the number of parameters, the
behavior is undefined. If the function is defined with a type that includes a prototype, and
either the prototype ends with an ellipsis (, ...) or the types of the arguments after
promotion are not compatible with the types of the parameters, the behavior is undefined.
If the function is defined with a type that does not include a prototype, and the types of
the arguments after promotion are not compatible with those of the parameters after
promotion, the behavior is undefined, except for the following cases:
one promoted type is a signed integer type, the other promoted type is the
corresponding unsigned integer type, and the value is representable in both types;
both types are pointers to qualified or unqualified versions of a character type or
void
基于这些观察,我相信
#if INT32_MAX < INT_MAX
int32_t x = va_arg(va, int);
#else
int32_t x = va_arg(va, int32_t);
这是因为如果int32_t
的范围不能包含int
的范围,那么int32_t
的范围是int
的子范围,这表示int32_t
的rank低于int
,这意味着进行整数提升 .
另一方面,如果int32_t
的范围可以包含int
的范围,那么int32_t
的范围就是int
的范围或者一个int
范围的超集,因此 int32_t
的 rank 大于或等于 int
的秩,这意味着整数促销 未执行 。
编辑
根据评论更正了测试。
#if INT32_MAX <= INT_MAX && INT32_MIN >= INT_MIN
int32_t x = va_arg(va, int);
#else
int32_t x = va_arg(va, int32_t);
编辑 2:
我现在对这个案例特别感兴趣:
int
是32位的补码整数。
int32_t
为32位二进制补码整数(扩展型)
- 宽度(与精度相同?)相同
- 但是因为"The rank of any standard integer type shall be greater than the rank of any extended integer type with the same width."
int
的排名高于int32_t
- 这意味着必须执行从
int32_t
到 int
的整数提升
- 尽管
int
不能表示 int32_t
中的所有值(具体来说,它不能表示 INT32_MIN
)
发生什么了?还是我遗漏了什么?
#define IS_INT_OR_PROMOTED(X) _Generic((X)0 + (X)0, int: 1, default: 0)
用法:
int32_t x = IS_INT_OR_PROMOTED(int32_t) ?
(int32_t)va_arg(list, int) :
va_arg(list, int32_t);
在我的 PC 上使用 gcc,宏 returns 1 用于 int8_t
、int16_t
和 int32_t
,0 用于 int64_t
。
使用 gcc-avr(16 位目标)宏 returns 1 用于 int8_t
和 int16_t
,0 用于 int32_t
和 int64_t
.
对于long
宏returns0不管是否sizeof(int)==sizeof(long)
.
我没有任何 64 位 int
s 的目标,但我不明白为什么它不能在这样的目标上工作。
虽然我不确定这是否适用于真正病态的实现实际上我现在很确定它适用于任何符合规范的实现。
7.16.1.1 2 描述 va_arg
如下(强调我的):
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.
根据我的理解,6.5.2.2(函数调用)似乎与我并不矛盾,虽然我可能是错的,默认的促销是:
char
到int
或unsigned
(指定的实现)signed char
到int
unsigned char
至unsigned
short
到int
unsigned short
到unsigned
float
到double
当您知道传递给 va_list
的确切基础类型(char
除外,AFAIK 不可能可移植地检索它时,这一切都很好,因为它的签名是实现指定的)。
当您希望将来自 <stdint.h>
的类型传递给您的 va_list
时,情况会变得更加复杂。
int8_t
和int16_t
,通过逻辑极限观察推演,保证提升或已经是int
类型。然而,依靠我的 原始 "logical" 极限观察 是非常可疑的,所以我正在寻求你(和标准)对这个推论的确认(我可能会遗漏一些极端情况我什至不知道)。- 同样适用于
uint8_t
和uint16_t
,除了基础类型是unsigned
int32_t
可能会也可能不会 晋升为int
。它可能大于、小于或完全等于int
。同样适用于uint32_t
,但适用于unsigned
。 如何可移植地检索传递给va_list
的int32_t
和uint32_t
?换句话说,如何确定int32_t
(uint32_t
) 已经提升为int
(unsigned
)? 也就是说,如何判断我是否应该使用va_arg(va, int)
或va_arg(va, int32_t)
来检索传递给可变参数函数的int32_t
而不会在任何平台上调用未定义的行为?- 我相信同样的问题也适用于
int64_t
和uint64_t
。
这是一个理论问题(仅涉及标准),假设 <stdint.h>
中的所有精确宽度类型都存在。我对 "what's true in practice" 类型的答案不感兴趣,因为我相信我已经知道了。
编辑
我想到的一个想法是使用_Generic
来确定int32_t
的基础类型。我不确定你会如何使用它。我正在寻找更好(更简单)的解决方案。
确实没有什么好的办法。我认为规范的答案是 "don't do this"。除了不将此类类型作为参数传递给可变函数之外,甚至避免将它们用作 "variables" 并且仅将它们用作 "storage" (在大量存在的数组和结构中)。当然,很容易犯错误并将这样的 element/member 作为参数传递给可变参数函数,所以这不是很令人满意。
只有当这些类型不是用您的代码不知道的特定于实现的扩展整数类型定义时,您对 _Generic
的想法才有效。
有一种糟糕但有效的方法,涉及使用正确的 "PRI*" 宏将 va_list
传递给 vsnprintf
,然后从字符串中解析整数,但这样做之后列表是在无法再次使用它的状态下,因此 if 仅适用于最终参数。
您最好的选择可能是尝试找到 "does this type get promoted by default promotions?" 的公式您可以轻松查询类型的最大值是否超过 INT_MAX
或 UINT_MAX
但这仍然没有如果存在具有相同范围的虚假扩展整数类型,则无助于形式正确性。
关于 #if
和 <limits.h>
解决方案,我发现了这个 (6.2.5.8):
For any two integer types with the same signedness and different integer conversion rank (see 6.3.1.1), the range of values of the type with smaller integer conversion rank is a subrange of the values of the other type.
和 6.3.3.1 声明(强调我的):
Every integer type has an integer conversion rank defined as follows:
- No two signed integer types shall have the same rank, even if they have the same representation.
- The rank of a signed integer type shall be greater than the rank of any signed integer type with less precision.
- The rank of long long int shall be greater than the rank of long int, which shall be greater than the rank of int, which shall be greater than the rank of short int, which shall be greater than the rank of signed char.
- The rank of any unsigned integer type shall equal the rank of the corresponding signed integer type, if any.
- The rank of any standard integer type shall be greater than the rank of any extended integer type with the same width.
- The rank of char shall equal the rank of signed char and unsigned char.
- The rank of _Bool shall be less than the rank of all other standard integer types.
- The rank of any enumerated type shall equal the rank of the compatible integer type (see 6.7.2.2).
- The rank of any extended signed integer type relative to another extended signed integer type with the same precision is implementation-defined, but still subject to the other rules for determining the integer conversion rank.
- For all integer types T1, T2, and T3, if T1 has greater rank than T2 and T2 has greater rank than T3, then T1 has greater rank than T3.
这就是 6.5.2.2 6 所说的(强调我的):
If the expression that denotes the called function has a type that does not include a prototype, the integer promotions are performed on each argument, and arguments that have type float are promoted to double. These are called the default argument promotions. If the number of arguments does not equal the number of parameters, the behavior is undefined. If the function is defined with a type that includes a prototype, and either the prototype ends with an ellipsis (, ...) or the types of the arguments after promotion are not compatible with the types of the parameters, the behavior is undefined. If the function is defined with a type that does not include a prototype, and the types of the arguments after promotion are not compatible with those of the parameters after promotion, the behavior is undefined, except for the following cases:
one promoted type is a signed integer type, the other promoted type is the corresponding unsigned integer type, and the value is representable in both types;
both types are pointers to qualified or unqualified versions of a character type or void
基于这些观察,我相信
#if INT32_MAX < INT_MAX
int32_t x = va_arg(va, int);
#else
int32_t x = va_arg(va, int32_t);
这是因为如果int32_t
的范围不能包含int
的范围,那么int32_t
的范围是int
的子范围,这表示int32_t
的rank低于int
,这意味着进行整数提升 .
另一方面,如果int32_t
的范围可以包含int
的范围,那么int32_t
的范围就是int
的范围或者一个int
范围的超集,因此 int32_t
的 rank 大于或等于 int
的秩,这意味着整数促销 未执行 。
编辑
根据评论更正了测试。
#if INT32_MAX <= INT_MAX && INT32_MIN >= INT_MIN
int32_t x = va_arg(va, int);
#else
int32_t x = va_arg(va, int32_t);
编辑 2:
我现在对这个案例特别感兴趣:
int
是32位的补码整数。int32_t
为32位二进制补码整数(扩展型)- 宽度(与精度相同?)相同
- 但是因为"The rank of any standard integer type shall be greater than the rank of any extended integer type with the same width."
int
的排名高于int32_t
- 这意味着必须执行从
int32_t
到int
的整数提升 - 尽管
int
不能表示int32_t
中的所有值(具体来说,它不能表示INT32_MIN
) 发生什么了?还是我遗漏了什么?
#define IS_INT_OR_PROMOTED(X) _Generic((X)0 + (X)0, int: 1, default: 0)
用法:
int32_t x = IS_INT_OR_PROMOTED(int32_t) ?
(int32_t)va_arg(list, int) :
va_arg(list, int32_t);
在我的 PC 上使用 gcc,宏 returns 1 用于 int8_t
、int16_t
和 int32_t
,0 用于 int64_t
。
使用 gcc-avr(16 位目标)宏 returns 1 用于 int8_t
和 int16_t
,0 用于 int32_t
和 int64_t
.
对于long
宏returns0不管是否sizeof(int)==sizeof(long)
.
我没有任何 64 位 int
s 的目标,但我不明白为什么它不能在这样的目标上工作。
虽然我不确定这是否适用于真正病态的实现实际上我现在很确定它适用于任何符合规范的实现。