检索传递给可变参数函数的 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(函数调用)似乎与我并不矛盾,虽然我可能是错的,默认的促销是:

当您知道传递给 va_list 的确切基础类型(char 除外,AFAIK 不可能可移植地检索它时,这一切都很好,因为它的签名是实现指定的)。

当您希望将来自 <stdint.h> 的类型传递给您的 va_list 时,情况会变得更加复杂。

这是一个理论问题(仅涉及标准),假设 <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_MAXUINT_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_trank低于int,这意味着进行整数提升 .

另一方面,如果int32_t的范围可以包含int的范围,那么int32_t的范围就是int的范围或者一个int 范围的超集,因此 int32_trank 大于或等于 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_tint 的整数提升
  • 尽管 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_tint16_tint32_t,0 用于 int64_t

使用 gcc-avr(16 位目标)宏 returns 1 用于 int8_tint16_t,0 用于 int32_tint64_t.

对于long宏returns0不管是否sizeof(int)==sizeof(long).

我没有任何 64 位 ints 的目标,但我不明白为什么它不能在这样的目标上工作。

虽然我不确定这是否适用于真正病态的实现实际上我现在很确定它适用于任何符合规范的实现。