代码之家  ›  专栏  ›  技术社区  ›  MarkWeston

检索传递给可变函数的int32\u t的便携式方法

  •  10
  • MarkWeston  · 技术社区  · 7 年前

    7.16.1.1 2 描写 va_arg 如下(重点矿山):

    如果没有实际的下一个参数,或 如果 类型与实际下一个参数的类型不兼容(根据 到默认参数升级) ,行为未定义,但以下情况除外 案例:

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

    现在据我所知,6.5.2.2(函数调用)似乎与我并不矛盾,尽管我可能错了,但默认的升级是:

    • char 至任一 int unsigned (指定实施)
    • signed char 内景
    • unsigned char 未签名
    • short 内景
    • unsigned short 未签名
    • float double

    当您知道传递给 va_list (除 烧焦 ,AFAIK无法通过可移植方式检索,因为其签名是由实现指定的)。

    当您期望 <stdint.h> 传递给您的 va\U列表 .

    • int8_t int16_t ,通过逻辑极限观察进行扣除,保证被提升或已经属于类型 内景 . 然而,依靠我的 原始“逻辑”极限观测值 ,因此,我正在寻求您(和标准)对该扣除额的确认(我可能遗漏了一些我甚至不知道的角落案例)。
    • 同样适用于 uint8_t uint16_t ,但基础类型为 未签名
    • int32_t 可能是也可能不是 提升至 内景 . 它可以大于、小于或完全等于 内景 . 同样适用于 uint32_t 但对于 未签名 . 如何可移植地检索 int32\u t uint32\u t 传递给 va\U列表 ? 换句话说, 如何确定 int32\u t ( uint32\u t )已升级为 内景 ( 未签名 )? 换句话说, 如何确定我是否应该使用 va_arg(va, int) va_arg(va, int32_t) 要检索 int32\u t 传递给变量函数,而不调用任何平台上的未定义行为?
    • 我相信同样的问题也适用于 int64_t uint64_t .

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

    编辑

    我想到的一个想法是 _Generic 确定的基础类型 int32\u t . 我不知道你会怎么用它。我正在寻找更好(更容易)的解决方案。

    3 回复  |  直到 7 年前
        1
  •  7
  •   n. m. could be an AI    7 年前
    #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);
    

    在我的电脑上使用gcc时,宏为返回1 int8_t , int16_t int32_t ,0表示 int64_t .

    对于gcc avr(16位目标),宏为返回1 int8\u t int16\U t ,0表示 int32\u t int64\u t .

    对于 long 无论是否 sizeof(int)==sizeof(long) .

    我没有任何64位的目标 int 但我不明白为什么它对这样一个目标不起作用。

    但我不确定这是否适用于真正病态的实现 事实上,我很确定现在它将与任何符合要求的实现一起工作。

        2
  •  0
  •   R.. GitHub STOP HELPING ICE    7 年前

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

    您的想法 _Generic 只有当这些类型没有使用代码不知道的特定于实现的扩展整数类型定义时,这些类型才有效。

    有一种可怕但有效的方法,包括通过 va_list vsnprintf 使用正确的“PRI*”宏,然后解析字符串中的整数,但执行此操作后,列表处于无法再次使用的状态,因此if仅适用于最终参数。

    您的最佳选择可能是尝试找到“此类型的促销是否在默认促销中获得促销”的公式您可以轻松查询类型的最大值是否超过 INT_MAX UINT_MAX 但如果有一个范围相同的伪扩展整数类型,这仍然无助于形式正确性。

        3
  •  0
  •   MarkWeston    7 年前

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

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

    和6.3.3.1条规定(强调矿山):

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

    • 任何两种有符号整数类型都不得具有相同的秩,即使它们具有相同的秩 代表性。
    • 有符号整数类型的秩应大于任何有符号整数的秩 类型 精度较低 .
    • long long int的等级应大于long int的等级,即 应大于int的秩,int的秩应大于short的秩 int,它应大于有符号字符的秩。
    • 任何无符号整数类型的秩应等于相应 有符号整数类型(如果有)。
    • 任何标准整数类型的秩应大于任何扩展整数类型的秩 宽度相同的整数类型。
    • 字符的秩应等于有符号字符和无符号字符的秩。
    • \u Bool的秩应小于所有其他标准整数类型的秩。
    • 任何枚举类型的秩应等于兼容整数类型的秩 (见6.7.2.2)。
    • 任何扩展有符号整数类型相对于另一个扩展有符号整数类型的秩 实现定义了精度相同的整数类型,但仍受 确定整数转换秩的其他规则。
    • 对于所有整数类型T1、T2和T3,如果T1的秩大于T2,则T2 等级大于T3,则T1的等级大于T3。

    这就是6.5.2.2 6所说的(我的重点):

    如果表示被调用函数的表达式的类型不包含 原型,整数 促销 对每个参数执行,并且 have类型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\u t 是的子范围 内景 ,这意味着 等级 属于 int32\u t 低于 内景 ,这意味着整数升级 已执行 .

    另一方面,如果 int32\u t 可以包含以下范围: 内景 ,然后是 int32\u t 是的范围 内景 或范围的超集 内景 ,因此 等级 属于 int32\u t 大于或等于 内景 ,这意味着整数升级 未执行 .

    编辑

    根据评论更正了测试。

    #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:

    我现在对这个案子特别感兴趣:

    • 内景 是32位一的补码整数。
    • int32\u t 是32位2的补码整数(扩展类型)
    • 宽度(与精度相同?)都是一样的
    • 但因为“任何标准整数类型的秩都应大于具有相同宽度的任何扩展整数类型的秩。”排名 内景 高于 int32\u t
    • 这意味着从 int32\u t 内景 必须执行
    • 尽管 内景 无法表示中的所有值 int32\u t (具体来说,它不能代表 INT32_MIN ) 发生了什么?还是我遗漏了什么?