【问题标题】:Assign result of sizeof() to ssize_t将 sizeof() 的结果赋值给 ssize_t
【发布时间】:2019-03-22 15:04:14
【问题描述】:

我碰巧需要将sizeof(x) 的结果与ssize_t 进行比较。

当然,GCC 给出了一个错误(幸运的是我(我使用了-Wall -Wextra -Werror)),我决定做一个宏来获得sizeof() 的签名版本。

#define ssizeof (ssize_t)sizeof

然后我可以这样使用它:

for (ssize_t i = 0; i < ssizeof(x); i++)

问题是,我对SSIZE_MAX &gt;= SIZE_MAX 有什么保证吗?我想可悲的是,这永远不会是真的。

或者至少是sizeof(ssize_t) == sizeof(size_t),这会减少一半的值,但仍然足够接近。

我在 POSIX 文档中没有找到 ssize_t 和 size_t 之间的任何关系。

相关问题:

What type should be used to loop through an array?

【问题讨论】:

  • 需要这个的实际问题是什么?这闻起来像 XY 问题。
  • 问题是我只想尽可能避免无符号类型,尤其是在循环中,因此,我总是使用ssize_t 来循环数组。但显然这是ssize_t 的糟糕设计。在我的情况下,我没问题,我可以在比较中使用我用来创建 VLA 的相同变量,但最好能够使用ssize_t,目的正是如此。
  • 将size_t 与ssize_t 进行比较是完全有效的,尽管编译器可能会警告——例如,关于有符号与无符号的比较——它不应该发出 error.
  • 至于ssize_t 优于size_t,请注意前者不是标准C 定义的。这是一个实证主义。这绝不意味着如果专门为 POSIX 编程,你应该避免它,但至少你应该记住这一点。
  • 好的。当然,您可以只使用经过结束测试的循环:{T i = 0; do { ... } while (i++ &lt; limit); }。这里,T应该是一个无符号类型以避免i++在limit是T_MAX时溢出。 (实际上,整数溢出不会捕获,并且可能溢出的最后一个值永远不会被使用。)

标签: c posix sizeof


【解决方案1】:

不保证SSIZE_MAX &gt;= SIZE_MAX。事实上,这种情况不太可能发生,因为size_t 和ssize_t 很可能是对应的无符号和有符号类型,所以(在所有实际架构上)SIZE_MAX &gt; SSIZE_MAX。将无符号值转换为不能保存该值的有符号类型是未定义行为。所以从技术上讲,你的宏是有问题的。

实际上,至少在 64 位平台上,如果要转换为 ssize_t 的值是实际存在的对象的大小,则不太可能遇到麻烦。但是如果对象是理论上的(例如sizeof(char[3][1ULL&lt;&lt;62])),你可能会得到一个不愉快的惊喜。

