【问题标题】:Portable way to retrieve a int32_t passed to variadic function检索传递给可变参数函数的 int32_t 的便携式方法
【发布时间】:2018-06-09 01:16:33
【问题描述】:

7.16.1.1 2 描述va_arg 如下(强调我的):

如果没有实际的下一个参数,或者 如果 type 与实际下一个参数的类型不兼容(根据提升 到默认参数promotions),行为是未定义的,除了以下 案例:

  • 一种是有符号整数类型,另一种是对应的无符号整数 类型,并且值可以在两种类型中表示;
  • 一种类型是指向 void 的指针,另一种是指向字符类型的指针。

现在据我了解,6.5.2.2(函数调用)似乎与我不矛盾,虽然我可能错了,但默认促销是:

  • charintunsigned(指定实现)
  • signed charint
  • unsigned charunsigned
  • shortint
  • unsigned shortunsigned
  • floatdouble

当您知道传递给va_list 的确切底层类型时,这一切都很好而且很花哨(char 除外,因为它的签名是实现指定的,所以 AFAIK 无法便携检索)。

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

  • int8_tint16_t,通过逻辑极限观察推断,保证被提升或已经是int 类型。然而,依靠我的原始“逻辑”极限观察是非常可疑的,所以我正在寻求你(和标准)对这个推论的确认(我可能会遗漏一些我什至没有的极端案例知道)。
  • 同样适用于uint8_tuint16_t,除了基础类型是unsigned
  • int32_t 可能会或可能不会晋升为int。它可能大于、小于或与int 完全相同。 uint32_t 也一样,但 unsigned 也一样。 如何可移植地检索传递给va_listint32_tuint32_t也就是说,如何判断int32_tuint32_t)是否已经提升为int (unsigned)? 换句话说,如何确定我应该使用va_arg(va, int) 还是va_arg(va, int32_t) 来检索传递给可变参数函数的int32_t,而不在任何平台上调用未定义的行为?
  • 我相信同样的问题也适用于int64_tuint64_t

这是一个理论(仅涉及标准)问题,假设<stdint.h> 中的所有精确宽度类型都存在。我对“实践中的真实情况”类型的答案不感兴趣,因为我相信我已经知道它们。

编辑

我想到的一个想法是使用_Generic 来确定@​​987654365@ 的基础类型。我不确定您将如何使用它。我正在寻找更好(更简单)的解决方案。

【问题讨论】:

  • int32_t 小于int 的可能性很小。这将是一个相当奇怪的实现,因为 64 位 int 意味着 short 是 16 位或 32 位,而其他宽度没有对应的“本机”类型。跨度>
  • @DevSolar:确实存在拜占庭式架构,尤其是在嵌入式领域。尽管如此,一个不将 int32_t 提升为 int_fast32_t 的系统会特别奇怪。就个人而言,除非有明显的解决方案,否则我不会打扰并满足于​​断言 PRIu32。
  • 一个不那么疯狂的解决方案是使用autoconf 来发现编译器行为和一两个宏。
  • 促销活动不尊重签名。根据 6.3.1.2 2,“如果 int 可以表示原始类型的所有值(受宽度限制,对于位域),则该值将转换为 int;否则,它将被转换为 unsigned int。”所以uint8_t 会变成int,而不是unsigned int
  • unsigned charunsigned short 如果可以表示源类型的所有值,则提升为 int,否则提升为 unsigned int

标签: c type-conversion language-lawyer variadic-functions integer-promotion


【解决方案1】:
#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 时,宏为 int8_tint16_tint32_t 返回 1,为 int64_t 返回 0。

对于 gcc-avr(一个 16 位目标),宏为 int8_tint16_t 返回 1,为 int32_tint64_t 返回 0。

对于long,无论是否sizeof(int)==sizeof(long),宏都返回0。

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

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

【讨论】:

  • 老实说,我不明白为什么它不能与任何符合要求的实现一起使用。除非有人可以提供警告,否则这可能是答案。
  • 大家对这个进行投票(除非有人发现了缺陷)。用于解决晦涩但有效的问题的深奥知识值得被认可。
  • 这很好,消除了我在更广泛地使用 <inttypes.h> 类型时遇到的最后一个阻塞问题。
【解决方案2】:

关于#if<limits.h> 解决方案,我发现了这个(6.2.5.8):

对于任意两个具有相同符号和不同整数转换等级的整数类型 (见 6.3.1.1),整数转换等级较小的类型的取值范围是 其他类型值的子范围。

并且 6.3.3.1 状态(强调我的):

每个整数类型都有一个整数转换等级,定义如下:

  • 没有两个有符号整数类型具有相同的等级,即使它们具有相同的等级 代表。
  • 有符号整数类型的等级应大于任何有符号整数的等级 以较低的精度输入
  • long long int 的秩应大于 long int 的秩,即 应该大于int的rank,int的rank应该大于short的rank int,应大于signed char的等级。
  • 任何无符号整数类型的等级应等于相应的等级 有符号整数类型(如果有)。
  • 任何标准整数类型的等级都应大于任何扩展的等级 等宽的整数类型。
  • char 的秩应等于signed char 和unsigned char 的秩。
  • _Bool 的等级应小于所有其他标准整数类型的等级。
  • 任何枚举类型的等级都应等于兼容整数类型的等级 (见 6.7.2.2)。
  • 任何扩展有符号整数类型相对于另一个扩展有符号整数类型的等级 具有相同精度的整数类型是实现定义的,但仍受制于 确定整数转换等级的其他规则。
  • 对于所有整数类型 T1、T2 和 T3,如果 T1 的秩大于 T2 并且 T2 具有 排名高于 T3,则 T1 排名高于 T3。