请注意,ssize_t 类型的唯一有效负值是 -1,这是一个错误指示。您可能会将由 Posix 定义的 ssize_t 与自 C99 以来在标准 C 中定义的 ptrdiff_t 混淆。这两种类型在大多数平台上是相同的,并且通常是对应于size_t 的有符号整数类型,但是这两种行为都没有得到任何标准的保证。但是这两种类型的语义不同,使用时应该注意:

  • ssize_t 由多个 Posix 接口返回,以允许函数发出已处理字节数或错误指示的信号;错误指示必须为 -1。不期望任何可能的大小都适合ssize_t; Posix 基本原理指出:

    符合标准的应用程序将被限制为不能以大于{SSIZE_MAX} 的片段执行 I/O。

    对于大多数返回ssize_t 的接口来说,这不是问题,因为Posix 通常不需要接口来保证处理所有数据。例如,read 和write 都接受一个size_t,它描述了要读/写的缓冲区的长度,并返回一个ssize_t,它描述了实际读/写的字节数;这意味着即使有更多数据可用,也不会读取/写入超过 SSIZE_MAX 字节。但是,Posix 基本原理还指出,特定实现可能会提供允许处理更大块的扩展(“如果实现提供了扩展范围,则使用扩展的符合要求的应用程序将能够使用整个范围”),其想法是例如,实现可以通过将返回值强制转换为size_t 来指定对-1 以外的返回值进行解释。这样的扩展是不可移植的;在实践中,大多数实现确实将可以在单个调用中处理的字节数限制为可以在ssize_t 中报告的数字。

  • ptrdiff_t 是(在标准 C 中)两个指针之间差异结果的类型。为了明确定义指针的减法,两个指针必须指向同一个对象,要么指向对象,要么指向紧跟在对象后面的字节。 C 委员会认识到,如果 ptrdiff_t 是 size_t 的有符号等价物,那么两个指针之间的差异可能无法表示,从而导致未定义的行为,但他们更愿意要求 ptrdiff_t 是比size_t 更大的类型。你可以对这个决定提出异议——很多人都有——但它自 C90 以来就已经存在,而且现在似乎不太可能改变。 (来自 §6.5.6/9 的当前标准措辞:“如果结果在该类型的对象中不可表示 [ptrdiff_t],则行为未定义。”)

    与 Posix 一样,C 标准没有定义未定义的行为,因此将其解释为禁止非常大的对象中两个指针相减是错误的。始终允许实现定义标准未定义的行为的结果,因此对于实现来说,指定如果P 和Q 是指向同一对象的两个指针P &gt;= Q 是完全有效的,那么(size_t)(P - Q) 是指针之间的数学正确差异,即使减法溢出也是如此。当然,依赖于这种扩展的代码不会完全可移植,但如果扩展足够普遍,那可能不是问题。

最后一点,使用 -1 作为错误指示(ssize_t)和指针减法的可能可转换结果(ptrdiff_t)在实践中不太可能存在size_t 和指针一样大。如果size_t 与指针一样大,那么P-Q 的数学正确值可能是(size_t)(-1)(又名SIZE_MAX)的唯一方法是P 和Q 所指的对象是大小SIZE_MAX,假设size_t 与指针的宽度相同,这意味着对象加上后面的字节占据了所有可能的指针值。这与某些指针值 (NULL) 与任何有效地址不同的要求相矛盾,因此我们可以得出结论,对象的真正最大大小必须小于 SIZE_MAX。

【讨论】:

  • 这是一个大问题,但最大的问题是ssize_t 甚至可能小于size_t,因为文档中没有其他说明。我认为这是 POSIX 中的一个大错误。阅读我的 cmets @Joshua 的答案。
  • @cacahuete:这个问题是众所周知的,但你不会在 SO 评论中抱怨它。实际上,问题的影响是有限的,因为ssize_t 的唯一有效负值是-1。 (当我回来时,我将在我的答案中添加该观察结果。)ssize_t 不保证与ptrdiff_t 相同。
  • 完美答案。是的,我应该使用ptrdiff_t,它更适合该任务,但具有讽刺意味的是,它仍然存在同样的问题,即无法表示所有可能的指针差异。很高兴知道至少他们考虑过,并决定让它成为 UB。感谢这个问题,我了解了它:)
【解决方案2】:

请注意,您实际上不能这样做。

x86 Linux 中可能的最大对象的大小略低于 0xB0000000,而 SSIZE_T_MAX 为 0x7FFFFFFF。

我还没有检查 read 和东西是否真的可以处理最大可能的对象,但如果它们可以这样工作:

ssize_t result = read(fd, buf, count);
if (result != -1) {
    size_t offset = (size_t) result;
    /* handle success */
} else {
    /* handle failure */
}

你可能会发现libc 被破坏了。如果是这样,如果内核是好的,这将起作用:

ssize_t result = sys_read(fd, buf, count);
if (result >= 0 || result < -256) {
    size_t offset = (size_t) result;
    /* handle success */
} else {
    errno = (int)-result;
    /* handle failure */
}