这就是 6.5.2.2 6 所说的(强调我的):

如果表示被调用函数的表达式的类型不包含 原型,整数 promotions 对每个参数执行,参数 具有 float 类型的提升为 double。这些被称为默认参数 促销。如果参数的数量不等于参数的数量,则 行为未定义。如果函数是使用包含原型的类型定义的,并且 原型以省略号 (, ...) 结尾或后面的参数类型 提升与参数的类型不兼容,行为未定义。 如果函数是用不包含原型的类型定义的,那么 提升后的参数与之后的参数不兼容 促销,行为未定义,但以下情况除外:

  • 一个提升类型是有符号整数类型,另一个提升类型是 对应的无符号整数类型,并且值在两种类型中都可以表示;

  • 这两种类型都是指向字符类型的限定或非限定版本的指针,或者 无效

基于这些观察,我相信

#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的子范围,也就是说rankint32_t 的 > 低于 int 的 >,这意味着进行了整数提升

另一方面,如果int32_t的范围可以包含int的范围,那么int32_t的范围就是int的范围或者int的范围的超集,并且因此int32_trank大于等于int的rank,表示整数提升没有进行

编辑

根据 cmets 更正了测试。

#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 位二进制补码整数(扩展类型)
  • 宽度(与精度相同?)相同
  • 但是因为“任何标准整数类型的等级应大于任何具有相同宽度的扩展整数类型的等级。” int 的排名高于int32_t
  • 这意味着必须执行从int32_tint 的整数提升
  • 即使int 不能代表int32_t 中的所有值(具体来说,它不能代表INT32_MIN) 发生什么了?还是我错过了什么?

【讨论】:

  • 如果int32_t 是与int 大小相同的扩展整数类型,则需要&lt;=,因为任何标准整数类型的等级都应大于任何具有相同宽度的扩展整数类型。
  • @EricPostpischil: 但是int32_t 可以 也可以是标准整数类型的 typedef,它不需要是扩展类型...如果是 @ 987654351@ 则不会进行促销。因此,我的评论是用条件来表达的。
  • @BenVoigt:我同意,int32_tlong int 的情况似乎有问题。
【解决方案3】:

确实没有什么好的方法可以做到这一点。我认为规范的答案是“不要这样做”。除了不将此类类型作为参数传递给可变参数函数之外,甚至避免将它们用作“变量”,而仅将它们用作“存储”(在大量存在的数组和结构中)。当然,很容易出错并将这样的元素/成员作为参数传递给您的可变参数函数,所以这不是很令人满意。

您对_Generic 的想法仅在这些类型未使用您的代码不知道的特定于实现的扩展整数类型定义时才有效。

有一种糟糕但有效的方法,包括使用正确的“PRI*”宏将va_list 传递给vsnprintf,然后从字符串中解析整数,但在这样做之后,列表处于您可以' t 再次使用它,所以 if 仅适用于最终参数。

您最好的选择可能是尝试找到“这种类型是否会在默认促销活动中得到提升?”的公式。您可以轻松查询该类型的最大值是否超过INT_MAXUINT_MAX,但是如果存在具有相同范围的虚假扩展整数类型,这仍然无助于形式正确性。

【讨论】:

  • 虚假扩展整数类型有什么问题?如果int32_t 值是int 值的子集,则int32_t 被提升为int,因此va_arg(list, int) 有效。如果没有,int32_t 不会被提升,所以va_arg(list, int32_t) 有效。我错过了什么?
  • @EricPostpischil:如果int 是16 位,那么va_arg(list, int) 就是UB;如果int 是64 位,那么va_arg(list, int32_t) 是UB;无论哪种方式,您都无法编写每次都有效的代码。
  • @rodrigo:代码为INT_MIN &lt;= INT32_MIN &amp;&amp; INT32_MAX &lt;= INT_MAX ? va_arg(list, int) : va_arg(list, int32_t)(或使用#if 的等效决定)。所以,是的,我们可以编写源代码,当int32_t 被提升为int 时使用int,而当它没有被提升为int 时使用int32_t。但 R.. 似乎表明这有一些问题。
  • @R.. 如果INT32_MAX&gt;&lt;==INT_MAX,这并不能保证int32_t 的排名更高、更低或等于int?警告在哪里?
  • @MarkWeston:当int32_t 的范围和int 的范围相等时,这不会使转换排名相等。这两个项目符号使事情复杂化: long long int 的等级应大于 long int 的等级,long int 的等级应大于 int 的等级,应大于 short int 的等级,应大于比有符号字符的秩。任何标准整数类型的秩都应大于任何具有相同宽度的扩展整数类型的秩。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2016-05-08
  • 1970-01-01
  • 1970-01-01
  • 2014-01-27
  • 2015-09-29
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多