【讨论】:

  • 那么我想这回答不能保证我所问的,但同时这证明了 POSIX 的 ssize_t 和 SSIZE_MAX 中的一个错误,对吧? ssize_t 应该能够容纳最大可能对象的大小,否则就没用了。
  • 因此,恕我直言,(sizeof(ssize_t) &gt; sizeof(size_t)) &amp;&amp; (SSIZE_MAX &gt;= SIZE_MAX) 这应该是正确的,因为该类型才有意义。
  • 在 Linux 上,您会发现一次无法读取()或写入()超过 2147479552 字节(2 GB - 4k)。但是无论如何,您都在使用围绕您的读/写的循环来处理短读和写以及 EINTR,所以一切正常。
  • 至少在某些版本的 OSX 上,尝试一次读取/写入超过 2 GB 完全失败。
  • @janneb:我想知道是否会这样。手册没有声明...
【解决方案3】:

ssize_t 是一种 POSIX 类型,它没有被定义为 C 标准的一部分。 POSIX 定义 ssize_t 必须能够处理区间 [-1, SSIZE_MAX] 中的数字,因此原则上它甚至不需要是普通的有符号类型。这个有点奇怪的定义的原因是 ssize_t 唯一被使用的地方是作为 read/write/etc 的返回值。功能。

在实践中,它始终是与 size_t 大小相同的普通有符号类型。但是如果你想对你的类型真正迂腐,你不应该将它用于处理 IO 系统调用的返回值之外的其他目的。对于一般的“指针大小”有符号整数类型,C89 定义了 ptrdiff_t。这实际上与 ssize_t 相同。

此外,如果您查看official spec for read(),您会看到对于“nbyte”参数,它表示“如果 nbyte 的值大于 {SSIZE_MAX},则结果是实现定义的。”。因此,即使 size_t 能够表示比 SSIZE_MAX 更大的值,使用比 IO 系统调用更大的值也是实现定义的行为(如前所述,这是唯一使用 ssize_t 的地方)。和 write() 等类似。

【讨论】:

    【解决方案4】:

    我将把它当作一个 X-Y 问题。您遇到的问题是您想将有符号数与无符号数进行比较。与其将sizeof 的结果转换为ssize_t,不如检查ssize_t 的值是否小于零。如果是,那么您知道它小于您的 size_t 值。如果没有,那么您可以将其转换为size_t,然后进行比较。

    例如,下面是一个比较函数,如果有符号数小于无符号数,则返回 -1,如果相等则返回 0,如果有符号数大于无符号数,则返回 1:

    int compare(ssize_t signed_number, size_t unsigned_number) {
        int ret;
        if (signed_number < 0 || (size_t) signed_number < unsigned_number) {
            ret = -1;
        }
        else {
            ret = (size_t) signed_number > unsigned_number;
        }
        return ret;
    }
    

    如果你想要的只是 &lt; 操作的等价物,你可以用这样的东西更简单一些:

    (signed_number < 0 || (size_t) signed_number < unsigned_number))
    

    如果signed_number 小于unsigned_number,那么该行将为您提供1,并且它限制了分支开销。只需要额外的&lt; 操作和logical-OR。

    【讨论】:

    • 是的,如果设计ssize_t 的人没有脑损伤,那么使用单个&lt; 可以完成的工作会产生一些开销。 :)
    • @CacahueteFrito:ssize_t:-1 中只有一个负数。
    • @Joshua POSIX 标准是否保证-1 是唯一的负数,或者-1 只是保证ssize_t 存储的唯一负数?跨度>
    • @Joshua 我知道,只需要那个返回错误,并且避免无限循环:)
    • @CacahueteFrito 我已经编辑了 &lt; 操作的直接替换,该操作的开销应该相当有限。
    猜你喜欢
    • 2017-03-06
    • 2020-06-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-04-25
    • 1970-01-01
    相关资源
    最近更新 更多